Fix silent bug in drop moves
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 # define T_(s) gettext(s)
135 #else 
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s) 
141 #   define N_(s) s 
142 #   define T_(s) s
143 # endif
144 #endif 
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
162                       int toX, int toY));
163 void HandleMachineMove P((char *message, ChessProgramState *cps));
164 int AutoPlayOneMove P((void));
165 int LoadGameOneMove P((ChessMove readAhead));
166 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
167 int LoadPositionFromFile P((char *filename, int n, char *title));
168 int SavePositionToFile P((char *filename));
169 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170                                                                                 Board board));
171 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
172 void ShowMove P((int fromX, int fromY, int toX, int toY));
173 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
174                    /*char*/int promoChar));
175 void BackwardInner P((int target));
176 void ForwardInner P((int target));
177 int Adjudicate P((ChessProgramState *cps));
178 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
179 void EditPositionDone P((Boolean fakeRights));
180 void PrintOpponents P((FILE *fp));
181 void PrintPosition P((FILE *fp, int move));
182 void StartChessProgram P((ChessProgramState *cps));
183 void SendToProgram P((char *message, ChessProgramState *cps));
184 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
185 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
186                            char *buf, int count, int error));
187 void SendTimeControl P((ChessProgramState *cps,
188                         int mps, long tc, int inc, int sd, int st));
189 char *TimeControlTagValue P((void));
190 void Attention P((ChessProgramState *cps));
191 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
192 void ResurrectChessProgram P((void));
193 void DisplayComment P((int moveNumber, char *text));
194 void DisplayMove P((int moveNumber));
195
196 void ParseGameHistory P((char *game));
197 void ParseBoard12 P((char *string));
198 void KeepAlive P((void));
199 void StartClocks P((void));
200 void SwitchClocks P((int nr));
201 void StopClocks P((void));
202 void ResetClocks P((void));
203 char *PGNDate P((void));
204 void SetGameInfo P((void));
205 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
206 int RegisterMove P((void));
207 void MakeRegisteredMove P((void));
208 void TruncateGame P((void));
209 int looking_at P((char *, int *, char *));
210 void CopyPlayerNameIntoFileName P((char **, char *));
211 char *SavePart P((char *));
212 int SaveGameOldStyle P((FILE *));
213 int SaveGamePGN P((FILE *));
214 void GetTimeMark P((TimeMark *));
215 long SubtractTimeMarks P((TimeMark *, TimeMark *));
216 int CheckFlags P((void));
217 long NextTickLength P((long));
218 void CheckTimeControl P((void));
219 void show_bytes P((FILE *, char *, int));
220 int string_to_rating P((char *str));
221 void ParseFeatures P((char* args, ChessProgramState *cps));
222 void InitBackEnd3 P((void));
223 void FeatureDone P((ChessProgramState* cps, int val));
224 void InitChessProgram P((ChessProgramState *cps, int setup));
225 void OutputKibitz(int window, char *text);
226 int PerpetualChase(int first, int last);
227 int EngineOutputIsUp();
228 void InitDrawingSizes(int x, int y);
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245
246 extern int tinyLayout, smallLayout;
247 ChessProgramStats programStats;
248 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 int endPV = -1;
250 static int exiting = 0; /* [HGM] moved to top */
251 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
252 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
253 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
254 int partnerHighlight[2];
255 Boolean partnerBoardValid = 0;
256 char partnerStatus[MSG_SIZ];
257 Boolean partnerUp;
258 Boolean originalFlip;
259 Boolean twoBoards = 0;
260 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
261 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
262 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
263 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
264 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
265 int opponentKibitzes;
266 int lastSavedGame; /* [HGM] save: ID of game */
267 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
268 extern int chatCount;
269 int chattingPartner;
270 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271
272 /* States for ics_getting_history */
273 #define H_FALSE 0
274 #define H_REQUESTED 1
275 #define H_GOT_REQ_HEADER 2
276 #define H_GOT_UNREQ_HEADER 3
277 #define H_GETTING_MOVES 4
278 #define H_GOT_UNWANTED_HEADER 5
279
280 /* whosays values for GameEnds */
281 #define GE_ICS 0
282 #define GE_ENGINE 1
283 #define GE_PLAYER 2
284 #define GE_FILE 3
285 #define GE_XBOARD 4
286 #define GE_ENGINE1 5
287 #define GE_ENGINE2 6
288
289 /* Maximum number of games in a cmail message */
290 #define CMAIL_MAX_GAMES 20
291
292 /* Different types of move when calling RegisterMove */
293 #define CMAIL_MOVE   0
294 #define CMAIL_RESIGN 1
295 #define CMAIL_DRAW   2
296 #define CMAIL_ACCEPT 3
297
298 /* Different types of result to remember for each game */
299 #define CMAIL_NOT_RESULT 0
300 #define CMAIL_OLD_RESULT 1
301 #define CMAIL_NEW_RESULT 2
302
303 /* Telnet protocol constants */
304 #define TN_WILL 0373
305 #define TN_WONT 0374
306 #define TN_DO   0375
307 #define TN_DONT 0376
308 #define TN_IAC  0377
309 #define TN_ECHO 0001
310 #define TN_SGA  0003
311 #define TN_PORT 23
312
313 /* [AS] */
314 static char * safeStrCpy( char * dst, const char * src, size_t count )
315 {
316     assert( dst != NULL );
317     assert( src != NULL );
318     assert( count > 0 );
319
320     strncpy( dst, src, count );
321     dst[ count-1 ] = '\0';
322     return dst;
323 }
324
325 /* Some compiler can't cast u64 to double
326  * This function do the job for us:
327
328  * We use the highest bit for cast, this only
329  * works if the highest bit is not
330  * in use (This should not happen)
331  *
332  * We used this for all compiler
333  */
334 double
335 u64ToDouble(u64 value)
336 {
337   double r;
338   u64 tmp = value & u64Const(0x7fffffffffffffff);
339   r = (double)(s64)tmp;
340   if (value & u64Const(0x8000000000000000))
341        r +=  9.2233720368547758080e18; /* 2^63 */
342  return r;
343 }
344
345 /* Fake up flags for now, as we aren't keeping track of castling
346    availability yet. [HGM] Change of logic: the flag now only
347    indicates the type of castlings allowed by the rule of the game.
348    The actual rights themselves are maintained in the array
349    castlingRights, as part of the game history, and are not probed
350    by this function.
351  */
352 int
353 PosFlags(index)
354 {
355   int flags = F_ALL_CASTLE_OK;
356   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
357   switch (gameInfo.variant) {
358   case VariantSuicide:
359     flags &= ~F_ALL_CASTLE_OK;
360   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
361     flags |= F_IGNORE_CHECK;
362   case VariantLosers:
363     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
364     break;
365   case VariantAtomic:
366     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
367     break;
368   case VariantKriegspiel:
369     flags |= F_KRIEGSPIEL_CAPTURE;
370     break;
371   case VariantCapaRandom: 
372   case VariantFischeRandom:
373     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
374   case VariantNoCastle:
375   case VariantShatranj:
376   case VariantCourier:
377   case VariantMakruk:
378     flags &= ~F_ALL_CASTLE_OK;
379     break;
380   default:
381     break;
382   }
383   return flags;
384 }
385
386 FILE *gameFileFP, *debugFP;
387
388 /* 
389     [AS] Note: sometimes, the sscanf() function is used to parse the input
390     into a fixed-size buffer. Because of this, we must be prepared to
391     receive strings as long as the size of the input buffer, which is currently
392     set to 4K for Windows and 8K for the rest.
393     So, we must either allocate sufficiently large buffers here, or
394     reduce the size of the input buffer in the input reading part.
395 */
396
397 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
398 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
399 char thinkOutput1[MSG_SIZ*10];
400
401 ChessProgramState first, second;
402
403 /* premove variables */
404 int premoveToX = 0;
405 int premoveToY = 0;
406 int premoveFromX = 0;
407 int premoveFromY = 0;
408 int premovePromoChar = 0;
409 int gotPremove = 0;
410 Boolean alarmSounded;
411 /* end premove variables */
412
413 char *ics_prefix = "$";
414 int ics_type = ICS_GENERIC;
415
416 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
417 int pauseExamForwardMostMove = 0;
418 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
419 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
420 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
421 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
422 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
423 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
424 int whiteFlag = FALSE, blackFlag = FALSE;
425 int userOfferedDraw = FALSE;
426 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
427 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
428 int cmailMoveType[CMAIL_MAX_GAMES];
429 long ics_clock_paused = 0;
430 ProcRef icsPR = NoProc, cmailPR = NoProc;
431 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
432 GameMode gameMode = BeginningOfGame;
433 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
434 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
435 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
436 int hiddenThinkOutputState = 0; /* [AS] */
437 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
438 int adjudicateLossPlies = 6;
439 char white_holding[64], black_holding[64];
440 TimeMark lastNodeCountTime;
441 long lastNodeCount=0;
442 int have_sent_ICS_logon = 0;
443 int movesPerSession;
444 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
445 long timeControl_2; /* [AS] Allow separate time controls */
446 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
447 long timeRemaining[2][MAX_MOVES];
448 int matchGame = 0;
449 TimeMark programStartTime;
450 char ics_handle[MSG_SIZ];
451 int have_set_title = 0;
452
453 /* animateTraining preserves the state of appData.animate
454  * when Training mode is activated. This allows the
455  * response to be animated when appData.animate == TRUE and
456  * appData.animateDragging == TRUE.
457  */
458 Boolean animateTraining;
459
460 GameInfo gameInfo;
461
462 AppData appData;
463
464 Board boards[MAX_MOVES];
465 /* [HGM] Following 7 needed for accurate legality tests: */
466 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
467 signed char  initialRights[BOARD_FILES];
468 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
469 int   initialRulePlies, FENrulePlies;
470 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
471 int loadFlag = 0; 
472 int shuffleOpenings;
473 int mute; // mute all sounds
474
475 // [HGM] vari: next 12 to save and restore variations
476 #define MAX_VARIATIONS 10
477 int framePtr = MAX_MOVES-1; // points to free stack entry
478 int storedGames = 0;
479 int savedFirst[MAX_VARIATIONS];
480 int savedLast[MAX_VARIATIONS];
481 int savedFramePtr[MAX_VARIATIONS];
482 char *savedDetails[MAX_VARIATIONS];
483 ChessMove savedResult[MAX_VARIATIONS];
484
485 void PushTail P((int firstMove, int lastMove));
486 Boolean PopTail P((Boolean annotate));
487 void CleanupTail P((void));
488
489 ChessSquare  FIDEArray[2][BOARD_FILES] = {
490     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
491         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
492     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
493         BlackKing, BlackBishop, BlackKnight, BlackRook }
494 };
495
496 ChessSquare twoKingsArray[2][BOARD_FILES] = {
497     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
498         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
499     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
500         BlackKing, BlackKing, BlackKnight, BlackRook }
501 };
502
503 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
504     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
505         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
506     { BlackRook, BlackMan, BlackBishop, BlackQueen,
507         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
508 };
509
510 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
511     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
514         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
515 };
516
517 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
518     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
519         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
521         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
522 };
523
524 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
525     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
526         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
527     { BlackRook, BlackKnight, BlackMan, BlackFerz,
528         BlackKing, BlackMan, BlackKnight, BlackRook }
529 };
530
531
532 #if (BOARD_FILES>=10)
533 ChessSquare ShogiArray[2][BOARD_FILES] = {
534     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
535         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
536     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
537         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
538 };
539
540 ChessSquare XiangqiArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
542         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
544         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare CapablancaArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
549         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
551         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
552 };
553
554 ChessSquare GreatArray[2][BOARD_FILES] = {
555     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
556         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
557     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
558         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
559 };
560
561 ChessSquare JanusArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
563         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
564     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
565         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
566 };
567
568 #ifdef GOTHIC
569 ChessSquare GothicArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
571         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
573         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
574 };
575 #else // !GOTHIC
576 #define GothicArray CapablancaArray
577 #endif // !GOTHIC
578
579 #ifdef FALCON
580 ChessSquare FalconArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
582         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
584         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
585 };
586 #else // !FALCON
587 #define FalconArray CapablancaArray
588 #endif // !FALCON
589
590 #else // !(BOARD_FILES>=10)
591 #define XiangqiPosition FIDEArray
592 #define CapablancaArray FIDEArray
593 #define GothicArray FIDEArray
594 #define GreatArray FIDEArray
595 #endif // !(BOARD_FILES>=10)
596
597 #if (BOARD_FILES>=12)
598 ChessSquare CourierArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
600         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
601     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
602         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
603 };
604 #else // !(BOARD_FILES>=12)
605 #define CourierArray CapablancaArray
606 #endif // !(BOARD_FILES>=12)
607
608
609 Board initialPosition;
610
611
612 /* Convert str to a rating. Checks for special cases of "----",
613
614    "++++", etc. Also strips ()'s */
615 int
616 string_to_rating(str)
617   char *str;
618 {
619   while(*str && !isdigit(*str)) ++str;
620   if (!*str)
621     return 0;   /* One of the special "no rating" cases */
622   else
623     return atoi(str);
624 }
625
626 void
627 ClearProgramStats()
628 {
629     /* Init programStats */
630     programStats.movelist[0] = 0;
631     programStats.depth = 0;
632     programStats.nr_moves = 0;
633     programStats.moves_left = 0;
634     programStats.nodes = 0;
635     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
636     programStats.score = 0;
637     programStats.got_only_move = 0;
638     programStats.got_fail = 0;
639     programStats.line_is_book = 0;
640 }
641
642 void
643 InitBackEnd1()
644 {
645     int matched, min, sec;
646
647     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
648     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
649
650     GetTimeMark(&programStartTime);
651     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
652
653     ClearProgramStats();
654     programStats.ok_to_send = 1;
655     programStats.seen_stat = 0;
656
657     /*
658      * Initialize game list
659      */
660     ListNew(&gameList);
661
662
663     /*
664      * Internet chess server status
665      */
666     if (appData.icsActive) {
667         appData.matchMode = FALSE;
668         appData.matchGames = 0;
669 #if ZIPPY       
670         appData.noChessProgram = !appData.zippyPlay;
671 #else
672         appData.zippyPlay = FALSE;
673         appData.zippyTalk = FALSE;
674         appData.noChessProgram = TRUE;
675 #endif
676         if (*appData.icsHelper != NULLCHAR) {
677             appData.useTelnet = TRUE;
678             appData.telnetProgram = appData.icsHelper;
679         }
680     } else {
681         appData.zippyTalk = appData.zippyPlay = FALSE;
682     }
683
684     /* [AS] Initialize pv info list [HGM] and game state */
685     {
686         int i, j;
687
688         for( i=0; i<=framePtr; i++ ) {
689             pvInfoList[i].depth = -1;
690             boards[i][EP_STATUS] = EP_NONE;
691             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
692         }
693     }
694
695     /*
696      * Parse timeControl resource
697      */
698     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
699                           appData.movesPerSession)) {
700         char buf[MSG_SIZ];
701         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
702         DisplayFatalError(buf, 0, 2);
703     }
704
705     /*
706      * Parse searchTime resource
707      */
708     if (*appData.searchTime != NULLCHAR) {
709         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
710         if (matched == 1) {
711             searchTime = min * 60;
712         } else if (matched == 2) {
713             searchTime = min * 60 + sec;
714         } else {
715             char buf[MSG_SIZ];
716             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
717             DisplayFatalError(buf, 0, 2);
718         }
719     }
720
721     /* [AS] Adjudication threshold */
722     adjudicateLossThreshold = appData.adjudicateLossThreshold;
723     
724     first.which = _("first");
725     second.which = _("second");
726     first.maybeThinking = second.maybeThinking = FALSE;
727     first.pr = second.pr = NoProc;
728     first.isr = second.isr = NULL;
729     first.sendTime = second.sendTime = 2;
730     first.sendDrawOffers = 1;
731     if (appData.firstPlaysBlack) {
732         first.twoMachinesColor = "black\n";
733         second.twoMachinesColor = "white\n";
734     } else {
735         first.twoMachinesColor = "white\n";
736         second.twoMachinesColor = "black\n";
737     }
738     first.program = appData.firstChessProgram;
739     second.program = appData.secondChessProgram;
740     first.host = appData.firstHost;
741     second.host = appData.secondHost;
742     first.dir = appData.firstDirectory;
743     second.dir = appData.secondDirectory;
744     first.other = &second;
745     second.other = &first;
746     first.initString = appData.initString;
747     second.initString = appData.secondInitString;
748     first.computerString = appData.firstComputerString;
749     second.computerString = appData.secondComputerString;
750     first.useSigint = second.useSigint = TRUE;
751     first.useSigterm = second.useSigterm = TRUE;
752     first.reuse = appData.reuseFirst;
753     second.reuse = appData.reuseSecond;
754     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
755     second.nps = appData.secondNPS;
756     first.useSetboard = second.useSetboard = FALSE;
757     first.useSAN = second.useSAN = FALSE;
758     first.usePing = second.usePing = FALSE;
759     first.lastPing = second.lastPing = 0;
760     first.lastPong = second.lastPong = 0;
761     first.usePlayother = second.usePlayother = FALSE;
762     first.useColors = second.useColors = TRUE;
763     first.useUsermove = second.useUsermove = FALSE;
764     first.sendICS = second.sendICS = FALSE;
765     first.sendName = second.sendName = appData.icsActive;
766     first.sdKludge = second.sdKludge = FALSE;
767     first.stKludge = second.stKludge = FALSE;
768     TidyProgramName(first.program, first.host, first.tidy);
769     TidyProgramName(second.program, second.host, second.tidy);
770     first.matchWins = second.matchWins = 0;
771     strcpy(first.variants, appData.variant);
772     strcpy(second.variants, appData.variant);
773     first.analysisSupport = second.analysisSupport = 2; /* detect */
774     first.analyzing = second.analyzing = FALSE;
775     first.initDone = second.initDone = FALSE;
776
777     /* New features added by Tord: */
778     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
779     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
780     /* End of new features added by Tord. */
781     first.fenOverride  = appData.fenOverride1;
782     second.fenOverride = appData.fenOverride2;
783
784     /* [HGM] time odds: set factor for each machine */
785     first.timeOdds  = appData.firstTimeOdds;
786     second.timeOdds = appData.secondTimeOdds;
787     { float norm = 1;
788         if(appData.timeOddsMode) {
789             norm = first.timeOdds;
790             if(norm > second.timeOdds) norm = second.timeOdds;
791         }
792         first.timeOdds /= norm;
793         second.timeOdds /= norm;
794     }
795
796     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797     first.accumulateTC = appData.firstAccumulateTC;
798     second.accumulateTC = appData.secondAccumulateTC;
799     first.maxNrOfSessions = second.maxNrOfSessions = 1;
800
801     /* [HGM] debug */
802     first.debug = second.debug = FALSE;
803     first.supportsNPS = second.supportsNPS = UNKNOWN;
804
805     /* [HGM] options */
806     first.optionSettings  = appData.firstOptions;
807     second.optionSettings = appData.secondOptions;
808
809     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
810     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
811     first.isUCI = appData.firstIsUCI; /* [AS] */
812     second.isUCI = appData.secondIsUCI; /* [AS] */
813     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
814     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
815
816     if (appData.firstProtocolVersion > PROTOVER ||
817         appData.firstProtocolVersion < 1) {
818       char buf[MSG_SIZ];
819       sprintf(buf, _("protocol version %d not supported"),
820               appData.firstProtocolVersion);
821       DisplayFatalError(buf, 0, 2);
822     } else {
823       first.protocolVersion = appData.firstProtocolVersion;
824     }
825
826     if (appData.secondProtocolVersion > PROTOVER ||
827         appData.secondProtocolVersion < 1) {
828       char buf[MSG_SIZ];
829       sprintf(buf, _("protocol version %d not supported"),
830               appData.secondProtocolVersion);
831       DisplayFatalError(buf, 0, 2);
832     } else {
833       second.protocolVersion = appData.secondProtocolVersion;
834     }
835
836     if (appData.icsActive) {
837         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
838 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
839     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
840         appData.clockMode = FALSE;
841         first.sendTime = second.sendTime = 0;
842     }
843     
844 #if ZIPPY
845     /* Override some settings from environment variables, for backward
846        compatibility.  Unfortunately it's not feasible to have the env
847        vars just set defaults, at least in xboard.  Ugh.
848     */
849     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
850       ZippyInit();
851     }
852 #endif
853     
854     if (appData.noChessProgram) {
855         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
856         sprintf(programVersion, "%s", PACKAGE_STRING);
857     } else {
858       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
859       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
860       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
861     }
862
863     if (!appData.icsActive) {
864       char buf[MSG_SIZ];
865       /* Check for variants that are supported only in ICS mode,
866          or not at all.  Some that are accepted here nevertheless
867          have bugs; see comments below.
868       */
869       VariantClass variant = StringToVariant(appData.variant);
870       switch (variant) {
871       case VariantBughouse:     /* need four players and two boards */
872       case VariantKriegspiel:   /* need to hide pieces and move details */
873       /* case VariantFischeRandom: (Fabien: moved below) */
874         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
875         DisplayFatalError(buf, 0, 2);
876         return;
877
878       case VariantUnknown:
879       case VariantLoadable:
880       case Variant29:
881       case Variant30:
882       case Variant31:
883       case Variant32:
884       case Variant33:
885       case Variant34:
886       case Variant35:
887       case Variant36:
888       default:
889         sprintf(buf, _("Unknown variant name %s"), appData.variant);
890         DisplayFatalError(buf, 0, 2);
891         return;
892
893       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
894       case VariantFairy:      /* [HGM] TestLegality definitely off! */
895       case VariantGothic:     /* [HGM] should work */
896       case VariantCapablanca: /* [HGM] should work */
897       case VariantCourier:    /* [HGM] initial forced moves not implemented */
898       case VariantShogi:      /* [HGM] drops not tested for legality */
899       case VariantKnightmate: /* [HGM] should work */
900       case VariantCylinder:   /* [HGM] untested */
901       case VariantFalcon:     /* [HGM] untested */
902       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
903                                  offboard interposition not understood */
904       case VariantNormal:     /* definitely works! */
905       case VariantWildCastle: /* pieces not automatically shuffled */
906       case VariantNoCastle:   /* pieces not automatically shuffled */
907       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
908       case VariantLosers:     /* should work except for win condition,
909                                  and doesn't know captures are mandatory */
910       case VariantSuicide:    /* should work except for win condition,
911                                  and doesn't know captures are mandatory */
912       case VariantGiveaway:   /* should work except for win condition,
913                                  and doesn't know captures are mandatory */
914       case VariantTwoKings:   /* should work */
915       case VariantAtomic:     /* should work except for win condition */
916       case Variant3Check:     /* should work except for win condition */
917       case VariantShatranj:   /* should work except for all win conditions */
918       case VariantMakruk:     /* should work except for daw countdown */
919       case VariantBerolina:   /* might work if TestLegality is off */
920       case VariantCapaRandom: /* should work */
921       case VariantJanus:      /* should work */
922       case VariantSuper:      /* experimental */
923       case VariantGreat:      /* experimental, requires legality testing to be off */
924         break;
925       }
926     }
927
928     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
929     InitEngineUCI( installDir, &second );
930 }
931
932 int NextIntegerFromString( char ** str, long * value )
933 {
934     int result = -1;
935     char * s = *str;
936
937     while( *s == ' ' || *s == '\t' ) {
938         s++;
939     }
940
941     *value = 0;
942
943     if( *s >= '0' && *s <= '9' ) {
944         while( *s >= '0' && *s <= '9' ) {
945             *value = *value * 10 + (*s - '0');
946             s++;
947         }
948
949         result = 0;
950     }
951
952     *str = s;
953
954     return result;
955 }
956
957 int NextTimeControlFromString( char ** str, long * value )
958 {
959     long temp;
960     int result = NextIntegerFromString( str, &temp );
961
962     if( result == 0 ) {
963         *value = temp * 60; /* Minutes */
964         if( **str == ':' ) {
965             (*str)++;
966             result = NextIntegerFromString( str, &temp );
967             *value += temp; /* Seconds */
968         }
969     }
970
971     return result;
972 }
973
974 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
975 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
976     int result = -1; long temp, temp2;
977
978     if(**str != '+') return -1; // old params remain in force!
979     (*str)++;
980     if( NextTimeControlFromString( str, &temp ) ) return -1;
981
982     if(**str != '/') {
983         /* time only: incremental or sudden-death time control */
984         if(**str == '+') { /* increment follows; read it */
985             (*str)++;
986             if(result = NextIntegerFromString( str, &temp2)) return -1;
987             *inc = temp2 * 1000;
988         } else *inc = 0;
989         *moves = 0; *tc = temp * 1000; 
990         return 0;
991     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
992
993     (*str)++; /* classical time control */
994     result = NextTimeControlFromString( str, &temp2);
995     if(result == 0) {
996         *moves = temp/60;
997         *tc    = temp2 * 1000;
998         *inc   = 0;
999     }
1000     return result;
1001 }
1002
1003 int GetTimeQuota(int movenr)
1004 {   /* [HGM] get time to add from the multi-session time-control string */
1005     int moves=1; /* kludge to force reading of first session */
1006     long time, increment;
1007     char *s = fullTimeControlString;
1008
1009     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1010     do {
1011         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1012         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1013         if(movenr == -1) return time;    /* last move before new session     */
1014         if(!moves) return increment;     /* current session is incremental   */
1015         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1016     } while(movenr >= -1);               /* try again for next session       */
1017
1018     return 0; // no new time quota on this move
1019 }
1020
1021 int
1022 ParseTimeControl(tc, ti, mps)
1023      char *tc;
1024      int ti;
1025      int mps;
1026 {
1027   long tc1;
1028   long tc2;
1029   char buf[MSG_SIZ];
1030   
1031   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1032   if(ti > 0) {
1033     if(mps)
1034       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1035     else sprintf(buf, "+%s+%d", tc, ti);
1036   } else {
1037     if(mps)
1038              sprintf(buf, "+%d/%s", mps, tc);
1039     else sprintf(buf, "+%s", tc);
1040   }
1041   fullTimeControlString = StrSave(buf);
1042   
1043   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1044     return FALSE;
1045   }
1046   
1047   if( *tc == '/' ) {
1048     /* Parse second time control */
1049     tc++;
1050     
1051     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1052       return FALSE;
1053     }
1054     
1055     if( tc2 == 0 ) {
1056       return FALSE;
1057     }
1058     
1059     timeControl_2 = tc2 * 1000;
1060   }
1061   else {
1062     timeControl_2 = 0;
1063   }
1064   
1065   if( tc1 == 0 ) {
1066     return FALSE;
1067   }
1068   
1069   timeControl = tc1 * 1000;
1070   
1071   if (ti >= 0) {
1072     timeIncrement = ti * 1000;  /* convert to ms */
1073     movesPerSession = 0;
1074   } else {
1075     timeIncrement = 0;
1076     movesPerSession = mps;
1077   }
1078   return TRUE;
1079 }
1080
1081 void
1082 InitBackEnd2()
1083 {
1084     if (appData.debugMode) {
1085         fprintf(debugFP, "%s\n", programVersion);
1086     }
1087
1088     set_cont_sequence(appData.wrapContSeq);
1089     if (appData.matchGames > 0) {
1090         appData.matchMode = TRUE;
1091     } else if (appData.matchMode) {
1092         appData.matchGames = 1;
1093     }
1094     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1095         appData.matchGames = appData.sameColorGames;
1096     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1097         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1098         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1099     }
1100     Reset(TRUE, FALSE);
1101     if (appData.noChessProgram || first.protocolVersion == 1) {
1102       InitBackEnd3();
1103     } else {
1104       /* kludge: allow timeout for initial "feature" commands */
1105       FreezeUI();
1106       DisplayMessage("", _("Starting chess program"));
1107       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1108     }
1109 }
1110
1111 void
1112 InitBackEnd3 P((void))
1113 {
1114     GameMode initialMode;
1115     char buf[MSG_SIZ];
1116     int err;
1117
1118     InitChessProgram(&first, startedFromSetupPosition);
1119
1120     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1121         free(programVersion);
1122         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1123         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1124     }
1125
1126     if (appData.icsActive) {
1127 #ifdef WIN32
1128         /* [DM] Make a console window if needed [HGM] merged ifs */
1129         ConsoleCreate(); 
1130 #endif
1131         err = establish();
1132         if (err != 0) {
1133             if (*appData.icsCommPort != NULLCHAR) {
1134                 sprintf(buf, _("Could not open comm port %s"),  
1135                         appData.icsCommPort);
1136             } else {
1137                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1138                         appData.icsHost, appData.icsPort);
1139             }
1140             DisplayFatalError(buf, err, 1);
1141             return;
1142         }
1143         SetICSMode();
1144         telnetISR =
1145           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1146         fromUserISR =
1147           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1148         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1149             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1150     } else if (appData.noChessProgram) {
1151         SetNCPMode();
1152     } else {
1153         SetGNUMode();
1154     }
1155
1156     if (*appData.cmailGameName != NULLCHAR) {
1157         SetCmailMode();
1158         OpenLoopback(&cmailPR);
1159         cmailISR =
1160           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1161     }
1162     
1163     ThawUI();
1164     DisplayMessage("", "");
1165     if (StrCaseCmp(appData.initialMode, "") == 0) {
1166       initialMode = BeginningOfGame;
1167     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1168       initialMode = TwoMachinesPlay;
1169     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1170       initialMode = AnalyzeFile; 
1171     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1172       initialMode = AnalyzeMode;
1173     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1174       initialMode = MachinePlaysWhite;
1175     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1176       initialMode = MachinePlaysBlack;
1177     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1178       initialMode = EditGame;
1179     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1180       initialMode = EditPosition;
1181     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1182       initialMode = Training;
1183     } else {
1184       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1185       DisplayFatalError(buf, 0, 2);
1186       return;
1187     }
1188
1189     if (appData.matchMode) {
1190         /* Set up machine vs. machine match */
1191         if (appData.noChessProgram) {
1192             DisplayFatalError(_("Can't have a match with no chess programs"),
1193                               0, 2);
1194             return;
1195         }
1196         matchMode = TRUE;
1197         matchGame = 1;
1198         if (*appData.loadGameFile != NULLCHAR) {
1199             int index = appData.loadGameIndex; // [HGM] autoinc
1200             if(index<0) lastIndex = index = 1;
1201             if (!LoadGameFromFile(appData.loadGameFile,
1202                                   index,
1203                                   appData.loadGameFile, FALSE)) {
1204                 DisplayFatalError(_("Bad game file"), 0, 1);
1205                 return;
1206             }
1207         } else if (*appData.loadPositionFile != NULLCHAR) {
1208             int index = appData.loadPositionIndex; // [HGM] autoinc
1209             if(index<0) lastIndex = index = 1;
1210             if (!LoadPositionFromFile(appData.loadPositionFile,
1211                                       index,
1212                                       appData.loadPositionFile)) {
1213                 DisplayFatalError(_("Bad position file"), 0, 1);
1214                 return;
1215             }
1216         }
1217         TwoMachinesEvent();
1218     } else if (*appData.cmailGameName != NULLCHAR) {
1219         /* Set up cmail mode */
1220         ReloadCmailMsgEvent(TRUE);
1221     } else {
1222         /* Set up other modes */
1223         if (initialMode == AnalyzeFile) {
1224           if (*appData.loadGameFile == NULLCHAR) {
1225             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1226             return;
1227           }
1228         }
1229         if (*appData.loadGameFile != NULLCHAR) {
1230             (void) LoadGameFromFile(appData.loadGameFile,
1231                                     appData.loadGameIndex,
1232                                     appData.loadGameFile, TRUE);
1233         } else if (*appData.loadPositionFile != NULLCHAR) {
1234             (void) LoadPositionFromFile(appData.loadPositionFile,
1235                                         appData.loadPositionIndex,
1236                                         appData.loadPositionFile);
1237             /* [HGM] try to make self-starting even after FEN load */
1238             /* to allow automatic setup of fairy variants with wtm */
1239             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1240                 gameMode = BeginningOfGame;
1241                 setboardSpoiledMachineBlack = 1;
1242             }
1243             /* [HGM] loadPos: make that every new game uses the setup */
1244             /* from file as long as we do not switch variant          */
1245             if(!blackPlaysFirst) {
1246                 startedFromPositionFile = TRUE;
1247                 CopyBoard(filePosition, boards[0]);
1248             }
1249         }
1250         if (initialMode == AnalyzeMode) {
1251           if (appData.noChessProgram) {
1252             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1253             return;
1254           }
1255           if (appData.icsActive) {
1256             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1257             return;
1258           }
1259           AnalyzeModeEvent();
1260         } else if (initialMode == AnalyzeFile) {
1261           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1262           ShowThinkingEvent();
1263           AnalyzeFileEvent();
1264           AnalysisPeriodicEvent(1);
1265         } else if (initialMode == MachinePlaysWhite) {
1266           if (appData.noChessProgram) {
1267             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1268                               0, 2);
1269             return;
1270           }
1271           if (appData.icsActive) {
1272             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1273                               0, 2);
1274             return;
1275           }
1276           MachineWhiteEvent();
1277         } else if (initialMode == MachinePlaysBlack) {
1278           if (appData.noChessProgram) {
1279             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1280                               0, 2);
1281             return;
1282           }
1283           if (appData.icsActive) {
1284             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1285                               0, 2);
1286             return;
1287           }
1288           MachineBlackEvent();
1289         } else if (initialMode == TwoMachinesPlay) {
1290           if (appData.noChessProgram) {
1291             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1292                               0, 2);
1293             return;
1294           }
1295           if (appData.icsActive) {
1296             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1297                               0, 2);
1298             return;
1299           }
1300           TwoMachinesEvent();
1301         } else if (initialMode == EditGame) {
1302           EditGameEvent();
1303         } else if (initialMode == EditPosition) {
1304           EditPositionEvent();
1305         } else if (initialMode == Training) {
1306           if (*appData.loadGameFile == NULLCHAR) {
1307             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1308             return;
1309           }
1310           TrainingEvent();
1311         }
1312     }
1313 }
1314
1315 /*
1316  * Establish will establish a contact to a remote host.port.
1317  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1318  *  used to talk to the host.
1319  * Returns 0 if okay, error code if not.
1320  */
1321 int
1322 establish()
1323 {
1324     char buf[MSG_SIZ];
1325
1326     if (*appData.icsCommPort != NULLCHAR) {
1327         /* Talk to the host through a serial comm port */
1328         return OpenCommPort(appData.icsCommPort, &icsPR);
1329
1330     } else if (*appData.gateway != NULLCHAR) {
1331         if (*appData.remoteShell == NULLCHAR) {
1332             /* Use the rcmd protocol to run telnet program on a gateway host */
1333             snprintf(buf, sizeof(buf), "%s %s %s",
1334                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1335             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1336
1337         } else {
1338             /* Use the rsh program to run telnet program on a gateway host */
1339             if (*appData.remoteUser == NULLCHAR) {
1340                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1341                         appData.gateway, appData.telnetProgram,
1342                         appData.icsHost, appData.icsPort);
1343             } else {
1344                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1345                         appData.remoteShell, appData.gateway, 
1346                         appData.remoteUser, appData.telnetProgram,
1347                         appData.icsHost, appData.icsPort);
1348             }
1349             return StartChildProcess(buf, "", &icsPR);
1350
1351         }
1352     } else if (appData.useTelnet) {
1353         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1354
1355     } else {
1356         /* TCP socket interface differs somewhat between
1357            Unix and NT; handle details in the front end.
1358            */
1359         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1360     }
1361 }
1362
1363 void EscapeExpand(char *p, char *q)
1364 {       // [HGM] initstring: routine to shape up string arguments
1365         while(*p++ = *q++) if(p[-1] == '\\')
1366             switch(*q++) {
1367                 case 'n': p[-1] = '\n'; break;
1368                 case 'r': p[-1] = '\r'; break;
1369                 case 't': p[-1] = '\t'; break;
1370                 case '\\': p[-1] = '\\'; break;
1371                 case 0: *p = 0; return;
1372                 default: p[-1] = q[-1]; break;
1373             }
1374 }
1375
1376 void
1377 show_bytes(fp, buf, count)
1378      FILE *fp;
1379      char *buf;
1380      int count;
1381 {
1382     while (count--) {
1383         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1384             fprintf(fp, "\\%03o", *buf & 0xff);
1385         } else {
1386             putc(*buf, fp);
1387         }
1388         buf++;
1389     }
1390     fflush(fp);
1391 }
1392
1393 /* Returns an errno value */
1394 int
1395 OutputMaybeTelnet(pr, message, count, outError)
1396      ProcRef pr;
1397      char *message;
1398      int count;
1399      int *outError;
1400 {
1401     char buf[8192], *p, *q, *buflim;
1402     int left, newcount, outcount;
1403
1404     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1405         *appData.gateway != NULLCHAR) {
1406         if (appData.debugMode) {
1407             fprintf(debugFP, ">ICS: ");
1408             show_bytes(debugFP, message, count);
1409             fprintf(debugFP, "\n");
1410         }
1411         return OutputToProcess(pr, message, count, outError);
1412     }
1413
1414     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1415     p = message;
1416     q = buf;
1417     left = count;
1418     newcount = 0;
1419     while (left) {
1420         if (q >= buflim) {
1421             if (appData.debugMode) {
1422                 fprintf(debugFP, ">ICS: ");
1423                 show_bytes(debugFP, buf, newcount);
1424                 fprintf(debugFP, "\n");
1425             }
1426             outcount = OutputToProcess(pr, buf, newcount, outError);
1427             if (outcount < newcount) return -1; /* to be sure */
1428             q = buf;
1429             newcount = 0;
1430         }
1431         if (*p == '\n') {
1432             *q++ = '\r';
1433             newcount++;
1434         } else if (((unsigned char) *p) == TN_IAC) {
1435             *q++ = (char) TN_IAC;
1436             newcount ++;
1437         }
1438         *q++ = *p++;
1439         newcount++;
1440         left--;
1441     }
1442     if (appData.debugMode) {
1443         fprintf(debugFP, ">ICS: ");
1444         show_bytes(debugFP, buf, newcount);
1445         fprintf(debugFP, "\n");
1446     }
1447     outcount = OutputToProcess(pr, buf, newcount, outError);
1448     if (outcount < newcount) return -1; /* to be sure */
1449     return count;
1450 }
1451
1452 void
1453 read_from_player(isr, closure, message, count, error)
1454      InputSourceRef isr;
1455      VOIDSTAR closure;
1456      char *message;
1457      int count;
1458      int error;
1459 {
1460     int outError, outCount;
1461     static int gotEof = 0;
1462
1463     /* Pass data read from player on to ICS */
1464     if (count > 0) {
1465         gotEof = 0;
1466         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1467         if (outCount < count) {
1468             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1469         }
1470     } else if (count < 0) {
1471         RemoveInputSource(isr);
1472         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1473     } else if (gotEof++ > 0) {
1474         RemoveInputSource(isr);
1475         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1476     }
1477 }
1478
1479 void
1480 KeepAlive()
1481 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1482     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1483     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1484     SendToICS("date\n");
1485     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1486 }
1487
1488 /* added routine for printf style output to ics */
1489 void ics_printf(char *format, ...)
1490 {
1491     char buffer[MSG_SIZ];
1492     va_list args;
1493
1494     va_start(args, format);
1495     vsnprintf(buffer, sizeof(buffer), format, args);
1496     buffer[sizeof(buffer)-1] = '\0';
1497     SendToICS(buffer);
1498     va_end(args);
1499 }
1500
1501 void
1502 SendToICS(s)
1503      char *s;
1504 {
1505     int count, outCount, outError;
1506
1507     if (icsPR == NULL) return;
1508
1509     count = strlen(s);
1510     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1511     if (outCount < count) {
1512         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1513     }
1514 }
1515
1516 /* This is used for sending logon scripts to the ICS. Sending
1517    without a delay causes problems when using timestamp on ICC
1518    (at least on my machine). */
1519 void
1520 SendToICSDelayed(s,msdelay)
1521      char *s;
1522      long msdelay;
1523 {
1524     int count, outCount, outError;
1525
1526     if (icsPR == NULL) return;
1527
1528     count = strlen(s);
1529     if (appData.debugMode) {
1530         fprintf(debugFP, ">ICS: ");
1531         show_bytes(debugFP, s, count);
1532         fprintf(debugFP, "\n");
1533     }
1534     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1535                                       msdelay);
1536     if (outCount < count) {
1537         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1538     }
1539 }
1540
1541
1542 /* Remove all highlighting escape sequences in s
1543    Also deletes any suffix starting with '(' 
1544    */
1545 char *
1546 StripHighlightAndTitle(s)
1547      char *s;
1548 {
1549     static char retbuf[MSG_SIZ];
1550     char *p = retbuf;
1551
1552     while (*s != NULLCHAR) {
1553         while (*s == '\033') {
1554             while (*s != NULLCHAR && !isalpha(*s)) s++;
1555             if (*s != NULLCHAR) s++;
1556         }
1557         while (*s != NULLCHAR && *s != '\033') {
1558             if (*s == '(' || *s == '[') {
1559                 *p = NULLCHAR;
1560                 return retbuf;
1561             }
1562             *p++ = *s++;
1563         }
1564     }
1565     *p = NULLCHAR;
1566     return retbuf;
1567 }
1568
1569 /* Remove all highlighting escape sequences in s */
1570 char *
1571 StripHighlight(s)
1572      char *s;
1573 {
1574     static char retbuf[MSG_SIZ];
1575     char *p = retbuf;
1576
1577     while (*s != NULLCHAR) {
1578         while (*s == '\033') {
1579             while (*s != NULLCHAR && !isalpha(*s)) s++;
1580             if (*s != NULLCHAR) s++;
1581         }
1582         while (*s != NULLCHAR && *s != '\033') {
1583             *p++ = *s++;
1584         }
1585     }
1586     *p = NULLCHAR;
1587     return retbuf;
1588 }
1589
1590 char *variantNames[] = VARIANT_NAMES;
1591 char *
1592 VariantName(v)
1593      VariantClass v;
1594 {
1595     return variantNames[v];
1596 }
1597
1598
1599 /* Identify a variant from the strings the chess servers use or the
1600    PGN Variant tag names we use. */
1601 VariantClass
1602 StringToVariant(e)
1603      char *e;
1604 {
1605     char *p;
1606     int wnum = -1;
1607     VariantClass v = VariantNormal;
1608     int i, found = FALSE;
1609     char buf[MSG_SIZ];
1610
1611     if (!e) return v;
1612
1613     /* [HGM] skip over optional board-size prefixes */
1614     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1615         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1616         while( *e++ != '_');
1617     }
1618
1619     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1620         v = VariantNormal;
1621         found = TRUE;
1622     } else
1623     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1624       if (StrCaseStr(e, variantNames[i])) {
1625         v = (VariantClass) i;
1626         found = TRUE;
1627         break;
1628       }
1629     }
1630
1631     if (!found) {
1632       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1633           || StrCaseStr(e, "wild/fr") 
1634           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1635         v = VariantFischeRandom;
1636       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1637                  (i = 1, p = StrCaseStr(e, "w"))) {
1638         p += i;
1639         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1640         if (isdigit(*p)) {
1641           wnum = atoi(p);
1642         } else {
1643           wnum = -1;
1644         }
1645         switch (wnum) {
1646         case 0: /* FICS only, actually */
1647         case 1:
1648           /* Castling legal even if K starts on d-file */
1649           v = VariantWildCastle;
1650           break;
1651         case 2:
1652         case 3:
1653         case 4:
1654           /* Castling illegal even if K & R happen to start in
1655              normal positions. */
1656           v = VariantNoCastle;
1657           break;
1658         case 5:
1659         case 7:
1660         case 8:
1661         case 10:
1662         case 11:
1663         case 12:
1664         case 13:
1665         case 14:
1666         case 15:
1667         case 18:
1668         case 19:
1669           /* Castling legal iff K & R start in normal positions */
1670           v = VariantNormal;
1671           break;
1672         case 6:
1673         case 20:
1674         case 21:
1675           /* Special wilds for position setup; unclear what to do here */
1676           v = VariantLoadable;
1677           break;
1678         case 9:
1679           /* Bizarre ICC game */
1680           v = VariantTwoKings;
1681           break;
1682         case 16:
1683           v = VariantKriegspiel;
1684           break;
1685         case 17:
1686           v = VariantLosers;
1687           break;
1688         case 22:
1689           v = VariantFischeRandom;
1690           break;
1691         case 23:
1692           v = VariantCrazyhouse;
1693           break;
1694         case 24:
1695           v = VariantBughouse;
1696           break;
1697         case 25:
1698           v = Variant3Check;
1699           break;
1700         case 26:
1701           /* Not quite the same as FICS suicide! */
1702           v = VariantGiveaway;
1703           break;
1704         case 27:
1705           v = VariantAtomic;
1706           break;
1707         case 28:
1708           v = VariantShatranj;
1709           break;
1710
1711         /* Temporary names for future ICC types.  The name *will* change in 
1712            the next xboard/WinBoard release after ICC defines it. */
1713         case 29:
1714           v = Variant29;
1715           break;
1716         case 30:
1717           v = Variant30;
1718           break;
1719         case 31:
1720           v = Variant31;
1721           break;
1722         case 32:
1723           v = Variant32;
1724           break;
1725         case 33:
1726           v = Variant33;
1727           break;
1728         case 34:
1729           v = Variant34;
1730           break;
1731         case 35:
1732           v = Variant35;
1733           break;
1734         case 36:
1735           v = Variant36;
1736           break;
1737         case 37:
1738           v = VariantShogi;
1739           break;
1740         case 38:
1741           v = VariantXiangqi;
1742           break;
1743         case 39:
1744           v = VariantCourier;
1745           break;
1746         case 40:
1747           v = VariantGothic;
1748           break;
1749         case 41:
1750           v = VariantCapablanca;
1751           break;
1752         case 42:
1753           v = VariantKnightmate;
1754           break;
1755         case 43:
1756           v = VariantFairy;
1757           break;
1758         case 44:
1759           v = VariantCylinder;
1760           break;
1761         case 45:
1762           v = VariantFalcon;
1763           break;
1764         case 46:
1765           v = VariantCapaRandom;
1766           break;
1767         case 47:
1768           v = VariantBerolina;
1769           break;
1770         case 48:
1771           v = VariantJanus;
1772           break;
1773         case 49:
1774           v = VariantSuper;
1775           break;
1776         case 50:
1777           v = VariantGreat;
1778           break;
1779         case -1:
1780           /* Found "wild" or "w" in the string but no number;
1781              must assume it's normal chess. */
1782           v = VariantNormal;
1783           break;
1784         default:
1785           sprintf(buf, _("Unknown wild type %d"), wnum);
1786           DisplayError(buf, 0);
1787           v = VariantUnknown;
1788           break;
1789         }
1790       }
1791     }
1792     if (appData.debugMode) {
1793       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1794               e, wnum, VariantName(v));
1795     }
1796     return v;
1797 }
1798
1799 static int leftover_start = 0, leftover_len = 0;
1800 char star_match[STAR_MATCH_N][MSG_SIZ];
1801
1802 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1803    advance *index beyond it, and set leftover_start to the new value of
1804    *index; else return FALSE.  If pattern contains the character '*', it
1805    matches any sequence of characters not containing '\r', '\n', or the
1806    character following the '*' (if any), and the matched sequence(s) are
1807    copied into star_match.
1808    */
1809 int
1810 looking_at(buf, index, pattern)
1811      char *buf;
1812      int *index;
1813      char *pattern;
1814 {
1815     char *bufp = &buf[*index], *patternp = pattern;
1816     int star_count = 0;
1817     char *matchp = star_match[0];
1818     
1819     for (;;) {
1820         if (*patternp == NULLCHAR) {
1821             *index = leftover_start = bufp - buf;
1822             *matchp = NULLCHAR;
1823             return TRUE;
1824         }
1825         if (*bufp == NULLCHAR) return FALSE;
1826         if (*patternp == '*') {
1827             if (*bufp == *(patternp + 1)) {
1828                 *matchp = NULLCHAR;
1829                 matchp = star_match[++star_count];
1830                 patternp += 2;
1831                 bufp++;
1832                 continue;
1833             } else if (*bufp == '\n' || *bufp == '\r') {
1834                 patternp++;
1835                 if (*patternp == NULLCHAR)
1836                   continue;
1837                 else
1838                   return FALSE;
1839             } else {
1840                 *matchp++ = *bufp++;
1841                 continue;
1842             }
1843         }
1844         if (*patternp != *bufp) return FALSE;
1845         patternp++;
1846         bufp++;
1847     }
1848 }
1849
1850 void
1851 SendToPlayer(data, length)
1852      char *data;
1853      int length;
1854 {
1855     int error, outCount;
1856     outCount = OutputToProcess(NoProc, data, length, &error);
1857     if (outCount < length) {
1858         DisplayFatalError(_("Error writing to display"), error, 1);
1859     }
1860 }
1861
1862 void
1863 PackHolding(packed, holding)
1864      char packed[];
1865      char *holding;
1866 {
1867     char *p = holding;
1868     char *q = packed;
1869     int runlength = 0;
1870     int curr = 9999;
1871     do {
1872         if (*p == curr) {
1873             runlength++;
1874         } else {
1875             switch (runlength) {
1876               case 0:
1877                 break;
1878               case 1:
1879                 *q++ = curr;
1880                 break;
1881               case 2:
1882                 *q++ = curr;
1883                 *q++ = curr;
1884                 break;
1885               default:
1886                 sprintf(q, "%d", runlength);
1887                 while (*q) q++;
1888                 *q++ = curr;
1889                 break;
1890             }
1891             runlength = 1;
1892             curr = *p;
1893         }
1894     } while (*p++);
1895     *q = NULLCHAR;
1896 }
1897
1898 /* Telnet protocol requests from the front end */
1899 void
1900 TelnetRequest(ddww, option)
1901      unsigned char ddww, option;
1902 {
1903     unsigned char msg[3];
1904     int outCount, outError;
1905
1906     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1907
1908     if (appData.debugMode) {
1909         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1910         switch (ddww) {
1911           case TN_DO:
1912             ddwwStr = "DO";
1913             break;
1914           case TN_DONT:
1915             ddwwStr = "DONT";
1916             break;
1917           case TN_WILL:
1918             ddwwStr = "WILL";
1919             break;
1920           case TN_WONT:
1921             ddwwStr = "WONT";
1922             break;
1923           default:
1924             ddwwStr = buf1;
1925             sprintf(buf1, "%d", ddww);
1926             break;
1927         }
1928         switch (option) {
1929           case TN_ECHO:
1930             optionStr = "ECHO";
1931             break;
1932           default:
1933             optionStr = buf2;
1934             sprintf(buf2, "%d", option);
1935             break;
1936         }
1937         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1938     }
1939     msg[0] = TN_IAC;
1940     msg[1] = ddww;
1941     msg[2] = option;
1942     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1943     if (outCount < 3) {
1944         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1945     }
1946 }
1947
1948 void
1949 DoEcho()
1950 {
1951     if (!appData.icsActive) return;
1952     TelnetRequest(TN_DO, TN_ECHO);
1953 }
1954
1955 void
1956 DontEcho()
1957 {
1958     if (!appData.icsActive) return;
1959     TelnetRequest(TN_DONT, TN_ECHO);
1960 }
1961
1962 void
1963 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1964 {
1965     /* put the holdings sent to us by the server on the board holdings area */
1966     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1967     char p;
1968     ChessSquare piece;
1969
1970     if(gameInfo.holdingsWidth < 2)  return;
1971     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1972         return; // prevent overwriting by pre-board holdings
1973
1974     if( (int)lowestPiece >= BlackPawn ) {
1975         holdingsColumn = 0;
1976         countsColumn = 1;
1977         holdingsStartRow = BOARD_HEIGHT-1;
1978         direction = -1;
1979     } else {
1980         holdingsColumn = BOARD_WIDTH-1;
1981         countsColumn = BOARD_WIDTH-2;
1982         holdingsStartRow = 0;
1983         direction = 1;
1984     }
1985
1986     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1987         board[i][holdingsColumn] = EmptySquare;
1988         board[i][countsColumn]   = (ChessSquare) 0;
1989     }
1990     while( (p=*holdings++) != NULLCHAR ) {
1991         piece = CharToPiece( ToUpper(p) );
1992         if(piece == EmptySquare) continue;
1993         /*j = (int) piece - (int) WhitePawn;*/
1994         j = PieceToNumber(piece);
1995         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1996         if(j < 0) continue;               /* should not happen */
1997         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1998         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1999         board[holdingsStartRow+j*direction][countsColumn]++;
2000     }
2001 }
2002
2003
2004 void
2005 VariantSwitch(Board board, VariantClass newVariant)
2006 {
2007    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2008    static Board oldBoard;
2009
2010    startedFromPositionFile = FALSE;
2011    if(gameInfo.variant == newVariant) return;
2012
2013    /* [HGM] This routine is called each time an assignment is made to
2014     * gameInfo.variant during a game, to make sure the board sizes
2015     * are set to match the new variant. If that means adding or deleting
2016     * holdings, we shift the playing board accordingly
2017     * This kludge is needed because in ICS observe mode, we get boards
2018     * of an ongoing game without knowing the variant, and learn about the
2019     * latter only later. This can be because of the move list we requested,
2020     * in which case the game history is refilled from the beginning anyway,
2021     * but also when receiving holdings of a crazyhouse game. In the latter
2022     * case we want to add those holdings to the already received position.
2023     */
2024
2025    
2026    if (appData.debugMode) {
2027      fprintf(debugFP, "Switch board from %s to %s\n",
2028              VariantName(gameInfo.variant), VariantName(newVariant));
2029      setbuf(debugFP, NULL);
2030    }
2031    shuffleOpenings = 0;       /* [HGM] shuffle */
2032    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2033    switch(newVariant) 
2034      {
2035      case VariantShogi:
2036        newWidth = 9;  newHeight = 9;
2037        gameInfo.holdingsSize = 7;
2038      case VariantBughouse:
2039      case VariantCrazyhouse:
2040        newHoldingsWidth = 2; break;
2041      case VariantGreat:
2042        newWidth = 10;
2043      case VariantSuper:
2044        newHoldingsWidth = 2;
2045        gameInfo.holdingsSize = 8;
2046        break;
2047      case VariantGothic:
2048      case VariantCapablanca:
2049      case VariantCapaRandom:
2050        newWidth = 10;
2051      default:
2052        newHoldingsWidth = gameInfo.holdingsSize = 0;
2053      };
2054    
2055    if(newWidth  != gameInfo.boardWidth  ||
2056       newHeight != gameInfo.boardHeight ||
2057       newHoldingsWidth != gameInfo.holdingsWidth ) {
2058      
2059      /* shift position to new playing area, if needed */
2060      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2061        for(i=0; i<BOARD_HEIGHT; i++) 
2062          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2063            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2064              board[i][j];
2065        for(i=0; i<newHeight; i++) {
2066          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2067          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2068        }
2069      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2070        for(i=0; i<BOARD_HEIGHT; i++)
2071          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2072            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2073              board[i][j];
2074      }
2075      gameInfo.boardWidth  = newWidth;
2076      gameInfo.boardHeight = newHeight;
2077      gameInfo.holdingsWidth = newHoldingsWidth;
2078      gameInfo.variant = newVariant;
2079      InitDrawingSizes(-2, 0);
2080    } else gameInfo.variant = newVariant;
2081    CopyBoard(oldBoard, board);   // remember correctly formatted board
2082      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2083    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2084 }
2085
2086 static int loggedOn = FALSE;
2087
2088 /*-- Game start info cache: --*/
2089 int gs_gamenum;
2090 char gs_kind[MSG_SIZ];
2091 static char player1Name[128] = "";
2092 static char player2Name[128] = "";
2093 static char cont_seq[] = "\n\\   ";
2094 static int player1Rating = -1;
2095 static int player2Rating = -1;
2096 /*----------------------------*/
2097
2098 ColorClass curColor = ColorNormal;
2099 int suppressKibitz = 0;
2100
2101 // [HGM] seekgraph
2102 Boolean soughtPending = FALSE;
2103 Boolean seekGraphUp;
2104 #define MAX_SEEK_ADS 200
2105 #define SQUARE 0x80
2106 char *seekAdList[MAX_SEEK_ADS];
2107 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2108 float tcList[MAX_SEEK_ADS];
2109 char colorList[MAX_SEEK_ADS];
2110 int nrOfSeekAds = 0;
2111 int minRating = 1010, maxRating = 2800;
2112 int hMargin = 10, vMargin = 20, h, w;
2113 extern int squareSize, lineGap;
2114
2115 void
2116 PlotSeekAd(int i)
2117 {
2118         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2119         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2120         if(r < minRating+100 && r >=0 ) r = minRating+100;
2121         if(r > maxRating) r = maxRating;
2122         if(tc < 1.) tc = 1.;
2123         if(tc > 95.) tc = 95.;
2124         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2125         y = ((double)r - minRating)/(maxRating - minRating)
2126             * (h-vMargin-squareSize/8-1) + vMargin;
2127         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2128         if(strstr(seekAdList[i], " u ")) color = 1;
2129         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2130            !strstr(seekAdList[i], "bullet") &&
2131            !strstr(seekAdList[i], "blitz") &&
2132            !strstr(seekAdList[i], "standard") ) color = 2;
2133         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2134         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2135 }
2136
2137 void
2138 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2139 {
2140         char buf[MSG_SIZ], *ext = "";
2141         VariantClass v = StringToVariant(type);
2142         if(strstr(type, "wild")) {
2143             ext = type + 4; // append wild number
2144             if(v == VariantFischeRandom) type = "chess960"; else
2145             if(v == VariantLoadable) type = "setup"; else
2146             type = VariantName(v);
2147         }
2148         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2149         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2150             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2151             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2152             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2153             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2154             seekNrList[nrOfSeekAds] = nr;
2155             zList[nrOfSeekAds] = 0;
2156             seekAdList[nrOfSeekAds++] = StrSave(buf);
2157             if(plot) PlotSeekAd(nrOfSeekAds-1);
2158         }
2159 }
2160
2161 void
2162 EraseSeekDot(int i)
2163 {
2164     int x = xList[i], y = yList[i], d=squareSize/4, k;
2165     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2166     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2167     // now replot every dot that overlapped
2168     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2169         int xx = xList[k], yy = yList[k];
2170         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2171             DrawSeekDot(xx, yy, colorList[k]);
2172     }
2173 }
2174
2175 void
2176 RemoveSeekAd(int nr)
2177 {
2178         int i;
2179         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2180             EraseSeekDot(i);
2181             if(seekAdList[i]) free(seekAdList[i]);
2182             seekAdList[i] = seekAdList[--nrOfSeekAds];
2183             seekNrList[i] = seekNrList[nrOfSeekAds];
2184             ratingList[i] = ratingList[nrOfSeekAds];
2185             colorList[i]  = colorList[nrOfSeekAds];
2186             tcList[i] = tcList[nrOfSeekAds];
2187             xList[i]  = xList[nrOfSeekAds];
2188             yList[i]  = yList[nrOfSeekAds];
2189             zList[i]  = zList[nrOfSeekAds];
2190             seekAdList[nrOfSeekAds] = NULL;
2191             break;
2192         }
2193 }
2194
2195 Boolean
2196 MatchSoughtLine(char *line)
2197 {
2198     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2199     int nr, base, inc, u=0; char dummy;
2200
2201     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2202        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2203        (u=1) &&
2204        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2205         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2206         // match: compact and save the line
2207         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2208         return TRUE;
2209     }
2210     return FALSE;
2211 }
2212
2213 int
2214 DrawSeekGraph()
2215 {
2216     int i;
2217     if(!seekGraphUp) return FALSE;
2218     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2219     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2220
2221     DrawSeekBackground(0, 0, w, h);
2222     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2223     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2224     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2225         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2226         yy = h-1-yy;
2227         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2228         if(i%500 == 0) {
2229             char buf[MSG_SIZ];
2230             sprintf(buf, "%d", i);
2231             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2232         }
2233     }
2234     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2235     for(i=1; i<100; i+=(i<10?1:5)) {
2236         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2237         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2238         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2239             char buf[MSG_SIZ];
2240             sprintf(buf, "%d", i);
2241             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2242         }
2243     }
2244     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2245     return TRUE;
2246 }
2247
2248 int SeekGraphClick(ClickType click, int x, int y, int moving)
2249 {
2250     static int lastDown = 0, displayed = 0, lastSecond;
2251     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2252         if(click == Release || moving) return FALSE;
2253         nrOfSeekAds = 0;
2254         soughtPending = TRUE;
2255         SendToICS(ics_prefix);
2256         SendToICS("sought\n"); // should this be "sought all"?
2257     } else { // issue challenge based on clicked ad
2258         int dist = 10000; int i, closest = 0, second = 0;
2259         for(i=0; i<nrOfSeekAds; i++) {
2260             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2261             if(d < dist) { dist = d; closest = i; }
2262             second += (d - zList[i] < 120); // count in-range ads
2263             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2264         }
2265         if(dist < 120) {
2266             char buf[MSG_SIZ];
2267             second = (second > 1);
2268             if(displayed != closest || second != lastSecond) {
2269                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2270                 lastSecond = second; displayed = closest;
2271             }
2272             if(click == Press) {
2273                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2274                 lastDown = closest;
2275                 return TRUE;
2276             } // on press 'hit', only show info
2277             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2278             sprintf(buf, "play %d\n", seekNrList[closest]);
2279             SendToICS(ics_prefix);
2280             SendToICS(buf);
2281             return TRUE; // let incoming board of started game pop down the graph
2282         } else if(click == Release) { // release 'miss' is ignored
2283             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2284             if(moving == 2) { // right up-click
2285                 nrOfSeekAds = 0; // refresh graph
2286                 soughtPending = TRUE;
2287                 SendToICS(ics_prefix);
2288                 SendToICS("sought\n"); // should this be "sought all"?
2289             }
2290             return TRUE;
2291         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2292         // press miss or release hit 'pop down' seek graph
2293         seekGraphUp = FALSE;
2294         DrawPosition(TRUE, NULL);
2295     }
2296     return TRUE;
2297 }
2298
2299 void
2300 read_from_ics(isr, closure, data, count, error)
2301      InputSourceRef isr;
2302      VOIDSTAR closure;
2303      char *data;
2304      int count;
2305      int error;
2306 {
2307 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2308 #define STARTED_NONE 0
2309 #define STARTED_MOVES 1
2310 #define STARTED_BOARD 2
2311 #define STARTED_OBSERVE 3
2312 #define STARTED_HOLDINGS 4
2313 #define STARTED_CHATTER 5
2314 #define STARTED_COMMENT 6
2315 #define STARTED_MOVES_NOHIDE 7
2316     
2317     static int started = STARTED_NONE;
2318     static char parse[20000];
2319     static int parse_pos = 0;
2320     static char buf[BUF_SIZE + 1];
2321     static int firstTime = TRUE, intfSet = FALSE;
2322     static ColorClass prevColor = ColorNormal;
2323     static int savingComment = FALSE;
2324     static int cmatch = 0; // continuation sequence match
2325     char *bp;
2326     char str[500];
2327     int i, oldi;
2328     int buf_len;
2329     int next_out;
2330     int tkind;
2331     int backup;    /* [DM] For zippy color lines */
2332     char *p;
2333     char talker[MSG_SIZ]; // [HGM] chat
2334     int channel;
2335
2336     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2337
2338     if (appData.debugMode) {
2339       if (!error) {
2340         fprintf(debugFP, "<ICS: ");
2341         show_bytes(debugFP, data, count);
2342         fprintf(debugFP, "\n");
2343       }
2344     }
2345
2346     if (appData.debugMode) { int f = forwardMostMove;
2347         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2348                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2349                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2350     }
2351     if (count > 0) {
2352         /* If last read ended with a partial line that we couldn't parse,
2353            prepend it to the new read and try again. */
2354         if (leftover_len > 0) {
2355             for (i=0; i<leftover_len; i++)
2356               buf[i] = buf[leftover_start + i];
2357         }
2358
2359     /* copy new characters into the buffer */
2360     bp = buf + leftover_len;
2361     buf_len=leftover_len;
2362     for (i=0; i<count; i++)
2363     {
2364         // ignore these
2365         if (data[i] == '\r')
2366             continue;
2367
2368         // join lines split by ICS?
2369         if (!appData.noJoin)
2370         {
2371             /*
2372                 Joining just consists of finding matches against the
2373                 continuation sequence, and discarding that sequence
2374                 if found instead of copying it.  So, until a match
2375                 fails, there's nothing to do since it might be the
2376                 complete sequence, and thus, something we don't want
2377                 copied.
2378             */
2379             if (data[i] == cont_seq[cmatch])
2380             {
2381                 cmatch++;
2382                 if (cmatch == strlen(cont_seq))
2383                 {
2384                     cmatch = 0; // complete match.  just reset the counter
2385
2386                     /*
2387                         it's possible for the ICS to not include the space
2388                         at the end of the last word, making our [correct]
2389                         join operation fuse two separate words.  the server
2390                         does this when the space occurs at the width setting.
2391                     */
2392                     if (!buf_len || buf[buf_len-1] != ' ')
2393                     {
2394                         *bp++ = ' ';
2395                         buf_len++;
2396                     }
2397                 }
2398                 continue;
2399             }
2400             else if (cmatch)
2401             {
2402                 /*
2403                     match failed, so we have to copy what matched before
2404                     falling through and copying this character.  In reality,
2405                     this will only ever be just the newline character, but
2406                     it doesn't hurt to be precise.
2407                 */
2408                 strncpy(bp, cont_seq, cmatch);
2409                 bp += cmatch;
2410                 buf_len += cmatch;
2411                 cmatch = 0;
2412             }
2413         }
2414
2415         // copy this char
2416         *bp++ = data[i];
2417         buf_len++;
2418     }
2419
2420         buf[buf_len] = NULLCHAR;
2421 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2422         next_out = 0;
2423         leftover_start = 0;
2424         
2425         i = 0;
2426         while (i < buf_len) {
2427             /* Deal with part of the TELNET option negotiation
2428                protocol.  We refuse to do anything beyond the
2429                defaults, except that we allow the WILL ECHO option,
2430                which ICS uses to turn off password echoing when we are
2431                directly connected to it.  We reject this option
2432                if localLineEditing mode is on (always on in xboard)
2433                and we are talking to port 23, which might be a real
2434                telnet server that will try to keep WILL ECHO on permanently.
2435              */
2436             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2437                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2438                 unsigned char option;
2439                 oldi = i;
2440                 switch ((unsigned char) buf[++i]) {
2441                   case TN_WILL:
2442                     if (appData.debugMode)
2443                       fprintf(debugFP, "\n<WILL ");
2444                     switch (option = (unsigned char) buf[++i]) {
2445                       case TN_ECHO:
2446                         if (appData.debugMode)
2447                           fprintf(debugFP, "ECHO ");
2448                         /* Reply only if this is a change, according
2449                            to the protocol rules. */
2450                         if (remoteEchoOption) break;
2451                         if (appData.localLineEditing &&
2452                             atoi(appData.icsPort) == TN_PORT) {
2453                             TelnetRequest(TN_DONT, TN_ECHO);
2454                         } else {
2455                             EchoOff();
2456                             TelnetRequest(TN_DO, TN_ECHO);
2457                             remoteEchoOption = TRUE;
2458                         }
2459                         break;
2460                       default:
2461                         if (appData.debugMode)
2462                           fprintf(debugFP, "%d ", option);
2463                         /* Whatever this is, we don't want it. */
2464                         TelnetRequest(TN_DONT, option);
2465                         break;
2466                     }
2467                     break;
2468                   case TN_WONT:
2469                     if (appData.debugMode)
2470                       fprintf(debugFP, "\n<WONT ");
2471                     switch (option = (unsigned char) buf[++i]) {
2472                       case TN_ECHO:
2473                         if (appData.debugMode)
2474                           fprintf(debugFP, "ECHO ");
2475                         /* Reply only if this is a change, according
2476                            to the protocol rules. */
2477                         if (!remoteEchoOption) break;
2478                         EchoOn();
2479                         TelnetRequest(TN_DONT, TN_ECHO);
2480                         remoteEchoOption = FALSE;
2481                         break;
2482                       default:
2483                         if (appData.debugMode)
2484                           fprintf(debugFP, "%d ", (unsigned char) option);
2485                         /* Whatever this is, it must already be turned
2486                            off, because we never agree to turn on
2487                            anything non-default, so according to the
2488                            protocol rules, we don't reply. */
2489                         break;
2490                     }
2491                     break;
2492                   case TN_DO:
2493                     if (appData.debugMode)
2494                       fprintf(debugFP, "\n<DO ");
2495                     switch (option = (unsigned char) buf[++i]) {
2496                       default:
2497                         /* Whatever this is, we refuse to do it. */
2498                         if (appData.debugMode)
2499                           fprintf(debugFP, "%d ", option);
2500                         TelnetRequest(TN_WONT, option);
2501                         break;
2502                     }
2503                     break;
2504                   case TN_DONT:
2505                     if (appData.debugMode)
2506                       fprintf(debugFP, "\n<DONT ");
2507                     switch (option = (unsigned char) buf[++i]) {
2508                       default:
2509                         if (appData.debugMode)
2510                           fprintf(debugFP, "%d ", option);
2511                         /* Whatever this is, we are already not doing
2512                            it, because we never agree to do anything
2513                            non-default, so according to the protocol
2514                            rules, we don't reply. */
2515                         break;
2516                     }
2517                     break;
2518                   case TN_IAC:
2519                     if (appData.debugMode)
2520                       fprintf(debugFP, "\n<IAC ");
2521                     /* Doubled IAC; pass it through */
2522                     i--;
2523                     break;
2524                   default:
2525                     if (appData.debugMode)
2526                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2527                     /* Drop all other telnet commands on the floor */
2528                     break;
2529                 }
2530                 if (oldi > next_out)
2531                   SendToPlayer(&buf[next_out], oldi - next_out);
2532                 if (++i > next_out)
2533                   next_out = i;
2534                 continue;
2535             }
2536                 
2537             /* OK, this at least will *usually* work */
2538             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2539                 loggedOn = TRUE;
2540             }
2541             
2542             if (loggedOn && !intfSet) {
2543                 if (ics_type == ICS_ICC) {
2544                   sprintf(str,
2545                           "/set-quietly interface %s\n/set-quietly style 12\n",
2546                           programVersion);
2547                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2548                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2549                 } else if (ics_type == ICS_CHESSNET) {
2550                   sprintf(str, "/style 12\n");
2551                 } else {
2552                   strcpy(str, "alias $ @\n$set interface ");
2553                   strcat(str, programVersion);
2554                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2555                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2556                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2557 #ifdef WIN32
2558                   strcat(str, "$iset nohighlight 1\n");
2559 #endif
2560                   strcat(str, "$iset lock 1\n$style 12\n");
2561                 }
2562                 SendToICS(str);
2563                 NotifyFrontendLogin();
2564                 intfSet = TRUE;
2565             }
2566
2567             if (started == STARTED_COMMENT) {
2568                 /* Accumulate characters in comment */
2569                 parse[parse_pos++] = buf[i];
2570                 if (buf[i] == '\n') {
2571                     parse[parse_pos] = NULLCHAR;
2572                     if(chattingPartner>=0) {
2573                         char mess[MSG_SIZ];
2574                         sprintf(mess, "%s%s", talker, parse);
2575                         OutputChatMessage(chattingPartner, mess);
2576                         chattingPartner = -1;
2577                         next_out = i+1; // [HGM] suppress printing in ICS window
2578                     } else
2579                     if(!suppressKibitz) // [HGM] kibitz
2580                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2581                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2582                         int nrDigit = 0, nrAlph = 0, j;
2583                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2584                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2585                         parse[parse_pos] = NULLCHAR;
2586                         // try to be smart: if it does not look like search info, it should go to
2587                         // ICS interaction window after all, not to engine-output window.
2588                         for(j=0; j<parse_pos; j++) { // count letters and digits
2589                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2590                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2591                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2592                         }
2593                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2594                             int depth=0; float score;
2595                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2596                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2597                                 pvInfoList[forwardMostMove-1].depth = depth;
2598                                 pvInfoList[forwardMostMove-1].score = 100*score;
2599                             }
2600                             OutputKibitz(suppressKibitz, parse);
2601                         } else {
2602                             char tmp[MSG_SIZ];
2603                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2604                             SendToPlayer(tmp, strlen(tmp));
2605                         }
2606                         next_out = i+1; // [HGM] suppress printing in ICS window
2607                     }
2608                     started = STARTED_NONE;
2609                 } else {
2610                     /* Don't match patterns against characters in comment */
2611                     i++;
2612                     continue;
2613                 }
2614             }
2615             if (started == STARTED_CHATTER) {
2616                 if (buf[i] != '\n') {
2617                     /* Don't match patterns against characters in chatter */
2618                     i++;
2619                     continue;
2620                 }
2621                 started = STARTED_NONE;
2622                 if(suppressKibitz) next_out = i+1;
2623             }
2624
2625             /* Kludge to deal with rcmd protocol */
2626             if (firstTime && looking_at(buf, &i, "\001*")) {
2627                 DisplayFatalError(&buf[1], 0, 1);
2628                 continue;
2629             } else {
2630                 firstTime = FALSE;
2631             }
2632
2633             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2634                 ics_type = ICS_ICC;
2635                 ics_prefix = "/";
2636                 if (appData.debugMode)
2637                   fprintf(debugFP, "ics_type %d\n", ics_type);
2638                 continue;
2639             }
2640             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2641                 ics_type = ICS_FICS;
2642                 ics_prefix = "$";
2643                 if (appData.debugMode)
2644                   fprintf(debugFP, "ics_type %d\n", ics_type);
2645                 continue;
2646             }
2647             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2648                 ics_type = ICS_CHESSNET;
2649                 ics_prefix = "/";
2650                 if (appData.debugMode)
2651                   fprintf(debugFP, "ics_type %d\n", ics_type);
2652                 continue;
2653             }
2654
2655             if (!loggedOn &&
2656                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2657                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2658                  looking_at(buf, &i, "will be \"*\""))) {
2659               strcpy(ics_handle, star_match[0]);
2660               continue;
2661             }
2662
2663             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2664               char buf[MSG_SIZ];
2665               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2666               DisplayIcsInteractionTitle(buf);
2667               have_set_title = TRUE;
2668             }
2669
2670             /* skip finger notes */
2671             if (started == STARTED_NONE &&
2672                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2673                  (buf[i] == '1' && buf[i+1] == '0')) &&
2674                 buf[i+2] == ':' && buf[i+3] == ' ') {
2675               started = STARTED_CHATTER;
2676               i += 3;
2677               continue;
2678             }
2679
2680             oldi = i;
2681             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2682             if(appData.seekGraph) {
2683                 if(soughtPending && MatchSoughtLine(buf+i)) {
2684                     i = strstr(buf+i, "rated") - buf;
2685                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2686                     next_out = leftover_start = i;
2687                     started = STARTED_CHATTER;
2688                     suppressKibitz = TRUE;
2689                     continue;
2690                 }
2691                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2692                         && looking_at(buf, &i, "* ads displayed")) {
2693                     soughtPending = FALSE;
2694                     seekGraphUp = TRUE;
2695                     DrawSeekGraph();
2696                     continue;
2697                 }
2698                 if(appData.autoRefresh) {
2699                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2700                         int s = (ics_type == ICS_ICC); // ICC format differs
2701                         if(seekGraphUp)
2702                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2703                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2704                         looking_at(buf, &i, "*% "); // eat prompt
2705                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2706                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2707                         next_out = i; // suppress
2708                         continue;
2709                     }
2710                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2711                         char *p = star_match[0];
2712                         while(*p) {
2713                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2714                             while(*p && *p++ != ' '); // next
2715                         }
2716                         looking_at(buf, &i, "*% "); // eat prompt
2717                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2718                         next_out = i;
2719                         continue;
2720                     }
2721                 }
2722             }
2723
2724             /* skip formula vars */
2725             if (started == STARTED_NONE &&
2726                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2727               started = STARTED_CHATTER;
2728               i += 3;
2729               continue;
2730             }
2731
2732             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2733             if (appData.autoKibitz && started == STARTED_NONE && 
2734                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2735                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2736                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2737                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2738                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2739                         suppressKibitz = TRUE;
2740                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2741                         next_out = i;
2742                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2743                                 && (gameMode == IcsPlayingWhite)) ||
2744                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2745                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2746                             started = STARTED_CHATTER; // own kibitz we simply discard
2747                         else {
2748                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2749                             parse_pos = 0; parse[0] = NULLCHAR;
2750                             savingComment = TRUE;
2751                             suppressKibitz = gameMode != IcsObserving ? 2 :
2752                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2753                         } 
2754                         continue;
2755                 } else
2756                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2757                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2758                          && atoi(star_match[0])) {
2759                     // suppress the acknowledgements of our own autoKibitz
2760                     char *p;
2761                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2762                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2763                     SendToPlayer(star_match[0], strlen(star_match[0]));
2764                     if(looking_at(buf, &i, "*% ")) // eat prompt
2765                         suppressKibitz = FALSE;
2766                     next_out = i;
2767                     continue;
2768                 }
2769             } // [HGM] kibitz: end of patch
2770
2771             // [HGM] chat: intercept tells by users for which we have an open chat window
2772             channel = -1;
2773             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2774                                            looking_at(buf, &i, "* whispers:") ||
2775                                            looking_at(buf, &i, "* kibitzes:") ||
2776                                            looking_at(buf, &i, "* shouts:") ||
2777                                            looking_at(buf, &i, "* c-shouts:") ||
2778                                            looking_at(buf, &i, "--> * ") ||
2779                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2780                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2781                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2782                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2783                 int p;
2784                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2785                 chattingPartner = -1;
2786
2787                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2788                 for(p=0; p<MAX_CHAT; p++) {
2789                     if(channel == atoi(chatPartner[p])) {
2790                     talker[0] = '['; strcat(talker, "] ");
2791                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2792                     chattingPartner = p; break;
2793                     }
2794                 } else
2795                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2796                 for(p=0; p<MAX_CHAT; p++) {
2797                     if(!strcmp("kibitzes", chatPartner[p])) {
2798                         talker[0] = '['; strcat(talker, "] ");
2799                         chattingPartner = p; break;
2800                     }
2801                 } else
2802                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2803                 for(p=0; p<MAX_CHAT; p++) {
2804                     if(!strcmp("whispers", chatPartner[p])) {
2805                         talker[0] = '['; strcat(talker, "] ");
2806                         chattingPartner = p; break;
2807                     }
2808                 } else
2809                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2810                   if(buf[i-8] == '-' && buf[i-3] == 't')
2811                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2812                     if(!strcmp("c-shouts", chatPartner[p])) {
2813                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2814                         chattingPartner = p; break;
2815                     }
2816                   }
2817                   if(chattingPartner < 0)
2818                   for(p=0; p<MAX_CHAT; p++) {
2819                     if(!strcmp("shouts", chatPartner[p])) {
2820                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2821                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2822                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2823                         chattingPartner = p; break;
2824                     }
2825                   }
2826                 }
2827                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2828                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2829                     talker[0] = 0; Colorize(ColorTell, FALSE);
2830                     chattingPartner = p; break;
2831                 }
2832                 if(chattingPartner<0) i = oldi; else {
2833                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2834                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2835                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2836                     started = STARTED_COMMENT;
2837                     parse_pos = 0; parse[0] = NULLCHAR;
2838                     savingComment = 3 + chattingPartner; // counts as TRUE
2839                     suppressKibitz = TRUE;
2840                     continue;
2841                 }
2842             } // [HGM] chat: end of patch
2843
2844             if (appData.zippyTalk || appData.zippyPlay) {
2845                 /* [DM] Backup address for color zippy lines */
2846                 backup = i;
2847 #if ZIPPY
2848                if (loggedOn == TRUE)
2849                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2850                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2851 #endif
2852             } // [DM] 'else { ' deleted
2853                 if (
2854                     /* Regular tells and says */
2855                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2856                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2857                     looking_at(buf, &i, "* says: ") ||
2858                     /* Don't color "message" or "messages" output */
2859                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2860                     looking_at(buf, &i, "*. * at *:*: ") ||
2861                     looking_at(buf, &i, "--* (*:*): ") ||
2862                     /* Message notifications (same color as tells) */
2863                     looking_at(buf, &i, "* has left a message ") ||
2864                     looking_at(buf, &i, "* just sent you a message:\n") ||
2865                     /* Whispers and kibitzes */
2866                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2867                     looking_at(buf, &i, "* kibitzes: ") ||
2868                     /* Channel tells */
2869                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2870
2871                   if (tkind == 1 && strchr(star_match[0], ':')) {
2872                       /* Avoid "tells you:" spoofs in channels */
2873                      tkind = 3;
2874                   }
2875                   if (star_match[0][0] == NULLCHAR ||
2876                       strchr(star_match[0], ' ') ||
2877                       (tkind == 3 && strchr(star_match[1], ' '))) {
2878                     /* Reject bogus matches */
2879                     i = oldi;
2880                   } else {
2881                     if (appData.colorize) {
2882                       if (oldi > next_out) {
2883                         SendToPlayer(&buf[next_out], oldi - next_out);
2884                         next_out = oldi;
2885                       }
2886                       switch (tkind) {
2887                       case 1:
2888                         Colorize(ColorTell, FALSE);
2889                         curColor = ColorTell;
2890                         break;
2891                       case 2:
2892                         Colorize(ColorKibitz, FALSE);
2893                         curColor = ColorKibitz;
2894                         break;
2895                       case 3:
2896                         p = strrchr(star_match[1], '(');
2897                         if (p == NULL) {
2898                           p = star_match[1];
2899                         } else {
2900                           p++;
2901                         }
2902                         if (atoi(p) == 1) {
2903                           Colorize(ColorChannel1, FALSE);
2904                           curColor = ColorChannel1;
2905                         } else {
2906                           Colorize(ColorChannel, FALSE);
2907                           curColor = ColorChannel;
2908                         }
2909                         break;
2910                       case 5:
2911                         curColor = ColorNormal;
2912                         break;
2913                       }
2914                     }
2915                     if (started == STARTED_NONE && appData.autoComment &&
2916                         (gameMode == IcsObserving ||
2917                          gameMode == IcsPlayingWhite ||
2918                          gameMode == IcsPlayingBlack)) {
2919                       parse_pos = i - oldi;
2920                       memcpy(parse, &buf[oldi], parse_pos);
2921                       parse[parse_pos] = NULLCHAR;
2922                       started = STARTED_COMMENT;
2923                       savingComment = TRUE;
2924                     } else {
2925                       started = STARTED_CHATTER;
2926                       savingComment = FALSE;
2927                     }
2928                     loggedOn = TRUE;
2929                     continue;
2930                   }
2931                 }
2932
2933                 if (looking_at(buf, &i, "* s-shouts: ") ||
2934                     looking_at(buf, &i, "* c-shouts: ")) {
2935                     if (appData.colorize) {
2936                         if (oldi > next_out) {
2937                             SendToPlayer(&buf[next_out], oldi - next_out);
2938                             next_out = oldi;
2939                         }
2940                         Colorize(ColorSShout, FALSE);
2941                         curColor = ColorSShout;
2942                     }
2943                     loggedOn = TRUE;
2944                     started = STARTED_CHATTER;
2945                     continue;
2946                 }
2947
2948                 if (looking_at(buf, &i, "--->")) {
2949                     loggedOn = TRUE;
2950                     continue;
2951                 }
2952
2953                 if (looking_at(buf, &i, "* shouts: ") ||
2954                     looking_at(buf, &i, "--> ")) {
2955                     if (appData.colorize) {
2956                         if (oldi > next_out) {
2957                             SendToPlayer(&buf[next_out], oldi - next_out);
2958                             next_out = oldi;
2959                         }
2960                         Colorize(ColorShout, FALSE);
2961                         curColor = ColorShout;
2962                     }
2963                     loggedOn = TRUE;
2964                     started = STARTED_CHATTER;
2965                     continue;
2966                 }
2967
2968                 if (looking_at( buf, &i, "Challenge:")) {
2969                     if (appData.colorize) {
2970                         if (oldi > next_out) {
2971                             SendToPlayer(&buf[next_out], oldi - next_out);
2972                             next_out = oldi;
2973                         }
2974                         Colorize(ColorChallenge, FALSE);
2975                         curColor = ColorChallenge;
2976                     }
2977                     loggedOn = TRUE;
2978                     continue;
2979                 }
2980
2981                 if (looking_at(buf, &i, "* offers you") ||
2982                     looking_at(buf, &i, "* offers to be") ||
2983                     looking_at(buf, &i, "* would like to") ||
2984                     looking_at(buf, &i, "* requests to") ||
2985                     looking_at(buf, &i, "Your opponent offers") ||
2986                     looking_at(buf, &i, "Your opponent requests")) {
2987
2988                     if (appData.colorize) {
2989                         if (oldi > next_out) {
2990                             SendToPlayer(&buf[next_out], oldi - next_out);
2991                             next_out = oldi;
2992                         }
2993                         Colorize(ColorRequest, FALSE);
2994                         curColor = ColorRequest;
2995                     }
2996                     continue;
2997                 }
2998
2999                 if (looking_at(buf, &i, "* (*) seeking")) {
3000                     if (appData.colorize) {
3001                         if (oldi > next_out) {
3002                             SendToPlayer(&buf[next_out], oldi - next_out);
3003                             next_out = oldi;
3004                         }
3005                         Colorize(ColorSeek, FALSE);
3006                         curColor = ColorSeek;
3007                     }
3008                     continue;
3009             }
3010
3011             if (looking_at(buf, &i, "\\   ")) {
3012                 if (prevColor != ColorNormal) {
3013                     if (oldi > next_out) {
3014                         SendToPlayer(&buf[next_out], oldi - next_out);
3015                         next_out = oldi;
3016                     }
3017                     Colorize(prevColor, TRUE);
3018                     curColor = prevColor;
3019                 }
3020                 if (savingComment) {
3021                     parse_pos = i - oldi;
3022                     memcpy(parse, &buf[oldi], parse_pos);
3023                     parse[parse_pos] = NULLCHAR;
3024                     started = STARTED_COMMENT;
3025                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3026                         chattingPartner = savingComment - 3; // kludge to remember the box
3027                 } else {
3028                     started = STARTED_CHATTER;
3029                 }
3030                 continue;
3031             }
3032
3033             if (looking_at(buf, &i, "Black Strength :") ||
3034                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3035                 looking_at(buf, &i, "<10>") ||
3036                 looking_at(buf, &i, "#@#")) {
3037                 /* Wrong board style */
3038                 loggedOn = TRUE;
3039                 SendToICS(ics_prefix);
3040                 SendToICS("set style 12\n");
3041                 SendToICS(ics_prefix);
3042                 SendToICS("refresh\n");
3043                 continue;
3044             }
3045             
3046             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3047                 ICSInitScript();
3048                 have_sent_ICS_logon = 1;
3049                 continue;
3050             }
3051               
3052             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3053                 (looking_at(buf, &i, "\n<12> ") ||
3054                  looking_at(buf, &i, "<12> "))) {
3055                 loggedOn = TRUE;
3056                 if (oldi > next_out) {
3057                     SendToPlayer(&buf[next_out], oldi - next_out);
3058                 }
3059                 next_out = i;
3060                 started = STARTED_BOARD;
3061                 parse_pos = 0;
3062                 continue;
3063             }
3064
3065             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3066                 looking_at(buf, &i, "<b1> ")) {
3067                 if (oldi > next_out) {
3068                     SendToPlayer(&buf[next_out], oldi - next_out);
3069                 }
3070                 next_out = i;
3071                 started = STARTED_HOLDINGS;
3072                 parse_pos = 0;
3073                 continue;
3074             }
3075
3076             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3077                 loggedOn = TRUE;
3078                 /* Header for a move list -- first line */
3079
3080                 switch (ics_getting_history) {
3081                   case H_FALSE:
3082                     switch (gameMode) {
3083                       case IcsIdle:
3084                       case BeginningOfGame:
3085                         /* User typed "moves" or "oldmoves" while we
3086                            were idle.  Pretend we asked for these
3087                            moves and soak them up so user can step
3088                            through them and/or save them.
3089                            */
3090                         Reset(FALSE, TRUE);
3091                         gameMode = IcsObserving;
3092                         ModeHighlight();
3093                         ics_gamenum = -1;
3094                         ics_getting_history = H_GOT_UNREQ_HEADER;
3095                         break;
3096                       case EditGame: /*?*/
3097                       case EditPosition: /*?*/
3098                         /* Should above feature work in these modes too? */
3099                         /* For now it doesn't */
3100                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3101                         break;
3102                       default:
3103                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3104                         break;
3105                     }
3106                     break;
3107                   case H_REQUESTED:
3108                     /* Is this the right one? */
3109                     if (gameInfo.white && gameInfo.black &&
3110                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3111                         strcmp(gameInfo.black, star_match[2]) == 0) {
3112                         /* All is well */
3113                         ics_getting_history = H_GOT_REQ_HEADER;
3114                     }
3115                     break;
3116                   case H_GOT_REQ_HEADER:
3117                   case H_GOT_UNREQ_HEADER:
3118                   case H_GOT_UNWANTED_HEADER:
3119                   case H_GETTING_MOVES:
3120                     /* Should not happen */
3121                     DisplayError(_("Error gathering move list: two headers"), 0);
3122                     ics_getting_history = H_FALSE;
3123                     break;
3124                 }
3125
3126                 /* Save player ratings into gameInfo if needed */
3127                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3128                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3129                     (gameInfo.whiteRating == -1 ||
3130                      gameInfo.blackRating == -1)) {
3131
3132                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3133                     gameInfo.blackRating = string_to_rating(star_match[3]);
3134                     if (appData.debugMode)
3135                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3136                               gameInfo.whiteRating, gameInfo.blackRating);
3137                 }
3138                 continue;
3139             }
3140
3141             if (looking_at(buf, &i,
3142               "* * match, initial time: * minute*, increment: * second")) {
3143                 /* Header for a move list -- second line */
3144                 /* Initial board will follow if this is a wild game */
3145                 if (gameInfo.event != NULL) free(gameInfo.event);
3146                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3147                 gameInfo.event = StrSave(str);
3148                 /* [HGM] we switched variant. Translate boards if needed. */
3149                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3150                 continue;
3151             }
3152
3153             if (looking_at(buf, &i, "Move  ")) {
3154                 /* Beginning of a move list */
3155                 switch (ics_getting_history) {
3156                   case H_FALSE:
3157                     /* Normally should not happen */
3158                     /* Maybe user hit reset while we were parsing */
3159                     break;
3160                   case H_REQUESTED:
3161                     /* Happens if we are ignoring a move list that is not
3162                      * the one we just requested.  Common if the user
3163                      * tries to observe two games without turning off
3164                      * getMoveList */
3165                     break;
3166                   case H_GETTING_MOVES:
3167                     /* Should not happen */
3168                     DisplayError(_("Error gathering move list: nested"), 0);
3169                     ics_getting_history = H_FALSE;
3170                     break;
3171                   case H_GOT_REQ_HEADER:
3172                     ics_getting_history = H_GETTING_MOVES;
3173                     started = STARTED_MOVES;
3174                     parse_pos = 0;
3175                     if (oldi > next_out) {
3176                         SendToPlayer(&buf[next_out], oldi - next_out);
3177                     }
3178                     break;
3179                   case H_GOT_UNREQ_HEADER:
3180                     ics_getting_history = H_GETTING_MOVES;
3181                     started = STARTED_MOVES_NOHIDE;
3182                     parse_pos = 0;
3183                     break;
3184                   case H_GOT_UNWANTED_HEADER:
3185                     ics_getting_history = H_FALSE;
3186                     break;
3187                 }
3188                 continue;
3189             }                           
3190             
3191             if (looking_at(buf, &i, "% ") ||
3192                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3193                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3194                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3195                     soughtPending = FALSE;
3196                     seekGraphUp = TRUE;
3197                     DrawSeekGraph();
3198                 }
3199                 if(suppressKibitz) next_out = i;
3200                 savingComment = FALSE;
3201                 suppressKibitz = 0;
3202                 switch (started) {
3203                   case STARTED_MOVES:
3204                   case STARTED_MOVES_NOHIDE:
3205                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3206                     parse[parse_pos + i - oldi] = NULLCHAR;
3207                     ParseGameHistory(parse);
3208 #if ZIPPY
3209                     if (appData.zippyPlay && first.initDone) {
3210                         FeedMovesToProgram(&first, forwardMostMove);
3211                         if (gameMode == IcsPlayingWhite) {
3212                             if (WhiteOnMove(forwardMostMove)) {
3213                                 if (first.sendTime) {
3214                                   if (first.useColors) {
3215                                     SendToProgram("black\n", &first); 
3216                                   }
3217                                   SendTimeRemaining(&first, TRUE);
3218                                 }
3219                                 if (first.useColors) {
3220                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3221                                 }
3222                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3223                                 first.maybeThinking = TRUE;
3224                             } else {
3225                                 if (first.usePlayother) {
3226                                   if (first.sendTime) {
3227                                     SendTimeRemaining(&first, TRUE);
3228                                   }
3229                                   SendToProgram("playother\n", &first);
3230                                   firstMove = FALSE;
3231                                 } else {
3232                                   firstMove = TRUE;
3233                                 }
3234                             }
3235                         } else if (gameMode == IcsPlayingBlack) {
3236                             if (!WhiteOnMove(forwardMostMove)) {
3237                                 if (first.sendTime) {
3238                                   if (first.useColors) {
3239                                     SendToProgram("white\n", &first);
3240                                   }
3241                                   SendTimeRemaining(&first, FALSE);
3242                                 }
3243                                 if (first.useColors) {
3244                                   SendToProgram("black\n", &first);
3245                                 }
3246                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3247                                 first.maybeThinking = TRUE;
3248                             } else {
3249                                 if (first.usePlayother) {
3250                                   if (first.sendTime) {
3251                                     SendTimeRemaining(&first, FALSE);
3252                                   }
3253                                   SendToProgram("playother\n", &first);
3254                                   firstMove = FALSE;
3255                                 } else {
3256                                   firstMove = TRUE;
3257                                 }
3258                             }
3259                         }                       
3260                     }
3261 #endif
3262                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3263                         /* Moves came from oldmoves or moves command
3264                            while we weren't doing anything else.
3265                            */
3266                         currentMove = forwardMostMove;
3267                         ClearHighlights();/*!!could figure this out*/
3268                         flipView = appData.flipView;
3269                         DrawPosition(TRUE, boards[currentMove]);
3270                         DisplayBothClocks();
3271                         sprintf(str, "%s vs. %s",
3272                                 gameInfo.white, gameInfo.black);
3273                         DisplayTitle(str);
3274                         gameMode = IcsIdle;
3275                     } else {
3276                         /* Moves were history of an active game */
3277                         if (gameInfo.resultDetails != NULL) {
3278                             free(gameInfo.resultDetails);
3279                             gameInfo.resultDetails = NULL;
3280                         }
3281                     }
3282                     HistorySet(parseList, backwardMostMove,
3283                                forwardMostMove, currentMove-1);
3284                     DisplayMove(currentMove - 1);
3285                     if (started == STARTED_MOVES) next_out = i;
3286                     started = STARTED_NONE;
3287                     ics_getting_history = H_FALSE;
3288                     break;
3289
3290                   case STARTED_OBSERVE:
3291                     started = STARTED_NONE;
3292                     SendToICS(ics_prefix);
3293                     SendToICS("refresh\n");
3294                     break;
3295
3296                   default:
3297                     break;
3298                 }
3299                 if(bookHit) { // [HGM] book: simulate book reply
3300                     static char bookMove[MSG_SIZ]; // a bit generous?
3301
3302                     programStats.nodes = programStats.depth = programStats.time = 
3303                     programStats.score = programStats.got_only_move = 0;
3304                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3305
3306                     strcpy(bookMove, "move ");
3307                     strcat(bookMove, bookHit);
3308                     HandleMachineMove(bookMove, &first);
3309                 }
3310                 continue;
3311             }
3312             
3313             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3314                  started == STARTED_HOLDINGS ||
3315                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3316                 /* Accumulate characters in move list or board */
3317                 parse[parse_pos++] = buf[i];
3318             }
3319             
3320             /* Start of game messages.  Mostly we detect start of game
3321                when the first board image arrives.  On some versions
3322                of the ICS, though, we need to do a "refresh" after starting
3323                to observe in order to get the current board right away. */
3324             if (looking_at(buf, &i, "Adding game * to observation list")) {
3325                 started = STARTED_OBSERVE;
3326                 continue;
3327             }
3328
3329             /* Handle auto-observe */
3330             if (appData.autoObserve &&
3331                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3332                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3333                 char *player;
3334                 /* Choose the player that was highlighted, if any. */
3335                 if (star_match[0][0] == '\033' ||
3336                     star_match[1][0] != '\033') {
3337                     player = star_match[0];
3338                 } else {
3339                     player = star_match[2];
3340                 }
3341                 sprintf(str, "%sobserve %s\n",
3342                         ics_prefix, StripHighlightAndTitle(player));
3343                 SendToICS(str);
3344
3345                 /* Save ratings from notify string */
3346                 strcpy(player1Name, star_match[0]);
3347                 player1Rating = string_to_rating(star_match[1]);
3348                 strcpy(player2Name, star_match[2]);
3349                 player2Rating = string_to_rating(star_match[3]);
3350
3351                 if (appData.debugMode)
3352                   fprintf(debugFP, 
3353                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3354                           player1Name, player1Rating,
3355                           player2Name, player2Rating);
3356
3357                 continue;
3358             }
3359
3360             /* Deal with automatic examine mode after a game,
3361                and with IcsObserving -> IcsExamining transition */
3362             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3363                 looking_at(buf, &i, "has made you an examiner of game *")) {
3364
3365                 int gamenum = atoi(star_match[0]);
3366                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3367                     gamenum == ics_gamenum) {
3368                     /* We were already playing or observing this game;
3369                        no need to refetch history */
3370                     gameMode = IcsExamining;
3371                     if (pausing) {
3372                         pauseExamForwardMostMove = forwardMostMove;
3373                     } else if (currentMove < forwardMostMove) {
3374                         ForwardInner(forwardMostMove);
3375                     }
3376                 } else {
3377                     /* I don't think this case really can happen */
3378                     SendToICS(ics_prefix);
3379                     SendToICS("refresh\n");
3380                 }
3381                 continue;
3382             }    
3383             
3384             /* Error messages */
3385 //          if (ics_user_moved) {
3386             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3387                 if (looking_at(buf, &i, "Illegal move") ||
3388                     looking_at(buf, &i, "Not a legal move") ||
3389                     looking_at(buf, &i, "Your king is in check") ||
3390                     looking_at(buf, &i, "It isn't your turn") ||
3391                     looking_at(buf, &i, "It is not your move")) {
3392                     /* Illegal move */
3393                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3394                         currentMove = forwardMostMove-1;
3395                         DisplayMove(currentMove - 1); /* before DMError */
3396                         DrawPosition(FALSE, boards[currentMove]);
3397                         SwitchClocks(forwardMostMove-1); // [HGM] race
3398                         DisplayBothClocks();
3399                     }
3400                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3401                     ics_user_moved = 0;
3402                     continue;
3403                 }
3404             }
3405
3406             if (looking_at(buf, &i, "still have time") ||
3407                 looking_at(buf, &i, "not out of time") ||
3408                 looking_at(buf, &i, "either player is out of time") ||
3409                 looking_at(buf, &i, "has timeseal; checking")) {
3410                 /* We must have called his flag a little too soon */
3411                 whiteFlag = blackFlag = FALSE;
3412                 continue;
3413             }
3414
3415             if (looking_at(buf, &i, "added * seconds to") ||
3416                 looking_at(buf, &i, "seconds were added to")) {
3417                 /* Update the clocks */
3418                 SendToICS(ics_prefix);
3419                 SendToICS("refresh\n");
3420                 continue;
3421             }
3422
3423             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3424                 ics_clock_paused = TRUE;
3425                 StopClocks();
3426                 continue;
3427             }
3428
3429             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3430                 ics_clock_paused = FALSE;
3431                 StartClocks();
3432                 continue;
3433             }
3434
3435             /* Grab player ratings from the Creating: message.
3436                Note we have to check for the special case when
3437                the ICS inserts things like [white] or [black]. */
3438             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3439                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3440                 /* star_matches:
3441                    0    player 1 name (not necessarily white)
3442                    1    player 1 rating
3443                    2    empty, white, or black (IGNORED)
3444                    3    player 2 name (not necessarily black)
3445                    4    player 2 rating
3446                    
3447                    The names/ratings are sorted out when the game
3448                    actually starts (below).
3449                 */
3450                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3451                 player1Rating = string_to_rating(star_match[1]);
3452                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3453                 player2Rating = string_to_rating(star_match[4]);
3454
3455                 if (appData.debugMode)
3456                   fprintf(debugFP, 
3457                           "Ratings from 'Creating:' %s %d, %s %d\n",
3458                           player1Name, player1Rating,
3459                           player2Name, player2Rating);
3460
3461                 continue;
3462             }
3463             
3464             /* Improved generic start/end-of-game messages */
3465             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3466                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3467                 /* If tkind == 0: */
3468                 /* star_match[0] is the game number */
3469                 /*           [1] is the white player's name */
3470                 /*           [2] is the black player's name */
3471                 /* For end-of-game: */
3472                 /*           [3] is the reason for the game end */
3473                 /*           [4] is a PGN end game-token, preceded by " " */
3474                 /* For start-of-game: */
3475                 /*           [3] begins with "Creating" or "Continuing" */
3476                 /*           [4] is " *" or empty (don't care). */
3477                 int gamenum = atoi(star_match[0]);
3478                 char *whitename, *blackname, *why, *endtoken;
3479                 ChessMove endtype = (ChessMove) 0;
3480
3481                 if (tkind == 0) {
3482                   whitename = star_match[1];
3483                   blackname = star_match[2];
3484                   why = star_match[3];
3485                   endtoken = star_match[4];
3486                 } else {
3487                   whitename = star_match[1];
3488                   blackname = star_match[3];
3489                   why = star_match[5];
3490                   endtoken = star_match[6];
3491                 }
3492
3493                 /* Game start messages */
3494                 if (strncmp(why, "Creating ", 9) == 0 ||
3495                     strncmp(why, "Continuing ", 11) == 0) {
3496                     gs_gamenum = gamenum;
3497                     strcpy(gs_kind, strchr(why, ' ') + 1);
3498                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3499 #if ZIPPY
3500                     if (appData.zippyPlay) {
3501                         ZippyGameStart(whitename, blackname);
3502                     }
3503 #endif /*ZIPPY*/
3504                     partnerBoardValid = FALSE; // [HGM] bughouse
3505                     continue;
3506                 }
3507
3508                 /* Game end messages */
3509                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3510                     ics_gamenum != gamenum) {
3511                     continue;
3512                 }
3513                 while (endtoken[0] == ' ') endtoken++;
3514                 switch (endtoken[0]) {
3515                   case '*':
3516                   default:
3517                     endtype = GameUnfinished;
3518                     break;
3519                   case '0':
3520                     endtype = BlackWins;
3521                     break;
3522                   case '1':
3523                     if (endtoken[1] == '/')
3524                       endtype = GameIsDrawn;
3525                     else
3526                       endtype = WhiteWins;
3527                     break;
3528                 }
3529                 GameEnds(endtype, why, GE_ICS);
3530 #if ZIPPY
3531                 if (appData.zippyPlay && first.initDone) {
3532                     ZippyGameEnd(endtype, why);
3533                     if (first.pr == NULL) {
3534                       /* Start the next process early so that we'll
3535                          be ready for the next challenge */
3536                       StartChessProgram(&first);
3537                     }
3538                     /* Send "new" early, in case this command takes
3539                        a long time to finish, so that we'll be ready
3540                        for the next challenge. */
3541                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3542                     Reset(TRUE, TRUE);
3543                 }
3544 #endif /*ZIPPY*/
3545                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3546                 continue;
3547             }
3548
3549             if (looking_at(buf, &i, "Removing game * from observation") ||
3550                 looking_at(buf, &i, "no longer observing game *") ||
3551                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3552                 if (gameMode == IcsObserving &&
3553                     atoi(star_match[0]) == ics_gamenum)
3554                   {
3555                       /* icsEngineAnalyze */
3556                       if (appData.icsEngineAnalyze) {
3557                             ExitAnalyzeMode();
3558                             ModeHighlight();
3559                       }
3560                       StopClocks();
3561                       gameMode = IcsIdle;
3562                       ics_gamenum = -1;
3563                       ics_user_moved = FALSE;
3564                   }
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "no longer examining game *")) {
3569                 if (gameMode == IcsExamining &&
3570                     atoi(star_match[0]) == ics_gamenum)
3571                   {
3572                       gameMode = IcsIdle;
3573                       ics_gamenum = -1;
3574                       ics_user_moved = FALSE;
3575                   }
3576                 continue;
3577             }
3578
3579             /* Advance leftover_start past any newlines we find,
3580                so only partial lines can get reparsed */
3581             if (looking_at(buf, &i, "\n")) {
3582                 prevColor = curColor;
3583                 if (curColor != ColorNormal) {
3584                     if (oldi > next_out) {
3585                         SendToPlayer(&buf[next_out], oldi - next_out);
3586                         next_out = oldi;
3587                     }
3588                     Colorize(ColorNormal, FALSE);
3589                     curColor = ColorNormal;
3590                 }
3591                 if (started == STARTED_BOARD) {
3592                     started = STARTED_NONE;
3593                     parse[parse_pos] = NULLCHAR;
3594                     ParseBoard12(parse);
3595                     ics_user_moved = 0;
3596
3597                     /* Send premove here */
3598                     if (appData.premove) {
3599                       char str[MSG_SIZ];
3600                       if (currentMove == 0 &&
3601                           gameMode == IcsPlayingWhite &&
3602                           appData.premoveWhite) {
3603                         sprintf(str, "%s\n", appData.premoveWhiteText);
3604                         if (appData.debugMode)
3605                           fprintf(debugFP, "Sending premove:\n");
3606                         SendToICS(str);
3607                       } else if (currentMove == 1 &&
3608                                  gameMode == IcsPlayingBlack &&
3609                                  appData.premoveBlack) {
3610                         sprintf(str, "%s\n", appData.premoveBlackText);
3611                         if (appData.debugMode)
3612                           fprintf(debugFP, "Sending premove:\n");
3613                         SendToICS(str);
3614                       } else if (gotPremove) {
3615                         gotPremove = 0;
3616                         ClearPremoveHighlights();
3617                         if (appData.debugMode)
3618                           fprintf(debugFP, "Sending premove:\n");
3619                           UserMoveEvent(premoveFromX, premoveFromY, 
3620                                         premoveToX, premoveToY, 
3621                                         premovePromoChar);
3622                       }
3623                     }
3624
3625                     /* Usually suppress following prompt */
3626                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3627                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3628                         if (looking_at(buf, &i, "*% ")) {
3629                             savingComment = FALSE;
3630                             suppressKibitz = 0;
3631                         }
3632                     }
3633                     next_out = i;
3634                 } else if (started == STARTED_HOLDINGS) {
3635                     int gamenum;
3636                     char new_piece[MSG_SIZ];
3637                     started = STARTED_NONE;
3638                     parse[parse_pos] = NULLCHAR;
3639                     if (appData.debugMode)
3640                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3641                                                         parse, currentMove);
3642                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3643                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3644                         if (gameInfo.variant == VariantNormal) {
3645                           /* [HGM] We seem to switch variant during a game!
3646                            * Presumably no holdings were displayed, so we have
3647                            * to move the position two files to the right to
3648                            * create room for them!
3649                            */
3650                           VariantClass newVariant;
3651                           switch(gameInfo.boardWidth) { // base guess on board width
3652                                 case 9:  newVariant = VariantShogi; break;
3653                                 case 10: newVariant = VariantGreat; break;
3654                                 default: newVariant = VariantCrazyhouse; break;
3655                           }
3656                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3657                           /* Get a move list just to see the header, which
3658                              will tell us whether this is really bug or zh */
3659                           if (ics_getting_history == H_FALSE) {
3660                             ics_getting_history = H_REQUESTED;
3661                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3662                             SendToICS(str);
3663                           }
3664                         }
3665                         new_piece[0] = NULLCHAR;
3666                         sscanf(parse, "game %d white [%s black [%s <- %s",
3667                                &gamenum, white_holding, black_holding,
3668                                new_piece);
3669                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3670                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3671                         /* [HGM] copy holdings to board holdings area */
3672                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3673                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3674                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3675 #if ZIPPY
3676                         if (appData.zippyPlay && first.initDone) {
3677                             ZippyHoldings(white_holding, black_holding,
3678                                           new_piece);
3679                         }
3680 #endif /*ZIPPY*/
3681                         if (tinyLayout || smallLayout) {
3682                             char wh[16], bh[16];
3683                             PackHolding(wh, white_holding);
3684                             PackHolding(bh, black_holding);
3685                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3686                                     gameInfo.white, gameInfo.black);
3687                         } else {
3688                             sprintf(str, "%s [%s] vs. %s [%s]",
3689                                     gameInfo.white, white_holding,
3690                                     gameInfo.black, black_holding);
3691                         }
3692                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3693                         DrawPosition(FALSE, boards[currentMove]);
3694                         DisplayTitle(str);
3695                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3696                         sscanf(parse, "game %d white [%s black [%s <- %s",
3697                                &gamenum, white_holding, black_holding,
3698                                new_piece);
3699                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3700                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3701                         /* [HGM] copy holdings to partner-board holdings area */
3702                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3703                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3704                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3705                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3706                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3707                       }
3708                     }
3709                     /* Suppress following prompt */
3710                     if (looking_at(buf, &i, "*% ")) {
3711                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3712                         savingComment = FALSE;
3713                         suppressKibitz = 0;
3714                     }
3715                     next_out = i;
3716                 }
3717                 continue;
3718             }
3719
3720             i++;                /* skip unparsed character and loop back */
3721         }
3722         
3723         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3724 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3725 //          SendToPlayer(&buf[next_out], i - next_out);
3726             started != STARTED_HOLDINGS && leftover_start > next_out) {
3727             SendToPlayer(&buf[next_out], leftover_start - next_out);
3728             next_out = i;
3729         }
3730         
3731         leftover_len = buf_len - leftover_start;
3732         /* if buffer ends with something we couldn't parse,
3733            reparse it after appending the next read */
3734         
3735     } else if (count == 0) {
3736         RemoveInputSource(isr);
3737         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3738     } else {
3739         DisplayFatalError(_("Error reading from ICS"), error, 1);
3740     }
3741 }
3742
3743
3744 /* Board style 12 looks like this:
3745    
3746    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3747    
3748  * The "<12> " is stripped before it gets to this routine.  The two
3749  * trailing 0's (flip state and clock ticking) are later addition, and
3750  * some chess servers may not have them, or may have only the first.
3751  * Additional trailing fields may be added in the future.  
3752  */
3753
3754 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3755
3756 #define RELATION_OBSERVING_PLAYED    0
3757 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3758 #define RELATION_PLAYING_MYMOVE      1
3759 #define RELATION_PLAYING_NOTMYMOVE  -1
3760 #define RELATION_EXAMINING           2
3761 #define RELATION_ISOLATED_BOARD     -3
3762 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3763
3764 void
3765 ParseBoard12(string)
3766      char *string;
3767
3768     GameMode newGameMode;
3769     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3770     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3771     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3772     char to_play, board_chars[200];
3773     char move_str[500], str[500], elapsed_time[500];
3774     char black[32], white[32];
3775     Board board;
3776     int prevMove = currentMove;
3777     int ticking = 2;
3778     ChessMove moveType;
3779     int fromX, fromY, toX, toY;
3780     char promoChar;
3781     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3782     char *bookHit = NULL; // [HGM] book
3783     Boolean weird = FALSE, reqFlag = FALSE;
3784
3785     fromX = fromY = toX = toY = -1;
3786     
3787     newGame = FALSE;
3788
3789     if (appData.debugMode)
3790       fprintf(debugFP, _("Parsing board: %s\n"), string);
3791
3792     move_str[0] = NULLCHAR;
3793     elapsed_time[0] = NULLCHAR;
3794     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3795         int  i = 0, j;
3796         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3797             if(string[i] == ' ') { ranks++; files = 0; }
3798             else files++;
3799             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3800             i++;
3801         }
3802         for(j = 0; j <i; j++) board_chars[j] = string[j];
3803         board_chars[i] = '\0';
3804         string += i + 1;
3805     }
3806     n = sscanf(string, PATTERN, &to_play, &double_push,
3807                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3808                &gamenum, white, black, &relation, &basetime, &increment,
3809                &white_stren, &black_stren, &white_time, &black_time,
3810                &moveNum, str, elapsed_time, move_str, &ics_flip,
3811                &ticking);
3812
3813     if (n < 21) {
3814         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3815         DisplayError(str, 0);
3816         return;
3817     }
3818
3819     /* Convert the move number to internal form */
3820     moveNum = (moveNum - 1) * 2;
3821     if (to_play == 'B') moveNum++;
3822     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3823       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3824                         0, 1);
3825       return;
3826     }
3827     
3828     switch (relation) {
3829       case RELATION_OBSERVING_PLAYED:
3830       case RELATION_OBSERVING_STATIC:
3831         if (gamenum == -1) {
3832             /* Old ICC buglet */
3833             relation = RELATION_OBSERVING_STATIC;
3834         }
3835         newGameMode = IcsObserving;
3836         break;
3837       case RELATION_PLAYING_MYMOVE:
3838       case RELATION_PLAYING_NOTMYMOVE:
3839         newGameMode =
3840           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3841             IcsPlayingWhite : IcsPlayingBlack;
3842         break;
3843       case RELATION_EXAMINING:
3844         newGameMode = IcsExamining;
3845         break;
3846       case RELATION_ISOLATED_BOARD:
3847       default:
3848         /* Just display this board.  If user was doing something else,
3849            we will forget about it until the next board comes. */ 
3850         newGameMode = IcsIdle;
3851         break;
3852       case RELATION_STARTING_POSITION:
3853         newGameMode = gameMode;
3854         break;
3855     }
3856     
3857     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3858          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3859       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3860       char *toSqr;
3861       for (k = 0; k < ranks; k++) {
3862         for (j = 0; j < files; j++)
3863           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3864         if(gameInfo.holdingsWidth > 1) {
3865              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3866              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3867         }
3868       }
3869       CopyBoard(partnerBoard, board);
3870       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3871         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3872         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3873       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3874       if(toSqr = strchr(str, '-')) {
3875         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3876         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3877       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3878       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3879       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3880       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3881       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3882       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3883                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3884       DisplayMessage(partnerStatus, "");
3885         partnerBoardValid = TRUE;
3886       return;
3887     }
3888
3889     /* Modify behavior for initial board display on move listing
3890        of wild games.
3891        */
3892     switch (ics_getting_history) {
3893       case H_FALSE:
3894       case H_REQUESTED:
3895         break;
3896       case H_GOT_REQ_HEADER:
3897       case H_GOT_UNREQ_HEADER:
3898         /* This is the initial position of the current game */
3899         gamenum = ics_gamenum;
3900         moveNum = 0;            /* old ICS bug workaround */
3901         if (to_play == 'B') {
3902           startedFromSetupPosition = TRUE;
3903           blackPlaysFirst = TRUE;
3904           moveNum = 1;
3905           if (forwardMostMove == 0) forwardMostMove = 1;
3906           if (backwardMostMove == 0) backwardMostMove = 1;
3907           if (currentMove == 0) currentMove = 1;
3908         }
3909         newGameMode = gameMode;
3910         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3911         break;
3912       case H_GOT_UNWANTED_HEADER:
3913         /* This is an initial board that we don't want */
3914         return;
3915       case H_GETTING_MOVES:
3916         /* Should not happen */
3917         DisplayError(_("Error gathering move list: extra board"), 0);
3918         ics_getting_history = H_FALSE;
3919         return;
3920     }
3921
3922    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3923                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3924      /* [HGM] We seem to have switched variant unexpectedly
3925       * Try to guess new variant from board size
3926       */
3927           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3928           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3929           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3930           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3931           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3932           if(!weird) newVariant = VariantNormal;
3933           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3934           /* Get a move list just to see the header, which
3935              will tell us whether this is really bug or zh */
3936           if (ics_getting_history == H_FALSE) {
3937             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3938             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3939             SendToICS(str);
3940           }
3941     }
3942     
3943     /* Take action if this is the first board of a new game, or of a
3944        different game than is currently being displayed.  */
3945     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3946         relation == RELATION_ISOLATED_BOARD) {
3947         
3948         /* Forget the old game and get the history (if any) of the new one */
3949         if (gameMode != BeginningOfGame) {
3950           Reset(TRUE, TRUE);
3951         }
3952         newGame = TRUE;
3953         if (appData.autoRaiseBoard) BoardToTop();
3954         prevMove = -3;
3955         if (gamenum == -1) {
3956             newGameMode = IcsIdle;
3957         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3958                    appData.getMoveList && !reqFlag) {
3959             /* Need to get game history */
3960             ics_getting_history = H_REQUESTED;
3961             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3962             SendToICS(str);
3963         }
3964         
3965         /* Initially flip the board to have black on the bottom if playing
3966            black or if the ICS flip flag is set, but let the user change
3967            it with the Flip View button. */
3968         flipView = appData.autoFlipView ? 
3969           (newGameMode == IcsPlayingBlack) || ics_flip :
3970           appData.flipView;
3971         
3972         /* Done with values from previous mode; copy in new ones */
3973         gameMode = newGameMode;
3974         ModeHighlight();
3975         ics_gamenum = gamenum;
3976         if (gamenum == gs_gamenum) {
3977             int klen = strlen(gs_kind);
3978             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3979             sprintf(str, "ICS %s", gs_kind);
3980             gameInfo.event = StrSave(str);
3981         } else {
3982             gameInfo.event = StrSave("ICS game");
3983         }
3984         gameInfo.site = StrSave(appData.icsHost);
3985         gameInfo.date = PGNDate();
3986         gameInfo.round = StrSave("-");
3987         gameInfo.white = StrSave(white);
3988         gameInfo.black = StrSave(black);
3989         timeControl = basetime * 60 * 1000;
3990         timeControl_2 = 0;
3991         timeIncrement = increment * 1000;
3992         movesPerSession = 0;
3993         gameInfo.timeControl = TimeControlTagValue();
3994         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3995   if (appData.debugMode) {
3996     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3997     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3998     setbuf(debugFP, NULL);
3999   }
4000
4001         gameInfo.outOfBook = NULL;
4002         
4003         /* Do we have the ratings? */
4004         if (strcmp(player1Name, white) == 0 &&
4005             strcmp(player2Name, black) == 0) {
4006             if (appData.debugMode)
4007               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4008                       player1Rating, player2Rating);
4009             gameInfo.whiteRating = player1Rating;
4010             gameInfo.blackRating = player2Rating;
4011         } else if (strcmp(player2Name, white) == 0 &&
4012                    strcmp(player1Name, black) == 0) {
4013             if (appData.debugMode)
4014               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4015                       player2Rating, player1Rating);
4016             gameInfo.whiteRating = player2Rating;
4017             gameInfo.blackRating = player1Rating;
4018         }
4019         player1Name[0] = player2Name[0] = NULLCHAR;
4020
4021         /* Silence shouts if requested */
4022         if (appData.quietPlay &&
4023             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4024             SendToICS(ics_prefix);
4025             SendToICS("set shout 0\n");
4026         }
4027     }
4028     
4029     /* Deal with midgame name changes */
4030     if (!newGame) {
4031         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4032             if (gameInfo.white) free(gameInfo.white);
4033             gameInfo.white = StrSave(white);
4034         }
4035         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4036             if (gameInfo.black) free(gameInfo.black);
4037             gameInfo.black = StrSave(black);
4038         }
4039     }
4040     
4041     /* Throw away game result if anything actually changes in examine mode */
4042     if (gameMode == IcsExamining && !newGame) {
4043         gameInfo.result = GameUnfinished;
4044         if (gameInfo.resultDetails != NULL) {
4045             free(gameInfo.resultDetails);
4046             gameInfo.resultDetails = NULL;
4047         }
4048     }
4049     
4050     /* In pausing && IcsExamining mode, we ignore boards coming
4051        in if they are in a different variation than we are. */
4052     if (pauseExamInvalid) return;
4053     if (pausing && gameMode == IcsExamining) {
4054         if (moveNum <= pauseExamForwardMostMove) {
4055             pauseExamInvalid = TRUE;
4056             forwardMostMove = pauseExamForwardMostMove;
4057             return;
4058         }
4059     }
4060     
4061   if (appData.debugMode) {
4062     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4063   }
4064     /* Parse the board */
4065     for (k = 0; k < ranks; k++) {
4066       for (j = 0; j < files; j++)
4067         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4068       if(gameInfo.holdingsWidth > 1) {
4069            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4070            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4071       }
4072     }
4073     CopyBoard(boards[moveNum], board);
4074     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4075     if (moveNum == 0) {
4076         startedFromSetupPosition =
4077           !CompareBoards(board, initialPosition);
4078         if(startedFromSetupPosition)
4079             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4080     }
4081
4082     /* [HGM] Set castling rights. Take the outermost Rooks,
4083        to make it also work for FRC opening positions. Note that board12
4084        is really defective for later FRC positions, as it has no way to
4085        indicate which Rook can castle if they are on the same side of King.
4086        For the initial position we grant rights to the outermost Rooks,
4087        and remember thos rights, and we then copy them on positions
4088        later in an FRC game. This means WB might not recognize castlings with
4089        Rooks that have moved back to their original position as illegal,
4090        but in ICS mode that is not its job anyway.
4091     */
4092     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4093     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4094
4095         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4096             if(board[0][i] == WhiteRook) j = i;
4097         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4098         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4099             if(board[0][i] == WhiteRook) j = i;
4100         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4101         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4102             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4103         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4104         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4105             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4106         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4107
4108         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4109         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4110             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4111         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4112             if(board[BOARD_HEIGHT-1][k] == bKing)
4113                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4114         if(gameInfo.variant == VariantTwoKings) {
4115             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4116             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4117             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4118         }
4119     } else { int r;
4120         r = boards[moveNum][CASTLING][0] = initialRights[0];
4121         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4122         r = boards[moveNum][CASTLING][1] = initialRights[1];
4123         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4124         r = boards[moveNum][CASTLING][3] = initialRights[3];
4125         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4126         r = boards[moveNum][CASTLING][4] = initialRights[4];
4127         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4128         /* wildcastle kludge: always assume King has rights */
4129         r = boards[moveNum][CASTLING][2] = initialRights[2];
4130         r = boards[moveNum][CASTLING][5] = initialRights[5];
4131     }
4132     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4133     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4134
4135     
4136     if (ics_getting_history == H_GOT_REQ_HEADER ||
4137         ics_getting_history == H_GOT_UNREQ_HEADER) {
4138         /* This was an initial position from a move list, not
4139            the current position */
4140         return;
4141     }
4142     
4143     /* Update currentMove and known move number limits */
4144     newMove = newGame || moveNum > forwardMostMove;
4145
4146     if (newGame) {
4147         forwardMostMove = backwardMostMove = currentMove = moveNum;
4148         if (gameMode == IcsExamining && moveNum == 0) {
4149           /* Workaround for ICS limitation: we are not told the wild
4150              type when starting to examine a game.  But if we ask for
4151              the move list, the move list header will tell us */
4152             ics_getting_history = H_REQUESTED;
4153             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4154             SendToICS(str);
4155         }
4156     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4157                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4158 #if ZIPPY
4159         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4160         /* [HGM] applied this also to an engine that is silently watching        */
4161         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4162             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4163             gameInfo.variant == currentlyInitializedVariant) {
4164           takeback = forwardMostMove - moveNum;
4165           for (i = 0; i < takeback; i++) {
4166             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4167             SendToProgram("undo\n", &first);
4168           }
4169         }
4170 #endif
4171
4172         forwardMostMove = moveNum;
4173         if (!pausing || currentMove > forwardMostMove)
4174           currentMove = forwardMostMove;
4175     } else {
4176         /* New part of history that is not contiguous with old part */ 
4177         if (pausing && gameMode == IcsExamining) {
4178             pauseExamInvalid = TRUE;
4179             forwardMostMove = pauseExamForwardMostMove;
4180             return;
4181         }
4182         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4183 #if ZIPPY
4184             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4185                 // [HGM] when we will receive the move list we now request, it will be
4186                 // fed to the engine from the first move on. So if the engine is not
4187                 // in the initial position now, bring it there.
4188                 InitChessProgram(&first, 0);
4189             }
4190 #endif
4191             ics_getting_history = H_REQUESTED;
4192             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4193             SendToICS(str);
4194         }
4195         forwardMostMove = backwardMostMove = currentMove = moveNum;
4196     }
4197     
4198     /* Update the clocks */
4199     if (strchr(elapsed_time, '.')) {
4200       /* Time is in ms */
4201       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4202       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4203     } else {
4204       /* Time is in seconds */
4205       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4206       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4207     }
4208       
4209
4210 #if ZIPPY
4211     if (appData.zippyPlay && newGame &&
4212         gameMode != IcsObserving && gameMode != IcsIdle &&
4213         gameMode != IcsExamining)
4214       ZippyFirstBoard(moveNum, basetime, increment);
4215 #endif
4216     
4217     /* Put the move on the move list, first converting
4218        to canonical algebraic form. */
4219     if (moveNum > 0) {
4220   if (appData.debugMode) {
4221     if (appData.debugMode) { int f = forwardMostMove;
4222         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4223                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4224                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4225     }
4226     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4227     fprintf(debugFP, "moveNum = %d\n", moveNum);
4228     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4229     setbuf(debugFP, NULL);
4230   }
4231         if (moveNum <= backwardMostMove) {
4232             /* We don't know what the board looked like before
4233                this move.  Punt. */
4234             strcpy(parseList[moveNum - 1], move_str);
4235             strcat(parseList[moveNum - 1], " ");
4236             strcat(parseList[moveNum - 1], elapsed_time);
4237             moveList[moveNum - 1][0] = NULLCHAR;
4238         } else if (strcmp(move_str, "none") == 0) {
4239             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4240             /* Again, we don't know what the board looked like;
4241                this is really the start of the game. */
4242             parseList[moveNum - 1][0] = NULLCHAR;
4243             moveList[moveNum - 1][0] = NULLCHAR;
4244             backwardMostMove = moveNum;
4245             startedFromSetupPosition = TRUE;
4246             fromX = fromY = toX = toY = -1;
4247         } else {
4248           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4249           //                 So we parse the long-algebraic move string in stead of the SAN move
4250           int valid; char buf[MSG_SIZ], *prom;
4251
4252           // str looks something like "Q/a1-a2"; kill the slash
4253           if(str[1] == '/') 
4254                 sprintf(buf, "%c%s", str[0], str+2);
4255           else  strcpy(buf, str); // might be castling
4256           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4257                 strcat(buf, prom); // long move lacks promo specification!
4258           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4259                 if(appData.debugMode) 
4260                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4261                 strcpy(move_str, buf);
4262           }
4263           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4264                                 &fromX, &fromY, &toX, &toY, &promoChar)
4265                || ParseOneMove(buf, moveNum - 1, &moveType,
4266                                 &fromX, &fromY, &toX, &toY, &promoChar);
4267           // end of long SAN patch
4268           if (valid) {
4269             (void) CoordsToAlgebraic(boards[moveNum - 1],
4270                                      PosFlags(moveNum - 1),
4271                                      fromY, fromX, toY, toX, promoChar,
4272                                      parseList[moveNum-1]);
4273             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4274               case MT_NONE:
4275               case MT_STALEMATE:
4276               default:
4277                 break;
4278               case MT_CHECK:
4279                 if(gameInfo.variant != VariantShogi)
4280                     strcat(parseList[moveNum - 1], "+");
4281                 break;
4282               case MT_CHECKMATE:
4283               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4284                 strcat(parseList[moveNum - 1], "#");
4285                 break;
4286             }
4287             strcat(parseList[moveNum - 1], " ");
4288             strcat(parseList[moveNum - 1], elapsed_time);
4289             /* currentMoveString is set as a side-effect of ParseOneMove */
4290             strcpy(moveList[moveNum - 1], currentMoveString);
4291             strcat(moveList[moveNum - 1], "\n");
4292           } else {
4293             /* Move from ICS was illegal!?  Punt. */
4294   if (appData.debugMode) {
4295     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4296     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4297   }
4298             strcpy(parseList[moveNum - 1], move_str);
4299             strcat(parseList[moveNum - 1], " ");
4300             strcat(parseList[moveNum - 1], elapsed_time);
4301             moveList[moveNum - 1][0] = NULLCHAR;
4302             fromX = fromY = toX = toY = -1;
4303           }
4304         }
4305   if (appData.debugMode) {
4306     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4307     setbuf(debugFP, NULL);
4308   }
4309
4310 #if ZIPPY
4311         /* Send move to chess program (BEFORE animating it). */
4312         if (appData.zippyPlay && !newGame && newMove && 
4313            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4314
4315             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4316                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4317                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4318                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4319                             move_str);
4320                     DisplayError(str, 0);
4321                 } else {
4322                     if (first.sendTime) {
4323                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4324                     }
4325                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4326                     if (firstMove && !bookHit) {
4327                         firstMove = FALSE;
4328                         if (first.useColors) {
4329                           SendToProgram(gameMode == IcsPlayingWhite ?
4330                                         "white\ngo\n" :
4331                                         "black\ngo\n", &first);
4332                         } else {
4333                           SendToProgram("go\n", &first);
4334                         }
4335                         first.maybeThinking = TRUE;
4336                     }
4337                 }
4338             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4339               if (moveList[moveNum - 1][0] == NULLCHAR) {
4340                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4341                 DisplayError(str, 0);
4342               } else {
4343                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4344                 SendMoveToProgram(moveNum - 1, &first);
4345               }
4346             }
4347         }
4348 #endif
4349     }
4350
4351     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4352         /* If move comes from a remote source, animate it.  If it
4353            isn't remote, it will have already been animated. */
4354         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4355             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4356         }
4357         if (!pausing && appData.highlightLastMove) {
4358             SetHighlights(fromX, fromY, toX, toY);
4359         }
4360     }
4361     
4362     /* Start the clocks */
4363     whiteFlag = blackFlag = FALSE;
4364     appData.clockMode = !(basetime == 0 && increment == 0);
4365     if (ticking == 0) {
4366       ics_clock_paused = TRUE;
4367       StopClocks();
4368     } else if (ticking == 1) {
4369       ics_clock_paused = FALSE;
4370     }
4371     if (gameMode == IcsIdle ||
4372         relation == RELATION_OBSERVING_STATIC ||
4373         relation == RELATION_EXAMINING ||
4374         ics_clock_paused)
4375       DisplayBothClocks();
4376     else
4377       StartClocks();
4378     
4379     /* Display opponents and material strengths */
4380     if (gameInfo.variant != VariantBughouse &&
4381         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4382         if (tinyLayout || smallLayout) {
4383             if(gameInfo.variant == VariantNormal)
4384                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4385                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4386                     basetime, increment);
4387             else
4388                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4389                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4390                     basetime, increment, (int) gameInfo.variant);
4391         } else {
4392             if(gameInfo.variant == VariantNormal)
4393                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4394                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4395                     basetime, increment);
4396             else
4397                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4398                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4399                     basetime, increment, VariantName(gameInfo.variant));
4400         }
4401         DisplayTitle(str);
4402   if (appData.debugMode) {
4403     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4404   }
4405     }
4406
4407
4408     /* Display the board */
4409     if (!pausing && !appData.noGUI) {
4410       
4411       if (appData.premove)
4412           if (!gotPremove || 
4413              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4414              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4415               ClearPremoveHighlights();
4416
4417       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4418         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4419       DrawPosition(j, boards[currentMove]);
4420
4421       DisplayMove(moveNum - 1);
4422       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4423             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4424               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4425         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4426       }
4427     }
4428
4429     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4430 #if ZIPPY
4431     if(bookHit) { // [HGM] book: simulate book reply
4432         static char bookMove[MSG_SIZ]; // a bit generous?
4433
4434         programStats.nodes = programStats.depth = programStats.time = 
4435         programStats.score = programStats.got_only_move = 0;
4436         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4437
4438         strcpy(bookMove, "move ");
4439         strcat(bookMove, bookHit);
4440         HandleMachineMove(bookMove, &first);
4441     }
4442 #endif
4443 }
4444
4445 void
4446 GetMoveListEvent()
4447 {
4448     char buf[MSG_SIZ];
4449     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4450         ics_getting_history = H_REQUESTED;
4451         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4452         SendToICS(buf);
4453     }
4454 }
4455
4456 void
4457 AnalysisPeriodicEvent(force)
4458      int force;
4459 {
4460     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4461          && !force) || !appData.periodicUpdates)
4462       return;
4463
4464     /* Send . command to Crafty to collect stats */
4465     SendToProgram(".\n", &first);
4466
4467     /* Don't send another until we get a response (this makes
4468        us stop sending to old Crafty's which don't understand
4469        the "." command (sending illegal cmds resets node count & time,
4470        which looks bad)) */
4471     programStats.ok_to_send = 0;
4472 }
4473
4474 void ics_update_width(new_width)
4475         int new_width;
4476 {
4477         ics_printf("set width %d\n", new_width);
4478 }
4479
4480 void
4481 SendMoveToProgram(moveNum, cps)
4482      int moveNum;
4483      ChessProgramState *cps;
4484 {
4485     char buf[MSG_SIZ];
4486
4487     if (cps->useUsermove) {
4488       SendToProgram("usermove ", cps);
4489     }
4490     if (cps->useSAN) {
4491       char *space;
4492       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4493         int len = space - parseList[moveNum];
4494         memcpy(buf, parseList[moveNum], len);
4495         buf[len++] = '\n';
4496         buf[len] = NULLCHAR;
4497       } else {
4498         sprintf(buf, "%s\n", parseList[moveNum]);
4499       }
4500       SendToProgram(buf, cps);
4501     } else {
4502       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4503         AlphaRank(moveList[moveNum], 4);
4504         SendToProgram(moveList[moveNum], cps);
4505         AlphaRank(moveList[moveNum], 4); // and back
4506       } else
4507       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4508        * the engine. It would be nice to have a better way to identify castle 
4509        * moves here. */
4510       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4511                                                                          && cps->useOOCastle) {
4512         int fromX = moveList[moveNum][0] - AAA; 
4513         int fromY = moveList[moveNum][1] - ONE;
4514         int toX = moveList[moveNum][2] - AAA; 
4515         int toY = moveList[moveNum][3] - ONE;
4516         if((boards[moveNum][fromY][fromX] == WhiteKing 
4517             && boards[moveNum][toY][toX] == WhiteRook)
4518            || (boards[moveNum][fromY][fromX] == BlackKing 
4519                && boards[moveNum][toY][toX] == BlackRook)) {
4520           if(toX > fromX) SendToProgram("O-O\n", cps);
4521           else SendToProgram("O-O-O\n", cps);
4522         }
4523         else SendToProgram(moveList[moveNum], cps);
4524       }
4525       else SendToProgram(moveList[moveNum], cps);
4526       /* End of additions by Tord */
4527     }
4528
4529     /* [HGM] setting up the opening has brought engine in force mode! */
4530     /*       Send 'go' if we are in a mode where machine should play. */
4531     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4532         (gameMode == TwoMachinesPlay   ||
4533 #if ZIPPY
4534          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4535 #endif
4536          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4537         SendToProgram("go\n", cps);
4538   if (appData.debugMode) {
4539     fprintf(debugFP, "(extra)\n");
4540   }
4541     }
4542     setboardSpoiledMachineBlack = 0;
4543 }
4544
4545 void
4546 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4547      ChessMove moveType;
4548      int fromX, fromY, toX, toY;
4549 {
4550     char user_move[MSG_SIZ];
4551
4552     switch (moveType) {
4553       default:
4554         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4555                 (int)moveType, fromX, fromY, toX, toY);
4556         DisplayError(user_move + strlen("say "), 0);
4557         break;
4558       case WhiteKingSideCastle:
4559       case BlackKingSideCastle:
4560       case WhiteQueenSideCastleWild:
4561       case BlackQueenSideCastleWild:
4562       /* PUSH Fabien */
4563       case WhiteHSideCastleFR:
4564       case BlackHSideCastleFR:
4565       /* POP Fabien */
4566         sprintf(user_move, "o-o\n");
4567         break;
4568       case WhiteQueenSideCastle:
4569       case BlackQueenSideCastle:
4570       case WhiteKingSideCastleWild:
4571       case BlackKingSideCastleWild:
4572       /* PUSH Fabien */
4573       case WhiteASideCastleFR:
4574       case BlackASideCastleFR:
4575       /* POP Fabien */
4576         sprintf(user_move, "o-o-o\n");
4577         break;
4578       case WhiteNonPromotion:
4579       case BlackNonPromotion:
4580         sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4581         break;
4582       case WhitePromotionQueen:
4583       case BlackPromotionQueen:
4584       case WhitePromotionRook:
4585       case BlackPromotionRook:
4586       case WhitePromotionBishop:
4587       case BlackPromotionBishop:
4588       case WhitePromotionKnight:
4589       case BlackPromotionKnight:
4590       case WhitePromotionKing:
4591       case BlackPromotionKing:
4592       case WhitePromotionChancellor:
4593       case BlackPromotionChancellor:
4594       case WhitePromotionArchbishop:
4595       case BlackPromotionArchbishop:
4596         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4597             sprintf(user_move, "%c%c%c%c=%c\n",
4598                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4599                 PieceToChar(WhiteFerz));
4600         else if(gameInfo.variant == VariantGreat)
4601             sprintf(user_move, "%c%c%c%c=%c\n",
4602                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4603                 PieceToChar(WhiteMan));
4604         else
4605             sprintf(user_move, "%c%c%c%c=%c\n",
4606                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4607                 PieceToChar(PromoPiece(moveType)));
4608         break;
4609       case WhiteDrop:
4610       case BlackDrop:
4611         sprintf(user_move, "%c@%c%c\n",
4612                 ToUpper(PieceToChar((ChessSquare) fromX)),
4613                 AAA + toX, ONE + toY);
4614         break;
4615       case NormalMove:
4616       case WhiteCapturesEnPassant:
4617       case BlackCapturesEnPassant:
4618       case IllegalMove:  /* could be a variant we don't quite understand */
4619         sprintf(user_move, "%c%c%c%c\n",
4620                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4621         break;
4622     }
4623     SendToICS(user_move);
4624     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4625         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4626 }
4627
4628 void
4629 UploadGameEvent()
4630 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4631     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4632     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4633     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4634         DisplayError("You cannot do this while you are playing or observing", 0);
4635         return;
4636     }
4637     if(gameMode != IcsExamining) { // is this ever not the case?
4638         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4639
4640         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4641             sprintf(command, "match %s", ics_handle);
4642         } else { // on FICS we must first go to general examine mode
4643             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4644         }
4645         if(gameInfo.variant != VariantNormal) {
4646             // try figure out wild number, as xboard names are not always valid on ICS
4647             for(i=1; i<=36; i++) {
4648                 sprintf(buf, "wild/%d", i);
4649                 if(StringToVariant(buf) == gameInfo.variant) break;
4650             }
4651             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4652             else if(i == 22) sprintf(buf, "%s fr\n", command);
4653             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4654         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4655         SendToICS(ics_prefix);
4656         SendToICS(buf);
4657         if(startedFromSetupPosition || backwardMostMove != 0) {
4658           fen = PositionToFEN(backwardMostMove, NULL);
4659           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4660             sprintf(buf, "loadfen %s\n", fen);
4661             SendToICS(buf);
4662           } else { // FICS: everything has to set by separate bsetup commands
4663             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4664             sprintf(buf, "bsetup fen %s\n", fen);
4665             SendToICS(buf);
4666             if(!WhiteOnMove(backwardMostMove)) {
4667                 SendToICS("bsetup tomove black\n");
4668             }
4669             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4670             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4671             SendToICS(buf);
4672             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4673             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4674             SendToICS(buf);
4675             i = boards[backwardMostMove][EP_STATUS];
4676             if(i >= 0) { // set e.p.
4677                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4678                 SendToICS(buf);
4679             }
4680             bsetup++;
4681           }
4682         }
4683       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4684             SendToICS("bsetup done\n"); // switch to normal examining.
4685     }
4686     for(i = backwardMostMove; i<last; i++) {
4687         char buf[20];
4688         sprintf(buf, "%s\n", parseList[i]);
4689         SendToICS(buf);
4690     }
4691     SendToICS(ics_prefix);
4692     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4693 }
4694
4695 void
4696 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4697      int rf, ff, rt, ft;
4698      char promoChar;
4699      char move[7];
4700 {
4701     if (rf == DROP_RANK) {
4702         sprintf(move, "%c@%c%c\n",
4703                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4704     } else {
4705         if (promoChar == 'x' || promoChar == NULLCHAR) {
4706             sprintf(move, "%c%c%c%c\n",
4707                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4708         } else {
4709             sprintf(move, "%c%c%c%c%c\n",
4710                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4711         }
4712     }
4713 }
4714
4715 void
4716 ProcessICSInitScript(f)
4717      FILE *f;
4718 {
4719     char buf[MSG_SIZ];
4720
4721     while (fgets(buf, MSG_SIZ, f)) {
4722         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4723     }
4724
4725     fclose(f);
4726 }
4727
4728
4729 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4730 void
4731 AlphaRank(char *move, int n)
4732 {
4733 //    char *p = move, c; int x, y;
4734
4735     if (appData.debugMode) {
4736         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4737     }
4738
4739     if(move[1]=='*' && 
4740        move[2]>='0' && move[2]<='9' &&
4741        move[3]>='a' && move[3]<='x'    ) {
4742         move[1] = '@';
4743         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4744         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4745     } else
4746     if(move[0]>='0' && move[0]<='9' &&
4747        move[1]>='a' && move[1]<='x' &&
4748        move[2]>='0' && move[2]<='9' &&
4749        move[3]>='a' && move[3]<='x'    ) {
4750         /* input move, Shogi -> normal */
4751         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4752         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4753         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4754         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4755     } else
4756     if(move[1]=='@' &&
4757        move[3]>='0' && move[3]<='9' &&
4758        move[2]>='a' && move[2]<='x'    ) {
4759         move[1] = '*';
4760         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4761         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4762     } else
4763     if(
4764        move[0]>='a' && move[0]<='x' &&
4765        move[3]>='0' && move[3]<='9' &&
4766        move[2]>='a' && move[2]<='x'    ) {
4767          /* output move, normal -> Shogi */
4768         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4769         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4770         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4771         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4772         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4773     }
4774     if (appData.debugMode) {
4775         fprintf(debugFP, "   out = '%s'\n", move);
4776     }
4777 }
4778
4779 char yy_textstr[8000];
4780
4781 /* Parser for moves from gnuchess, ICS, or user typein box */
4782 Boolean
4783 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4784      char *move;
4785      int moveNum;
4786      ChessMove *moveType;
4787      int *fromX, *fromY, *toX, *toY;
4788      char *promoChar;
4789 {       
4790     if (appData.debugMode) {
4791         fprintf(debugFP, "move to parse: %s\n", move);
4792     }
4793     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4794
4795     switch (*moveType) {
4796       case WhitePromotionChancellor:
4797       case BlackPromotionChancellor:
4798       case WhitePromotionArchbishop:
4799       case BlackPromotionArchbishop:
4800       case WhitePromotionQueen:
4801       case BlackPromotionQueen:
4802       case WhitePromotionRook:
4803       case BlackPromotionRook:
4804       case WhitePromotionBishop:
4805       case BlackPromotionBishop:
4806       case WhitePromotionKnight:
4807       case BlackPromotionKnight:
4808       case WhitePromotionKing:
4809       case BlackPromotionKing:
4810       case WhiteNonPromotion:
4811       case BlackNonPromotion:
4812       case NormalMove:
4813       case WhiteCapturesEnPassant:
4814       case BlackCapturesEnPassant:
4815       case WhiteKingSideCastle:
4816       case WhiteQueenSideCastle:
4817       case BlackKingSideCastle:
4818       case BlackQueenSideCastle:
4819       case WhiteKingSideCastleWild:
4820       case WhiteQueenSideCastleWild:
4821       case BlackKingSideCastleWild:
4822       case BlackQueenSideCastleWild:
4823       /* Code added by Tord: */
4824       case WhiteHSideCastleFR:
4825       case WhiteASideCastleFR:
4826       case BlackHSideCastleFR:
4827       case BlackASideCastleFR:
4828       /* End of code added by Tord */
4829       case IllegalMove:         /* bug or odd chess variant */
4830         *fromX = currentMoveString[0] - AAA;
4831         *fromY = currentMoveString[1] - ONE;
4832         *toX = currentMoveString[2] - AAA;
4833         *toY = currentMoveString[3] - ONE;
4834         *promoChar = currentMoveString[4];
4835         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4836             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4837     if (appData.debugMode) {
4838         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4839     }
4840             *fromX = *fromY = *toX = *toY = 0;
4841             return FALSE;
4842         }
4843         if (appData.testLegality) {
4844           return (*moveType != IllegalMove);
4845         } else {
4846           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4847                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4848         }
4849
4850       case WhiteDrop:
4851       case BlackDrop:
4852         *fromX = *moveType == WhiteDrop ?
4853           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4854           (int) CharToPiece(ToLower(currentMoveString[0]));
4855         *fromY = DROP_RANK;
4856         *toX = currentMoveString[2] - AAA;
4857         *toY = currentMoveString[3] - ONE;
4858         *promoChar = NULLCHAR;
4859         return TRUE;
4860
4861       case AmbiguousMove:
4862       case ImpossibleMove:
4863       case (ChessMove) 0:       /* end of file */
4864       case ElapsedTime:
4865       case Comment:
4866       case PGNTag:
4867       case NAG:
4868       case WhiteWins:
4869       case BlackWins:
4870       case GameIsDrawn:
4871       default:
4872     if (appData.debugMode) {
4873         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4874     }
4875         /* bug? */
4876         *fromX = *fromY = *toX = *toY = 0;
4877         *promoChar = NULLCHAR;
4878         return FALSE;
4879     }
4880 }
4881
4882
4883 void
4884 ParsePV(char *pv, Boolean storeComments)
4885 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4886   int fromX, fromY, toX, toY; char promoChar;
4887   ChessMove moveType;
4888   Boolean valid;
4889   int nr = 0;
4890
4891   endPV = forwardMostMove;
4892   do {
4893     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4894     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4895     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4896 if(appData.debugMode){
4897 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
4898 }
4899     if(!valid && nr == 0 &&
4900        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4901         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4902         // Hande case where played move is different from leading PV move
4903         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4904         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4905         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4906         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4907           endPV += 2; // if position different, keep this
4908           moveList[endPV-1][0] = fromX + AAA;
4909           moveList[endPV-1][1] = fromY + ONE;
4910           moveList[endPV-1][2] = toX + AAA;
4911           moveList[endPV-1][3] = toY + ONE;
4912           parseList[endPV-1][0] = NULLCHAR;
4913           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4914         }
4915       }
4916     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4917     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4918     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4919     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4920         valid++; // allow comments in PV
4921         continue;
4922     }
4923     nr++;
4924     if(endPV+1 > framePtr) break; // no space, truncate
4925     if(!valid) break;
4926     endPV++;
4927     CopyBoard(boards[endPV], boards[endPV-1]);
4928     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4929     moveList[endPV-1][0] = fromX + AAA;
4930     moveList[endPV-1][1] = fromY + ONE;
4931     moveList[endPV-1][2] = toX + AAA;
4932     moveList[endPV-1][3] = toY + ONE;
4933     if(storeComments)
4934         CoordsToAlgebraic(boards[endPV - 1],
4935                              PosFlags(endPV - 1),
4936                              fromY, fromX, toY, toX, promoChar,
4937                              parseList[endPV - 1]);
4938     else
4939         parseList[endPV-1][0] = NULLCHAR;
4940   } while(valid);
4941   currentMove = endPV;
4942   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4943   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4944                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4945   DrawPosition(TRUE, boards[currentMove]);
4946 }
4947
4948 static int lastX, lastY;
4949
4950 Boolean
4951 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4952 {
4953         int startPV;
4954         char *p;
4955
4956         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4957         lastX = x; lastY = y;
4958         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4959         startPV = index;
4960         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4961         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4962         index = startPV;
4963         do{ while(buf[index] && buf[index] != '\n') index++;
4964         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4965         buf[index] = 0;
4966         ParsePV(buf+startPV, FALSE);
4967         *start = startPV; *end = index-1;
4968         return TRUE;
4969 }
4970
4971 Boolean
4972 LoadPV(int x, int y)
4973 { // called on right mouse click to load PV
4974   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4975   lastX = x; lastY = y;
4976   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4977   return TRUE;
4978 }
4979
4980 void
4981 UnLoadPV()
4982 {
4983   if(endPV < 0) return;
4984   endPV = -1;
4985   currentMove = forwardMostMove;
4986   ClearPremoveHighlights();
4987   DrawPosition(TRUE, boards[currentMove]);
4988 }
4989
4990 void
4991 MovePV(int x, int y, int h)
4992 { // step through PV based on mouse coordinates (called on mouse move)
4993   int margin = h>>3, step = 0;
4994
4995   if(endPV < 0) return;
4996   // we must somehow check if right button is still down (might be released off board!)
4997   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4998   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4999   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5000   if(!step) return;
5001   lastX = x; lastY = y;
5002   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5003   currentMove += step;
5004   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5005   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5006                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5007   DrawPosition(FALSE, boards[currentMove]);
5008 }
5009
5010
5011 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5012 // All positions will have equal probability, but the current method will not provide a unique
5013 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5014 #define DARK 1
5015 #define LITE 2
5016 #define ANY 3
5017
5018 int squaresLeft[4];
5019 int piecesLeft[(int)BlackPawn];
5020 int seed, nrOfShuffles;
5021
5022 void GetPositionNumber()
5023 {       // sets global variable seed
5024         int i;
5025
5026         seed = appData.defaultFrcPosition;
5027         if(seed < 0) { // randomize based on time for negative FRC position numbers
5028                 for(i=0; i<50; i++) seed += random();
5029                 seed = random() ^ random() >> 8 ^ random() << 8;
5030                 if(seed<0) seed = -seed;
5031         }
5032 }
5033
5034 int put(Board board, int pieceType, int rank, int n, int shade)
5035 // put the piece on the (n-1)-th empty squares of the given shade
5036 {
5037         int i;
5038
5039         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5040                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5041                         board[rank][i] = (ChessSquare) pieceType;
5042                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5043                         squaresLeft[ANY]--;
5044                         piecesLeft[pieceType]--; 
5045                         return i;
5046                 }
5047         }
5048         return -1;
5049 }
5050
5051
5052 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5053 // calculate where the next piece goes, (any empty square), and put it there
5054 {
5055         int i;
5056
5057         i = seed % squaresLeft[shade];
5058         nrOfShuffles *= squaresLeft[shade];
5059         seed /= squaresLeft[shade];
5060         put(board, pieceType, rank, i, shade);
5061 }
5062
5063 void AddTwoPieces(Board board, int pieceType, int rank)
5064 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5065 {
5066         int i, n=squaresLeft[ANY], j=n-1, k;
5067
5068         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5069         i = seed % k;  // pick one
5070         nrOfShuffles *= k;
5071         seed /= k;
5072         while(i >= j) i -= j--;
5073         j = n - 1 - j; i += j;
5074         put(board, pieceType, rank, j, ANY);
5075         put(board, pieceType, rank, i, ANY);
5076 }
5077
5078 void SetUpShuffle(Board board, int number)
5079 {
5080         int i, p, first=1;
5081
5082         GetPositionNumber(); nrOfShuffles = 1;
5083
5084         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5085         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5086         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5087
5088         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5089
5090         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5091             p = (int) board[0][i];
5092             if(p < (int) BlackPawn) piecesLeft[p] ++;
5093             board[0][i] = EmptySquare;
5094         }
5095
5096         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5097             // shuffles restricted to allow normal castling put KRR first
5098             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5099                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5100             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5101                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5102             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5103                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5104             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5105                 put(board, WhiteRook, 0, 0, ANY);
5106             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5107         }
5108
5109         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5110             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5111             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5112                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5113                 while(piecesLeft[p] >= 2) {
5114                     AddOnePiece(board, p, 0, LITE);
5115                     AddOnePiece(board, p, 0, DARK);
5116                 }
5117                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5118             }
5119
5120         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5121             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5122             // but we leave King and Rooks for last, to possibly obey FRC restriction
5123             if(p == (int)WhiteRook) continue;
5124             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5125             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5126         }
5127
5128         // now everything is placed, except perhaps King (Unicorn) and Rooks
5129
5130         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5131             // Last King gets castling rights
5132             while(piecesLeft[(int)WhiteUnicorn]) {
5133                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5134                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5135             }
5136
5137             while(piecesLeft[(int)WhiteKing]) {
5138                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5139                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5140             }
5141
5142
5143         } else {
5144             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5145             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5146         }
5147
5148         // Only Rooks can be left; simply place them all
5149         while(piecesLeft[(int)WhiteRook]) {
5150                 i = put(board, WhiteRook, 0, 0, ANY);
5151                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5152                         if(first) {
5153                                 first=0;
5154                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5155                         }
5156                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5157                 }
5158         }
5159         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5160             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5161         }
5162
5163         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5164 }
5165
5166 int SetCharTable( char *table, const char * map )
5167 /* [HGM] moved here from winboard.c because of its general usefulness */
5168 /*       Basically a safe strcpy that uses the last character as King */
5169 {
5170     int result = FALSE; int NrPieces;
5171
5172     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5173                     && NrPieces >= 12 && !(NrPieces&1)) {
5174         int i; /* [HGM] Accept even length from 12 to 34 */
5175
5176         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5177         for( i=0; i<NrPieces/2-1; i++ ) {
5178             table[i] = map[i];
5179             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5180         }
5181         table[(int) WhiteKing]  = map[NrPieces/2-1];
5182         table[(int) BlackKing]  = map[NrPieces-1];
5183
5184         result = TRUE;
5185     }
5186
5187     return result;
5188 }
5189
5190 void Prelude(Board board)
5191 {       // [HGM] superchess: random selection of exo-pieces
5192         int i, j, k; ChessSquare p; 
5193         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5194
5195         GetPositionNumber(); // use FRC position number
5196
5197         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5198             SetCharTable(pieceToChar, appData.pieceToCharTable);
5199             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5200                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5201         }
5202
5203         j = seed%4;                 seed /= 4; 
5204         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5205         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5206         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5207         j = seed%3 + (seed%3 >= j); seed /= 3; 
5208         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5209         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5210         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5211         j = seed%3;                 seed /= 3; 
5212         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5213         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5214         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5215         j = seed%2 + (seed%2 >= j); seed /= 2; 
5216         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5217         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5218         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5219         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5220         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5221         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5222         put(board, exoPieces[0],    0, 0, ANY);
5223         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5224 }
5225
5226 void
5227 InitPosition(redraw)
5228      int redraw;
5229 {
5230     ChessSquare (* pieces)[BOARD_FILES];
5231     int i, j, pawnRow, overrule,
5232     oldx = gameInfo.boardWidth,
5233     oldy = gameInfo.boardHeight,
5234     oldh = gameInfo.holdingsWidth,
5235     oldv = gameInfo.variant;
5236
5237     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5238
5239     /* [AS] Initialize pv info list [HGM] and game status */
5240     {
5241         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5242             pvInfoList[i].depth = 0;
5243             boards[i][EP_STATUS] = EP_NONE;
5244             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5245         }
5246
5247         initialRulePlies = 0; /* 50-move counter start */
5248
5249         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5250         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5251     }
5252
5253     
5254     /* [HGM] logic here is completely changed. In stead of full positions */
5255     /* the initialized data only consist of the two backranks. The switch */
5256     /* selects which one we will use, which is than copied to the Board   */
5257     /* initialPosition, which for the rest is initialized by Pawns and    */
5258     /* empty squares. This initial position is then copied to boards[0],  */
5259     /* possibly after shuffling, so that it remains available.            */
5260
5261     gameInfo.holdingsWidth = 0; /* default board sizes */
5262     gameInfo.boardWidth    = 8;
5263     gameInfo.boardHeight   = 8;
5264     gameInfo.holdingsSize  = 0;
5265     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5266     for(i=0; i<BOARD_FILES-2; i++)
5267       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5268     initialPosition[EP_STATUS] = EP_NONE;
5269     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5270     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5271          SetCharTable(pieceNickName, appData.pieceNickNames);
5272     else SetCharTable(pieceNickName, "............");
5273
5274     switch (gameInfo.variant) {
5275     case VariantFischeRandom:
5276       shuffleOpenings = TRUE;
5277     default:
5278       pieces = FIDEArray;
5279       break;
5280     case VariantShatranj:
5281       pieces = ShatranjArray;
5282       nrCastlingRights = 0;
5283       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5284       break;
5285     case VariantMakruk:
5286       pieces = makrukArray;
5287       nrCastlingRights = 0;
5288       startedFromSetupPosition = TRUE;
5289       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5290       break;
5291     case VariantTwoKings:
5292       pieces = twoKingsArray;
5293       break;
5294     case VariantCapaRandom:
5295       shuffleOpenings = TRUE;
5296     case VariantCapablanca:
5297       pieces = CapablancaArray;
5298       gameInfo.boardWidth = 10;
5299       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5300       break;
5301     case VariantGothic:
5302       pieces = GothicArray;
5303       gameInfo.boardWidth = 10;
5304       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5305       break;
5306     case VariantJanus:
5307       pieces = JanusArray;
5308       gameInfo.boardWidth = 10;
5309       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5310       nrCastlingRights = 6;
5311         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5312         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5313         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5314         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5315         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5316         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5317       break;
5318     case VariantFalcon:
5319       pieces = FalconArray;
5320       gameInfo.boardWidth = 10;
5321       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5322       break;
5323     case VariantXiangqi:
5324       pieces = XiangqiArray;
5325       gameInfo.boardWidth  = 9;
5326       gameInfo.boardHeight = 10;
5327       nrCastlingRights = 0;
5328       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5329       break;
5330     case VariantShogi:
5331       pieces = ShogiArray;
5332       gameInfo.boardWidth  = 9;
5333       gameInfo.boardHeight = 9;
5334       gameInfo.holdingsSize = 7;
5335       nrCastlingRights = 0;
5336       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5337       break;
5338     case VariantCourier:
5339       pieces = CourierArray;
5340       gameInfo.boardWidth  = 12;
5341       nrCastlingRights = 0;
5342       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5343       break;
5344     case VariantKnightmate:
5345       pieces = KnightmateArray;
5346       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5347       break;
5348     case VariantFairy:
5349       pieces = fairyArray;
5350       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5351       break;
5352     case VariantGreat:
5353       pieces = GreatArray;
5354       gameInfo.boardWidth = 10;
5355       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5356       gameInfo.holdingsSize = 8;
5357       break;
5358     case VariantSuper:
5359       pieces = FIDEArray;
5360       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5361       gameInfo.holdingsSize = 8;
5362       startedFromSetupPosition = TRUE;
5363       break;
5364     case VariantCrazyhouse:
5365     case VariantBughouse:
5366       pieces = FIDEArray;
5367       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5368       gameInfo.holdingsSize = 5;
5369       break;
5370     case VariantWildCastle:
5371       pieces = FIDEArray;
5372       /* !!?shuffle with kings guaranteed to be on d or e file */
5373       shuffleOpenings = 1;
5374       break;
5375     case VariantNoCastle:
5376       pieces = FIDEArray;
5377       nrCastlingRights = 0;
5378       /* !!?unconstrained back-rank shuffle */
5379       shuffleOpenings = 1;
5380       break;
5381     }
5382
5383     overrule = 0;
5384     if(appData.NrFiles >= 0) {
5385         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5386         gameInfo.boardWidth = appData.NrFiles;
5387     }
5388     if(appData.NrRanks >= 0) {
5389         gameInfo.boardHeight = appData.NrRanks;
5390     }
5391     if(appData.holdingsSize >= 0) {
5392         i = appData.holdingsSize;
5393         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5394         gameInfo.holdingsSize = i;
5395     }
5396     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5397     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5398         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5399
5400     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5401     if(pawnRow < 1) pawnRow = 1;
5402     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5403
5404     /* User pieceToChar list overrules defaults */
5405     if(appData.pieceToCharTable != NULL)
5406         SetCharTable(pieceToChar, appData.pieceToCharTable);
5407
5408     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5409
5410         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5411             s = (ChessSquare) 0; /* account holding counts in guard band */
5412         for( i=0; i<BOARD_HEIGHT; i++ )
5413             initialPosition[i][j] = s;
5414
5415         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5416         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5417         initialPosition[pawnRow][j] = WhitePawn;
5418         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5419         if(gameInfo.variant == VariantXiangqi) {
5420             if(j&1) {
5421                 initialPosition[pawnRow][j] = 
5422                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5423                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5424                    initialPosition[2][j] = WhiteCannon;
5425                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5426                 }
5427             }
5428         }
5429         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5430     }
5431     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5432
5433             j=BOARD_LEFT+1;
5434             initialPosition[1][j] = WhiteBishop;
5435             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5436             j=BOARD_RGHT-2;
5437             initialPosition[1][j] = WhiteRook;
5438             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5439     }
5440
5441     if( nrCastlingRights == -1) {
5442         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5443         /*       This sets default castling rights from none to normal corners   */
5444         /* Variants with other castling rights must set them themselves above    */
5445         nrCastlingRights = 6;
5446        
5447         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5448         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5449         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5450         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5451         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5452         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5453      }
5454
5455      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5456      if(gameInfo.variant == VariantGreat) { // promotion commoners
5457         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5458         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5459         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5460         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5461      }
5462   if (appData.debugMode) {
5463     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5464   }
5465     if(shuffleOpenings) {
5466         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5467         startedFromSetupPosition = TRUE;
5468     }
5469     if(startedFromPositionFile) {
5470       /* [HGM] loadPos: use PositionFile for every new game */
5471       CopyBoard(initialPosition, filePosition);
5472       for(i=0; i<nrCastlingRights; i++)
5473           initialRights[i] = filePosition[CASTLING][i];
5474       startedFromSetupPosition = TRUE;
5475     }
5476
5477     CopyBoard(boards[0], initialPosition);
5478
5479     if(oldx != gameInfo.boardWidth ||
5480        oldy != gameInfo.boardHeight ||
5481        oldh != gameInfo.holdingsWidth
5482 #ifdef GOTHIC
5483        || oldv == VariantGothic ||        // For licensing popups
5484        gameInfo.variant == VariantGothic
5485 #endif
5486 #ifdef FALCON
5487        || oldv == VariantFalcon ||
5488        gameInfo.variant == VariantFalcon
5489 #endif
5490                                          )
5491             InitDrawingSizes(-2 ,0);
5492
5493     if (redraw)
5494       DrawPosition(TRUE, boards[currentMove]);
5495 }
5496
5497 void
5498 SendBoard(cps, moveNum)
5499      ChessProgramState *cps;
5500      int moveNum;
5501 {
5502     char message[MSG_SIZ];
5503     
5504     if (cps->useSetboard) {
5505       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5506       sprintf(message, "setboard %s\n", fen);
5507       SendToProgram(message, cps);
5508       free(fen);
5509
5510     } else {
5511       ChessSquare *bp;
5512       int i, j;
5513       /* Kludge to set black to move, avoiding the troublesome and now
5514        * deprecated "black" command.
5515        */
5516       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5517
5518       SendToProgram("edit\n", cps);
5519       SendToProgram("#\n", cps);
5520       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5521         bp = &boards[moveNum][i][BOARD_LEFT];
5522         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5523           if ((int) *bp < (int) BlackPawn) {
5524             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5525                     AAA + j, ONE + i);
5526             if(message[0] == '+' || message[0] == '~') {
5527                 sprintf(message, "%c%c%c+\n",
5528                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5529                         AAA + j, ONE + i);
5530             }
5531             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5532                 message[1] = BOARD_RGHT   - 1 - j + '1';
5533                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5534             }
5535             SendToProgram(message, cps);
5536           }
5537         }
5538       }
5539     
5540       SendToProgram("c\n", cps);
5541       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5542         bp = &boards[moveNum][i][BOARD_LEFT];
5543         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5544           if (((int) *bp != (int) EmptySquare)
5545               && ((int) *bp >= (int) BlackPawn)) {
5546             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5547                     AAA + j, ONE + i);
5548             if(message[0] == '+' || message[0] == '~') {
5549                 sprintf(message, "%c%c%c+\n",
5550                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5551                         AAA + j, ONE + i);
5552             }
5553             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5554                 message[1] = BOARD_RGHT   - 1 - j + '1';
5555                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5556             }
5557             SendToProgram(message, cps);
5558           }
5559         }
5560       }
5561     
5562       SendToProgram(".\n", cps);
5563     }
5564     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5565 }
5566
5567 static int autoQueen; // [HGM] oneclick
5568
5569 int
5570 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5571 {
5572     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5573     /* [HGM] add Shogi promotions */
5574     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5575     ChessSquare piece;
5576     ChessMove moveType;
5577     Boolean premove;
5578
5579     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5580     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5581
5582     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5583       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5584         return FALSE;
5585
5586     piece = boards[currentMove][fromY][fromX];
5587     if(gameInfo.variant == VariantShogi) {
5588         promotionZoneSize = 3;
5589         highestPromotingPiece = (int)WhiteFerz;
5590     } else if(gameInfo.variant == VariantMakruk) {
5591         promotionZoneSize = 3;
5592     }
5593
5594     // next weed out all moves that do not touch the promotion zone at all
5595     if((int)piece >= BlackPawn) {
5596         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5597              return FALSE;
5598         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5599     } else {
5600         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5601            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5602     }
5603
5604     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5605
5606     // weed out mandatory Shogi promotions
5607     if(gameInfo.variant == VariantShogi) {
5608         if(piece >= BlackPawn) {
5609             if(toY == 0 && piece == BlackPawn ||
5610                toY == 0 && piece == BlackQueen ||
5611                toY <= 1 && piece == BlackKnight) {
5612                 *promoChoice = '+';
5613                 return FALSE;
5614             }
5615         } else {
5616             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5617                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5618                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5619                 *promoChoice = '+';
5620                 return FALSE;
5621             }
5622         }
5623     }
5624
5625     // weed out obviously illegal Pawn moves
5626     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5627         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5628         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5629         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5630         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5631         // note we are not allowed to test for valid (non-)capture, due to premove
5632     }
5633
5634     // we either have a choice what to promote to, or (in Shogi) whether to promote
5635     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5636         *promoChoice = PieceToChar(BlackFerz);  // no choice
5637         return FALSE;
5638     }
5639     if(autoQueen) { // predetermined
5640         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5641              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5642         else *promoChoice = PieceToChar(BlackQueen);
5643         return FALSE;
5644     }
5645
5646     // suppress promotion popup on illegal moves that are not premoves
5647     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5648               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5649     if(appData.testLegality && !premove) {
5650         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5651                         fromY, fromX, toY, toX, NULLCHAR);
5652         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5653            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5654             return FALSE;
5655     }
5656
5657     return TRUE;
5658 }
5659
5660 int
5661 InPalace(row, column)
5662      int row, column;
5663 {   /* [HGM] for Xiangqi */
5664     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5665          column < (BOARD_WIDTH + 4)/2 &&
5666          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5667     return FALSE;
5668 }
5669
5670 int
5671 PieceForSquare (x, y)
5672      int x;
5673      int y;
5674 {
5675   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5676      return -1;
5677   else
5678      return boards[currentMove][y][x];
5679 }
5680
5681 int
5682 OKToStartUserMove(x, y)
5683      int x, y;
5684 {
5685     ChessSquare from_piece;
5686     int white_piece;
5687
5688     if (matchMode) return FALSE;
5689     if (gameMode == EditPosition) return TRUE;
5690
5691     if (x >= 0 && y >= 0)
5692       from_piece = boards[currentMove][y][x];
5693     else
5694       from_piece = EmptySquare;
5695
5696     if (from_piece == EmptySquare) return FALSE;
5697
5698     white_piece = (int)from_piece >= (int)WhitePawn &&
5699       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5700
5701     switch (gameMode) {
5702       case PlayFromGameFile:
5703       case AnalyzeFile:
5704       case TwoMachinesPlay:
5705       case EndOfGame:
5706         return FALSE;
5707
5708       case IcsObserving:
5709       case IcsIdle:
5710         return FALSE;
5711
5712       case MachinePlaysWhite:
5713       case IcsPlayingBlack:
5714         if (appData.zippyPlay) return FALSE;
5715         if (white_piece) {
5716             DisplayMoveError(_("You are playing Black"));
5717             return FALSE;
5718         }
5719         break;
5720
5721       case MachinePlaysBlack:
5722       case IcsPlayingWhite:
5723         if (appData.zippyPlay) return FALSE;
5724         if (!white_piece) {
5725             DisplayMoveError(_("You are playing White"));
5726             return FALSE;
5727         }
5728         break;
5729
5730       case EditGame:
5731         if (!white_piece && WhiteOnMove(currentMove)) {
5732             DisplayMoveError(_("It is White's turn"));
5733             return FALSE;
5734         }           
5735         if (white_piece && !WhiteOnMove(currentMove)) {
5736             DisplayMoveError(_("It is Black's turn"));
5737             return FALSE;
5738         }           
5739         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5740             /* Editing correspondence game history */
5741             /* Could disallow this or prompt for confirmation */
5742             cmailOldMove = -1;
5743         }
5744         break;
5745
5746       case BeginningOfGame:
5747         if (appData.icsActive) return FALSE;
5748         if (!appData.noChessProgram) {
5749             if (!white_piece) {
5750                 DisplayMoveError(_("You are playing White"));
5751                 return FALSE;
5752             }
5753         }
5754         break;
5755         
5756       case Training:
5757         if (!white_piece && WhiteOnMove(currentMove)) {
5758             DisplayMoveError(_("It is White's turn"));
5759             return FALSE;
5760         }           
5761         if (white_piece && !WhiteOnMove(currentMove)) {
5762             DisplayMoveError(_("It is Black's turn"));
5763             return FALSE;
5764         }           
5765         break;
5766
5767       default:
5768       case IcsExamining:
5769         break;
5770     }
5771     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5772         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5773         && gameMode != AnalyzeFile && gameMode != Training) {
5774         DisplayMoveError(_("Displayed position is not current"));
5775         return FALSE;
5776     }
5777     return TRUE;
5778 }
5779
5780 Boolean
5781 OnlyMove(int *x, int *y, Boolean captures) {
5782     DisambiguateClosure cl;
5783     if (appData.zippyPlay) return FALSE;
5784     switch(gameMode) {
5785       case MachinePlaysBlack:
5786       case IcsPlayingWhite:
5787       case BeginningOfGame:
5788         if(!WhiteOnMove(currentMove)) return FALSE;
5789         break;
5790       case MachinePlaysWhite:
5791       case IcsPlayingBlack:
5792         if(WhiteOnMove(currentMove)) return FALSE;
5793         break;
5794       default:
5795         return FALSE;
5796     }
5797     cl.pieceIn = EmptySquare; 
5798     cl.rfIn = *y;
5799     cl.ffIn = *x;
5800     cl.rtIn = -1;
5801     cl.ftIn = -1;
5802     cl.promoCharIn = NULLCHAR;
5803     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5804     if( cl.kind == NormalMove ||
5805         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5806         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5807         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5808         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5809       fromX = cl.ff;
5810       fromY = cl.rf;
5811       *x = cl.ft;
5812       *y = cl.rt;
5813       return TRUE;
5814     }
5815     if(cl.kind != ImpossibleMove) return FALSE;
5816     cl.pieceIn = EmptySquare;
5817     cl.rfIn = -1;
5818     cl.ffIn = -1;
5819     cl.rtIn = *y;
5820     cl.ftIn = *x;
5821     cl.promoCharIn = NULLCHAR;
5822     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5823     if( cl.kind == NormalMove ||
5824         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5825         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5826         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5827         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5828       fromX = cl.ff;
5829       fromY = cl.rf;
5830       *x = cl.ft;
5831       *y = cl.rt;
5832       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5833       return TRUE;
5834     }
5835     return FALSE;
5836 }
5837
5838 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5839 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5840 int lastLoadGameUseList = FALSE;
5841 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5842 ChessMove lastLoadGameStart = (ChessMove) 0;
5843
5844 ChessMove
5845 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5846      int fromX, fromY, toX, toY;
5847      int promoChar;
5848      Boolean captureOwn;
5849 {
5850     ChessMove moveType;
5851     ChessSquare pdown, pup;
5852
5853     /* Check if the user is playing in turn.  This is complicated because we
5854        let the user "pick up" a piece before it is his turn.  So the piece he
5855        tried to pick up may have been captured by the time he puts it down!
5856        Therefore we use the color the user is supposed to be playing in this
5857        test, not the color of the piece that is currently on the starting
5858        square---except in EditGame mode, where the user is playing both
5859        sides; fortunately there the capture race can't happen.  (It can
5860        now happen in IcsExamining mode, but that's just too bad.  The user
5861        will get a somewhat confusing message in that case.)
5862        */
5863
5864     switch (gameMode) {
5865       case PlayFromGameFile:
5866       case AnalyzeFile:
5867       case TwoMachinesPlay:
5868       case EndOfGame:
5869       case IcsObserving:
5870       case IcsIdle:
5871         /* We switched into a game mode where moves are not accepted,
5872            perhaps while the mouse button was down. */
5873         return ImpossibleMove;
5874
5875       case MachinePlaysWhite:
5876         /* User is moving for Black */
5877         if (WhiteOnMove(currentMove)) {
5878             DisplayMoveError(_("It is White's turn"));
5879             return ImpossibleMove;
5880         }
5881         break;
5882
5883       case MachinePlaysBlack:
5884         /* User is moving for White */
5885         if (!WhiteOnMove(currentMove)) {
5886             DisplayMoveError(_("It is Black's turn"));
5887             return ImpossibleMove;
5888         }
5889         break;
5890
5891       case EditGame:
5892       case IcsExamining:
5893       case BeginningOfGame:
5894       case AnalyzeMode:
5895       case Training:
5896         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5897             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5898             /* User is moving for Black */
5899             if (WhiteOnMove(currentMove)) {
5900                 DisplayMoveError(_("It is White's turn"));
5901                 return ImpossibleMove;
5902             }
5903         } else {
5904             /* User is moving for White */
5905             if (!WhiteOnMove(currentMove)) {
5906                 DisplayMoveError(_("It is Black's turn"));
5907                 return ImpossibleMove;
5908             }
5909         }
5910         break;
5911
5912       case IcsPlayingBlack:
5913         /* User is moving for Black */
5914         if (WhiteOnMove(currentMove)) {
5915             if (!appData.premove) {
5916                 DisplayMoveError(_("It is White's turn"));
5917             } else if (toX >= 0 && toY >= 0) {
5918                 premoveToX = toX;
5919                 premoveToY = toY;
5920                 premoveFromX = fromX;
5921                 premoveFromY = fromY;
5922                 premovePromoChar = promoChar;
5923                 gotPremove = 1;
5924                 if (appData.debugMode) 
5925                     fprintf(debugFP, "Got premove: fromX %d,"
5926                             "fromY %d, toX %d, toY %d\n",
5927                             fromX, fromY, toX, toY);
5928             }
5929             return ImpossibleMove;
5930         }
5931         break;
5932
5933       case IcsPlayingWhite:
5934         /* User is moving for White */
5935         if (!WhiteOnMove(currentMove)) {
5936             if (!appData.premove) {
5937                 DisplayMoveError(_("It is Black's turn"));
5938             } else if (toX >= 0 && toY >= 0) {
5939                 premoveToX = toX;
5940                 premoveToY = toY;
5941                 premoveFromX = fromX;
5942                 premoveFromY = fromY;
5943                 premovePromoChar = promoChar;
5944                 gotPremove = 1;
5945                 if (appData.debugMode) 
5946                     fprintf(debugFP, "Got premove: fromX %d,"
5947                             "fromY %d, toX %d, toY %d\n",
5948                             fromX, fromY, toX, toY);
5949             }
5950             return ImpossibleMove;
5951         }
5952         break;
5953
5954       default:
5955         break;
5956
5957       case EditPosition:
5958         /* EditPosition, empty square, or different color piece;
5959            click-click move is possible */
5960         if (toX == -2 || toY == -2) {
5961             boards[0][fromY][fromX] = EmptySquare;
5962             return AmbiguousMove;
5963         } else if (toX >= 0 && toY >= 0) {
5964             boards[0][toY][toX] = boards[0][fromY][fromX];
5965             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5966                 if(boards[0][fromY][0] != EmptySquare) {
5967                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5968                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5969                 }
5970             } else
5971             if(fromX == BOARD_RGHT+1) {
5972                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5973                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5974                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5975                 }
5976             } else
5977             boards[0][fromY][fromX] = EmptySquare;
5978             return AmbiguousMove;
5979         }
5980         return ImpossibleMove;
5981     }
5982
5983     if(toX < 0 || toY < 0) return ImpossibleMove;
5984     pdown = boards[currentMove][fromY][fromX];
5985     pup = boards[currentMove][toY][toX];
5986
5987     /* [HGM] If move started in holdings, it means a drop */
5988     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5989          if( pup != EmptySquare ) return ImpossibleMove;
5990          if(appData.testLegality) {
5991              /* it would be more logical if LegalityTest() also figured out
5992               * which drops are legal. For now we forbid pawns on back rank.
5993               * Shogi is on its own here...
5994               */
5995              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5996                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5997                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5998          }
5999          return WhiteDrop; /* Not needed to specify white or black yet */
6000     }
6001
6002     /* [HGM] always test for legality, to get promotion info */
6003     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6004                                          fromY, fromX, toY, toX, promoChar);
6005     /* [HGM] but possibly ignore an IllegalMove result */
6006     if (appData.testLegality) {
6007         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6008             DisplayMoveError(_("Illegal move"));
6009             return ImpossibleMove;
6010         }
6011     }
6012
6013     return moveType;
6014     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
6015        function is made into one that returns an OK move type if FinishMove
6016        should be called. This to give the calling driver routine the
6017        opportunity to finish the userMove input with a promotion popup,
6018        without bothering the user with this for invalid or illegal moves */
6019
6020 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
6021 }
6022
6023 /* Common tail of UserMoveEvent and DropMenuEvent */
6024 int
6025 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6026      ChessMove moveType;
6027      int fromX, fromY, toX, toY;
6028      /*char*/int promoChar;
6029 {
6030     char *bookHit = 0;
6031
6032     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6033         // [HGM] superchess: suppress promotions to non-available piece
6034         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6035         if(WhiteOnMove(currentMove)) {
6036             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6037         } else {
6038             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6039         }
6040     }
6041
6042     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6043        move type in caller when we know the move is a legal promotion */
6044     if(moveType == NormalMove && promoChar)
6045         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6046
6047     /* [HGM] convert drag-and-drop piece drops to standard form */
6048     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6049          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6050            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6051                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6052            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6053            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6054            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6055            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6056          fromY = DROP_RANK;
6057     }
6058
6059     /* [HGM] <popupFix> The following if has been moved here from
6060        UserMoveEvent(). Because it seemed to belong here (why not allow
6061        piece drops in training games?), and because it can only be
6062        performed after it is known to what we promote. */
6063     if (gameMode == Training) {
6064       /* compare the move played on the board to the next move in the
6065        * game. If they match, display the move and the opponent's response. 
6066        * If they don't match, display an error message.
6067        */
6068       int saveAnimate;
6069       Board testBoard;
6070       CopyBoard(testBoard, boards[currentMove]);
6071       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6072
6073       if (CompareBoards(testBoard, boards[currentMove+1])) {
6074         ForwardInner(currentMove+1);
6075
6076         /* Autoplay the opponent's response.
6077          * if appData.animate was TRUE when Training mode was entered,
6078          * the response will be animated.
6079          */
6080         saveAnimate = appData.animate;
6081         appData.animate = animateTraining;
6082         ForwardInner(currentMove+1);
6083         appData.animate = saveAnimate;
6084
6085         /* check for the end of the game */
6086         if (currentMove >= forwardMostMove) {
6087           gameMode = PlayFromGameFile;
6088           ModeHighlight();
6089           SetTrainingModeOff();
6090           DisplayInformation(_("End of game"));
6091         }
6092       } else {
6093         DisplayError(_("Incorrect move"), 0);
6094       }
6095       return 1;
6096     }
6097
6098   /* Ok, now we know that the move is good, so we can kill
6099      the previous line in Analysis Mode */
6100   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6101                                 && currentMove < forwardMostMove) {
6102     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6103   }
6104
6105   /* If we need the chess program but it's dead, restart it */
6106   ResurrectChessProgram();
6107
6108   /* A user move restarts a paused game*/
6109   if (pausing)
6110     PauseEvent();
6111
6112   thinkOutput[0] = NULLCHAR;
6113
6114   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6115
6116   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6117
6118   if (gameMode == BeginningOfGame) {
6119     if (appData.noChessProgram) {
6120       gameMode = EditGame;
6121       SetGameInfo();
6122     } else {
6123       char buf[MSG_SIZ];
6124       gameMode = MachinePlaysBlack;
6125       StartClocks();
6126       SetGameInfo();
6127       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6128       DisplayTitle(buf);
6129       if (first.sendName) {
6130         sprintf(buf, "name %s\n", gameInfo.white);
6131         SendToProgram(buf, &first);
6132       }
6133       StartClocks();
6134     }
6135     ModeHighlight();
6136   }
6137
6138   /* Relay move to ICS or chess engine */
6139   if (appData.icsActive) {
6140     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6141         gameMode == IcsExamining) {
6142       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6143         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6144         SendToICS("draw ");
6145         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6146       }
6147       // also send plain move, in case ICS does not understand atomic claims
6148       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6149       ics_user_moved = 1;
6150     }
6151   } else {
6152     if (first.sendTime && (gameMode == BeginningOfGame ||
6153                            gameMode == MachinePlaysWhite ||
6154                            gameMode == MachinePlaysBlack)) {
6155       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6156     }
6157     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6158          // [HGM] book: if program might be playing, let it use book
6159         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6160         first.maybeThinking = TRUE;
6161     } else SendMoveToProgram(forwardMostMove-1, &first);
6162     if (currentMove == cmailOldMove + 1) {
6163       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6164     }
6165   }
6166
6167   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6168
6169   switch (gameMode) {
6170   case EditGame:
6171     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6172     case MT_NONE:
6173     case MT_CHECK:
6174       break;
6175     case MT_CHECKMATE:
6176     case MT_STAINMATE:
6177       if (WhiteOnMove(currentMove)) {
6178         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6179       } else {
6180         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6181       }
6182       break;
6183     case MT_STALEMATE:
6184       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6185       break;
6186     }
6187     break;
6188     
6189   case MachinePlaysBlack:
6190   case MachinePlaysWhite:
6191     /* disable certain menu options while machine is thinking */
6192     SetMachineThinkingEnables();
6193     break;
6194
6195   default:
6196     break;
6197   }
6198
6199   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6200         
6201   if(bookHit) { // [HGM] book: simulate book reply
6202         static char bookMove[MSG_SIZ]; // a bit generous?
6203
6204         programStats.nodes = programStats.depth = programStats.time = 
6205         programStats.score = programStats.got_only_move = 0;
6206         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6207
6208         strcpy(bookMove, "move ");
6209         strcat(bookMove, bookHit);
6210         HandleMachineMove(bookMove, &first);
6211   }
6212   return 1;
6213 }
6214
6215 void
6216 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6217      int fromX, fromY, toX, toY;
6218      int promoChar;
6219 {
6220     /* [HGM] This routine was added to allow calling of its two logical
6221        parts from other modules in the old way. Before, UserMoveEvent()
6222        automatically called FinishMove() if the move was OK, and returned
6223        otherwise. I separated the two, in order to make it possible to
6224        slip a promotion popup in between. But that it always needs two
6225        calls, to the first part, (now called UserMoveTest() ), and to
6226        FinishMove if the first part succeeded. Calls that do not need
6227        to do anything in between, can call this routine the old way. 
6228     */
6229     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6230 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6231     if(moveType == AmbiguousMove)
6232         DrawPosition(FALSE, boards[currentMove]);
6233     else if(moveType != ImpossibleMove && moveType != Comment)
6234         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6235 }
6236
6237 void
6238 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6239      Board board;
6240      int flags;
6241      ChessMove kind;
6242      int rf, ff, rt, ft;
6243      VOIDSTAR closure;
6244 {
6245     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6246     Markers *m = (Markers *) closure;
6247     if(rf == fromY && ff == fromX)
6248         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6249                          || kind == WhiteCapturesEnPassant
6250                          || kind == BlackCapturesEnPassant);
6251     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6252 }
6253
6254 void
6255 MarkTargetSquares(int clear)
6256 {
6257   int x, y;
6258   if(!appData.markers || !appData.highlightDragging || 
6259      !appData.testLegality || gameMode == EditPosition) return;
6260   if(clear) {
6261     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6262   } else {
6263     int capt = 0;
6264     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6265     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6266       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6267       if(capt)
6268       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6269     }
6270   }
6271   DrawPosition(TRUE, NULL);
6272 }
6273
6274 void LeftClick(ClickType clickType, int xPix, int yPix)
6275 {
6276     int x, y;
6277     Boolean saveAnimate;
6278     static int second = 0, promotionChoice = 0, dragging = 0;
6279     char promoChoice = NULLCHAR;
6280
6281     if(appData.seekGraph && appData.icsActive && loggedOn &&
6282         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6283         SeekGraphClick(clickType, xPix, yPix, 0);
6284         return;
6285     }
6286
6287     if (clickType == Press) ErrorPopDown();
6288     MarkTargetSquares(1);
6289
6290     x = EventToSquare(xPix, BOARD_WIDTH);
6291     y = EventToSquare(yPix, BOARD_HEIGHT);
6292     if (!flipView && y >= 0) {
6293         y = BOARD_HEIGHT - 1 - y;
6294     }
6295     if (flipView && x >= 0) {
6296         x = BOARD_WIDTH - 1 - x;
6297     }
6298
6299     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6300         if(clickType == Release) return; // ignore upclick of click-click destination
6301         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6302         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6303         if(gameInfo.holdingsWidth && 
6304                 (WhiteOnMove(currentMove) 
6305                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6306                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6307             // click in right holdings, for determining promotion piece
6308             ChessSquare p = boards[currentMove][y][x];
6309             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6310             if(p != EmptySquare) {
6311                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6312                 fromX = fromY = -1;
6313                 return;
6314             }
6315         }
6316         DrawPosition(FALSE, boards[currentMove]);
6317         return;
6318     }
6319
6320     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6321     if(clickType == Press
6322             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6323               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6324               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6325         return;
6326
6327     autoQueen = appData.alwaysPromoteToQueen;
6328
6329     if (fromX == -1) {
6330       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6331         if (clickType == Press) {
6332             /* First square */
6333             if (OKToStartUserMove(x, y)) {
6334                 fromX = x;
6335                 fromY = y;
6336                 second = 0;
6337                 MarkTargetSquares(0);
6338                 DragPieceBegin(xPix, yPix); dragging = 1;
6339                 if (appData.highlightDragging) {
6340                     SetHighlights(x, y, -1, -1);
6341                 }
6342             }
6343         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6344             DragPieceEnd(xPix, yPix); dragging = 0;
6345             DrawPosition(FALSE, NULL);
6346         }
6347         return;
6348       }
6349     }
6350
6351     /* fromX != -1 */
6352     if (clickType == Press && gameMode != EditPosition) {
6353         ChessSquare fromP;
6354         ChessSquare toP;
6355         int frc;
6356
6357         // ignore off-board to clicks
6358         if(y < 0 || x < 0) return;
6359
6360         /* Check if clicking again on the same color piece */
6361         fromP = boards[currentMove][fromY][fromX];
6362         toP = boards[currentMove][y][x];
6363         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6364         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6365              WhitePawn <= toP && toP <= WhiteKing &&
6366              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6367              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6368             (BlackPawn <= fromP && fromP <= BlackKing && 
6369              BlackPawn <= toP && toP <= BlackKing &&
6370              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6371              !(fromP == BlackKing && toP == BlackRook && frc))) {
6372             /* Clicked again on same color piece -- changed his mind */
6373             second = (x == fromX && y == fromY);
6374            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6375             if (appData.highlightDragging) {
6376                 SetHighlights(x, y, -1, -1);
6377             } else {
6378                 ClearHighlights();
6379             }
6380             if (OKToStartUserMove(x, y)) {
6381                 fromX = x;
6382                 fromY = y; dragging = 1;
6383                 MarkTargetSquares(0);
6384                 DragPieceBegin(xPix, yPix);
6385             }
6386             return;
6387            }
6388         }
6389         // ignore clicks on holdings
6390         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6391     }
6392
6393     if (clickType == Release && x == fromX && y == fromY) {
6394         DragPieceEnd(xPix, yPix); dragging = 0;
6395         if (appData.animateDragging) {
6396             /* Undo animation damage if any */
6397             DrawPosition(FALSE, NULL);
6398         }
6399         if (second) {
6400             /* Second up/down in same square; just abort move */
6401             second = 0;
6402             fromX = fromY = -1;
6403             ClearHighlights();
6404             gotPremove = 0;
6405             ClearPremoveHighlights();
6406         } else {
6407             /* First upclick in same square; start click-click mode */
6408             SetHighlights(x, y, -1, -1);
6409         }
6410         return;
6411     }
6412
6413     /* we now have a different from- and (possibly off-board) to-square */
6414     /* Completed move */
6415     toX = x;
6416     toY = y;
6417     saveAnimate = appData.animate;
6418     if (clickType == Press) {
6419         /* Finish clickclick move */
6420         if (appData.animate || appData.highlightLastMove) {
6421             SetHighlights(fromX, fromY, toX, toY);
6422         } else {
6423             ClearHighlights();
6424         }
6425     } else {
6426         /* Finish drag move */
6427         if (appData.highlightLastMove) {
6428             SetHighlights(fromX, fromY, toX, toY);
6429         } else {
6430             ClearHighlights();
6431         }
6432         DragPieceEnd(xPix, yPix); dragging = 0;
6433         /* Don't animate move and drag both */
6434         appData.animate = FALSE;
6435     }
6436
6437     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6438     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6439         ChessSquare piece = boards[currentMove][fromY][fromX];
6440         if(gameMode == EditPosition && piece != EmptySquare &&
6441            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6442             int n;
6443              
6444             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6445                 n = PieceToNumber(piece - (int)BlackPawn);
6446                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6447                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6448                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6449             } else
6450             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6451                 n = PieceToNumber(piece);
6452                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6453                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6454                 boards[currentMove][n][BOARD_WIDTH-2]++;
6455             }
6456             boards[currentMove][fromY][fromX] = EmptySquare;
6457         }
6458         ClearHighlights();
6459         fromX = fromY = -1;
6460         DrawPosition(TRUE, boards[currentMove]);
6461         return;
6462     }
6463
6464     // off-board moves should not be highlighted
6465     if(x < 0 || x < 0) ClearHighlights();
6466
6467     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6468         SetHighlights(fromX, fromY, toX, toY);
6469         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6470             // [HGM] super: promotion to captured piece selected from holdings
6471             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6472             promotionChoice = TRUE;
6473             // kludge follows to temporarily execute move on display, without promoting yet
6474             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6475             boards[currentMove][toY][toX] = p;
6476             DrawPosition(FALSE, boards[currentMove]);
6477             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6478             boards[currentMove][toY][toX] = q;
6479             DisplayMessage("Click in holdings to choose piece", "");
6480             return;
6481         }
6482         PromotionPopUp();
6483     } else {
6484         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6485         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6486         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6487         fromX = fromY = -1;
6488     }
6489     appData.animate = saveAnimate;
6490     if (appData.animate || appData.animateDragging) {
6491         /* Undo animation damage if needed */
6492         DrawPosition(FALSE, NULL);
6493     }
6494 }
6495
6496 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6497 {   // front-end-free part taken out of PieceMenuPopup
6498     int whichMenu; int xSqr, ySqr;
6499
6500     if(seekGraphUp) { // [HGM] seekgraph
6501         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6502         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6503         return -2;
6504     }
6505
6506     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6507          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6508         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6509         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6510         if(action == Press)   {
6511             originalFlip = flipView;
6512             flipView = !flipView; // temporarily flip board to see game from partners perspective
6513             DrawPosition(TRUE, partnerBoard);
6514             DisplayMessage(partnerStatus, "");
6515             partnerUp = TRUE;
6516         } else if(action == Release) {
6517             flipView = originalFlip;
6518             DrawPosition(TRUE, boards[currentMove]);
6519             partnerUp = FALSE;
6520         }
6521         return -2;
6522     }
6523
6524     xSqr = EventToSquare(x, BOARD_WIDTH);
6525     ySqr = EventToSquare(y, BOARD_HEIGHT);
6526     if (action == Release) UnLoadPV(); // [HGM] pv
6527     if (action != Press) return -2; // return code to be ignored
6528     switch (gameMode) {
6529       case IcsExamining:
6530         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6531       case EditPosition:
6532         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6533         if (xSqr < 0 || ySqr < 0) return -1;\r
6534         whichMenu = 0; // edit-position menu
6535         break;
6536       case IcsObserving:
6537         if(!appData.icsEngineAnalyze) return -1;
6538       case IcsPlayingWhite:
6539       case IcsPlayingBlack:
6540         if(!appData.zippyPlay) goto noZip;
6541       case AnalyzeMode:
6542       case AnalyzeFile:
6543       case MachinePlaysWhite:
6544       case MachinePlaysBlack:
6545       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6546         if (!appData.dropMenu) {
6547           LoadPV(x, y);
6548           return 2; // flag front-end to grab mouse events
6549         }
6550         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6551            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6552       case EditGame:
6553       noZip:
6554         if (xSqr < 0 || ySqr < 0) return -1;
6555         if (!appData.dropMenu || appData.testLegality &&
6556             gameInfo.variant != VariantBughouse &&
6557             gameInfo.variant != VariantCrazyhouse) return -1;
6558         whichMenu = 1; // drop menu
6559         break;
6560       default:
6561         return -1;
6562     }
6563
6564     if (((*fromX = xSqr) < 0) ||
6565         ((*fromY = ySqr) < 0)) {
6566         *fromX = *fromY = -1;
6567         return -1;
6568     }
6569     if (flipView)
6570       *fromX = BOARD_WIDTH - 1 - *fromX;
6571     else
6572       *fromY = BOARD_HEIGHT - 1 - *fromY;
6573
6574     return whichMenu;
6575 }
6576
6577 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6578 {
6579 //    char * hint = lastHint;
6580     FrontEndProgramStats stats;
6581
6582     stats.which = cps == &first ? 0 : 1;
6583     stats.depth = cpstats->depth;
6584     stats.nodes = cpstats->nodes;
6585     stats.score = cpstats->score;
6586     stats.time = cpstats->time;
6587     stats.pv = cpstats->movelist;
6588     stats.hint = lastHint;
6589     stats.an_move_index = 0;
6590     stats.an_move_count = 0;
6591
6592     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6593         stats.hint = cpstats->move_name;
6594         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6595         stats.an_move_count = cpstats->nr_moves;
6596     }
6597
6598     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6599
6600     SetProgramStats( &stats );
6601 }
6602
6603 void
6604 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6605 {       // count all piece types
6606         int p, f, r;
6607         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6608         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6609         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6610                 p = board[r][f];
6611                 pCnt[p]++;
6612                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6613                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6614                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6615                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6616                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6617                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6618         }
6619 }
6620
6621 int
6622 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6623 {
6624         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6625         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6626                    
6627         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6628         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6629         if(myPawns == 2 && nMine == 3) // KPP
6630             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6631         if(myPawns == 1 && nMine == 2) // KP
6632             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6633         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6634             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6635         if(myPawns) return FALSE;
6636         if(pCnt[WhiteRook+side])
6637             return pCnt[BlackRook-side] || 
6638                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6639                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6640                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6641         if(pCnt[WhiteCannon+side]) {
6642             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6643             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6644         }
6645         if(pCnt[WhiteKnight+side])
6646             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6647         return FALSE;
6648 }
6649
6650 int
6651 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6652 {
6653         VariantClass v = gameInfo.variant;
6654
6655         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6656         if(v == VariantShatranj) return TRUE; // always winnable through baring
6657         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6658         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6659
6660         if(v == VariantXiangqi) {
6661                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6662
6663                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6664                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6665                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6666                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6667                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6668                 if(stale) // we have at least one last-rank P plus perhaps C
6669                     return majors // KPKX
6670                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6671                 else // KCA*E*
6672                     return pCnt[WhiteFerz+side] // KCAK
6673                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6674                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6675                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6676
6677         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6678                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6679                 
6680                 if(nMine == 1) return FALSE; // bare King
6681                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
6682                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6683                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6684                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6685                 if(pCnt[WhiteKnight+side])
6686                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] + 
6687                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6688                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6689                 if(nBishops)
6690                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6691                 if(pCnt[WhiteAlfil+side])
6692                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6693                 if(pCnt[WhiteWazir+side])
6694                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6695         }
6696
6697         return TRUE;
6698 }
6699
6700 int
6701 Adjudicate(ChessProgramState *cps)
6702 {       // [HGM] some adjudications useful with buggy engines
6703         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6704         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6705         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6706         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6707         int k, count = 0; static int bare = 1;
6708         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6709         Boolean canAdjudicate = !appData.icsActive;
6710
6711         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6712         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6713             if( appData.testLegality )
6714             {   /* [HGM] Some more adjudications for obstinate engines */
6715                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6716                 static int moveCount = 6;
6717                 ChessMove result;
6718                 char *reason = NULL;
6719
6720                 /* Count what is on board. */
6721                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6722
6723                 /* Some material-based adjudications that have to be made before stalemate test */
6724                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6725                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6726                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6727                      if(canAdjudicate && appData.checkMates) {
6728                          if(engineOpponent)
6729                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6730                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6731                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6732                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6733                          return 1;
6734                      }
6735                 }
6736
6737                 /* Bare King in Shatranj (loses) or Losers (wins) */
6738                 if( nrW == 1 || nrB == 1) {
6739                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6740                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6741                      if(canAdjudicate && appData.checkMates) {
6742                          if(engineOpponent)
6743                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6744                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6745                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6746                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6747                          return 1;
6748                      }
6749                   } else
6750                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6751                   {    /* bare King */
6752                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6753                         if(canAdjudicate && appData.checkMates) {
6754                             /* but only adjudicate if adjudication enabled */
6755                             if(engineOpponent)
6756                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6757                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6758                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, 
6759                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6760                             return 1;
6761                         }
6762                   }
6763                 } else bare = 1;
6764
6765
6766             // don't wait for engine to announce game end if we can judge ourselves
6767             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6768               case MT_CHECK:
6769                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6770                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6771                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6772                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6773                             checkCnt++;
6774                         if(checkCnt >= 2) {
6775                             reason = "Xboard adjudication: 3rd check";
6776                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6777                             break;
6778                         }
6779                     }
6780                 }
6781               case MT_NONE:
6782               default:
6783                 break;
6784               case MT_STALEMATE:
6785               case MT_STAINMATE:
6786                 reason = "Xboard adjudication: Stalemate";
6787                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6788                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6789                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6790                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6791                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6792                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6793                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6794                                                                         EP_CHECKMATE : EP_WINS);
6795                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6796                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6797                 }
6798                 break;
6799               case MT_CHECKMATE:
6800                 reason = "Xboard adjudication: Checkmate";
6801                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6802                 break;
6803             }
6804
6805                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6806                     case EP_STALEMATE:
6807                         result = GameIsDrawn; break;
6808                     case EP_CHECKMATE:
6809                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6810                     case EP_WINS:
6811                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6812                     default:
6813                         result = (ChessMove) 0;
6814                 }
6815                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6816                     if(engineOpponent)
6817                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6818                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6819                     GameEnds( result, reason, GE_XBOARD );
6820                     return 1;
6821                 }
6822
6823                 /* Next absolutely insufficient mating material. */
6824                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6825                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6826                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6827
6828                      /* always flag draws, for judging claims */
6829                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6830
6831                      if(canAdjudicate && appData.materialDraws) {
6832                          /* but only adjudicate them if adjudication enabled */
6833                          if(engineOpponent) {
6834                            SendToProgram("force\n", engineOpponent); // suppress reply
6835                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6836                          }
6837                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6838                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6839                          return 1;
6840                      }
6841                 }
6842
6843                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6844                 if(gameInfo.variant == VariantXiangqi ?
6845                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6846                  : nrW + nrB == 4 && 
6847                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6848                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6849                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6850                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6851                    ) ) {
6852                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6853                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6854                           if(engineOpponent) {
6855                             SendToProgram("force\n", engineOpponent); // suppress reply
6856                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6857                           }
6858                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6859                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6860                           return 1;
6861                      }
6862                 } else moveCount = 6;
6863             }
6864         }
6865           
6866         if (appData.debugMode) { int i;
6867             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6868                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6869                     appData.drawRepeats);
6870             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6871               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6872             
6873         }
6874
6875         // Repetition draws and 50-move rule can be applied independently of legality testing
6876
6877                 /* Check for rep-draws */
6878                 count = 0;
6879                 for(k = forwardMostMove-2;
6880                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6881                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6882                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6883                     k-=2)
6884                 {   int rights=0;
6885                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6886                         /* compare castling rights */
6887                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6888                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6889                                 rights++; /* King lost rights, while rook still had them */
6890                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6891                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6892                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6893                                    rights++; /* but at least one rook lost them */
6894                         }
6895                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6896                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6897                                 rights++; 
6898                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6899                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6900                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6901                                    rights++;
6902                         }
6903                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6904                             && appData.drawRepeats > 1) {
6905                              /* adjudicate after user-specified nr of repeats */
6906                              int result = GameIsDrawn;
6907                              char *details = "XBoard adjudication: repetition draw";
6908                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6909                                 // [HGM] xiangqi: check for forbidden perpetuals
6910                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6911                                 for(m=forwardMostMove; m>k; m-=2) {
6912                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6913                                         ourPerpetual = 0; // the current mover did not always check
6914                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6915                                         hisPerpetual = 0; // the opponent did not always check
6916                                 }
6917                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6918                                                                         ourPerpetual, hisPerpetual);
6919                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6920                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6921                                     details = "Xboard adjudication: perpetual checking";
6922                                 } else
6923                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6924                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6925                                 } else
6926                                 // Now check for perpetual chases
6927                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6928                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6929                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6930                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6931                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6932                                         details = "Xboard adjudication: perpetual chasing";
6933                                     } else
6934                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6935                                         break; // Abort repetition-checking loop.
6936                                 }
6937                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6938                              }
6939                              if(engineOpponent) {
6940                                SendToProgram("force\n", engineOpponent); // suppress reply
6941                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6942                              }
6943                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6944                              GameEnds( result, details, GE_XBOARD );
6945                              return 1;
6946                         }
6947                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6948                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6949                     }
6950                 }
6951
6952                 /* Now we test for 50-move draws. Determine ply count */
6953                 count = forwardMostMove;
6954                 /* look for last irreversble move */
6955                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6956                     count--;
6957                 /* if we hit starting position, add initial plies */
6958                 if( count == backwardMostMove )
6959                     count -= initialRulePlies;
6960                 count = forwardMostMove - count; 
6961                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6962                         // adjust reversible move counter for checks in Xiangqi
6963                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6964                         if(i < backwardMostMove) i = backwardMostMove;
6965                         while(i <= forwardMostMove) {
6966                                 lastCheck = inCheck; // check evasion does not count
6967                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6968                                 if(inCheck || lastCheck) count--; // check does not count
6969                                 i++;
6970                         }
6971                 }
6972                 if( count >= 100)
6973                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6974                          /* this is used to judge if draw claims are legal */
6975                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6976                          if(engineOpponent) {
6977                            SendToProgram("force\n", engineOpponent); // suppress reply
6978                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6979                          }
6980                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6981                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6982                          return 1;
6983                 }
6984
6985                 /* if draw offer is pending, treat it as a draw claim
6986                  * when draw condition present, to allow engines a way to
6987                  * claim draws before making their move to avoid a race
6988                  * condition occurring after their move
6989                  */
6990                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6991                          char *p = NULL;
6992                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6993                              p = "Draw claim: 50-move rule";
6994                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6995                              p = "Draw claim: 3-fold repetition";
6996                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6997                              p = "Draw claim: insufficient mating material";
6998                          if( p != NULL && canAdjudicate) {
6999                              if(engineOpponent) {
7000                                SendToProgram("force\n", engineOpponent); // suppress reply
7001                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7002                              }
7003                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7004                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7005                              return 1;
7006                          }
7007                 }
7008
7009                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7010                     if(engineOpponent) {
7011                       SendToProgram("force\n", engineOpponent); // suppress reply
7012                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7013                     }
7014                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7015                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7016                     return 1;
7017                 }
7018         return 0;
7019 }
7020
7021 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7022 {   // [HGM] book: this routine intercepts moves to simulate book replies
7023     char *bookHit = NULL;
7024
7025     //first determine if the incoming move brings opponent into his book
7026     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7027         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7028     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7029     if(bookHit != NULL && !cps->bookSuspend) {
7030         // make sure opponent is not going to reply after receiving move to book position
7031         SendToProgram("force\n", cps);
7032         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7033     }
7034     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7035     // now arrange restart after book miss
7036     if(bookHit) {
7037         // after a book hit we never send 'go', and the code after the call to this routine
7038         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7039         char buf[MSG_SIZ];
7040         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7041         SendToProgram(buf, cps);
7042         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7043     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7044         SendToProgram("go\n", cps);
7045         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7046     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7047         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7048             SendToProgram("go\n", cps); 
7049         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7050     }
7051     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7052 }
7053
7054 char *savedMessage;
7055 ChessProgramState *savedState;
7056 void DeferredBookMove(void)
7057 {
7058         if(savedState->lastPing != savedState->lastPong)
7059                     ScheduleDelayedEvent(DeferredBookMove, 10);
7060         else
7061         HandleMachineMove(savedMessage, savedState);
7062 }
7063
7064 void
7065 HandleMachineMove(message, cps)
7066      char *message;
7067      ChessProgramState *cps;
7068 {
7069     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7070     char realname[MSG_SIZ];
7071     int fromX, fromY, toX, toY;
7072     ChessMove moveType;
7073     char promoChar;
7074     char *p;
7075     int machineWhite;
7076     char *bookHit;
7077
7078     cps->userError = 0;
7079
7080 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7081     /*
7082      * Kludge to ignore BEL characters
7083      */
7084     while (*message == '\007') message++;
7085
7086     /*
7087      * [HGM] engine debug message: ignore lines starting with '#' character
7088      */
7089     if(cps->debug && *message == '#') return;
7090
7091     /*
7092      * Look for book output
7093      */
7094     if (cps == &first && bookRequested) {
7095         if (message[0] == '\t' || message[0] == ' ') {
7096             /* Part of the book output is here; append it */
7097             strcat(bookOutput, message);
7098             strcat(bookOutput, "  \n");
7099             return;
7100         } else if (bookOutput[0] != NULLCHAR) {
7101             /* All of book output has arrived; display it */
7102             char *p = bookOutput;
7103             while (*p != NULLCHAR) {
7104                 if (*p == '\t') *p = ' ';
7105                 p++;
7106             }
7107             DisplayInformation(bookOutput);
7108             bookRequested = FALSE;
7109             /* Fall through to parse the current output */
7110         }
7111     }
7112
7113     /*
7114      * Look for machine move.
7115      */
7116     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7117         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7118     {
7119         /* This method is only useful on engines that support ping */
7120         if (cps->lastPing != cps->lastPong) {
7121           if (gameMode == BeginningOfGame) {
7122             /* Extra move from before last new; ignore */
7123             if (appData.debugMode) {
7124                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7125             }
7126           } else {
7127             if (appData.debugMode) {
7128                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7129                         cps->which, gameMode);
7130             }
7131
7132             SendToProgram("undo\n", cps);
7133           }
7134           return;
7135         }
7136
7137         switch (gameMode) {
7138           case BeginningOfGame:
7139             /* Extra move from before last reset; ignore */
7140             if (appData.debugMode) {
7141                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7142             }
7143             return;
7144
7145           case EndOfGame:
7146           case IcsIdle:
7147           default:
7148             /* Extra move after we tried to stop.  The mode test is
7149                not a reliable way of detecting this problem, but it's
7150                the best we can do on engines that don't support ping.
7151             */
7152             if (appData.debugMode) {
7153                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7154                         cps->which, gameMode);
7155             }
7156             SendToProgram("undo\n", cps);
7157             return;
7158
7159           case MachinePlaysWhite:
7160           case IcsPlayingWhite:
7161             machineWhite = TRUE;
7162             break;
7163
7164           case MachinePlaysBlack:
7165           case IcsPlayingBlack:
7166             machineWhite = FALSE;
7167             break;
7168
7169           case TwoMachinesPlay:
7170             machineWhite = (cps->twoMachinesColor[0] == 'w');
7171             break;
7172         }
7173         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7174             if (appData.debugMode) {
7175                 fprintf(debugFP,
7176                         "Ignoring move out of turn by %s, gameMode %d"
7177                         ", forwardMost %d\n",
7178                         cps->which, gameMode, forwardMostMove);
7179             }
7180             return;
7181         }
7182
7183     if (appData.debugMode) { int f = forwardMostMove;
7184         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7185                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7186                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7187     }
7188         if(cps->alphaRank) AlphaRank(machineMove, 4);
7189         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7190                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7191             /* Machine move could not be parsed; ignore it. */
7192             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7193                     machineMove, cps->which);
7194             DisplayError(buf1, 0);
7195             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7196                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7197             if (gameMode == TwoMachinesPlay) {
7198               GameEnds(machineWhite ? BlackWins : WhiteWins,
7199                        buf1, GE_XBOARD);
7200             }
7201             return;
7202         }
7203
7204         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7205         /* So we have to redo legality test with true e.p. status here,  */
7206         /* to make sure an illegal e.p. capture does not slip through,   */
7207         /* to cause a forfeit on a justified illegal-move complaint      */
7208         /* of the opponent.                                              */
7209         if( gameMode==TwoMachinesPlay && appData.testLegality
7210             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7211                                                               ) {
7212            ChessMove moveType;
7213            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7214                              fromY, fromX, toY, toX, promoChar);
7215             if (appData.debugMode) {
7216                 int i;
7217                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7218                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7219                 fprintf(debugFP, "castling rights\n");
7220             }
7221             if(moveType == IllegalMove) {
7222                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7223                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7224                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7225                            buf1, GE_XBOARD);
7226                 return;
7227            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7228            /* [HGM] Kludge to handle engines that send FRC-style castling
7229               when they shouldn't (like TSCP-Gothic) */
7230            switch(moveType) {
7231              case WhiteASideCastleFR:
7232              case BlackASideCastleFR:
7233                toX+=2;
7234                currentMoveString[2]++;
7235                break;
7236              case WhiteHSideCastleFR:
7237              case BlackHSideCastleFR:
7238                toX--;
7239                currentMoveString[2]--;
7240                break;
7241              default: ; // nothing to do, but suppresses warning of pedantic compilers
7242            }
7243         }
7244         hintRequested = FALSE;
7245         lastHint[0] = NULLCHAR;
7246         bookRequested = FALSE;
7247         /* Program may be pondering now */
7248         cps->maybeThinking = TRUE;
7249         if (cps->sendTime == 2) cps->sendTime = 1;
7250         if (cps->offeredDraw) cps->offeredDraw--;
7251
7252         /* currentMoveString is set as a side-effect of ParseOneMove */
7253         strcpy(machineMove, currentMoveString);
7254         strcat(machineMove, "\n");
7255         strcpy(moveList[forwardMostMove], machineMove);
7256
7257         /* [AS] Save move info*/
7258         pvInfoList[ forwardMostMove ].score = programStats.score;
7259         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7260         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7261
7262         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7263
7264         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7265         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7266             int count = 0;
7267
7268             while( count < adjudicateLossPlies ) {
7269                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7270
7271                 if( count & 1 ) {
7272                     score = -score; /* Flip score for winning side */
7273                 }
7274
7275                 if( score > adjudicateLossThreshold ) {
7276                     break;
7277                 }
7278
7279                 count++;
7280             }
7281
7282             if( count >= adjudicateLossPlies ) {
7283                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7284
7285                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7286                     "Xboard adjudication", 
7287                     GE_XBOARD );
7288
7289                 return;
7290             }
7291         }
7292
7293         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7294
7295 #if ZIPPY
7296         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7297             first.initDone) {
7298           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7299                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7300                 SendToICS("draw ");
7301                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7302           }
7303           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7304           ics_user_moved = 1;
7305           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7306                 char buf[3*MSG_SIZ];
7307
7308                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7309                         programStats.score / 100.,
7310                         programStats.depth,
7311                         programStats.time / 100.,
7312                         (unsigned int)programStats.nodes,
7313                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7314                         programStats.movelist);
7315                 SendToICS(buf);
7316 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7317           }
7318         }
7319 #endif
7320
7321         /* [AS] Clear stats for next move */
7322         ClearProgramStats();
7323         thinkOutput[0] = NULLCHAR;
7324         hiddenThinkOutputState = 0;
7325
7326         bookHit = NULL;
7327         if (gameMode == TwoMachinesPlay) {
7328             /* [HGM] relaying draw offers moved to after reception of move */
7329             /* and interpreting offer as claim if it brings draw condition */
7330             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7331                 SendToProgram("draw\n", cps->other);
7332             }
7333             if (cps->other->sendTime) {
7334                 SendTimeRemaining(cps->other,
7335                                   cps->other->twoMachinesColor[0] == 'w');
7336             }
7337             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7338             if (firstMove && !bookHit) {
7339                 firstMove = FALSE;
7340                 if (cps->other->useColors) {
7341                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7342                 }
7343                 SendToProgram("go\n", cps->other);
7344             }
7345             cps->other->maybeThinking = TRUE;
7346         }
7347
7348         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7349         
7350         if (!pausing && appData.ringBellAfterMoves) {
7351             RingBell();
7352         }
7353
7354         /* 
7355          * Reenable menu items that were disabled while
7356          * machine was thinking
7357          */
7358         if (gameMode != TwoMachinesPlay)
7359             SetUserThinkingEnables();
7360
7361         // [HGM] book: after book hit opponent has received move and is now in force mode
7362         // force the book reply into it, and then fake that it outputted this move by jumping
7363         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7364         if(bookHit) {
7365                 static char bookMove[MSG_SIZ]; // a bit generous?
7366
7367                 strcpy(bookMove, "move ");
7368                 strcat(bookMove, bookHit);
7369                 message = bookMove;
7370                 cps = cps->other;
7371                 programStats.nodes = programStats.depth = programStats.time = 
7372                 programStats.score = programStats.got_only_move = 0;
7373                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7374
7375                 if(cps->lastPing != cps->lastPong) {
7376                     savedMessage = message; // args for deferred call
7377                     savedState = cps;
7378                     ScheduleDelayedEvent(DeferredBookMove, 10);
7379                     return;
7380                 }
7381                 goto FakeBookMove;
7382         }
7383
7384         return;
7385     }
7386
7387     /* Set special modes for chess engines.  Later something general
7388      *  could be added here; for now there is just one kludge feature,
7389      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7390      *  when "xboard" is given as an interactive command.
7391      */
7392     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7393         cps->useSigint = FALSE;
7394         cps->useSigterm = FALSE;
7395     }
7396     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7397       ParseFeatures(message+8, cps);
7398       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7399     }
7400
7401     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7402      * want this, I was asked to put it in, and obliged.
7403      */
7404     if (!strncmp(message, "setboard ", 9)) {
7405         Board initial_position;
7406
7407         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7408
7409         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7410             DisplayError(_("Bad FEN received from engine"), 0);
7411             return ;
7412         } else {
7413            Reset(TRUE, FALSE);
7414            CopyBoard(boards[0], initial_position);
7415            initialRulePlies = FENrulePlies;
7416            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7417            else gameMode = MachinePlaysBlack;                 
7418            DrawPosition(FALSE, boards[currentMove]);
7419         }
7420         return;
7421     }
7422
7423     /*
7424      * Look for communication commands
7425      */
7426     if (!strncmp(message, "telluser ", 9)) {
7427         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7428         DisplayNote(message + 9);
7429         return;
7430     }
7431     if (!strncmp(message, "tellusererror ", 14)) {
7432         cps->userError = 1;
7433         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7434         DisplayError(message + 14, 0);
7435         return;
7436     }
7437     if (!strncmp(message, "tellopponent ", 13)) {
7438       if (appData.icsActive) {
7439         if (loggedOn) {
7440           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7441           SendToICS(buf1);
7442         }
7443       } else {
7444         DisplayNote(message + 13);
7445       }
7446       return;
7447     }
7448     if (!strncmp(message, "tellothers ", 11)) {
7449       if (appData.icsActive) {
7450         if (loggedOn) {
7451           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7452           SendToICS(buf1);
7453         }
7454       }
7455       return;
7456     }
7457     if (!strncmp(message, "tellall ", 8)) {
7458       if (appData.icsActive) {
7459         if (loggedOn) {
7460           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7461           SendToICS(buf1);
7462         }
7463       } else {
7464         DisplayNote(message + 8);
7465       }
7466       return;
7467     }
7468     if (strncmp(message, "warning", 7) == 0) {
7469         /* Undocumented feature, use tellusererror in new code */
7470         DisplayError(message, 0);
7471         return;
7472     }
7473     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7474         strcpy(realname, cps->tidy);
7475         strcat(realname, " query");
7476         AskQuestion(realname, buf2, buf1, cps->pr);
7477         return;
7478     }
7479     /* Commands from the engine directly to ICS.  We don't allow these to be 
7480      *  sent until we are logged on. Crafty kibitzes have been known to 
7481      *  interfere with the login process.
7482      */
7483     if (loggedOn) {
7484         if (!strncmp(message, "tellics ", 8)) {
7485             SendToICS(message + 8);
7486             SendToICS("\n");
7487             return;
7488         }
7489         if (!strncmp(message, "tellicsnoalias ", 15)) {
7490             SendToICS(ics_prefix);
7491             SendToICS(message + 15);
7492             SendToICS("\n");
7493             return;
7494         }
7495         /* The following are for backward compatibility only */
7496         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7497             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7498             SendToICS(ics_prefix);
7499             SendToICS(message);
7500             SendToICS("\n");
7501             return;
7502         }
7503     }
7504     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7505         return;
7506     }
7507     /*
7508      * If the move is illegal, cancel it and redraw the board.
7509      * Also deal with other error cases.  Matching is rather loose
7510      * here to accommodate engines written before the spec.
7511      */
7512     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7513         strncmp(message, "Error", 5) == 0) {
7514         if (StrStr(message, "name") || 
7515             StrStr(message, "rating") || StrStr(message, "?") ||
7516             StrStr(message, "result") || StrStr(message, "board") ||
7517             StrStr(message, "bk") || StrStr(message, "computer") ||
7518             StrStr(message, "variant") || StrStr(message, "hint") ||
7519             StrStr(message, "random") || StrStr(message, "depth") ||
7520             StrStr(message, "accepted")) {
7521             return;
7522         }
7523         if (StrStr(message, "protover")) {
7524           /* Program is responding to input, so it's apparently done
7525              initializing, and this error message indicates it is
7526              protocol version 1.  So we don't need to wait any longer
7527              for it to initialize and send feature commands. */
7528           FeatureDone(cps, 1);
7529           cps->protocolVersion = 1;
7530           return;
7531         }
7532         cps->maybeThinking = FALSE;
7533
7534         if (StrStr(message, "draw")) {
7535             /* Program doesn't have "draw" command */
7536             cps->sendDrawOffers = 0;
7537             return;
7538         }
7539         if (cps->sendTime != 1 &&
7540             (StrStr(message, "time") || StrStr(message, "otim"))) {
7541           /* Program apparently doesn't have "time" or "otim" command */
7542           cps->sendTime = 0;
7543           return;
7544         }
7545         if (StrStr(message, "analyze")) {
7546             cps->analysisSupport = FALSE;
7547             cps->analyzing = FALSE;
7548             Reset(FALSE, TRUE);
7549             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7550             DisplayError(buf2, 0);
7551             return;
7552         }
7553         if (StrStr(message, "(no matching move)st")) {
7554           /* Special kludge for GNU Chess 4 only */
7555           cps->stKludge = TRUE;
7556           SendTimeControl(cps, movesPerSession, timeControl,
7557                           timeIncrement, appData.searchDepth,
7558                           searchTime);
7559           return;
7560         }
7561         if (StrStr(message, "(no matching move)sd")) {
7562           /* Special kludge for GNU Chess 4 only */
7563           cps->sdKludge = TRUE;
7564           SendTimeControl(cps, movesPerSession, timeControl,
7565                           timeIncrement, appData.searchDepth,
7566                           searchTime);
7567           return;
7568         }
7569         if (!StrStr(message, "llegal")) {
7570             return;
7571         }
7572         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7573             gameMode == IcsIdle) return;
7574         if (forwardMostMove <= backwardMostMove) return;
7575         if (pausing) PauseEvent();
7576       if(appData.forceIllegal) {
7577             // [HGM] illegal: machine refused move; force position after move into it
7578           SendToProgram("force\n", cps);
7579           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7580                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7581                 // when black is to move, while there might be nothing on a2 or black
7582                 // might already have the move. So send the board as if white has the move.
7583                 // But first we must change the stm of the engine, as it refused the last move
7584                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7585                 if(WhiteOnMove(forwardMostMove)) {
7586                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7587                     SendBoard(cps, forwardMostMove); // kludgeless board
7588                 } else {
7589                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7590                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7591                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7592                 }
7593           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7594             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7595                  gameMode == TwoMachinesPlay)
7596               SendToProgram("go\n", cps);
7597             return;
7598       } else
7599         if (gameMode == PlayFromGameFile) {
7600             /* Stop reading this game file */
7601             gameMode = EditGame;
7602             ModeHighlight();
7603         }
7604         currentMove = forwardMostMove-1;
7605         DisplayMove(currentMove-1); /* before DisplayMoveError */
7606         SwitchClocks(forwardMostMove-1); // [HGM] race
7607         DisplayBothClocks();
7608         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7609                 parseList[currentMove], cps->which);
7610         DisplayMoveError(buf1);
7611         DrawPosition(FALSE, boards[currentMove]);
7612
7613         /* [HGM] illegal-move claim should forfeit game when Xboard */
7614         /* only passes fully legal moves                            */
7615         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7616             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7617                                 "False illegal-move claim", GE_XBOARD );
7618         }
7619         return;
7620     }
7621     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7622         /* Program has a broken "time" command that
7623            outputs a string not ending in newline.
7624            Don't use it. */
7625         cps->sendTime = 0;
7626     }
7627     
7628     /*
7629      * If chess program startup fails, exit with an error message.
7630      * Attempts to recover here are futile.
7631      */
7632     if ((StrStr(message, "unknown host") != NULL)
7633         || (StrStr(message, "No remote directory") != NULL)
7634         || (StrStr(message, "not found") != NULL)
7635         || (StrStr(message, "No such file") != NULL)
7636         || (StrStr(message, "can't alloc") != NULL)
7637         || (StrStr(message, "Permission denied") != NULL)) {
7638
7639         cps->maybeThinking = FALSE;
7640         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7641                 cps->which, cps->program, cps->host, message);
7642         RemoveInputSource(cps->isr);
7643         DisplayFatalError(buf1, 0, 1);
7644         return;
7645     }
7646     
7647     /* 
7648      * Look for hint output
7649      */
7650     if (sscanf(message, "Hint: %s", buf1) == 1) {
7651         if (cps == &first && hintRequested) {
7652             hintRequested = FALSE;
7653             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7654                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7655                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7656                                     PosFlags(forwardMostMove),
7657                                     fromY, fromX, toY, toX, promoChar, buf1);
7658                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7659                 DisplayInformation(buf2);
7660             } else {
7661                 /* Hint move could not be parsed!? */
7662               snprintf(buf2, sizeof(buf2),
7663                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7664                         buf1, cps->which);
7665                 DisplayError(buf2, 0);
7666             }
7667         } else {
7668             strcpy(lastHint, buf1);
7669         }
7670         return;
7671     }
7672
7673     /*
7674      * Ignore other messages if game is not in progress
7675      */
7676     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7677         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7678
7679     /*
7680      * look for win, lose, draw, or draw offer
7681      */
7682     if (strncmp(message, "1-0", 3) == 0) {
7683         char *p, *q, *r = "";
7684         p = strchr(message, '{');
7685         if (p) {
7686             q = strchr(p, '}');
7687             if (q) {
7688                 *q = NULLCHAR;
7689                 r = p + 1;
7690             }
7691         }
7692         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7693         return;
7694     } else if (strncmp(message, "0-1", 3) == 0) {
7695         char *p, *q, *r = "";
7696         p = strchr(message, '{');
7697         if (p) {
7698             q = strchr(p, '}');
7699             if (q) {
7700                 *q = NULLCHAR;
7701                 r = p + 1;
7702             }
7703         }
7704         /* Kludge for Arasan 4.1 bug */
7705         if (strcmp(r, "Black resigns") == 0) {
7706             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7707             return;
7708         }
7709         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7710         return;
7711     } else if (strncmp(message, "1/2", 3) == 0) {
7712         char *p, *q, *r = "";
7713         p = strchr(message, '{');
7714         if (p) {
7715             q = strchr(p, '}');
7716             if (q) {
7717                 *q = NULLCHAR;
7718                 r = p + 1;
7719             }
7720         }
7721             
7722         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7723         return;
7724
7725     } else if (strncmp(message, "White resign", 12) == 0) {
7726         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7727         return;
7728     } else if (strncmp(message, "Black resign", 12) == 0) {
7729         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7730         return;
7731     } else if (strncmp(message, "White matches", 13) == 0 ||
7732                strncmp(message, "Black matches", 13) == 0   ) {
7733         /* [HGM] ignore GNUShogi noises */
7734         return;
7735     } else if (strncmp(message, "White", 5) == 0 &&
7736                message[5] != '(' &&
7737                StrStr(message, "Black") == NULL) {
7738         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7739         return;
7740     } else if (strncmp(message, "Black", 5) == 0 &&
7741                message[5] != '(') {
7742         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7743         return;
7744     } else if (strcmp(message, "resign") == 0 ||
7745                strcmp(message, "computer resigns") == 0) {
7746         switch (gameMode) {
7747           case MachinePlaysBlack:
7748           case IcsPlayingBlack:
7749             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7750             break;
7751           case MachinePlaysWhite:
7752           case IcsPlayingWhite:
7753             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7754             break;
7755           case TwoMachinesPlay:
7756             if (cps->twoMachinesColor[0] == 'w')
7757               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7758             else
7759               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7760             break;
7761           default:
7762             /* can't happen */
7763             break;
7764         }
7765         return;
7766     } else if (strncmp(message, "opponent mates", 14) == 0) {
7767         switch (gameMode) {
7768           case MachinePlaysBlack:
7769           case IcsPlayingBlack:
7770             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7771             break;
7772           case MachinePlaysWhite:
7773           case IcsPlayingWhite:
7774             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7775             break;
7776           case TwoMachinesPlay:
7777             if (cps->twoMachinesColor[0] == 'w')
7778               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7779             else
7780               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7781             break;
7782           default:
7783             /* can't happen */
7784             break;
7785         }
7786         return;
7787     } else if (strncmp(message, "computer mates", 14) == 0) {
7788         switch (gameMode) {
7789           case MachinePlaysBlack:
7790           case IcsPlayingBlack:
7791             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7792             break;
7793           case MachinePlaysWhite:
7794           case IcsPlayingWhite:
7795             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7796             break;
7797           case TwoMachinesPlay:
7798             if (cps->twoMachinesColor[0] == 'w')
7799               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7800             else
7801               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7802             break;
7803           default:
7804             /* can't happen */
7805             break;
7806         }
7807         return;
7808     } else if (strncmp(message, "checkmate", 9) == 0) {
7809         if (WhiteOnMove(forwardMostMove)) {
7810             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7811         } else {
7812             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7813         }
7814         return;
7815     } else if (strstr(message, "Draw") != NULL ||
7816                strstr(message, "game is a draw") != NULL) {
7817         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7818         return;
7819     } else if (strstr(message, "offer") != NULL &&
7820                strstr(message, "draw") != NULL) {
7821 #if ZIPPY
7822         if (appData.zippyPlay && first.initDone) {
7823             /* Relay offer to ICS */
7824             SendToICS(ics_prefix);
7825             SendToICS("draw\n");
7826         }
7827 #endif
7828         cps->offeredDraw = 2; /* valid until this engine moves twice */
7829         if (gameMode == TwoMachinesPlay) {
7830             if (cps->other->offeredDraw) {
7831                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7832             /* [HGM] in two-machine mode we delay relaying draw offer      */
7833             /* until after we also have move, to see if it is really claim */
7834             }
7835         } else if (gameMode == MachinePlaysWhite ||
7836                    gameMode == MachinePlaysBlack) {
7837           if (userOfferedDraw) {
7838             DisplayInformation(_("Machine accepts your draw offer"));
7839             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7840           } else {
7841             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7842           }
7843         }
7844     }
7845
7846     
7847     /*
7848      * Look for thinking output
7849      */
7850     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7851           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7852                                 ) {
7853         int plylev, mvleft, mvtot, curscore, time;
7854         char mvname[MOVE_LEN];
7855         u64 nodes; // [DM]
7856         char plyext;
7857         int ignore = FALSE;
7858         int prefixHint = FALSE;
7859         mvname[0] = NULLCHAR;
7860
7861         switch (gameMode) {
7862           case MachinePlaysBlack:
7863           case IcsPlayingBlack:
7864             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7865             break;
7866           case MachinePlaysWhite:
7867           case IcsPlayingWhite:
7868             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7869             break;
7870           case AnalyzeMode:
7871           case AnalyzeFile:
7872             break;
7873           case IcsObserving: /* [DM] icsEngineAnalyze */
7874             if (!appData.icsEngineAnalyze) ignore = TRUE;
7875             break;
7876           case TwoMachinesPlay:
7877             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7878                 ignore = TRUE;
7879             }
7880             break;
7881           default:
7882             ignore = TRUE;
7883             break;
7884         }
7885
7886         if (!ignore) {
7887             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7888             buf1[0] = NULLCHAR;
7889             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7890                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7891
7892                 if (plyext != ' ' && plyext != '\t') {
7893                     time *= 100;
7894                 }
7895
7896                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7897                 if( cps->scoreIsAbsolute && 
7898                     ( gameMode == MachinePlaysBlack ||
7899                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7900                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7901                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7902                      !WhiteOnMove(currentMove)
7903                     ) )
7904                 {
7905                     curscore = -curscore;
7906                 }
7907
7908
7909                 tempStats.depth = plylev;
7910                 tempStats.nodes = nodes;
7911                 tempStats.time = time;
7912                 tempStats.score = curscore;
7913                 tempStats.got_only_move = 0;
7914
7915                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7916                         int ticklen;
7917
7918                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7919                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7920                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7921                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7922                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7923                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7924                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7925                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7926                 }
7927
7928                 /* Buffer overflow protection */
7929                 if (buf1[0] != NULLCHAR) {
7930                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7931                         && appData.debugMode) {
7932                         fprintf(debugFP,
7933                                 "PV is too long; using the first %u bytes.\n",
7934                                 (unsigned) sizeof(tempStats.movelist) - 1);
7935                     }
7936
7937                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7938                 } else {
7939                     sprintf(tempStats.movelist, " no PV\n");
7940                 }
7941
7942                 if (tempStats.seen_stat) {
7943                     tempStats.ok_to_send = 1;
7944                 }
7945
7946                 if (strchr(tempStats.movelist, '(') != NULL) {
7947                     tempStats.line_is_book = 1;
7948                     tempStats.nr_moves = 0;
7949                     tempStats.moves_left = 0;
7950                 } else {
7951                     tempStats.line_is_book = 0;
7952                 }
7953
7954                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7955                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7956
7957                 SendProgramStatsToFrontend( cps, &tempStats );
7958
7959                 /* 
7960                     [AS] Protect the thinkOutput buffer from overflow... this
7961                     is only useful if buf1 hasn't overflowed first!
7962                 */
7963                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7964                         plylev, 
7965                         (gameMode == TwoMachinesPlay ?
7966                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7967                         ((double) curscore) / 100.0,
7968                         prefixHint ? lastHint : "",
7969                         prefixHint ? " " : "" );
7970
7971                 if( buf1[0] != NULLCHAR ) {
7972                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7973
7974                     if( strlen(buf1) > max_len ) {
7975                         if( appData.debugMode) {
7976                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7977                         }
7978                         buf1[max_len+1] = '\0';
7979                     }
7980
7981                     strcat( thinkOutput, buf1 );
7982                 }
7983
7984                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7985                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7986                     DisplayMove(currentMove - 1);
7987                 }
7988                 return;
7989
7990             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7991                 /* crafty (9.25+) says "(only move) <move>"
7992                  * if there is only 1 legal move
7993                  */
7994                 sscanf(p, "(only move) %s", buf1);
7995                 sprintf(thinkOutput, "%s (only move)", buf1);
7996                 sprintf(programStats.movelist, "%s (only move)", buf1);
7997                 programStats.depth = 1;
7998                 programStats.nr_moves = 1;
7999                 programStats.moves_left = 1;
8000                 programStats.nodes = 1;
8001                 programStats.time = 1;
8002                 programStats.got_only_move = 1;
8003
8004                 /* Not really, but we also use this member to
8005                    mean "line isn't going to change" (Crafty
8006                    isn't searching, so stats won't change) */
8007                 programStats.line_is_book = 1;
8008
8009                 SendProgramStatsToFrontend( cps, &programStats );
8010                 
8011                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
8012                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8013                     DisplayMove(currentMove - 1);
8014                 }
8015                 return;
8016             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8017                               &time, &nodes, &plylev, &mvleft,
8018                               &mvtot, mvname) >= 5) {
8019                 /* The stat01: line is from Crafty (9.29+) in response
8020                    to the "." command */
8021                 programStats.seen_stat = 1;
8022                 cps->maybeThinking = TRUE;
8023
8024                 if (programStats.got_only_move || !appData.periodicUpdates)
8025                   return;
8026
8027                 programStats.depth = plylev;
8028                 programStats.time = time;
8029                 programStats.nodes = nodes;
8030                 programStats.moves_left = mvleft;
8031                 programStats.nr_moves = mvtot;
8032                 strcpy(programStats.move_name, mvname);
8033                 programStats.ok_to_send = 1;
8034                 programStats.movelist[0] = '\0';
8035
8036                 SendProgramStatsToFrontend( cps, &programStats );
8037
8038                 return;
8039
8040             } else if (strncmp(message,"++",2) == 0) {
8041                 /* Crafty 9.29+ outputs this */
8042                 programStats.got_fail = 2;
8043                 return;
8044
8045             } else if (strncmp(message,"--",2) == 0) {
8046                 /* Crafty 9.29+ outputs this */
8047                 programStats.got_fail = 1;
8048                 return;
8049
8050             } else if (thinkOutput[0] != NULLCHAR &&
8051                        strncmp(message, "    ", 4) == 0) {
8052                 unsigned message_len;
8053
8054                 p = message;
8055                 while (*p && *p == ' ') p++;
8056
8057                 message_len = strlen( p );
8058
8059                 /* [AS] Avoid buffer overflow */
8060                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8061                     strcat(thinkOutput, " ");
8062                     strcat(thinkOutput, p);
8063                 }
8064
8065                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8066                     strcat(programStats.movelist, " ");
8067                     strcat(programStats.movelist, p);
8068                 }
8069
8070                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8071                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8072                     DisplayMove(currentMove - 1);
8073                 }
8074                 return;
8075             }
8076         }
8077         else {
8078             buf1[0] = NULLCHAR;
8079
8080             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8081                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8082             {
8083                 ChessProgramStats cpstats;
8084
8085                 if (plyext != ' ' && plyext != '\t') {
8086                     time *= 100;
8087                 }
8088
8089                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8090                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8091                     curscore = -curscore;
8092                 }
8093
8094                 cpstats.depth = plylev;
8095                 cpstats.nodes = nodes;
8096                 cpstats.time = time;
8097                 cpstats.score = curscore;
8098                 cpstats.got_only_move = 0;
8099                 cpstats.movelist[0] = '\0';
8100
8101                 if (buf1[0] != NULLCHAR) {
8102                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8103                 }
8104
8105                 cpstats.ok_to_send = 0;
8106                 cpstats.line_is_book = 0;
8107                 cpstats.nr_moves = 0;
8108                 cpstats.moves_left = 0;
8109
8110                 SendProgramStatsToFrontend( cps, &cpstats );
8111             }
8112         }
8113     }
8114 }
8115
8116
8117 /* Parse a game score from the character string "game", and
8118    record it as the history of the current game.  The game
8119    score is NOT assumed to start from the standard position. 
8120    The display is not updated in any way.
8121    */
8122 void
8123 ParseGameHistory(game)
8124      char *game;
8125 {
8126     ChessMove moveType;
8127     int fromX, fromY, toX, toY, boardIndex;
8128     char promoChar;
8129     char *p, *q;
8130     char buf[MSG_SIZ];
8131
8132     if (appData.debugMode)
8133       fprintf(debugFP, "Parsing game history: %s\n", game);
8134
8135     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8136     gameInfo.site = StrSave(appData.icsHost);
8137     gameInfo.date = PGNDate();
8138     gameInfo.round = StrSave("-");
8139
8140     /* Parse out names of players */
8141     while (*game == ' ') game++;
8142     p = buf;
8143     while (*game != ' ') *p++ = *game++;
8144     *p = NULLCHAR;
8145     gameInfo.white = StrSave(buf);
8146     while (*game == ' ') game++;
8147     p = buf;
8148     while (*game != ' ' && *game != '\n') *p++ = *game++;
8149     *p = NULLCHAR;
8150     gameInfo.black = StrSave(buf);
8151
8152     /* Parse moves */
8153     boardIndex = blackPlaysFirst ? 1 : 0;
8154     yynewstr(game);
8155     for (;;) {
8156         yyboardindex = boardIndex;
8157         moveType = (ChessMove) yylex();
8158         switch (moveType) {
8159           case IllegalMove:             /* maybe suicide chess, etc. */
8160   if (appData.debugMode) {
8161     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8162     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8163     setbuf(debugFP, NULL);
8164   }
8165           case WhitePromotionChancellor:
8166           case BlackPromotionChancellor:
8167           case WhitePromotionArchbishop:
8168           case BlackPromotionArchbishop:
8169           case WhitePromotionQueen:
8170           case BlackPromotionQueen:
8171           case WhitePromotionRook:
8172           case BlackPromotionRook:
8173           case WhitePromotionBishop:
8174           case BlackPromotionBishop:
8175           case WhitePromotionKnight:
8176           case BlackPromotionKnight:
8177           case WhitePromotionKing:
8178           case BlackPromotionKing:
8179           case WhiteNonPromotion:
8180           case BlackNonPromotion:
8181           case NormalMove:
8182           case WhiteCapturesEnPassant:
8183           case BlackCapturesEnPassant:
8184           case WhiteKingSideCastle:
8185           case WhiteQueenSideCastle:
8186           case BlackKingSideCastle:
8187           case BlackQueenSideCastle:
8188           case WhiteKingSideCastleWild:
8189           case WhiteQueenSideCastleWild:
8190           case BlackKingSideCastleWild:
8191           case BlackQueenSideCastleWild:
8192           /* PUSH Fabien */
8193           case WhiteHSideCastleFR:
8194           case WhiteASideCastleFR:
8195           case BlackHSideCastleFR:
8196           case BlackASideCastleFR:
8197           /* POP Fabien */
8198             fromX = currentMoveString[0] - AAA;
8199             fromY = currentMoveString[1] - ONE;
8200             toX = currentMoveString[2] - AAA;
8201             toY = currentMoveString[3] - ONE;
8202             promoChar = currentMoveString[4];
8203             break;
8204           case WhiteDrop:
8205           case BlackDrop:
8206             fromX = moveType == WhiteDrop ?
8207               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8208             (int) CharToPiece(ToLower(currentMoveString[0]));
8209             fromY = DROP_RANK;
8210             toX = currentMoveString[2] - AAA;
8211             toY = currentMoveString[3] - ONE;
8212             promoChar = NULLCHAR;
8213             break;
8214           case AmbiguousMove:
8215             /* bug? */
8216             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8217   if (appData.debugMode) {
8218     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8219     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8220     setbuf(debugFP, NULL);
8221   }
8222             DisplayError(buf, 0);
8223             return;
8224           case ImpossibleMove:
8225             /* bug? */
8226             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8227   if (appData.debugMode) {
8228     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8229     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8230     setbuf(debugFP, NULL);
8231   }
8232             DisplayError(buf, 0);
8233             return;
8234           case (ChessMove) 0:   /* end of file */
8235             if (boardIndex < backwardMostMove) {
8236                 /* Oops, gap.  How did that happen? */
8237                 DisplayError(_("Gap in move list"), 0);
8238                 return;
8239             }
8240             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8241             if (boardIndex > forwardMostMove) {
8242                 forwardMostMove = boardIndex;
8243             }
8244             return;
8245           case ElapsedTime:
8246             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8247                 strcat(parseList[boardIndex-1], " ");
8248                 strcat(parseList[boardIndex-1], yy_text);
8249             }
8250             continue;
8251           case Comment:
8252           case PGNTag:
8253           case NAG:
8254           default:
8255             /* ignore */
8256             continue;
8257           case WhiteWins:
8258           case BlackWins:
8259           case GameIsDrawn:
8260           case GameUnfinished:
8261             if (gameMode == IcsExamining) {
8262                 if (boardIndex < backwardMostMove) {
8263                     /* Oops, gap.  How did that happen? */
8264                     return;
8265                 }
8266                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8267                 return;
8268             }
8269             gameInfo.result = moveType;
8270             p = strchr(yy_text, '{');
8271             if (p == NULL) p = strchr(yy_text, '(');
8272             if (p == NULL) {
8273                 p = yy_text;
8274                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8275             } else {
8276                 q = strchr(p, *p == '{' ? '}' : ')');
8277                 if (q != NULL) *q = NULLCHAR;
8278                 p++;
8279             }
8280             gameInfo.resultDetails = StrSave(p);
8281             continue;
8282         }
8283         if (boardIndex >= forwardMostMove &&
8284             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8285             backwardMostMove = blackPlaysFirst ? 1 : 0;
8286             return;
8287         }
8288         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8289                                  fromY, fromX, toY, toX, promoChar,
8290                                  parseList[boardIndex]);
8291         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8292         /* currentMoveString is set as a side-effect of yylex */
8293         strcpy(moveList[boardIndex], currentMoveString);
8294         strcat(moveList[boardIndex], "\n");
8295         boardIndex++;
8296         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8297         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8298           case MT_NONE:
8299           case MT_STALEMATE:
8300           default:
8301             break;
8302           case MT_CHECK:
8303             if(gameInfo.variant != VariantShogi)
8304                 strcat(parseList[boardIndex - 1], "+");
8305             break;
8306           case MT_CHECKMATE:
8307           case MT_STAINMATE:
8308             strcat(parseList[boardIndex - 1], "#");
8309             break;
8310         }
8311     }
8312 }
8313
8314
8315 /* Apply a move to the given board  */
8316 void
8317 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8318      int fromX, fromY, toX, toY;
8319      int promoChar;
8320      Board board;
8321 {
8322   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8323   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8324
8325     /* [HGM] compute & store e.p. status and castling rights for new position */
8326     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8327
8328       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8329       oldEP = (signed char)board[EP_STATUS];
8330       board[EP_STATUS] = EP_NONE;
8331
8332       if( board[toY][toX] != EmptySquare ) 
8333            board[EP_STATUS] = EP_CAPTURE;  
8334
8335   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8336   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8337        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8338          
8339   if (fromY == DROP_RANK) {
8340         /* must be first */
8341         piece = board[toY][toX] = (ChessSquare) fromX;
8342   } else {
8343       int i;
8344
8345       if( board[fromY][fromX] == WhitePawn ) {
8346            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8347                board[EP_STATUS] = EP_PAWN_MOVE;
8348            if( toY-fromY==2) {
8349                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8350                         gameInfo.variant != VariantBerolina || toX < fromX)
8351                       board[EP_STATUS] = toX | berolina;
8352                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8353                         gameInfo.variant != VariantBerolina || toX > fromX) 
8354                       board[EP_STATUS] = toX;
8355            }
8356       } else 
8357       if( board[fromY][fromX] == BlackPawn ) {
8358            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8359                board[EP_STATUS] = EP_PAWN_MOVE; 
8360            if( toY-fromY== -2) {
8361                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8362                         gameInfo.variant != VariantBerolina || toX < fromX)
8363                       board[EP_STATUS] = toX | berolina;
8364                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8365                         gameInfo.variant != VariantBerolina || toX > fromX) 
8366                       board[EP_STATUS] = toX;
8367            }
8368        }
8369
8370        for(i=0; i<nrCastlingRights; i++) {
8371            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8372               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8373              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8374        }
8375
8376      if (fromX == toX && fromY == toY) return;
8377
8378      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8379      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8380      if(gameInfo.variant == VariantKnightmate)
8381          king += (int) WhiteUnicorn - (int) WhiteKing;
8382
8383     /* Code added by Tord: */
8384     /* FRC castling assumed when king captures friendly rook. */
8385     if (board[fromY][fromX] == WhiteKing &&
8386              board[toY][toX] == WhiteRook) {
8387       board[fromY][fromX] = EmptySquare;
8388       board[toY][toX] = EmptySquare;
8389       if(toX > fromX) {
8390         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8391       } else {
8392         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8393       }
8394     } else if (board[fromY][fromX] == BlackKing &&
8395                board[toY][toX] == BlackRook) {
8396       board[fromY][fromX] = EmptySquare;
8397       board[toY][toX] = EmptySquare;
8398       if(toX > fromX) {
8399         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8400       } else {
8401         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8402       }
8403     /* End of code added by Tord */
8404
8405     } else if (board[fromY][fromX] == king
8406         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8407         && toY == fromY && toX > fromX+1) {
8408         board[fromY][fromX] = EmptySquare;
8409         board[toY][toX] = king;
8410         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8411         board[fromY][BOARD_RGHT-1] = EmptySquare;
8412     } else if (board[fromY][fromX] == king
8413         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8414                && toY == fromY && toX < fromX-1) {
8415         board[fromY][fromX] = EmptySquare;
8416         board[toY][toX] = king;
8417         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8418         board[fromY][BOARD_LEFT] = EmptySquare;
8419     } else if (board[fromY][fromX] == WhitePawn
8420                && toY >= BOARD_HEIGHT-promoRank
8421                && gameInfo.variant != VariantXiangqi
8422                ) {
8423         /* white pawn promotion */
8424         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8425         if (board[toY][toX] == EmptySquare) {
8426             board[toY][toX] = WhiteQueen;
8427         }
8428         if(gameInfo.variant==VariantBughouse ||
8429            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8430             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8431         board[fromY][fromX] = EmptySquare;
8432     } else if ((fromY == BOARD_HEIGHT-4)
8433                && (toX != fromX)
8434                && gameInfo.variant != VariantXiangqi
8435                && gameInfo.variant != VariantBerolina
8436                && (board[fromY][fromX] == WhitePawn)
8437                && (board[toY][toX] == EmptySquare)) {
8438         board[fromY][fromX] = EmptySquare;
8439         board[toY][toX] = WhitePawn;
8440         captured = board[toY - 1][toX];
8441         board[toY - 1][toX] = EmptySquare;
8442     } else if ((fromY == BOARD_HEIGHT-4)
8443                && (toX == fromX)
8444                && gameInfo.variant == VariantBerolina
8445                && (board[fromY][fromX] == WhitePawn)
8446                && (board[toY][toX] == EmptySquare)) {
8447         board[fromY][fromX] = EmptySquare;
8448         board[toY][toX] = WhitePawn;
8449         if(oldEP & EP_BEROLIN_A) {
8450                 captured = board[fromY][fromX-1];
8451                 board[fromY][fromX-1] = EmptySquare;
8452         }else{  captured = board[fromY][fromX+1];
8453                 board[fromY][fromX+1] = EmptySquare;
8454         }
8455     } else if (board[fromY][fromX] == king
8456         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8457                && toY == fromY && toX > fromX+1) {
8458         board[fromY][fromX] = EmptySquare;
8459         board[toY][toX] = king;
8460         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8461         board[fromY][BOARD_RGHT-1] = EmptySquare;
8462     } else if (board[fromY][fromX] == king
8463         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8464                && toY == fromY && toX < fromX-1) {
8465         board[fromY][fromX] = EmptySquare;
8466         board[toY][toX] = king;
8467         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8468         board[fromY][BOARD_LEFT] = EmptySquare;
8469     } else if (fromY == 7 && fromX == 3
8470                && board[fromY][fromX] == BlackKing
8471                && toY == 7 && toX == 5) {
8472         board[fromY][fromX] = EmptySquare;
8473         board[toY][toX] = BlackKing;
8474         board[fromY][7] = EmptySquare;
8475         board[toY][4] = BlackRook;
8476     } else if (fromY == 7 && fromX == 3
8477                && board[fromY][fromX] == BlackKing
8478                && toY == 7 && toX == 1) {
8479         board[fromY][fromX] = EmptySquare;
8480         board[toY][toX] = BlackKing;
8481         board[fromY][0] = EmptySquare;
8482         board[toY][2] = BlackRook;
8483     } else if (board[fromY][fromX] == BlackPawn
8484                && toY < promoRank
8485                && gameInfo.variant != VariantXiangqi
8486                ) {
8487         /* black pawn promotion */
8488         board[toY][toX] = CharToPiece(ToLower(promoChar));
8489         if (board[toY][toX] == EmptySquare) {
8490             board[toY][toX] = BlackQueen;
8491         }
8492         if(gameInfo.variant==VariantBughouse ||
8493            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8494             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8495         board[fromY][fromX] = EmptySquare;
8496     } else if ((fromY == 3)
8497                && (toX != fromX)
8498                && gameInfo.variant != VariantXiangqi
8499                && gameInfo.variant != VariantBerolina
8500                && (board[fromY][fromX] == BlackPawn)
8501                && (board[toY][toX] == EmptySquare)) {
8502         board[fromY][fromX] = EmptySquare;
8503         board[toY][toX] = BlackPawn;
8504         captured = board[toY + 1][toX];
8505         board[toY + 1][toX] = EmptySquare;
8506     } else if ((fromY == 3)
8507                && (toX == fromX)
8508                && gameInfo.variant == VariantBerolina
8509                && (board[fromY][fromX] == BlackPawn)
8510                && (board[toY][toX] == EmptySquare)) {
8511         board[fromY][fromX] = EmptySquare;
8512         board[toY][toX] = BlackPawn;
8513         if(oldEP & EP_BEROLIN_A) {
8514                 captured = board[fromY][fromX-1];
8515                 board[fromY][fromX-1] = EmptySquare;
8516         }else{  captured = board[fromY][fromX+1];
8517                 board[fromY][fromX+1] = EmptySquare;
8518         }
8519     } else {
8520         board[toY][toX] = board[fromY][fromX];
8521         board[fromY][fromX] = EmptySquare;
8522     }
8523
8524     /* [HGM] now we promote for Shogi, if needed */
8525     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8526         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8527   }
8528
8529     if (gameInfo.holdingsWidth != 0) {
8530
8531       /* !!A lot more code needs to be written to support holdings  */
8532       /* [HGM] OK, so I have written it. Holdings are stored in the */
8533       /* penultimate board files, so they are automaticlly stored   */
8534       /* in the game history.                                       */
8535       if (fromY == DROP_RANK) {
8536         /* Delete from holdings, by decreasing count */
8537         /* and erasing image if necessary            */
8538         p = (int) fromX;
8539         if(p < (int) BlackPawn) { /* white drop */
8540              p -= (int)WhitePawn;
8541                  p = PieceToNumber((ChessSquare)p);
8542              if(p >= gameInfo.holdingsSize) p = 0;
8543              if(--board[p][BOARD_WIDTH-2] <= 0)
8544                   board[p][BOARD_WIDTH-1] = EmptySquare;
8545              if((int)board[p][BOARD_WIDTH-2] < 0)
8546                         board[p][BOARD_WIDTH-2] = 0;
8547         } else {                  /* black drop */
8548              p -= (int)BlackPawn;
8549                  p = PieceToNumber((ChessSquare)p);
8550              if(p >= gameInfo.holdingsSize) p = 0;
8551              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8552                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8553              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8554                         board[BOARD_HEIGHT-1-p][1] = 0;
8555         }
8556       }
8557       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8558           && gameInfo.variant != VariantBughouse        ) {
8559         /* [HGM] holdings: Add to holdings, if holdings exist */
8560         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8561                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8562                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8563         }
8564         p = (int) captured;
8565         if (p >= (int) BlackPawn) {
8566           p -= (int)BlackPawn;
8567           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8568                   /* in Shogi restore piece to its original  first */
8569                   captured = (ChessSquare) (DEMOTED captured);
8570                   p = DEMOTED p;
8571           }
8572           p = PieceToNumber((ChessSquare)p);
8573           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8574           board[p][BOARD_WIDTH-2]++;
8575           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8576         } else {
8577           p -= (int)WhitePawn;
8578           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8579                   captured = (ChessSquare) (DEMOTED captured);
8580                   p = DEMOTED p;
8581           }
8582           p = PieceToNumber((ChessSquare)p);
8583           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8584           board[BOARD_HEIGHT-1-p][1]++;
8585           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8586         }
8587       }
8588     } else if (gameInfo.variant == VariantAtomic) {
8589       if (captured != EmptySquare) {
8590         int y, x;
8591         for (y = toY-1; y <= toY+1; y++) {
8592           for (x = toX-1; x <= toX+1; x++) {
8593             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8594                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8595               board[y][x] = EmptySquare;
8596             }
8597           }
8598         }
8599         board[toY][toX] = EmptySquare;
8600       }
8601     }
8602     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8603         /* [HGM] Shogi promotions */
8604         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8605     }
8606
8607     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8608                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8609         // [HGM] superchess: take promotion piece out of holdings
8610         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8611         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8612             if(!--board[k][BOARD_WIDTH-2])
8613                 board[k][BOARD_WIDTH-1] = EmptySquare;
8614         } else {
8615             if(!--board[BOARD_HEIGHT-1-k][1])
8616                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8617         }
8618     }
8619
8620 }
8621
8622 /* Updates forwardMostMove */
8623 void
8624 MakeMove(fromX, fromY, toX, toY, promoChar)
8625      int fromX, fromY, toX, toY;
8626      int promoChar;
8627 {
8628 //    forwardMostMove++; // [HGM] bare: moved downstream
8629
8630     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8631         int timeLeft; static int lastLoadFlag=0; int king, piece;
8632         piece = boards[forwardMostMove][fromY][fromX];
8633         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8634         if(gameInfo.variant == VariantKnightmate)
8635             king += (int) WhiteUnicorn - (int) WhiteKing;
8636         if(forwardMostMove == 0) {
8637             if(blackPlaysFirst) 
8638                 fprintf(serverMoves, "%s;", second.tidy);
8639             fprintf(serverMoves, "%s;", first.tidy);
8640             if(!blackPlaysFirst) 
8641                 fprintf(serverMoves, "%s;", second.tidy);
8642         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8643         lastLoadFlag = loadFlag;
8644         // print base move
8645         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8646         // print castling suffix
8647         if( toY == fromY && piece == king ) {
8648             if(toX-fromX > 1)
8649                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8650             if(fromX-toX >1)
8651                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8652         }
8653         // e.p. suffix
8654         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8655              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8656              boards[forwardMostMove][toY][toX] == EmptySquare
8657              && fromX != toX && fromY != toY)
8658                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8659         // promotion suffix
8660         if(promoChar != NULLCHAR)
8661                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8662         if(!loadFlag) {
8663             fprintf(serverMoves, "/%d/%d",
8664                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8665             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8666             else                      timeLeft = blackTimeRemaining/1000;
8667             fprintf(serverMoves, "/%d", timeLeft);
8668         }
8669         fflush(serverMoves);
8670     }
8671
8672     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8673       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8674                         0, 1);
8675       return;
8676     }
8677     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8678     if (commentList[forwardMostMove+1] != NULL) {
8679         free(commentList[forwardMostMove+1]);
8680         commentList[forwardMostMove+1] = NULL;
8681     }
8682     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8683     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8684     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8685     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8686     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8687     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8688     gameInfo.result = GameUnfinished;
8689     if (gameInfo.resultDetails != NULL) {
8690         free(gameInfo.resultDetails);
8691         gameInfo.resultDetails = NULL;
8692     }
8693     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8694                               moveList[forwardMostMove - 1]);
8695     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8696                              PosFlags(forwardMostMove - 1),
8697                              fromY, fromX, toY, toX, promoChar,
8698                              parseList[forwardMostMove - 1]);
8699     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8700       case MT_NONE:
8701       case MT_STALEMATE:
8702       default:
8703         break;
8704       case MT_CHECK:
8705         if(gameInfo.variant != VariantShogi)
8706             strcat(parseList[forwardMostMove - 1], "+");
8707         break;
8708       case MT_CHECKMATE:
8709       case MT_STAINMATE:
8710         strcat(parseList[forwardMostMove - 1], "#");
8711         break;
8712     }
8713     if (appData.debugMode) {
8714         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8715     }
8716
8717 }
8718
8719 /* Updates currentMove if not pausing */
8720 void
8721 ShowMove(fromX, fromY, toX, toY)
8722 {
8723     int instant = (gameMode == PlayFromGameFile) ?
8724         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8725     if(appData.noGUI) return;
8726     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8727         if (!instant) {
8728             if (forwardMostMove == currentMove + 1) {
8729                 AnimateMove(boards[forwardMostMove - 1],
8730                             fromX, fromY, toX, toY);
8731             }
8732             if (appData.highlightLastMove) {
8733                 SetHighlights(fromX, fromY, toX, toY);
8734             }
8735         }
8736         currentMove = forwardMostMove;
8737     }
8738
8739     if (instant) return;
8740
8741     DisplayMove(currentMove - 1);
8742     DrawPosition(FALSE, boards[currentMove]);
8743     DisplayBothClocks();
8744     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8745 }
8746
8747 void SendEgtPath(ChessProgramState *cps)
8748 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8749         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8750
8751         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8752
8753         while(*p) {
8754             char c, *q = name+1, *r, *s;
8755
8756             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8757             while(*p && *p != ',') *q++ = *p++;
8758             *q++ = ':'; *q = 0;
8759             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8760                 strcmp(name, ",nalimov:") == 0 ) {
8761                 // take nalimov path from the menu-changeable option first, if it is defined
8762                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8763                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8764             } else
8765             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8766                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8767                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8768                 s = r = StrStr(s, ":") + 1; // beginning of path info
8769                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8770                 c = *r; *r = 0;             // temporarily null-terminate path info
8771                     *--q = 0;               // strip of trailig ':' from name
8772                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8773                 *r = c;
8774                 SendToProgram(buf,cps);     // send egtbpath command for this format
8775             }
8776             if(*p == ',') p++; // read away comma to position for next format name
8777         }
8778 }
8779
8780 void
8781 InitChessProgram(cps, setup)
8782      ChessProgramState *cps;
8783      int setup; /* [HGM] needed to setup FRC opening position */
8784 {
8785     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8786     if (appData.noChessProgram) return;
8787     hintRequested = FALSE;
8788     bookRequested = FALSE;
8789
8790     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8791     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8792     if(cps->memSize) { /* [HGM] memory */
8793         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8794         SendToProgram(buf, cps);
8795     }
8796     SendEgtPath(cps); /* [HGM] EGT */
8797     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8798         sprintf(buf, "cores %d\n", appData.smpCores);
8799         SendToProgram(buf, cps);
8800     }
8801
8802     SendToProgram(cps->initString, cps);
8803     if (gameInfo.variant != VariantNormal &&
8804         gameInfo.variant != VariantLoadable
8805         /* [HGM] also send variant if board size non-standard */
8806         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8807                                             ) {
8808       char *v = VariantName(gameInfo.variant);
8809       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8810         /* [HGM] in protocol 1 we have to assume all variants valid */
8811         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8812         DisplayFatalError(buf, 0, 1);
8813         return;
8814       }
8815
8816       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8817       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8818       if( gameInfo.variant == VariantXiangqi )
8819            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8820       if( gameInfo.variant == VariantShogi )
8821            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8822       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8823            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8824       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8825                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8826            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8827       if( gameInfo.variant == VariantCourier )
8828            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8829       if( gameInfo.variant == VariantSuper )
8830            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8831       if( gameInfo.variant == VariantGreat )
8832            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8833
8834       if(overruled) {
8835            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8836                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8837            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8838            if(StrStr(cps->variants, b) == NULL) { 
8839                // specific sized variant not known, check if general sizing allowed
8840                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8841                    if(StrStr(cps->variants, "boardsize") == NULL) {
8842                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8843                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8844                        DisplayFatalError(buf, 0, 1);
8845                        return;
8846                    }
8847                    /* [HGM] here we really should compare with the maximum supported board size */
8848                }
8849            }
8850       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8851       sprintf(buf, "variant %s\n", b);
8852       SendToProgram(buf, cps);
8853     }
8854     currentlyInitializedVariant = gameInfo.variant;
8855
8856     /* [HGM] send opening position in FRC to first engine */
8857     if(setup) {
8858           SendToProgram("force\n", cps);
8859           SendBoard(cps, 0);
8860           /* engine is now in force mode! Set flag to wake it up after first move. */
8861           setboardSpoiledMachineBlack = 1;
8862     }
8863
8864     if (cps->sendICS) {
8865       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8866       SendToProgram(buf, cps);
8867     }
8868     cps->maybeThinking = FALSE;
8869     cps->offeredDraw = 0;
8870     if (!appData.icsActive) {
8871         SendTimeControl(cps, movesPerSession, timeControl,
8872                         timeIncrement, appData.searchDepth,
8873                         searchTime);
8874     }
8875     if (appData.showThinking 
8876         // [HGM] thinking: four options require thinking output to be sent
8877         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8878                                 ) {
8879         SendToProgram("post\n", cps);
8880     }
8881     SendToProgram("hard\n", cps);
8882     if (!appData.ponderNextMove) {
8883         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8884            it without being sure what state we are in first.  "hard"
8885            is not a toggle, so that one is OK.
8886          */
8887         SendToProgram("easy\n", cps);
8888     }
8889     if (cps->usePing) {
8890       sprintf(buf, "ping %d\n", ++cps->lastPing);
8891       SendToProgram(buf, cps);
8892     }
8893     cps->initDone = TRUE;
8894 }   
8895
8896
8897 void
8898 StartChessProgram(cps)
8899      ChessProgramState *cps;
8900 {
8901     char buf[MSG_SIZ];
8902     int err;
8903
8904     if (appData.noChessProgram) return;
8905     cps->initDone = FALSE;
8906
8907     if (strcmp(cps->host, "localhost") == 0) {
8908         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8909     } else if (*appData.remoteShell == NULLCHAR) {
8910         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8911     } else {
8912         if (*appData.remoteUser == NULLCHAR) {
8913           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8914                     cps->program);
8915         } else {
8916           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8917                     cps->host, appData.remoteUser, cps->program);
8918         }
8919         err = StartChildProcess(buf, "", &cps->pr);
8920     }
8921     
8922     if (err != 0) {
8923         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8924         DisplayFatalError(buf, err, 1);
8925         cps->pr = NoProc;
8926         cps->isr = NULL;
8927         return;
8928     }
8929     
8930     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8931     if (cps->protocolVersion > 1) {
8932       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8933       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8934       cps->comboCnt = 0;  //                and values of combo boxes
8935       SendToProgram(buf, cps);
8936     } else {
8937       SendToProgram("xboard\n", cps);
8938     }
8939 }
8940
8941
8942 void
8943 TwoMachinesEventIfReady P((void))
8944 {
8945   if (first.lastPing != first.lastPong) {
8946     DisplayMessage("", _("Waiting for first chess program"));
8947     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8948     return;
8949   }
8950   if (second.lastPing != second.lastPong) {
8951     DisplayMessage("", _("Waiting for second chess program"));
8952     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8953     return;
8954   }
8955   ThawUI();
8956   TwoMachinesEvent();
8957 }
8958
8959 void
8960 NextMatchGame P((void))
8961 {
8962     int index; /* [HGM] autoinc: step load index during match */
8963     Reset(FALSE, TRUE);
8964     if (*appData.loadGameFile != NULLCHAR) {
8965         index = appData.loadGameIndex;
8966         if(index < 0) { // [HGM] autoinc
8967             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8968             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8969         } 
8970         LoadGameFromFile(appData.loadGameFile,
8971                          index,
8972                          appData.loadGameFile, FALSE);
8973     } else if (*appData.loadPositionFile != NULLCHAR) {
8974         index = appData.loadPositionIndex;
8975         if(index < 0) { // [HGM] autoinc
8976             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8977             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8978         } 
8979         LoadPositionFromFile(appData.loadPositionFile,
8980                              index,
8981                              appData.loadPositionFile);
8982     }
8983     TwoMachinesEventIfReady();
8984 }
8985
8986 void UserAdjudicationEvent( int result )
8987 {
8988     ChessMove gameResult = GameIsDrawn;
8989
8990     if( result > 0 ) {
8991         gameResult = WhiteWins;
8992     }
8993     else if( result < 0 ) {
8994         gameResult = BlackWins;
8995     }
8996
8997     if( gameMode == TwoMachinesPlay ) {
8998         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8999     }
9000 }
9001
9002
9003 // [HGM] save: calculate checksum of game to make games easily identifiable
9004 int StringCheckSum(char *s)
9005 {
9006         int i = 0;
9007         if(s==NULL) return 0;
9008         while(*s) i = i*259 + *s++;
9009         return i;
9010 }
9011
9012 int GameCheckSum()
9013 {
9014         int i, sum=0;
9015         for(i=backwardMostMove; i<forwardMostMove; i++) {
9016                 sum += pvInfoList[i].depth;
9017                 sum += StringCheckSum(parseList[i]);
9018                 sum += StringCheckSum(commentList[i]);
9019                 sum *= 261;
9020         }
9021         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9022         return sum + StringCheckSum(commentList[i]);
9023 } // end of save patch
9024
9025 void
9026 GameEnds(result, resultDetails, whosays)
9027      ChessMove result;
9028      char *resultDetails;
9029      int whosays;
9030 {
9031     GameMode nextGameMode;
9032     int isIcsGame;
9033     char buf[MSG_SIZ], popupRequested = 0;
9034
9035     if(endingGame) return; /* [HGM] crash: forbid recursion */
9036     endingGame = 1;
9037     if(twoBoards) { // [HGM] dual: switch back to one board
9038         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9039         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9040     }
9041     if (appData.debugMode) {
9042       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9043               result, resultDetails ? resultDetails : "(null)", whosays);
9044     }
9045
9046     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9047
9048     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9049         /* If we are playing on ICS, the server decides when the
9050            game is over, but the engine can offer to draw, claim 
9051            a draw, or resign. 
9052          */
9053 #if ZIPPY
9054         if (appData.zippyPlay && first.initDone) {
9055             if (result == GameIsDrawn) {
9056                 /* In case draw still needs to be claimed */
9057                 SendToICS(ics_prefix);
9058                 SendToICS("draw\n");
9059             } else if (StrCaseStr(resultDetails, "resign")) {
9060                 SendToICS(ics_prefix);
9061                 SendToICS("resign\n");
9062             }
9063         }
9064 #endif
9065         endingGame = 0; /* [HGM] crash */
9066         return;
9067     }
9068
9069     /* If we're loading the game from a file, stop */
9070     if (whosays == GE_FILE) {
9071       (void) StopLoadGameTimer();
9072       gameFileFP = NULL;
9073     }
9074
9075     /* Cancel draw offers */
9076     first.offeredDraw = second.offeredDraw = 0;
9077
9078     /* If this is an ICS game, only ICS can really say it's done;
9079        if not, anyone can. */
9080     isIcsGame = (gameMode == IcsPlayingWhite || 
9081                  gameMode == IcsPlayingBlack || 
9082                  gameMode == IcsObserving    || 
9083                  gameMode == IcsExamining);
9084
9085     if (!isIcsGame || whosays == GE_ICS) {
9086         /* OK -- not an ICS game, or ICS said it was done */
9087         StopClocks();
9088         if (!isIcsGame && !appData.noChessProgram) 
9089           SetUserThinkingEnables();
9090     
9091         /* [HGM] if a machine claims the game end we verify this claim */
9092         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9093             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9094                 char claimer;
9095                 ChessMove trueResult = (ChessMove) -1;
9096
9097                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9098                                             first.twoMachinesColor[0] :
9099                                             second.twoMachinesColor[0] ;
9100
9101                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9102                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9103                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9104                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9105                 } else
9106                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9107                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9108                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9109                 } else
9110                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9111                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9112                 }
9113
9114                 // now verify win claims, but not in drop games, as we don't understand those yet
9115                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9116                                                  || gameInfo.variant == VariantGreat) &&
9117                     (result == WhiteWins && claimer == 'w' ||
9118                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9119                       if (appData.debugMode) {
9120                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9121                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9122                       }
9123                       if(result != trueResult) {
9124                               sprintf(buf, "False win claim: '%s'", resultDetails);
9125                               result = claimer == 'w' ? BlackWins : WhiteWins;
9126                               resultDetails = buf;
9127                       }
9128                 } else
9129                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9130                     && (forwardMostMove <= backwardMostMove ||
9131                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9132                         (claimer=='b')==(forwardMostMove&1))
9133                                                                                   ) {
9134                       /* [HGM] verify: draws that were not flagged are false claims */
9135                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9136                       result = claimer == 'w' ? BlackWins : WhiteWins;
9137                       resultDetails = buf;
9138                 }
9139                 /* (Claiming a loss is accepted no questions asked!) */
9140             }
9141             /* [HGM] bare: don't allow bare King to win */
9142             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9143                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9144                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9145                && result != GameIsDrawn)
9146             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9147                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9148                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9149                         if(p >= 0 && p <= (int)WhiteKing) k++;
9150                 }
9151                 if (appData.debugMode) {
9152                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9153                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9154                 }
9155                 if(k <= 1) {
9156                         result = GameIsDrawn;
9157                         sprintf(buf, "%s but bare king", resultDetails);
9158                         resultDetails = buf;
9159                 }
9160             }
9161         }
9162
9163
9164         if(serverMoves != NULL && !loadFlag) { char c = '=';
9165             if(result==WhiteWins) c = '+';
9166             if(result==BlackWins) c = '-';
9167             if(resultDetails != NULL)
9168                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9169         }
9170         if (resultDetails != NULL) {
9171             gameInfo.result = result;
9172             gameInfo.resultDetails = StrSave(resultDetails);
9173
9174             /* display last move only if game was not loaded from file */
9175             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9176                 DisplayMove(currentMove - 1);
9177     
9178             if (forwardMostMove != 0) {
9179                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9180                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9181                                                                 ) {
9182                     if (*appData.saveGameFile != NULLCHAR) {
9183                         SaveGameToFile(appData.saveGameFile, TRUE);
9184                     } else if (appData.autoSaveGames) {
9185                         AutoSaveGame();
9186                     }
9187                     if (*appData.savePositionFile != NULLCHAR) {
9188                         SavePositionToFile(appData.savePositionFile);
9189                     }
9190                 }
9191             }
9192
9193             /* Tell program how game ended in case it is learning */
9194             /* [HGM] Moved this to after saving the PGN, just in case */
9195             /* engine died and we got here through time loss. In that */
9196             /* case we will get a fatal error writing the pipe, which */
9197             /* would otherwise lose us the PGN.                       */
9198             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9199             /* output during GameEnds should never be fatal anymore   */
9200             if (gameMode == MachinePlaysWhite ||
9201                 gameMode == MachinePlaysBlack ||
9202                 gameMode == TwoMachinesPlay ||
9203                 gameMode == IcsPlayingWhite ||
9204                 gameMode == IcsPlayingBlack ||
9205                 gameMode == BeginningOfGame) {
9206                 char buf[MSG_SIZ];
9207                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9208                         resultDetails);
9209                 if (first.pr != NoProc) {
9210                     SendToProgram(buf, &first);
9211                 }
9212                 if (second.pr != NoProc &&
9213                     gameMode == TwoMachinesPlay) {
9214                     SendToProgram(buf, &second);
9215                 }
9216             }
9217         }
9218
9219         if (appData.icsActive) {
9220             if (appData.quietPlay &&
9221                 (gameMode == IcsPlayingWhite ||
9222                  gameMode == IcsPlayingBlack)) {
9223                 SendToICS(ics_prefix);
9224                 SendToICS("set shout 1\n");
9225             }
9226             nextGameMode = IcsIdle;
9227             ics_user_moved = FALSE;
9228             /* clean up premove.  It's ugly when the game has ended and the
9229              * premove highlights are still on the board.
9230              */
9231             if (gotPremove) {
9232               gotPremove = FALSE;
9233               ClearPremoveHighlights();
9234               DrawPosition(FALSE, boards[currentMove]);
9235             }
9236             if (whosays == GE_ICS) {
9237                 switch (result) {
9238                 case WhiteWins:
9239                     if (gameMode == IcsPlayingWhite)
9240                         PlayIcsWinSound();
9241                     else if(gameMode == IcsPlayingBlack)
9242                         PlayIcsLossSound();
9243                     break;
9244                 case BlackWins:
9245                     if (gameMode == IcsPlayingBlack)
9246                         PlayIcsWinSound();
9247                     else if(gameMode == IcsPlayingWhite)
9248                         PlayIcsLossSound();
9249                     break;
9250                 case GameIsDrawn:
9251                     PlayIcsDrawSound();
9252                     break;
9253                 default:
9254                     PlayIcsUnfinishedSound();
9255                 }
9256             }
9257         } else if (gameMode == EditGame ||
9258                    gameMode == PlayFromGameFile || 
9259                    gameMode == AnalyzeMode || 
9260                    gameMode == AnalyzeFile) {
9261             nextGameMode = gameMode;
9262         } else {
9263             nextGameMode = EndOfGame;
9264         }
9265         pausing = FALSE;
9266         ModeHighlight();
9267     } else {
9268         nextGameMode = gameMode;
9269     }
9270
9271     if (appData.noChessProgram) {
9272         gameMode = nextGameMode;
9273         ModeHighlight();
9274         endingGame = 0; /* [HGM] crash */
9275         return;
9276     }
9277
9278     if (first.reuse) {
9279         /* Put first chess program into idle state */
9280         if (first.pr != NoProc &&
9281             (gameMode == MachinePlaysWhite ||
9282              gameMode == MachinePlaysBlack ||
9283              gameMode == TwoMachinesPlay ||
9284              gameMode == IcsPlayingWhite ||
9285              gameMode == IcsPlayingBlack ||
9286              gameMode == BeginningOfGame)) {
9287             SendToProgram("force\n", &first);
9288             if (first.usePing) {
9289               char buf[MSG_SIZ];
9290               sprintf(buf, "ping %d\n", ++first.lastPing);
9291               SendToProgram(buf, &first);
9292             }
9293         }
9294     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9295         /* Kill off first chess program */
9296         if (first.isr != NULL)
9297           RemoveInputSource(first.isr);
9298         first.isr = NULL;
9299     
9300         if (first.pr != NoProc) {
9301             ExitAnalyzeMode();
9302             DoSleep( appData.delayBeforeQuit );
9303             SendToProgram("quit\n", &first);
9304             DoSleep( appData.delayAfterQuit );
9305             DestroyChildProcess(first.pr, first.useSigterm);
9306         }
9307         first.pr = NoProc;
9308     }
9309     if (second.reuse) {
9310         /* Put second chess program into idle state */
9311         if (second.pr != NoProc &&
9312             gameMode == TwoMachinesPlay) {
9313             SendToProgram("force\n", &second);
9314             if (second.usePing) {
9315               char buf[MSG_SIZ];
9316               sprintf(buf, "ping %d\n", ++second.lastPing);
9317               SendToProgram(buf, &second);
9318             }
9319         }
9320     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9321         /* Kill off second chess program */
9322         if (second.isr != NULL)
9323           RemoveInputSource(second.isr);
9324         second.isr = NULL;
9325     
9326         if (second.pr != NoProc) {
9327             DoSleep( appData.delayBeforeQuit );
9328             SendToProgram("quit\n", &second);
9329             DoSleep( appData.delayAfterQuit );
9330             DestroyChildProcess(second.pr, second.useSigterm);
9331         }
9332         second.pr = NoProc;
9333     }
9334
9335     if (matchMode && gameMode == TwoMachinesPlay) {
9336         switch (result) {
9337         case WhiteWins:
9338           if (first.twoMachinesColor[0] == 'w') {
9339             first.matchWins++;
9340           } else {
9341             second.matchWins++;
9342           }
9343           break;
9344         case BlackWins:
9345           if (first.twoMachinesColor[0] == 'b') {
9346             first.matchWins++;
9347           } else {
9348             second.matchWins++;
9349           }
9350           break;
9351         default:
9352           break;
9353         }
9354         if (matchGame < appData.matchGames) {
9355             char *tmp;
9356             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9357                 tmp = first.twoMachinesColor;
9358                 first.twoMachinesColor = second.twoMachinesColor;
9359                 second.twoMachinesColor = tmp;
9360             }
9361             gameMode = nextGameMode;
9362             matchGame++;
9363             if(appData.matchPause>10000 || appData.matchPause<10)
9364                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9365             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9366             endingGame = 0; /* [HGM] crash */
9367             return;
9368         } else {
9369             gameMode = nextGameMode;
9370             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9371                     first.tidy, second.tidy,
9372                     first.matchWins, second.matchWins,
9373                     appData.matchGames - (first.matchWins + second.matchWins));
9374             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9375         }
9376     }
9377     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9378         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9379       ExitAnalyzeMode();
9380     gameMode = nextGameMode;
9381     ModeHighlight();
9382     endingGame = 0;  /* [HGM] crash */
9383     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9384       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9385         matchMode = FALSE; appData.matchGames = matchGame = 0;
9386         DisplayNote(buf);
9387       }
9388     }
9389 }
9390
9391 /* Assumes program was just initialized (initString sent).
9392    Leaves program in force mode. */
9393 void
9394 FeedMovesToProgram(cps, upto) 
9395      ChessProgramState *cps;
9396      int upto;
9397 {
9398     int i;
9399     
9400     if (appData.debugMode)
9401       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9402               startedFromSetupPosition ? "position and " : "",
9403               backwardMostMove, upto, cps->which);
9404     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9405         // [HGM] variantswitch: make engine aware of new variant
9406         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9407                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9408         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9409         SendToProgram(buf, cps);
9410         currentlyInitializedVariant = gameInfo.variant;
9411     }
9412     SendToProgram("force\n", cps);
9413     if (startedFromSetupPosition) {
9414         SendBoard(cps, backwardMostMove);
9415     if (appData.debugMode) {
9416         fprintf(debugFP, "feedMoves\n");
9417     }
9418     }
9419     for (i = backwardMostMove; i < upto; i++) {
9420         SendMoveToProgram(i, cps);
9421     }
9422 }
9423
9424
9425 void
9426 ResurrectChessProgram()
9427 {
9428      /* The chess program may have exited.
9429         If so, restart it and feed it all the moves made so far. */
9430
9431     if (appData.noChessProgram || first.pr != NoProc) return;
9432     
9433     StartChessProgram(&first);
9434     InitChessProgram(&first, FALSE);
9435     FeedMovesToProgram(&first, currentMove);
9436
9437     if (!first.sendTime) {
9438         /* can't tell gnuchess what its clock should read,
9439            so we bow to its notion. */
9440         ResetClocks();
9441         timeRemaining[0][currentMove] = whiteTimeRemaining;
9442         timeRemaining[1][currentMove] = blackTimeRemaining;
9443     }
9444
9445     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9446                 appData.icsEngineAnalyze) && first.analysisSupport) {
9447       SendToProgram("analyze\n", &first);
9448       first.analyzing = TRUE;
9449     }
9450 }
9451
9452 /*
9453  * Button procedures
9454  */
9455 void
9456 Reset(redraw, init)
9457      int redraw, init;
9458 {
9459     int i;
9460
9461     if (appData.debugMode) {
9462         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9463                 redraw, init, gameMode);
9464     }
9465     CleanupTail(); // [HGM] vari: delete any stored variations
9466     pausing = pauseExamInvalid = FALSE;
9467     startedFromSetupPosition = blackPlaysFirst = FALSE;
9468     firstMove = TRUE;
9469     whiteFlag = blackFlag = FALSE;
9470     userOfferedDraw = FALSE;
9471     hintRequested = bookRequested = FALSE;
9472     first.maybeThinking = FALSE;
9473     second.maybeThinking = FALSE;
9474     first.bookSuspend = FALSE; // [HGM] book
9475     second.bookSuspend = FALSE;
9476     thinkOutput[0] = NULLCHAR;
9477     lastHint[0] = NULLCHAR;
9478     ClearGameInfo(&gameInfo);
9479     gameInfo.variant = StringToVariant(appData.variant);
9480     ics_user_moved = ics_clock_paused = FALSE;
9481     ics_getting_history = H_FALSE;
9482     ics_gamenum = -1;
9483     white_holding[0] = black_holding[0] = NULLCHAR;
9484     ClearProgramStats();
9485     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9486     
9487     ResetFrontEnd();
9488     ClearHighlights();
9489     flipView = appData.flipView;
9490     ClearPremoveHighlights();
9491     gotPremove = FALSE;
9492     alarmSounded = FALSE;
9493
9494     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9495     if(appData.serverMovesName != NULL) {
9496         /* [HGM] prepare to make moves file for broadcasting */
9497         clock_t t = clock();
9498         if(serverMoves != NULL) fclose(serverMoves);
9499         serverMoves = fopen(appData.serverMovesName, "r");
9500         if(serverMoves != NULL) {
9501             fclose(serverMoves);
9502             /* delay 15 sec before overwriting, so all clients can see end */
9503             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9504         }
9505         serverMoves = fopen(appData.serverMovesName, "w");
9506     }
9507
9508     ExitAnalyzeMode();
9509     gameMode = BeginningOfGame;
9510     ModeHighlight();
9511     if(appData.icsActive) gameInfo.variant = VariantNormal;
9512     currentMove = forwardMostMove = backwardMostMove = 0;
9513     InitPosition(redraw);
9514     for (i = 0; i < MAX_MOVES; i++) {
9515         if (commentList[i] != NULL) {
9516             free(commentList[i]);
9517             commentList[i] = NULL;
9518         }
9519     }
9520     ResetClocks();
9521     timeRemaining[0][0] = whiteTimeRemaining;
9522     timeRemaining[1][0] = blackTimeRemaining;
9523     if (first.pr == NULL) {
9524         StartChessProgram(&first);
9525     }
9526     if (init) {
9527             InitChessProgram(&first, startedFromSetupPosition);
9528     }
9529     DisplayTitle("");
9530     DisplayMessage("", "");
9531     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9532     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9533 }
9534
9535 void
9536 AutoPlayGameLoop()
9537 {
9538     for (;;) {
9539         if (!AutoPlayOneMove())
9540           return;
9541         if (matchMode || appData.timeDelay == 0)
9542           continue;
9543         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9544           return;
9545         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9546         break;
9547     }
9548 }
9549
9550
9551 int
9552 AutoPlayOneMove()
9553 {
9554     int fromX, fromY, toX, toY;
9555
9556     if (appData.debugMode) {
9557       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9558     }
9559
9560     if (gameMode != PlayFromGameFile)
9561       return FALSE;
9562
9563     if (currentMove >= forwardMostMove) {
9564       gameMode = EditGame;
9565       ModeHighlight();
9566
9567       /* [AS] Clear current move marker at the end of a game */
9568       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9569
9570       return FALSE;
9571     }
9572     
9573     toX = moveList[currentMove][2] - AAA;
9574     toY = moveList[currentMove][3] - ONE;
9575
9576     if (moveList[currentMove][1] == '@') {
9577         if (appData.highlightLastMove) {
9578             SetHighlights(-1, -1, toX, toY);
9579         }
9580     } else {
9581         fromX = moveList[currentMove][0] - AAA;
9582         fromY = moveList[currentMove][1] - ONE;
9583
9584         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9585
9586         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9587
9588         if (appData.highlightLastMove) {
9589             SetHighlights(fromX, fromY, toX, toY);
9590         }
9591     }
9592     DisplayMove(currentMove);
9593     SendMoveToProgram(currentMove++, &first);
9594     DisplayBothClocks();
9595     DrawPosition(FALSE, boards[currentMove]);
9596     // [HGM] PV info: always display, routine tests if empty
9597     DisplayComment(currentMove - 1, commentList[currentMove]);
9598     return TRUE;
9599 }
9600
9601
9602 int
9603 LoadGameOneMove(readAhead)
9604      ChessMove readAhead;
9605 {
9606     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9607     char promoChar = NULLCHAR;
9608     ChessMove moveType;
9609     char move[MSG_SIZ];
9610     char *p, *q;
9611     
9612     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9613         gameMode != AnalyzeMode && gameMode != Training) {
9614         gameFileFP = NULL;
9615         return FALSE;
9616     }
9617     
9618     yyboardindex = forwardMostMove;
9619     if (readAhead != (ChessMove)0) {
9620       moveType = readAhead;
9621     } else {
9622       if (gameFileFP == NULL)
9623           return FALSE;
9624       moveType = (ChessMove) yylex();
9625     }
9626     
9627     done = FALSE;
9628     switch (moveType) {
9629       case Comment:
9630         if (appData.debugMode) 
9631           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9632         p = yy_text;
9633
9634         /* append the comment but don't display it */
9635         AppendComment(currentMove, p, FALSE);
9636         return TRUE;
9637
9638       case WhiteCapturesEnPassant:
9639       case BlackCapturesEnPassant:
9640       case WhitePromotionChancellor:
9641       case BlackPromotionChancellor:
9642       case WhitePromotionArchbishop:
9643       case BlackPromotionArchbishop:
9644       case WhitePromotionCentaur:
9645       case BlackPromotionCentaur:
9646       case WhitePromotionQueen:
9647       case BlackPromotionQueen:
9648       case WhitePromotionRook:
9649       case BlackPromotionRook:
9650       case WhitePromotionBishop:
9651       case BlackPromotionBishop:
9652       case WhitePromotionKnight:
9653       case BlackPromotionKnight:
9654       case WhitePromotionKing:
9655       case BlackPromotionKing:
9656       case WhiteNonPromotion:
9657       case BlackNonPromotion:
9658       case NormalMove:
9659       case WhiteKingSideCastle:
9660       case WhiteQueenSideCastle:
9661       case BlackKingSideCastle:
9662       case BlackQueenSideCastle:
9663       case WhiteKingSideCastleWild:
9664       case WhiteQueenSideCastleWild:
9665       case BlackKingSideCastleWild:
9666       case BlackQueenSideCastleWild:
9667       /* PUSH Fabien */
9668       case WhiteHSideCastleFR:
9669       case WhiteASideCastleFR:
9670       case BlackHSideCastleFR:
9671       case BlackASideCastleFR:
9672       /* POP Fabien */
9673         if (appData.debugMode)
9674           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9675         fromX = currentMoveString[0] - AAA;
9676         fromY = currentMoveString[1] - ONE;
9677         toX = currentMoveString[2] - AAA;
9678         toY = currentMoveString[3] - ONE;
9679         promoChar = currentMoveString[4];
9680         break;
9681
9682       case WhiteDrop:
9683       case BlackDrop:
9684         if (appData.debugMode)
9685           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9686         fromX = moveType == WhiteDrop ?
9687           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9688         (int) CharToPiece(ToLower(currentMoveString[0]));
9689         fromY = DROP_RANK;
9690         toX = currentMoveString[2] - AAA;
9691         toY = currentMoveString[3] - ONE;
9692         break;
9693
9694       case WhiteWins:
9695       case BlackWins:
9696       case GameIsDrawn:
9697       case GameUnfinished:
9698         if (appData.debugMode)
9699           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9700         p = strchr(yy_text, '{');
9701         if (p == NULL) p = strchr(yy_text, '(');
9702         if (p == NULL) {
9703             p = yy_text;
9704             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9705         } else {
9706             q = strchr(p, *p == '{' ? '}' : ')');
9707             if (q != NULL) *q = NULLCHAR;
9708             p++;
9709         }
9710         GameEnds(moveType, p, GE_FILE);
9711         done = TRUE;
9712         if (cmailMsgLoaded) {
9713             ClearHighlights();
9714             flipView = WhiteOnMove(currentMove);
9715             if (moveType == GameUnfinished) flipView = !flipView;
9716             if (appData.debugMode)
9717               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9718         }
9719         break;
9720
9721       case (ChessMove) 0:       /* end of file */
9722         if (appData.debugMode)
9723           fprintf(debugFP, "Parser hit end of file\n");
9724         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9725           case MT_NONE:
9726           case MT_CHECK:
9727             break;
9728           case MT_CHECKMATE:
9729           case MT_STAINMATE:
9730             if (WhiteOnMove(currentMove)) {
9731                 GameEnds(BlackWins, "Black mates", GE_FILE);
9732             } else {
9733                 GameEnds(WhiteWins, "White mates", GE_FILE);
9734             }
9735             break;
9736           case MT_STALEMATE:
9737             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9738             break;
9739         }
9740         done = TRUE;
9741         break;
9742
9743       case MoveNumberOne:
9744         if (lastLoadGameStart == GNUChessGame) {
9745             /* GNUChessGames have numbers, but they aren't move numbers */
9746             if (appData.debugMode)
9747               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9748                       yy_text, (int) moveType);
9749             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9750         }
9751         /* else fall thru */
9752
9753       case XBoardGame:
9754       case GNUChessGame:
9755       case PGNTag:
9756         /* Reached start of next game in file */
9757         if (appData.debugMode)
9758           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9759         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9760           case MT_NONE:
9761           case MT_CHECK:
9762             break;
9763           case MT_CHECKMATE:
9764           case MT_STAINMATE:
9765             if (WhiteOnMove(currentMove)) {
9766                 GameEnds(BlackWins, "Black mates", GE_FILE);
9767             } else {
9768                 GameEnds(WhiteWins, "White mates", GE_FILE);
9769             }
9770             break;
9771           case MT_STALEMATE:
9772             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9773             break;
9774         }
9775         done = TRUE;
9776         break;
9777
9778       case PositionDiagram:     /* should not happen; ignore */
9779       case ElapsedTime:         /* ignore */
9780       case NAG:                 /* ignore */
9781         if (appData.debugMode)
9782           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9783                   yy_text, (int) moveType);
9784         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9785
9786       case IllegalMove:
9787         if (appData.testLegality) {
9788             if (appData.debugMode)
9789               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9790             sprintf(move, _("Illegal move: %d.%s%s"),
9791                     (forwardMostMove / 2) + 1,
9792                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9793             DisplayError(move, 0);
9794             done = TRUE;
9795         } else {
9796             if (appData.debugMode)
9797               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9798                       yy_text, currentMoveString);
9799             fromX = currentMoveString[0] - AAA;
9800             fromY = currentMoveString[1] - ONE;
9801             toX = currentMoveString[2] - AAA;
9802             toY = currentMoveString[3] - ONE;
9803             promoChar = currentMoveString[4];
9804         }
9805         break;
9806
9807       case AmbiguousMove:
9808         if (appData.debugMode)
9809           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9810         sprintf(move, _("Ambiguous move: %d.%s%s"),
9811                 (forwardMostMove / 2) + 1,
9812                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9813         DisplayError(move, 0);
9814         done = TRUE;
9815         break;
9816
9817       default:
9818       case ImpossibleMove:
9819         if (appData.debugMode)
9820           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9821         sprintf(move, _("Illegal move: %d.%s%s"),
9822                 (forwardMostMove / 2) + 1,
9823                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9824         DisplayError(move, 0);
9825         done = TRUE;
9826         break;
9827     }
9828
9829     if (done) {
9830         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9831             DrawPosition(FALSE, boards[currentMove]);
9832             DisplayBothClocks();
9833             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9834               DisplayComment(currentMove - 1, commentList[currentMove]);
9835         }
9836         (void) StopLoadGameTimer();
9837         gameFileFP = NULL;
9838         cmailOldMove = forwardMostMove;
9839         return FALSE;
9840     } else {
9841         /* currentMoveString is set as a side-effect of yylex */
9842         strcat(currentMoveString, "\n");
9843         strcpy(moveList[forwardMostMove], currentMoveString);
9844         
9845         thinkOutput[0] = NULLCHAR;
9846         MakeMove(fromX, fromY, toX, toY, promoChar);
9847         currentMove = forwardMostMove;
9848         return TRUE;
9849     }
9850 }
9851
9852 /* Load the nth game from the given file */
9853 int
9854 LoadGameFromFile(filename, n, title, useList)
9855      char *filename;
9856      int n;
9857      char *title;
9858      /*Boolean*/ int useList;
9859 {
9860     FILE *f;
9861     char buf[MSG_SIZ];
9862
9863     if (strcmp(filename, "-") == 0) {
9864         f = stdin;
9865         title = "stdin";
9866     } else {
9867         f = fopen(filename, "rb");
9868         if (f == NULL) {
9869           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9870             DisplayError(buf, errno);
9871             return FALSE;
9872         }
9873     }
9874     if (fseek(f, 0, 0) == -1) {
9875         /* f is not seekable; probably a pipe */
9876         useList = FALSE;
9877     }
9878     if (useList && n == 0) {
9879         int error = GameListBuild(f);
9880         if (error) {
9881             DisplayError(_("Cannot build game list"), error);
9882         } else if (!ListEmpty(&gameList) &&
9883                    ((ListGame *) gameList.tailPred)->number > 1) {
9884             GameListPopUp(f, title);
9885             return TRUE;
9886         }
9887         GameListDestroy();
9888         n = 1;
9889     }
9890     if (n == 0) n = 1;
9891     return LoadGame(f, n, title, FALSE);
9892 }
9893
9894
9895 void
9896 MakeRegisteredMove()
9897 {
9898     int fromX, fromY, toX, toY;
9899     char promoChar;
9900     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9901         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9902           case CMAIL_MOVE:
9903           case CMAIL_DRAW:
9904             if (appData.debugMode)
9905               fprintf(debugFP, "Restoring %s for game %d\n",
9906                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9907     
9908             thinkOutput[0] = NULLCHAR;
9909             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9910             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9911             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9912             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9913             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9914             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9915             MakeMove(fromX, fromY, toX, toY, promoChar);
9916             ShowMove(fromX, fromY, toX, toY);
9917               
9918             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9919               case MT_NONE:
9920               case MT_CHECK:
9921                 break;
9922                 
9923               case MT_CHECKMATE:
9924               case MT_STAINMATE:
9925                 if (WhiteOnMove(currentMove)) {
9926                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9927                 } else {
9928                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9929                 }
9930                 break;
9931                 
9932               case MT_STALEMATE:
9933                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9934                 break;
9935             }
9936
9937             break;
9938             
9939           case CMAIL_RESIGN:
9940             if (WhiteOnMove(currentMove)) {
9941                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9942             } else {
9943                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9944             }
9945             break;
9946             
9947           case CMAIL_ACCEPT:
9948             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9949             break;
9950               
9951           default:
9952             break;
9953         }
9954     }
9955
9956     return;
9957 }
9958
9959 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9960 int
9961 CmailLoadGame(f, gameNumber, title, useList)
9962      FILE *f;
9963      int gameNumber;
9964      char *title;
9965      int useList;
9966 {
9967     int retVal;
9968
9969     if (gameNumber > nCmailGames) {
9970         DisplayError(_("No more games in this message"), 0);
9971         return FALSE;
9972     }
9973     if (f == lastLoadGameFP) {
9974         int offset = gameNumber - lastLoadGameNumber;
9975         if (offset == 0) {
9976             cmailMsg[0] = NULLCHAR;
9977             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9978                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9979                 nCmailMovesRegistered--;
9980             }
9981             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9982             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9983                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9984             }
9985         } else {
9986             if (! RegisterMove()) return FALSE;
9987         }
9988     }
9989
9990     retVal = LoadGame(f, gameNumber, title, useList);
9991
9992     /* Make move registered during previous look at this game, if any */
9993     MakeRegisteredMove();
9994
9995     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9996         commentList[currentMove]
9997           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9998         DisplayComment(currentMove - 1, commentList[currentMove]);
9999     }
10000
10001     return retVal;
10002 }
10003
10004 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10005 int
10006 ReloadGame(offset)
10007      int offset;
10008 {
10009     int gameNumber = lastLoadGameNumber + offset;
10010     if (lastLoadGameFP == NULL) {
10011         DisplayError(_("No game has been loaded yet"), 0);
10012         return FALSE;
10013     }
10014     if (gameNumber <= 0) {
10015         DisplayError(_("Can't back up any further"), 0);
10016         return FALSE;
10017     }
10018     if (cmailMsgLoaded) {
10019         return CmailLoadGame(lastLoadGameFP, gameNumber,
10020                              lastLoadGameTitle, lastLoadGameUseList);
10021     } else {
10022         return LoadGame(lastLoadGameFP, gameNumber,
10023                         lastLoadGameTitle, lastLoadGameUseList);
10024     }
10025 }
10026
10027
10028
10029 /* Load the nth game from open file f */
10030 int
10031 LoadGame(f, gameNumber, title, useList)
10032      FILE *f;
10033      int gameNumber;
10034      char *title;
10035      int useList;
10036 {
10037     ChessMove cm;
10038     char buf[MSG_SIZ];
10039     int gn = gameNumber;
10040     ListGame *lg = NULL;
10041     int numPGNTags = 0;
10042     int err;
10043     GameMode oldGameMode;
10044     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10045
10046     if (appData.debugMode) 
10047         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10048
10049     if (gameMode == Training )
10050         SetTrainingModeOff();
10051
10052     oldGameMode = gameMode;
10053     if (gameMode != BeginningOfGame) {
10054       Reset(FALSE, TRUE);
10055     }
10056
10057     gameFileFP = f;
10058     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10059         fclose(lastLoadGameFP);
10060     }
10061
10062     if (useList) {
10063         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10064         
10065         if (lg) {
10066             fseek(f, lg->offset, 0);
10067             GameListHighlight(gameNumber);
10068             gn = 1;
10069         }
10070         else {
10071             DisplayError(_("Game number out of range"), 0);
10072             return FALSE;
10073         }
10074     } else {
10075         GameListDestroy();
10076         if (fseek(f, 0, 0) == -1) {
10077             if (f == lastLoadGameFP ?
10078                 gameNumber == lastLoadGameNumber + 1 :
10079                 gameNumber == 1) {
10080                 gn = 1;
10081             } else {
10082                 DisplayError(_("Can't seek on game file"), 0);
10083                 return FALSE;
10084             }
10085         }
10086     }
10087     lastLoadGameFP = f;
10088     lastLoadGameNumber = gameNumber;
10089     strcpy(lastLoadGameTitle, title);
10090     lastLoadGameUseList = useList;
10091
10092     yynewfile(f);
10093
10094     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10095       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10096                 lg->gameInfo.black);
10097             DisplayTitle(buf);
10098     } else if (*title != NULLCHAR) {
10099         if (gameNumber > 1) {
10100             sprintf(buf, "%s %d", title, gameNumber);
10101             DisplayTitle(buf);
10102         } else {
10103             DisplayTitle(title);
10104         }
10105     }
10106
10107     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10108         gameMode = PlayFromGameFile;
10109         ModeHighlight();
10110     }
10111
10112     currentMove = forwardMostMove = backwardMostMove = 0;
10113     CopyBoard(boards[0], initialPosition);
10114     StopClocks();
10115
10116     /*
10117      * Skip the first gn-1 games in the file.
10118      * Also skip over anything that precedes an identifiable 
10119      * start of game marker, to avoid being confused by 
10120      * garbage at the start of the file.  Currently 
10121      * recognized start of game markers are the move number "1",
10122      * the pattern "gnuchess .* game", the pattern
10123      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10124      * A game that starts with one of the latter two patterns
10125      * will also have a move number 1, possibly
10126      * following a position diagram.
10127      * 5-4-02: Let's try being more lenient and allowing a game to
10128      * start with an unnumbered move.  Does that break anything?
10129      */
10130     cm = lastLoadGameStart = (ChessMove) 0;
10131     while (gn > 0) {
10132         yyboardindex = forwardMostMove;
10133         cm = (ChessMove) yylex();
10134         switch (cm) {
10135           case (ChessMove) 0:
10136             if (cmailMsgLoaded) {
10137                 nCmailGames = CMAIL_MAX_GAMES - gn;
10138             } else {
10139                 Reset(TRUE, TRUE);
10140                 DisplayError(_("Game not found in file"), 0);
10141             }
10142             return FALSE;
10143
10144           case GNUChessGame:
10145           case XBoardGame:
10146             gn--;
10147             lastLoadGameStart = cm;
10148             break;
10149             
10150           case MoveNumberOne:
10151             switch (lastLoadGameStart) {
10152               case GNUChessGame:
10153               case XBoardGame:
10154               case PGNTag:
10155                 break;
10156               case MoveNumberOne:
10157               case (ChessMove) 0:
10158                 gn--;           /* count this game */
10159                 lastLoadGameStart = cm;
10160                 break;
10161               default:
10162                 /* impossible */
10163                 break;
10164             }
10165             break;
10166
10167           case PGNTag:
10168             switch (lastLoadGameStart) {
10169               case GNUChessGame:
10170               case PGNTag:
10171               case MoveNumberOne:
10172               case (ChessMove) 0:
10173                 gn--;           /* count this game */
10174                 lastLoadGameStart = cm;
10175                 break;
10176               case XBoardGame:
10177                 lastLoadGameStart = cm; /* game counted already */
10178                 break;
10179               default:
10180                 /* impossible */
10181                 break;
10182             }
10183             if (gn > 0) {
10184                 do {
10185                     yyboardindex = forwardMostMove;
10186                     cm = (ChessMove) yylex();
10187                 } while (cm == PGNTag || cm == Comment);
10188             }
10189             break;
10190
10191           case WhiteWins:
10192           case BlackWins:
10193           case GameIsDrawn:
10194             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10195                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10196                     != CMAIL_OLD_RESULT) {
10197                     nCmailResults ++ ;
10198                     cmailResult[  CMAIL_MAX_GAMES
10199                                 - gn - 1] = CMAIL_OLD_RESULT;
10200                 }
10201             }
10202             break;
10203
10204           case NormalMove:
10205             /* Only a NormalMove can be at the start of a game
10206              * without a position diagram. */
10207             if (lastLoadGameStart == (ChessMove) 0) {
10208               gn--;
10209               lastLoadGameStart = MoveNumberOne;
10210             }
10211             break;
10212
10213           default:
10214             break;
10215         }
10216     }
10217     
10218     if (appData.debugMode)
10219       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10220
10221     if (cm == XBoardGame) {
10222         /* Skip any header junk before position diagram and/or move 1 */
10223         for (;;) {
10224             yyboardindex = forwardMostMove;
10225             cm = (ChessMove) yylex();
10226
10227             if (cm == (ChessMove) 0 ||
10228                 cm == GNUChessGame || cm == XBoardGame) {
10229                 /* Empty game; pretend end-of-file and handle later */
10230                 cm = (ChessMove) 0;
10231                 break;
10232             }
10233
10234             if (cm == MoveNumberOne || cm == PositionDiagram ||
10235                 cm == PGNTag || cm == Comment)
10236               break;
10237         }
10238     } else if (cm == GNUChessGame) {
10239         if (gameInfo.event != NULL) {
10240             free(gameInfo.event);
10241         }
10242         gameInfo.event = StrSave(yy_text);
10243     }   
10244
10245     startedFromSetupPosition = FALSE;
10246     while (cm == PGNTag) {
10247         if (appData.debugMode) 
10248           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10249         err = ParsePGNTag(yy_text, &gameInfo);
10250         if (!err) numPGNTags++;
10251
10252         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10253         if(gameInfo.variant != oldVariant) {
10254             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10255             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10256             InitPosition(TRUE);
10257             oldVariant = gameInfo.variant;
10258             if (appData.debugMode) 
10259               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10260         }
10261
10262
10263         if (gameInfo.fen != NULL) {
10264           Board initial_position;
10265           startedFromSetupPosition = TRUE;
10266           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10267             Reset(TRUE, TRUE);
10268             DisplayError(_("Bad FEN position in file"), 0);
10269             return FALSE;
10270           }
10271           CopyBoard(boards[0], initial_position);
10272           if (blackPlaysFirst) {
10273             currentMove = forwardMostMove = backwardMostMove = 1;
10274             CopyBoard(boards[1], initial_position);
10275             strcpy(moveList[0], "");
10276             strcpy(parseList[0], "");
10277             timeRemaining[0][1] = whiteTimeRemaining;
10278             timeRemaining[1][1] = blackTimeRemaining;
10279             if (commentList[0] != NULL) {
10280               commentList[1] = commentList[0];
10281               commentList[0] = NULL;
10282             }
10283           } else {
10284             currentMove = forwardMostMove = backwardMostMove = 0;
10285           }
10286           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10287           {   int i;
10288               initialRulePlies = FENrulePlies;
10289               for( i=0; i< nrCastlingRights; i++ )
10290                   initialRights[i] = initial_position[CASTLING][i];
10291           }
10292           yyboardindex = forwardMostMove;
10293           free(gameInfo.fen);
10294           gameInfo.fen = NULL;
10295         }
10296
10297         yyboardindex = forwardMostMove;
10298         cm = (ChessMove) yylex();
10299
10300         /* Handle comments interspersed among the tags */
10301         while (cm == Comment) {
10302             char *p;
10303             if (appData.debugMode) 
10304               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10305             p = yy_text;
10306             AppendComment(currentMove, p, FALSE);
10307             yyboardindex = forwardMostMove;
10308             cm = (ChessMove) yylex();
10309         }
10310     }
10311
10312     /* don't rely on existence of Event tag since if game was
10313      * pasted from clipboard the Event tag may not exist
10314      */
10315     if (numPGNTags > 0){
10316         char *tags;
10317         if (gameInfo.variant == VariantNormal) {
10318           VariantClass v = StringToVariant(gameInfo.event);
10319           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10320           if(v < VariantShogi) gameInfo.variant = v;
10321         }
10322         if (!matchMode) {
10323           if( appData.autoDisplayTags ) {
10324             tags = PGNTags(&gameInfo);
10325             TagsPopUp(tags, CmailMsg());
10326             free(tags);
10327           }
10328         }
10329     } else {
10330         /* Make something up, but don't display it now */
10331         SetGameInfo();
10332         TagsPopDown();
10333     }
10334
10335     if (cm == PositionDiagram) {
10336         int i, j;
10337         char *p;
10338         Board initial_position;
10339
10340         if (appData.debugMode)
10341           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10342
10343         if (!startedFromSetupPosition) {
10344             p = yy_text;
10345             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10346               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10347                 switch (*p) {
10348                   case '[':
10349                   case '-':
10350                   case ' ':
10351                   case '\t':
10352                   case '\n':
10353                   case '\r':
10354                     break;
10355                   default:
10356                     initial_position[i][j++] = CharToPiece(*p);
10357                     break;
10358                 }
10359             while (*p == ' ' || *p == '\t' ||
10360                    *p == '\n' || *p == '\r') p++;
10361         
10362             if (strncmp(p, "black", strlen("black"))==0)
10363               blackPlaysFirst = TRUE;
10364             else
10365               blackPlaysFirst = FALSE;
10366             startedFromSetupPosition = TRUE;
10367         
10368             CopyBoard(boards[0], initial_position);
10369             if (blackPlaysFirst) {
10370                 currentMove = forwardMostMove = backwardMostMove = 1;
10371                 CopyBoard(boards[1], initial_position);
10372                 strcpy(moveList[0], "");
10373                 strcpy(parseList[0], "");
10374                 timeRemaining[0][1] = whiteTimeRemaining;
10375                 timeRemaining[1][1] = blackTimeRemaining;
10376                 if (commentList[0] != NULL) {
10377                     commentList[1] = commentList[0];
10378                     commentList[0] = NULL;
10379                 }
10380             } else {
10381                 currentMove = forwardMostMove = backwardMostMove = 0;
10382             }
10383         }
10384         yyboardindex = forwardMostMove;
10385         cm = (ChessMove) yylex();
10386     }
10387
10388     if (first.pr == NoProc) {
10389         StartChessProgram(&first);
10390     }
10391     InitChessProgram(&first, FALSE);
10392     SendToProgram("force\n", &first);
10393     if (startedFromSetupPosition) {
10394         SendBoard(&first, forwardMostMove);
10395     if (appData.debugMode) {
10396         fprintf(debugFP, "Load Game\n");
10397     }
10398         DisplayBothClocks();
10399     }      
10400
10401     /* [HGM] server: flag to write setup moves in broadcast file as one */
10402     loadFlag = appData.suppressLoadMoves;
10403
10404     while (cm == Comment) {
10405         char *p;
10406         if (appData.debugMode) 
10407           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10408         p = yy_text;
10409         AppendComment(currentMove, p, FALSE);
10410         yyboardindex = forwardMostMove;
10411         cm = (ChessMove) yylex();
10412     }
10413
10414     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10415         cm == WhiteWins || cm == BlackWins ||
10416         cm == GameIsDrawn || cm == GameUnfinished) {
10417         DisplayMessage("", _("No moves in game"));
10418         if (cmailMsgLoaded) {
10419             if (appData.debugMode)
10420               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10421             ClearHighlights();
10422             flipView = FALSE;
10423         }
10424         DrawPosition(FALSE, boards[currentMove]);
10425         DisplayBothClocks();
10426         gameMode = EditGame;
10427         ModeHighlight();
10428         gameFileFP = NULL;
10429         cmailOldMove = 0;
10430         return TRUE;
10431     }
10432
10433     // [HGM] PV info: routine tests if comment empty
10434     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10435         DisplayComment(currentMove - 1, commentList[currentMove]);
10436     }
10437     if (!matchMode && appData.timeDelay != 0) 
10438       DrawPosition(FALSE, boards[currentMove]);
10439
10440     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10441       programStats.ok_to_send = 1;
10442     }
10443
10444     /* if the first token after the PGN tags is a move
10445      * and not move number 1, retrieve it from the parser 
10446      */
10447     if (cm != MoveNumberOne)
10448         LoadGameOneMove(cm);
10449
10450     /* load the remaining moves from the file */
10451     while (LoadGameOneMove((ChessMove)0)) {
10452       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10453       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10454     }
10455
10456     /* rewind to the start of the game */
10457     currentMove = backwardMostMove;
10458
10459     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10460
10461     if (oldGameMode == AnalyzeFile ||
10462         oldGameMode == AnalyzeMode) {
10463       AnalyzeFileEvent();
10464     }
10465
10466     if (matchMode || appData.timeDelay == 0) {
10467       ToEndEvent();
10468       gameMode = EditGame;
10469       ModeHighlight();
10470     } else if (appData.timeDelay > 0) {
10471       AutoPlayGameLoop();
10472     }
10473
10474     if (appData.debugMode) 
10475         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10476
10477     loadFlag = 0; /* [HGM] true game starts */
10478     return TRUE;
10479 }
10480
10481 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10482 int
10483 ReloadPosition(offset)
10484      int offset;
10485 {
10486     int positionNumber = lastLoadPositionNumber + offset;
10487     if (lastLoadPositionFP == NULL) {
10488         DisplayError(_("No position has been loaded yet"), 0);
10489         return FALSE;
10490     }
10491     if (positionNumber <= 0) {
10492         DisplayError(_("Can't back up any further"), 0);
10493         return FALSE;
10494     }
10495     return LoadPosition(lastLoadPositionFP, positionNumber,
10496                         lastLoadPositionTitle);
10497 }
10498
10499 /* Load the nth position from the given file */
10500 int
10501 LoadPositionFromFile(filename, n, title)
10502      char *filename;
10503      int n;
10504      char *title;
10505 {
10506     FILE *f;
10507     char buf[MSG_SIZ];
10508
10509     if (strcmp(filename, "-") == 0) {
10510         return LoadPosition(stdin, n, "stdin");
10511     } else {
10512         f = fopen(filename, "rb");
10513         if (f == NULL) {
10514             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10515             DisplayError(buf, errno);
10516             return FALSE;
10517         } else {
10518             return LoadPosition(f, n, title);
10519         }
10520     }
10521 }
10522
10523 /* Load the nth position from the given open file, and close it */
10524 int
10525 LoadPosition(f, positionNumber, title)
10526      FILE *f;
10527      int positionNumber;
10528      char *title;
10529 {
10530     char *p, line[MSG_SIZ];
10531     Board initial_position;
10532     int i, j, fenMode, pn;
10533     
10534     if (gameMode == Training )
10535         SetTrainingModeOff();
10536
10537     if (gameMode != BeginningOfGame) {
10538         Reset(FALSE, TRUE);
10539     }
10540     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10541         fclose(lastLoadPositionFP);
10542     }
10543     if (positionNumber == 0) positionNumber = 1;
10544     lastLoadPositionFP = f;
10545     lastLoadPositionNumber = positionNumber;
10546     strcpy(lastLoadPositionTitle, title);
10547     if (first.pr == NoProc) {
10548       StartChessProgram(&first);
10549       InitChessProgram(&first, FALSE);
10550     }    
10551     pn = positionNumber;
10552     if (positionNumber < 0) {
10553         /* Negative position number means to seek to that byte offset */
10554         if (fseek(f, -positionNumber, 0) == -1) {
10555             DisplayError(_("Can't seek on position file"), 0);
10556             return FALSE;
10557         };
10558         pn = 1;
10559     } else {
10560         if (fseek(f, 0, 0) == -1) {
10561             if (f == lastLoadPositionFP ?
10562                 positionNumber == lastLoadPositionNumber + 1 :
10563                 positionNumber == 1) {
10564                 pn = 1;
10565             } else {
10566                 DisplayError(_("Can't seek on position file"), 0);
10567                 return FALSE;
10568             }
10569         }
10570     }
10571     /* See if this file is FEN or old-style xboard */
10572     if (fgets(line, MSG_SIZ, f) == NULL) {
10573         DisplayError(_("Position not found in file"), 0);
10574         return FALSE;
10575     }
10576     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10577     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10578
10579     if (pn >= 2) {
10580         if (fenMode || line[0] == '#') pn--;
10581         while (pn > 0) {
10582             /* skip positions before number pn */
10583             if (fgets(line, MSG_SIZ, f) == NULL) {
10584                 Reset(TRUE, TRUE);
10585                 DisplayError(_("Position not found in file"), 0);
10586                 return FALSE;
10587             }
10588             if (fenMode || line[0] == '#') pn--;
10589         }
10590     }
10591
10592     if (fenMode) {
10593         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10594             DisplayError(_("Bad FEN position in file"), 0);
10595             return FALSE;
10596         }
10597     } else {
10598         (void) fgets(line, MSG_SIZ, f);
10599         (void) fgets(line, MSG_SIZ, f);
10600     
10601         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10602             (void) fgets(line, MSG_SIZ, f);
10603             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10604                 if (*p == ' ')
10605                   continue;
10606                 initial_position[i][j++] = CharToPiece(*p);
10607             }
10608         }
10609     
10610         blackPlaysFirst = FALSE;
10611         if (!feof(f)) {
10612             (void) fgets(line, MSG_SIZ, f);
10613             if (strncmp(line, "black", strlen("black"))==0)
10614               blackPlaysFirst = TRUE;
10615         }
10616     }
10617     startedFromSetupPosition = TRUE;
10618     
10619     SendToProgram("force\n", &first);
10620     CopyBoard(boards[0], initial_position);
10621     if (blackPlaysFirst) {
10622         currentMove = forwardMostMove = backwardMostMove = 1;
10623         strcpy(moveList[0], "");
10624         strcpy(parseList[0], "");
10625         CopyBoard(boards[1], initial_position);
10626         DisplayMessage("", _("Black to play"));
10627     } else {
10628         currentMove = forwardMostMove = backwardMostMove = 0;
10629         DisplayMessage("", _("White to play"));
10630     }
10631     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10632     SendBoard(&first, forwardMostMove);
10633     if (appData.debugMode) {
10634 int i, j;
10635   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10636   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10637         fprintf(debugFP, "Load Position\n");
10638     }
10639
10640     if (positionNumber > 1) {
10641         sprintf(line, "%s %d", title, positionNumber);
10642         DisplayTitle(line);
10643     } else {
10644         DisplayTitle(title);
10645     }
10646     gameMode = EditGame;
10647     ModeHighlight();
10648     ResetClocks();
10649     timeRemaining[0][1] = whiteTimeRemaining;
10650     timeRemaining[1][1] = blackTimeRemaining;
10651     DrawPosition(FALSE, boards[currentMove]);
10652    
10653     return TRUE;
10654 }
10655
10656
10657 void
10658 CopyPlayerNameIntoFileName(dest, src)
10659      char **dest, *src;
10660 {
10661     while (*src != NULLCHAR && *src != ',') {
10662         if (*src == ' ') {
10663             *(*dest)++ = '_';
10664             src++;
10665         } else {
10666             *(*dest)++ = *src++;
10667         }
10668     }
10669 }
10670
10671 char *DefaultFileName(ext)
10672      char *ext;
10673 {
10674     static char def[MSG_SIZ];
10675     char *p;
10676
10677     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10678         p = def;
10679         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10680         *p++ = '-';
10681         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10682         *p++ = '.';
10683         strcpy(p, ext);
10684     } else {
10685         def[0] = NULLCHAR;
10686     }
10687     return def;
10688 }
10689
10690 /* Save the current game to the given file */
10691 int
10692 SaveGameToFile(filename, append)
10693      char *filename;
10694      int append;
10695 {
10696     FILE *f;
10697     char buf[MSG_SIZ];
10698
10699     if (strcmp(filename, "-") == 0) {
10700         return SaveGame(stdout, 0, NULL);
10701     } else {
10702         f = fopen(filename, append ? "a" : "w");
10703         if (f == NULL) {
10704             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10705             DisplayError(buf, errno);
10706             return FALSE;
10707         } else {
10708             return SaveGame(f, 0, NULL);
10709         }
10710     }
10711 }
10712
10713 char *
10714 SavePart(str)
10715      char *str;
10716 {
10717     static char buf[MSG_SIZ];
10718     char *p;
10719     
10720     p = strchr(str, ' ');
10721     if (p == NULL) return str;
10722     strncpy(buf, str, p - str);
10723     buf[p - str] = NULLCHAR;
10724     return buf;
10725 }
10726
10727 #define PGN_MAX_LINE 75
10728
10729 #define PGN_SIDE_WHITE  0
10730 #define PGN_SIDE_BLACK  1
10731
10732 /* [AS] */
10733 static int FindFirstMoveOutOfBook( int side )
10734 {
10735     int result = -1;
10736
10737     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10738         int index = backwardMostMove;
10739         int has_book_hit = 0;
10740
10741         if( (index % 2) != side ) {
10742             index++;
10743         }
10744
10745         while( index < forwardMostMove ) {
10746             /* Check to see if engine is in book */
10747             int depth = pvInfoList[index].depth;
10748             int score = pvInfoList[index].score;
10749             int in_book = 0;
10750
10751             if( depth <= 2 ) {
10752                 in_book = 1;
10753             }
10754             else if( score == 0 && depth == 63 ) {
10755                 in_book = 1; /* Zappa */
10756             }
10757             else if( score == 2 && depth == 99 ) {
10758                 in_book = 1; /* Abrok */
10759             }
10760
10761             has_book_hit += in_book;
10762
10763             if( ! in_book ) {
10764                 result = index;
10765
10766                 break;
10767             }
10768
10769             index += 2;
10770         }
10771     }
10772
10773     return result;
10774 }
10775
10776 /* [AS] */
10777 void GetOutOfBookInfo( char * buf )
10778 {
10779     int oob[2];
10780     int i;
10781     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10782
10783     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10784     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10785
10786     *buf = '\0';
10787
10788     if( oob[0] >= 0 || oob[1] >= 0 ) {
10789         for( i=0; i<2; i++ ) {
10790             int idx = oob[i];
10791
10792             if( idx >= 0 ) {
10793                 if( i > 0 && oob[0] >= 0 ) {
10794                     strcat( buf, "   " );
10795                 }
10796
10797                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10798                 sprintf( buf+strlen(buf), "%s%.2f", 
10799                     pvInfoList[idx].score >= 0 ? "+" : "",
10800                     pvInfoList[idx].score / 100.0 );
10801             }
10802         }
10803     }
10804 }
10805
10806 /* Save game in PGN style and close the file */
10807 int
10808 SaveGamePGN(f)
10809      FILE *f;
10810 {
10811     int i, offset, linelen, newblock;
10812     time_t tm;
10813 //    char *movetext;
10814     char numtext[32];
10815     int movelen, numlen, blank;
10816     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10817
10818     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10819     
10820     tm = time((time_t *) NULL);
10821     
10822     PrintPGNTags(f, &gameInfo);
10823     
10824     if (backwardMostMove > 0 || startedFromSetupPosition) {
10825         char *fen = PositionToFEN(backwardMostMove, NULL);
10826         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10827         fprintf(f, "\n{--------------\n");
10828         PrintPosition(f, backwardMostMove);
10829         fprintf(f, "--------------}\n");
10830         free(fen);
10831     }
10832     else {
10833         /* [AS] Out of book annotation */
10834         if( appData.saveOutOfBookInfo ) {
10835             char buf[64];
10836
10837             GetOutOfBookInfo( buf );
10838
10839             if( buf[0] != '\0' ) {
10840                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10841             }
10842         }
10843
10844         fprintf(f, "\n");
10845     }
10846
10847     i = backwardMostMove;
10848     linelen = 0;
10849     newblock = TRUE;
10850
10851     while (i < forwardMostMove) {
10852         /* Print comments preceding this move */
10853         if (commentList[i] != NULL) {
10854             if (linelen > 0) fprintf(f, "\n");
10855             fprintf(f, "%s", commentList[i]);
10856             linelen = 0;
10857             newblock = TRUE;
10858         }
10859
10860         /* Format move number */
10861         if ((i % 2) == 0) {
10862             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10863         } else {
10864             if (newblock) {
10865                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10866             } else {
10867                 numtext[0] = NULLCHAR;
10868             }
10869         }
10870         numlen = strlen(numtext);
10871         newblock = FALSE;
10872
10873         /* Print move number */
10874         blank = linelen > 0 && numlen > 0;
10875         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10876             fprintf(f, "\n");
10877             linelen = 0;
10878             blank = 0;
10879         }
10880         if (blank) {
10881             fprintf(f, " ");
10882             linelen++;
10883         }
10884         fprintf(f, "%s", numtext);
10885         linelen += numlen;
10886
10887         /* Get move */
10888         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10889         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10890
10891         /* Print move */
10892         blank = linelen > 0 && movelen > 0;
10893         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10894             fprintf(f, "\n");
10895             linelen = 0;
10896             blank = 0;
10897         }
10898         if (blank) {
10899             fprintf(f, " ");
10900             linelen++;
10901         }
10902         fprintf(f, "%s", move_buffer);
10903         linelen += movelen;
10904
10905         /* [AS] Add PV info if present */
10906         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10907             /* [HGM] add time */
10908             char buf[MSG_SIZ]; int seconds;
10909
10910             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10911
10912             if( seconds <= 0) buf[0] = 0; else
10913             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10914                 seconds = (seconds + 4)/10; // round to full seconds
10915                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10916                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10917             }
10918
10919             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10920                 pvInfoList[i].score >= 0 ? "+" : "",
10921                 pvInfoList[i].score / 100.0,
10922                 pvInfoList[i].depth,
10923                 buf );
10924
10925             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10926
10927             /* Print score/depth */
10928             blank = linelen > 0 && movelen > 0;
10929             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10930                 fprintf(f, "\n");
10931                 linelen = 0;
10932                 blank = 0;
10933             }
10934             if (blank) {
10935                 fprintf(f, " ");
10936                 linelen++;
10937             }
10938             fprintf(f, "%s", move_buffer);
10939             linelen += movelen;
10940         }
10941
10942         i++;
10943     }
10944     
10945     /* Start a new line */
10946     if (linelen > 0) fprintf(f, "\n");
10947
10948     /* Print comments after last move */
10949     if (commentList[i] != NULL) {
10950         fprintf(f, "%s\n", commentList[i]);
10951     }
10952
10953     /* Print result */
10954     if (gameInfo.resultDetails != NULL &&
10955         gameInfo.resultDetails[0] != NULLCHAR) {
10956         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10957                 PGNResult(gameInfo.result));
10958     } else {
10959         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10960     }
10961
10962     fclose(f);
10963     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10964     return TRUE;
10965 }
10966
10967 /* Save game in old style and close the file */
10968 int
10969 SaveGameOldStyle(f)
10970      FILE *f;
10971 {
10972     int i, offset;
10973     time_t tm;
10974     
10975     tm = time((time_t *) NULL);
10976     
10977     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10978     PrintOpponents(f);
10979     
10980     if (backwardMostMove > 0 || startedFromSetupPosition) {
10981         fprintf(f, "\n[--------------\n");
10982         PrintPosition(f, backwardMostMove);
10983         fprintf(f, "--------------]\n");
10984     } else {
10985         fprintf(f, "\n");
10986     }
10987
10988     i = backwardMostMove;
10989     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10990
10991     while (i < forwardMostMove) {
10992         if (commentList[i] != NULL) {
10993             fprintf(f, "[%s]\n", commentList[i]);
10994         }
10995
10996         if ((i % 2) == 1) {
10997             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10998             i++;
10999         } else {
11000             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11001             i++;
11002             if (commentList[i] != NULL) {
11003                 fprintf(f, "\n");
11004                 continue;
11005             }
11006             if (i >= forwardMostMove) {
11007                 fprintf(f, "\n");
11008                 break;
11009             }
11010             fprintf(f, "%s\n", parseList[i]);
11011             i++;
11012         }
11013     }
11014     
11015     if (commentList[i] != NULL) {
11016         fprintf(f, "[%s]\n", commentList[i]);
11017     }
11018
11019     /* This isn't really the old style, but it's close enough */
11020     if (gameInfo.resultDetails != NULL &&
11021         gameInfo.resultDetails[0] != NULLCHAR) {
11022         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11023                 gameInfo.resultDetails);
11024     } else {
11025         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11026     }
11027
11028     fclose(f);
11029     return TRUE;
11030 }
11031
11032 /* Save the current game to open file f and close the file */
11033 int
11034 SaveGame(f, dummy, dummy2)
11035      FILE *f;
11036      int dummy;
11037      char *dummy2;
11038 {
11039     if (gameMode == EditPosition) EditPositionDone(TRUE);
11040     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11041     if (appData.oldSaveStyle)
11042       return SaveGameOldStyle(f);
11043     else
11044       return SaveGamePGN(f);
11045 }
11046
11047 /* Save the current position to the given file */
11048 int
11049 SavePositionToFile(filename)
11050      char *filename;
11051 {
11052     FILE *f;
11053     char buf[MSG_SIZ];
11054
11055     if (strcmp(filename, "-") == 0) {
11056         return SavePosition(stdout, 0, NULL);
11057     } else {
11058         f = fopen(filename, "a");
11059         if (f == NULL) {
11060             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11061             DisplayError(buf, errno);
11062             return FALSE;
11063         } else {
11064             SavePosition(f, 0, NULL);
11065             return TRUE;
11066         }
11067     }
11068 }
11069
11070 /* Save the current position to the given open file and close the file */
11071 int
11072 SavePosition(f, dummy, dummy2)
11073      FILE *f;
11074      int dummy;
11075      char *dummy2;
11076 {
11077     time_t tm;
11078     char *fen;
11079     
11080     if (gameMode == EditPosition) EditPositionDone(TRUE);
11081     if (appData.oldSaveStyle) {
11082         tm = time((time_t *) NULL);
11083     
11084         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11085         PrintOpponents(f);
11086         fprintf(f, "[--------------\n");
11087         PrintPosition(f, currentMove);
11088         fprintf(f, "--------------]\n");
11089     } else {
11090         fen = PositionToFEN(currentMove, NULL);
11091         fprintf(f, "%s\n", fen);
11092         free(fen);
11093     }
11094     fclose(f);
11095     return TRUE;
11096 }
11097
11098 void
11099 ReloadCmailMsgEvent(unregister)
11100      int unregister;
11101 {
11102 #if !WIN32
11103     static char *inFilename = NULL;
11104     static char *outFilename;
11105     int i;
11106     struct stat inbuf, outbuf;
11107     int status;
11108     
11109     /* Any registered moves are unregistered if unregister is set, */
11110     /* i.e. invoked by the signal handler */
11111     if (unregister) {
11112         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11113             cmailMoveRegistered[i] = FALSE;
11114             if (cmailCommentList[i] != NULL) {
11115                 free(cmailCommentList[i]);
11116                 cmailCommentList[i] = NULL;
11117             }
11118         }
11119         nCmailMovesRegistered = 0;
11120     }
11121
11122     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11123         cmailResult[i] = CMAIL_NOT_RESULT;
11124     }
11125     nCmailResults = 0;
11126
11127     if (inFilename == NULL) {
11128         /* Because the filenames are static they only get malloced once  */
11129         /* and they never get freed                                      */
11130         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11131         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11132
11133         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11134         sprintf(outFilename, "%s.out", appData.cmailGameName);
11135     }
11136     
11137     status = stat(outFilename, &outbuf);
11138     if (status < 0) {
11139         cmailMailedMove = FALSE;
11140     } else {
11141         status = stat(inFilename, &inbuf);
11142         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11143     }
11144     
11145     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11146        counts the games, notes how each one terminated, etc.
11147        
11148        It would be nice to remove this kludge and instead gather all
11149        the information while building the game list.  (And to keep it
11150        in the game list nodes instead of having a bunch of fixed-size
11151        parallel arrays.)  Note this will require getting each game's
11152        termination from the PGN tags, as the game list builder does
11153        not process the game moves.  --mann
11154        */
11155     cmailMsgLoaded = TRUE;
11156     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11157     
11158     /* Load first game in the file or popup game menu */
11159     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11160
11161 #endif /* !WIN32 */
11162     return;
11163 }
11164
11165 int
11166 RegisterMove()
11167 {
11168     FILE *f;
11169     char string[MSG_SIZ];
11170
11171     if (   cmailMailedMove
11172         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11173         return TRUE;            /* Allow free viewing  */
11174     }
11175
11176     /* Unregister move to ensure that we don't leave RegisterMove        */
11177     /* with the move registered when the conditions for registering no   */
11178     /* longer hold                                                       */
11179     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11180         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11181         nCmailMovesRegistered --;
11182
11183         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11184           {
11185               free(cmailCommentList[lastLoadGameNumber - 1]);
11186               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11187           }
11188     }
11189
11190     if (cmailOldMove == -1) {
11191         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11192         return FALSE;
11193     }
11194
11195     if (currentMove > cmailOldMove + 1) {
11196         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11197         return FALSE;
11198     }
11199
11200     if (currentMove < cmailOldMove) {
11201         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11202         return FALSE;
11203     }
11204
11205     if (forwardMostMove > currentMove) {
11206         /* Silently truncate extra moves */
11207         TruncateGame();
11208     }
11209
11210     if (   (currentMove == cmailOldMove + 1)
11211         || (   (currentMove == cmailOldMove)
11212             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11213                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11214         if (gameInfo.result != GameUnfinished) {
11215             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11216         }
11217
11218         if (commentList[currentMove] != NULL) {
11219             cmailCommentList[lastLoadGameNumber - 1]
11220               = StrSave(commentList[currentMove]);
11221         }
11222         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11223
11224         if (appData.debugMode)
11225           fprintf(debugFP, "Saving %s for game %d\n",
11226                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11227
11228         sprintf(string,
11229                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11230         
11231         f = fopen(string, "w");
11232         if (appData.oldSaveStyle) {
11233             SaveGameOldStyle(f); /* also closes the file */
11234             
11235             sprintf(string, "%s.pos.out", appData.cmailGameName);
11236             f = fopen(string, "w");
11237             SavePosition(f, 0, NULL); /* also closes the file */
11238         } else {
11239             fprintf(f, "{--------------\n");
11240             PrintPosition(f, currentMove);
11241             fprintf(f, "--------------}\n\n");
11242             
11243             SaveGame(f, 0, NULL); /* also closes the file*/
11244         }
11245         
11246         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11247         nCmailMovesRegistered ++;
11248     } else if (nCmailGames == 1) {
11249         DisplayError(_("You have not made a move yet"), 0);
11250         return FALSE;
11251     }
11252
11253     return TRUE;
11254 }
11255
11256 void
11257 MailMoveEvent()
11258 {
11259 #if !WIN32
11260     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11261     FILE *commandOutput;
11262     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11263     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11264     int nBuffers;
11265     int i;
11266     int archived;
11267     char *arcDir;
11268
11269     if (! cmailMsgLoaded) {
11270         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11271         return;
11272     }
11273
11274     if (nCmailGames == nCmailResults) {
11275         DisplayError(_("No unfinished games"), 0);
11276         return;
11277     }
11278
11279 #if CMAIL_PROHIBIT_REMAIL
11280     if (cmailMailedMove) {
11281         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);
11282         DisplayError(msg, 0);
11283         return;
11284     }
11285 #endif
11286
11287     if (! (cmailMailedMove || RegisterMove())) return;
11288     
11289     if (   cmailMailedMove
11290         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11291         sprintf(string, partCommandString,
11292                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11293         commandOutput = popen(string, "r");
11294
11295         if (commandOutput == NULL) {
11296             DisplayError(_("Failed to invoke cmail"), 0);
11297         } else {
11298             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11299                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11300             }
11301             if (nBuffers > 1) {
11302                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11303                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11304                 nBytes = MSG_SIZ - 1;
11305             } else {
11306                 (void) memcpy(msg, buffer, nBytes);
11307             }
11308             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11309
11310             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11311                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11312
11313                 archived = TRUE;
11314                 for (i = 0; i < nCmailGames; i ++) {
11315                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11316                         archived = FALSE;
11317                     }
11318                 }
11319                 if (   archived
11320                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11321                         != NULL)) {
11322                     sprintf(buffer, "%s/%s.%s.archive",
11323                             arcDir,
11324                             appData.cmailGameName,
11325                             gameInfo.date);
11326                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11327                     cmailMsgLoaded = FALSE;
11328                 }
11329             }
11330
11331             DisplayInformation(msg);
11332             pclose(commandOutput);
11333         }
11334     } else {
11335         if ((*cmailMsg) != '\0') {
11336             DisplayInformation(cmailMsg);
11337         }
11338     }
11339
11340     return;
11341 #endif /* !WIN32 */
11342 }
11343
11344 char *
11345 CmailMsg()
11346 {
11347 #if WIN32
11348     return NULL;
11349 #else
11350     int  prependComma = 0;
11351     char number[5];
11352     char string[MSG_SIZ];       /* Space for game-list */
11353     int  i;
11354     
11355     if (!cmailMsgLoaded) return "";
11356
11357     if (cmailMailedMove) {
11358         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11359     } else {
11360         /* Create a list of games left */
11361         sprintf(string, "[");
11362         for (i = 0; i < nCmailGames; i ++) {
11363             if (! (   cmailMoveRegistered[i]
11364                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11365                 if (prependComma) {
11366                     sprintf(number, ",%d", i + 1);
11367                 } else {
11368                     sprintf(number, "%d", i + 1);
11369                     prependComma = 1;
11370                 }
11371                 
11372                 strcat(string, number);
11373             }
11374         }
11375         strcat(string, "]");
11376
11377         if (nCmailMovesRegistered + nCmailResults == 0) {
11378             switch (nCmailGames) {
11379               case 1:
11380                 sprintf(cmailMsg,
11381                         _("Still need to make move for game\n"));
11382                 break;
11383                 
11384               case 2:
11385                 sprintf(cmailMsg,
11386                         _("Still need to make moves for both games\n"));
11387                 break;
11388                 
11389               default:
11390                 sprintf(cmailMsg,
11391                         _("Still need to make moves for all %d games\n"),
11392                         nCmailGames);
11393                 break;
11394             }
11395         } else {
11396             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11397               case 1:
11398                 sprintf(cmailMsg,
11399                         _("Still need to make a move for game %s\n"),
11400                         string);
11401                 break;
11402                 
11403               case 0:
11404                 if (nCmailResults == nCmailGames) {
11405                     sprintf(cmailMsg, _("No unfinished games\n"));
11406                 } else {
11407                     sprintf(cmailMsg, _("Ready to send mail\n"));
11408                 }
11409                 break;
11410                 
11411               default:
11412                 sprintf(cmailMsg,
11413                         _("Still need to make moves for games %s\n"),
11414                         string);
11415             }
11416         }
11417     }
11418     return cmailMsg;
11419 #endif /* WIN32 */
11420 }
11421
11422 void
11423 ResetGameEvent()
11424 {
11425     if (gameMode == Training)
11426       SetTrainingModeOff();
11427
11428     Reset(TRUE, TRUE);
11429     cmailMsgLoaded = FALSE;
11430     if (appData.icsActive) {
11431       SendToICS(ics_prefix);
11432       SendToICS("refresh\n");
11433     }
11434 }
11435
11436 void
11437 ExitEvent(status)
11438      int status;
11439 {
11440     exiting++;
11441     if (exiting > 2) {
11442       /* Give up on clean exit */
11443       exit(status);
11444     }
11445     if (exiting > 1) {
11446       /* Keep trying for clean exit */
11447       return;
11448     }
11449
11450     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11451
11452     if (telnetISR != NULL) {
11453       RemoveInputSource(telnetISR);
11454     }
11455     if (icsPR != NoProc) {
11456       DestroyChildProcess(icsPR, TRUE);
11457     }
11458
11459     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11460     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11461
11462     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11463     /* make sure this other one finishes before killing it!                  */
11464     if(endingGame) { int count = 0;
11465         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11466         while(endingGame && count++ < 10) DoSleep(1);
11467         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11468     }
11469
11470     /* Kill off chess programs */
11471     if (first.pr != NoProc) {
11472         ExitAnalyzeMode();
11473         
11474         DoSleep( appData.delayBeforeQuit );
11475         SendToProgram("quit\n", &first);
11476         DoSleep( appData.delayAfterQuit );
11477         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11478     }
11479     if (second.pr != NoProc) {
11480         DoSleep( appData.delayBeforeQuit );
11481         SendToProgram("quit\n", &second);
11482         DoSleep( appData.delayAfterQuit );
11483         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11484     }
11485     if (first.isr != NULL) {
11486         RemoveInputSource(first.isr);
11487     }
11488     if (second.isr != NULL) {
11489         RemoveInputSource(second.isr);
11490     }
11491
11492     ShutDownFrontEnd();
11493     exit(status);
11494 }
11495
11496 void
11497 PauseEvent()
11498 {
11499     if (appData.debugMode)
11500         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11501     if (pausing) {
11502         pausing = FALSE;
11503         ModeHighlight();
11504         if (gameMode == MachinePlaysWhite ||
11505             gameMode == MachinePlaysBlack) {
11506             StartClocks();
11507         } else {
11508             DisplayBothClocks();
11509         }
11510         if (gameMode == PlayFromGameFile) {
11511             if (appData.timeDelay >= 0) 
11512                 AutoPlayGameLoop();
11513         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11514             Reset(FALSE, TRUE);
11515             SendToICS(ics_prefix);
11516             SendToICS("refresh\n");
11517         } else if (currentMove < forwardMostMove) {
11518             ForwardInner(forwardMostMove);
11519         }
11520         pauseExamInvalid = FALSE;
11521     } else {
11522         switch (gameMode) {
11523           default:
11524             return;
11525           case IcsExamining:
11526             pauseExamForwardMostMove = forwardMostMove;
11527             pauseExamInvalid = FALSE;
11528             /* fall through */
11529           case IcsObserving:
11530           case IcsPlayingWhite:
11531           case IcsPlayingBlack:
11532             pausing = TRUE;
11533             ModeHighlight();
11534             return;
11535           case PlayFromGameFile:
11536             (void) StopLoadGameTimer();
11537             pausing = TRUE;
11538             ModeHighlight();
11539             break;
11540           case BeginningOfGame:
11541             if (appData.icsActive) return;
11542             /* else fall through */
11543           case MachinePlaysWhite:
11544           case MachinePlaysBlack:
11545           case TwoMachinesPlay:
11546             if (forwardMostMove == 0)
11547               return;           /* don't pause if no one has moved */
11548             if ((gameMode == MachinePlaysWhite &&
11549                  !WhiteOnMove(forwardMostMove)) ||
11550                 (gameMode == MachinePlaysBlack &&
11551                  WhiteOnMove(forwardMostMove))) {
11552                 StopClocks();
11553             }
11554             pausing = TRUE;
11555             ModeHighlight();
11556             break;
11557         }
11558     }
11559 }
11560
11561 void
11562 EditCommentEvent()
11563 {
11564     char title[MSG_SIZ];
11565
11566     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11567         strcpy(title, _("Edit comment"));
11568     } else {
11569         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11570                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11571                 parseList[currentMove - 1]);
11572     }
11573
11574     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11575 }
11576
11577
11578 void
11579 EditTagsEvent()
11580 {
11581     char *tags = PGNTags(&gameInfo);
11582     EditTagsPopUp(tags);
11583     free(tags);
11584 }
11585
11586 void
11587 AnalyzeModeEvent()
11588 {
11589     if (appData.noChessProgram || gameMode == AnalyzeMode)
11590       return;
11591
11592     if (gameMode != AnalyzeFile) {
11593         if (!appData.icsEngineAnalyze) {
11594                EditGameEvent();
11595                if (gameMode != EditGame) return;
11596         }
11597         ResurrectChessProgram();
11598         SendToProgram("analyze\n", &first);
11599         first.analyzing = TRUE;
11600         /*first.maybeThinking = TRUE;*/
11601         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11602         EngineOutputPopUp();
11603     }
11604     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11605     pausing = FALSE;
11606     ModeHighlight();
11607     SetGameInfo();
11608
11609     StartAnalysisClock();
11610     GetTimeMark(&lastNodeCountTime);
11611     lastNodeCount = 0;
11612 }
11613
11614 void
11615 AnalyzeFileEvent()
11616 {
11617     if (appData.noChessProgram || gameMode == AnalyzeFile)
11618       return;
11619
11620     if (gameMode != AnalyzeMode) {
11621         EditGameEvent();
11622         if (gameMode != EditGame) return;
11623         ResurrectChessProgram();
11624         SendToProgram("analyze\n", &first);
11625         first.analyzing = TRUE;
11626         /*first.maybeThinking = TRUE;*/
11627         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11628         EngineOutputPopUp();
11629     }
11630     gameMode = AnalyzeFile;
11631     pausing = FALSE;
11632     ModeHighlight();
11633     SetGameInfo();
11634
11635     StartAnalysisClock();
11636     GetTimeMark(&lastNodeCountTime);
11637     lastNodeCount = 0;
11638 }
11639
11640 void
11641 MachineWhiteEvent()
11642 {
11643     char buf[MSG_SIZ];
11644     char *bookHit = NULL;
11645
11646     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11647       return;
11648
11649
11650     if (gameMode == PlayFromGameFile || 
11651         gameMode == TwoMachinesPlay  || 
11652         gameMode == Training         || 
11653         gameMode == AnalyzeMode      || 
11654         gameMode == EndOfGame)
11655         EditGameEvent();
11656
11657     if (gameMode == EditPosition) 
11658         EditPositionDone(TRUE);
11659
11660     if (!WhiteOnMove(currentMove)) {
11661         DisplayError(_("It is not White's turn"), 0);
11662         return;
11663     }
11664   
11665     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11666       ExitAnalyzeMode();
11667
11668     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11669         gameMode == AnalyzeFile)
11670         TruncateGame();
11671
11672     ResurrectChessProgram();    /* in case it isn't running */
11673     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11674         gameMode = MachinePlaysWhite;
11675         ResetClocks();
11676     } else
11677     gameMode = MachinePlaysWhite;
11678     pausing = FALSE;
11679     ModeHighlight();
11680     SetGameInfo();
11681     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11682     DisplayTitle(buf);
11683     if (first.sendName) {
11684       sprintf(buf, "name %s\n", gameInfo.black);
11685       SendToProgram(buf, &first);
11686     }
11687     if (first.sendTime) {
11688       if (first.useColors) {
11689         SendToProgram("black\n", &first); /*gnu kludge*/
11690       }
11691       SendTimeRemaining(&first, TRUE);
11692     }
11693     if (first.useColors) {
11694       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11695     }
11696     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11697     SetMachineThinkingEnables();
11698     first.maybeThinking = TRUE;
11699     StartClocks();
11700     firstMove = FALSE;
11701
11702     if (appData.autoFlipView && !flipView) {
11703       flipView = !flipView;
11704       DrawPosition(FALSE, NULL);
11705       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11706     }
11707
11708     if(bookHit) { // [HGM] book: simulate book reply
11709         static char bookMove[MSG_SIZ]; // a bit generous?
11710
11711         programStats.nodes = programStats.depth = programStats.time = 
11712         programStats.score = programStats.got_only_move = 0;
11713         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11714
11715         strcpy(bookMove, "move ");
11716         strcat(bookMove, bookHit);
11717         HandleMachineMove(bookMove, &first);
11718     }
11719 }
11720
11721 void
11722 MachineBlackEvent()
11723 {
11724     char buf[MSG_SIZ];
11725    char *bookHit = NULL;
11726
11727     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11728         return;
11729
11730
11731     if (gameMode == PlayFromGameFile || 
11732         gameMode == TwoMachinesPlay  || 
11733         gameMode == Training         || 
11734         gameMode == AnalyzeMode      || 
11735         gameMode == EndOfGame)
11736         EditGameEvent();
11737
11738     if (gameMode == EditPosition) 
11739         EditPositionDone(TRUE);
11740
11741     if (WhiteOnMove(currentMove)) {
11742         DisplayError(_("It is not Black's turn"), 0);
11743         return;
11744     }
11745     
11746     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11747       ExitAnalyzeMode();
11748
11749     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11750         gameMode == AnalyzeFile)
11751         TruncateGame();
11752
11753     ResurrectChessProgram();    /* in case it isn't running */
11754     gameMode = MachinePlaysBlack;
11755     pausing = FALSE;
11756     ModeHighlight();
11757     SetGameInfo();
11758     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11759     DisplayTitle(buf);
11760     if (first.sendName) {
11761       sprintf(buf, "name %s\n", gameInfo.white);
11762       SendToProgram(buf, &first);
11763     }
11764     if (first.sendTime) {
11765       if (first.useColors) {
11766         SendToProgram("white\n", &first); /*gnu kludge*/
11767       }
11768       SendTimeRemaining(&first, FALSE);
11769     }
11770     if (first.useColors) {
11771       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11772     }
11773     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11774     SetMachineThinkingEnables();
11775     first.maybeThinking = TRUE;
11776     StartClocks();
11777
11778     if (appData.autoFlipView && flipView) {
11779       flipView = !flipView;
11780       DrawPosition(FALSE, NULL);
11781       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11782     }
11783     if(bookHit) { // [HGM] book: simulate book reply
11784         static char bookMove[MSG_SIZ]; // a bit generous?
11785
11786         programStats.nodes = programStats.depth = programStats.time = 
11787         programStats.score = programStats.got_only_move = 0;
11788         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11789
11790         strcpy(bookMove, "move ");
11791         strcat(bookMove, bookHit);
11792         HandleMachineMove(bookMove, &first);
11793     }
11794 }
11795
11796
11797 void
11798 DisplayTwoMachinesTitle()
11799 {
11800     char buf[MSG_SIZ];
11801     if (appData.matchGames > 0) {
11802         if (first.twoMachinesColor[0] == 'w') {
11803             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11804                     gameInfo.white, gameInfo.black,
11805                     first.matchWins, second.matchWins,
11806                     matchGame - 1 - (first.matchWins + second.matchWins));
11807         } else {
11808             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11809                     gameInfo.white, gameInfo.black,
11810                     second.matchWins, first.matchWins,
11811                     matchGame - 1 - (first.matchWins + second.matchWins));
11812         }
11813     } else {
11814         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11815     }
11816     DisplayTitle(buf);
11817 }
11818
11819 void
11820 TwoMachinesEvent P((void))
11821 {
11822     int i;
11823     char buf[MSG_SIZ];
11824     ChessProgramState *onmove;
11825     char *bookHit = NULL;
11826     
11827     if (appData.noChessProgram) return;
11828
11829     switch (gameMode) {
11830       case TwoMachinesPlay:
11831         return;
11832       case MachinePlaysWhite:
11833       case MachinePlaysBlack:
11834         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11835             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11836             return;
11837         }
11838         /* fall through */
11839       case BeginningOfGame:
11840       case PlayFromGameFile:
11841       case EndOfGame:
11842         EditGameEvent();
11843         if (gameMode != EditGame) return;
11844         break;
11845       case EditPosition:
11846         EditPositionDone(TRUE);
11847         break;
11848       case AnalyzeMode:
11849       case AnalyzeFile:
11850         ExitAnalyzeMode();
11851         break;
11852       case EditGame:
11853       default:
11854         break;
11855     }
11856
11857 //    forwardMostMove = currentMove;
11858     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11859     ResurrectChessProgram();    /* in case first program isn't running */
11860
11861     if (second.pr == NULL) {
11862         StartChessProgram(&second);
11863         if (second.protocolVersion == 1) {
11864           TwoMachinesEventIfReady();
11865         } else {
11866           /* kludge: allow timeout for initial "feature" command */
11867           FreezeUI();
11868           DisplayMessage("", _("Starting second chess program"));
11869           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11870         }
11871         return;
11872     }
11873     DisplayMessage("", "");
11874     InitChessProgram(&second, FALSE);
11875     SendToProgram("force\n", &second);
11876     if (startedFromSetupPosition) {
11877         SendBoard(&second, backwardMostMove);
11878     if (appData.debugMode) {
11879         fprintf(debugFP, "Two Machines\n");
11880     }
11881     }
11882     for (i = backwardMostMove; i < forwardMostMove; i++) {
11883         SendMoveToProgram(i, &second);
11884     }
11885
11886     gameMode = TwoMachinesPlay;
11887     pausing = FALSE;
11888     ModeHighlight();
11889     SetGameInfo();
11890     DisplayTwoMachinesTitle();
11891     firstMove = TRUE;
11892     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11893         onmove = &first;
11894     } else {
11895         onmove = &second;
11896     }
11897
11898     SendToProgram(first.computerString, &first);
11899     if (first.sendName) {
11900       sprintf(buf, "name %s\n", second.tidy);
11901       SendToProgram(buf, &first);
11902     }
11903     SendToProgram(second.computerString, &second);
11904     if (second.sendName) {
11905       sprintf(buf, "name %s\n", first.tidy);
11906       SendToProgram(buf, &second);
11907     }
11908
11909     ResetClocks();
11910     if (!first.sendTime || !second.sendTime) {
11911         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11912         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11913     }
11914     if (onmove->sendTime) {
11915       if (onmove->useColors) {
11916         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11917       }
11918       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11919     }
11920     if (onmove->useColors) {
11921       SendToProgram(onmove->twoMachinesColor, onmove);
11922     }
11923     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11924 //    SendToProgram("go\n", onmove);
11925     onmove->maybeThinking = TRUE;
11926     SetMachineThinkingEnables();
11927
11928     StartClocks();
11929
11930     if(bookHit) { // [HGM] book: simulate book reply
11931         static char bookMove[MSG_SIZ]; // a bit generous?
11932
11933         programStats.nodes = programStats.depth = programStats.time = 
11934         programStats.score = programStats.got_only_move = 0;
11935         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11936
11937         strcpy(bookMove, "move ");
11938         strcat(bookMove, bookHit);
11939         savedMessage = bookMove; // args for deferred call
11940         savedState = onmove;
11941         ScheduleDelayedEvent(DeferredBookMove, 1);
11942     }
11943 }
11944
11945 void
11946 TrainingEvent()
11947 {
11948     if (gameMode == Training) {
11949       SetTrainingModeOff();
11950       gameMode = PlayFromGameFile;
11951       DisplayMessage("", _("Training mode off"));
11952     } else {
11953       gameMode = Training;
11954       animateTraining = appData.animate;
11955
11956       /* make sure we are not already at the end of the game */
11957       if (currentMove < forwardMostMove) {
11958         SetTrainingModeOn();
11959         DisplayMessage("", _("Training mode on"));
11960       } else {
11961         gameMode = PlayFromGameFile;
11962         DisplayError(_("Already at end of game"), 0);
11963       }
11964     }
11965     ModeHighlight();
11966 }
11967
11968 void
11969 IcsClientEvent()
11970 {
11971     if (!appData.icsActive) return;
11972     switch (gameMode) {
11973       case IcsPlayingWhite:
11974       case IcsPlayingBlack:
11975       case IcsObserving:
11976       case IcsIdle:
11977       case BeginningOfGame:
11978       case IcsExamining:
11979         return;
11980
11981       case EditGame:
11982         break;
11983
11984       case EditPosition:
11985         EditPositionDone(TRUE);
11986         break;
11987
11988       case AnalyzeMode:
11989       case AnalyzeFile:
11990         ExitAnalyzeMode();
11991         break;
11992         
11993       default:
11994         EditGameEvent();
11995         break;
11996     }
11997
11998     gameMode = IcsIdle;
11999     ModeHighlight();
12000     return;
12001 }
12002
12003
12004 void
12005 EditGameEvent()
12006 {
12007     int i;
12008
12009     switch (gameMode) {
12010       case Training:
12011         SetTrainingModeOff();
12012         break;
12013       case MachinePlaysWhite:
12014       case MachinePlaysBlack:
12015       case BeginningOfGame:
12016         SendToProgram("force\n", &first);
12017         SetUserThinkingEnables();
12018         break;
12019       case PlayFromGameFile:
12020         (void) StopLoadGameTimer();
12021         if (gameFileFP != NULL) {
12022             gameFileFP = NULL;
12023         }
12024         break;
12025       case EditPosition:
12026         EditPositionDone(TRUE);
12027         break;
12028       case AnalyzeMode:
12029       case AnalyzeFile:
12030         ExitAnalyzeMode();
12031         SendToProgram("force\n", &first);
12032         break;
12033       case TwoMachinesPlay:
12034         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
12035         ResurrectChessProgram();
12036         SetUserThinkingEnables();
12037         break;
12038       case EndOfGame:
12039         ResurrectChessProgram();
12040         break;
12041       case IcsPlayingBlack:
12042       case IcsPlayingWhite:
12043         DisplayError(_("Warning: You are still playing a game"), 0);
12044         break;
12045       case IcsObserving:
12046         DisplayError(_("Warning: You are still observing a game"), 0);
12047         break;
12048       case IcsExamining:
12049         DisplayError(_("Warning: You are still examining a game"), 0);
12050         break;
12051       case IcsIdle:
12052         break;
12053       case EditGame:
12054       default:
12055         return;
12056     }
12057     
12058     pausing = FALSE;
12059     StopClocks();
12060     first.offeredDraw = second.offeredDraw = 0;
12061
12062     if (gameMode == PlayFromGameFile) {
12063         whiteTimeRemaining = timeRemaining[0][currentMove];
12064         blackTimeRemaining = timeRemaining[1][currentMove];
12065         DisplayTitle("");
12066     }
12067
12068     if (gameMode == MachinePlaysWhite ||
12069         gameMode == MachinePlaysBlack ||
12070         gameMode == TwoMachinesPlay ||
12071         gameMode == EndOfGame) {
12072         i = forwardMostMove;
12073         while (i > currentMove) {
12074             SendToProgram("undo\n", &first);
12075             i--;
12076         }
12077         whiteTimeRemaining = timeRemaining[0][currentMove];
12078         blackTimeRemaining = timeRemaining[1][currentMove];
12079         DisplayBothClocks();
12080         if (whiteFlag || blackFlag) {
12081             whiteFlag = blackFlag = 0;
12082         }
12083         DisplayTitle("");
12084     }           
12085     
12086     gameMode = EditGame;
12087     ModeHighlight();
12088     SetGameInfo();
12089 }
12090
12091
12092 void
12093 EditPositionEvent()
12094 {
12095     if (gameMode == EditPosition) {
12096         EditGameEvent();
12097         return;
12098     }
12099     
12100     EditGameEvent();
12101     if (gameMode != EditGame) return;
12102     
12103     gameMode = EditPosition;
12104     ModeHighlight();
12105     SetGameInfo();
12106     if (currentMove > 0)
12107       CopyBoard(boards[0], boards[currentMove]);
12108     
12109     blackPlaysFirst = !WhiteOnMove(currentMove);
12110     ResetClocks();
12111     currentMove = forwardMostMove = backwardMostMove = 0;
12112     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12113     DisplayMove(-1);
12114 }
12115
12116 void
12117 ExitAnalyzeMode()
12118 {
12119     /* [DM] icsEngineAnalyze - possible call from other functions */
12120     if (appData.icsEngineAnalyze) {
12121         appData.icsEngineAnalyze = FALSE;
12122
12123         DisplayMessage("",_("Close ICS engine analyze..."));
12124     }
12125     if (first.analysisSupport && first.analyzing) {
12126       SendToProgram("exit\n", &first);
12127       first.analyzing = FALSE;
12128     }
12129     thinkOutput[0] = NULLCHAR;
12130 }
12131
12132 void
12133 EditPositionDone(Boolean fakeRights)
12134 {
12135     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12136
12137     startedFromSetupPosition = TRUE;
12138     InitChessProgram(&first, FALSE);
12139     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12140       boards[0][EP_STATUS] = EP_NONE;
12141       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12142     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12143         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12144         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12145       } else boards[0][CASTLING][2] = NoRights;
12146     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12147         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12148         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12149       } else boards[0][CASTLING][5] = NoRights;
12150     }
12151     SendToProgram("force\n", &first);
12152     if (blackPlaysFirst) {
12153         strcpy(moveList[0], "");
12154         strcpy(parseList[0], "");
12155         currentMove = forwardMostMove = backwardMostMove = 1;
12156         CopyBoard(boards[1], boards[0]);
12157     } else {
12158         currentMove = forwardMostMove = backwardMostMove = 0;
12159     }
12160     SendBoard(&first, forwardMostMove);
12161     if (appData.debugMode) {
12162         fprintf(debugFP, "EditPosDone\n");
12163     }
12164     DisplayTitle("");
12165     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12166     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12167     gameMode = EditGame;
12168     ModeHighlight();
12169     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12170     ClearHighlights(); /* [AS] */
12171 }
12172
12173 /* Pause for `ms' milliseconds */
12174 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12175 void
12176 TimeDelay(ms)
12177      long ms;
12178 {
12179     TimeMark m1, m2;
12180
12181     GetTimeMark(&m1);
12182     do {
12183         GetTimeMark(&m2);
12184     } while (SubtractTimeMarks(&m2, &m1) < ms);
12185 }
12186
12187 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12188 void
12189 SendMultiLineToICS(buf)
12190      char *buf;
12191 {
12192     char temp[MSG_SIZ+1], *p;
12193     int len;
12194
12195     len = strlen(buf);
12196     if (len > MSG_SIZ)
12197       len = MSG_SIZ;
12198   
12199     strncpy(temp, buf, len);
12200     temp[len] = 0;
12201
12202     p = temp;
12203     while (*p) {
12204         if (*p == '\n' || *p == '\r')
12205           *p = ' ';
12206         ++p;
12207     }
12208
12209     strcat(temp, "\n");
12210     SendToICS(temp);
12211     SendToPlayer(temp, strlen(temp));
12212 }
12213
12214 void
12215 SetWhiteToPlayEvent()
12216 {
12217     if (gameMode == EditPosition) {
12218         blackPlaysFirst = FALSE;
12219         DisplayBothClocks();    /* works because currentMove is 0 */
12220     } else if (gameMode == IcsExamining) {
12221         SendToICS(ics_prefix);
12222         SendToICS("tomove white\n");
12223     }
12224 }
12225
12226 void
12227 SetBlackToPlayEvent()
12228 {
12229     if (gameMode == EditPosition) {
12230         blackPlaysFirst = TRUE;
12231         currentMove = 1;        /* kludge */
12232         DisplayBothClocks();
12233         currentMove = 0;
12234     } else if (gameMode == IcsExamining) {
12235         SendToICS(ics_prefix);
12236         SendToICS("tomove black\n");
12237     }
12238 }
12239
12240 void
12241 EditPositionMenuEvent(selection, x, y)
12242      ChessSquare selection;
12243      int x, y;
12244 {
12245     char buf[MSG_SIZ];
12246     ChessSquare piece = boards[0][y][x];
12247
12248     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12249
12250     switch (selection) {
12251       case ClearBoard:
12252         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12253             SendToICS(ics_prefix);
12254             SendToICS("bsetup clear\n");
12255         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12256             SendToICS(ics_prefix);
12257             SendToICS("clearboard\n");
12258         } else {
12259             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12260                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12261                 for (y = 0; y < BOARD_HEIGHT; y++) {
12262                     if (gameMode == IcsExamining) {
12263                         if (boards[currentMove][y][x] != EmptySquare) {
12264                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12265                                     AAA + x, ONE + y);
12266                             SendToICS(buf);
12267                         }
12268                     } else {
12269                         boards[0][y][x] = p;
12270                     }
12271                 }
12272             }
12273         }
12274         if (gameMode == EditPosition) {
12275             DrawPosition(FALSE, boards[0]);
12276         }
12277         break;
12278
12279       case WhitePlay:
12280         SetWhiteToPlayEvent();
12281         break;
12282
12283       case BlackPlay:
12284         SetBlackToPlayEvent();
12285         break;
12286
12287       case EmptySquare:
12288         if (gameMode == IcsExamining) {
12289             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12290             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12291             SendToICS(buf);
12292         } else {
12293             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12294                 if(x == BOARD_LEFT-2) {
12295                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12296                     boards[0][y][1] = 0;
12297                 } else
12298                 if(x == BOARD_RGHT+1) {
12299                     if(y >= gameInfo.holdingsSize) break;
12300                     boards[0][y][BOARD_WIDTH-2] = 0;
12301                 } else break;
12302             }
12303             boards[0][y][x] = EmptySquare;
12304             DrawPosition(FALSE, boards[0]);
12305         }
12306         break;
12307
12308       case PromotePiece:
12309         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12310            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12311             selection = (ChessSquare) (PROMOTED piece);
12312         } else if(piece == EmptySquare) selection = WhiteSilver;
12313         else selection = (ChessSquare)((int)piece - 1);
12314         goto defaultlabel;
12315
12316       case DemotePiece:
12317         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12318            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12319             selection = (ChessSquare) (DEMOTED piece);
12320         } else if(piece == EmptySquare) selection = BlackSilver;
12321         else selection = (ChessSquare)((int)piece + 1);       
12322         goto defaultlabel;
12323
12324       case WhiteQueen:
12325       case BlackQueen:
12326         if(gameInfo.variant == VariantShatranj ||
12327            gameInfo.variant == VariantXiangqi  ||
12328            gameInfo.variant == VariantCourier  ||
12329            gameInfo.variant == VariantMakruk     )
12330             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12331         goto defaultlabel;
12332
12333       case WhiteKing:
12334       case BlackKing:
12335         if(gameInfo.variant == VariantXiangqi)
12336             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12337         if(gameInfo.variant == VariantKnightmate)
12338             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12339       default:
12340         defaultlabel:
12341         if (gameMode == IcsExamining) {
12342             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12343             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12344                     PieceToChar(selection), AAA + x, ONE + y);
12345             SendToICS(buf);
12346         } else {
12347             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12348                 int n;
12349                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12350                     n = PieceToNumber(selection - BlackPawn);
12351                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12352                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12353                     boards[0][BOARD_HEIGHT-1-n][1]++;
12354                 } else
12355                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12356                     n = PieceToNumber(selection);
12357                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12358                     boards[0][n][BOARD_WIDTH-1] = selection;
12359                     boards[0][n][BOARD_WIDTH-2]++;
12360                 }
12361             } else
12362             boards[0][y][x] = selection;
12363             DrawPosition(TRUE, boards[0]);
12364         }
12365         break;
12366     }
12367 }
12368
12369
12370 void
12371 DropMenuEvent(selection, x, y)
12372      ChessSquare selection;
12373      int x, y;
12374 {
12375     ChessMove moveType;
12376
12377     switch (gameMode) {
12378       case IcsPlayingWhite:
12379       case MachinePlaysBlack:
12380         if (!WhiteOnMove(currentMove)) {
12381             DisplayMoveError(_("It is Black's turn"));
12382             return;
12383         }
12384         moveType = WhiteDrop;
12385         break;
12386       case IcsPlayingBlack:
12387       case MachinePlaysWhite:
12388         if (WhiteOnMove(currentMove)) {
12389             DisplayMoveError(_("It is White's turn"));
12390             return;
12391         }
12392         moveType = BlackDrop;
12393         break;
12394       case EditGame:
12395         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12396         break;
12397       default:
12398         return;
12399     }
12400
12401     if (moveType == BlackDrop && selection < BlackPawn) {
12402       selection = (ChessSquare) ((int) selection
12403                                  + (int) BlackPawn - (int) WhitePawn);
12404     }
12405     if (boards[currentMove][y][x] != EmptySquare) {
12406         DisplayMoveError(_("That square is occupied"));
12407         return;
12408     }
12409
12410     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12411 }
12412
12413 void
12414 AcceptEvent()
12415 {
12416     /* Accept a pending offer of any kind from opponent */
12417     
12418     if (appData.icsActive) {
12419         SendToICS(ics_prefix);
12420         SendToICS("accept\n");
12421     } else if (cmailMsgLoaded) {
12422         if (currentMove == cmailOldMove &&
12423             commentList[cmailOldMove] != NULL &&
12424             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12425                    "Black offers a draw" : "White offers a draw")) {
12426             TruncateGame();
12427             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12428             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12429         } else {
12430             DisplayError(_("There is no pending offer on this move"), 0);
12431             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12432         }
12433     } else {
12434         /* Not used for offers from chess program */
12435     }
12436 }
12437
12438 void
12439 DeclineEvent()
12440 {
12441     /* Decline a pending offer of any kind from opponent */
12442     
12443     if (appData.icsActive) {
12444         SendToICS(ics_prefix);
12445         SendToICS("decline\n");
12446     } else if (cmailMsgLoaded) {
12447         if (currentMove == cmailOldMove &&
12448             commentList[cmailOldMove] != NULL &&
12449             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12450                    "Black offers a draw" : "White offers a draw")) {
12451 #ifdef NOTDEF
12452             AppendComment(cmailOldMove, "Draw declined", TRUE);
12453             DisplayComment(cmailOldMove - 1, "Draw declined");
12454 #endif /*NOTDEF*/
12455         } else {
12456             DisplayError(_("There is no pending offer on this move"), 0);
12457         }
12458     } else {
12459         /* Not used for offers from chess program */
12460     }
12461 }
12462
12463 void
12464 RematchEvent()
12465 {
12466     /* Issue ICS rematch command */
12467     if (appData.icsActive) {
12468         SendToICS(ics_prefix);
12469         SendToICS("rematch\n");
12470     }
12471 }
12472
12473 void
12474 CallFlagEvent()
12475 {
12476     /* Call your opponent's flag (claim a win on time) */
12477     if (appData.icsActive) {
12478         SendToICS(ics_prefix);
12479         SendToICS("flag\n");
12480     } else {
12481         switch (gameMode) {
12482           default:
12483             return;
12484           case MachinePlaysWhite:
12485             if (whiteFlag) {
12486                 if (blackFlag)
12487                   GameEnds(GameIsDrawn, "Both players ran out of time",
12488                            GE_PLAYER);
12489                 else
12490                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12491             } else {
12492                 DisplayError(_("Your opponent is not out of time"), 0);
12493             }
12494             break;
12495           case MachinePlaysBlack:
12496             if (blackFlag) {
12497                 if (whiteFlag)
12498                   GameEnds(GameIsDrawn, "Both players ran out of time",
12499                            GE_PLAYER);
12500                 else
12501                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12502             } else {
12503                 DisplayError(_("Your opponent is not out of time"), 0);
12504             }
12505             break;
12506         }
12507     }
12508 }
12509
12510 void
12511 DrawEvent()
12512 {
12513     /* Offer draw or accept pending draw offer from opponent */
12514     
12515     if (appData.icsActive) {
12516         /* Note: tournament rules require draw offers to be
12517            made after you make your move but before you punch
12518            your clock.  Currently ICS doesn't let you do that;
12519            instead, you immediately punch your clock after making
12520            a move, but you can offer a draw at any time. */
12521         
12522         SendToICS(ics_prefix);
12523         SendToICS("draw\n");
12524         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12525     } else if (cmailMsgLoaded) {
12526         if (currentMove == cmailOldMove &&
12527             commentList[cmailOldMove] != NULL &&
12528             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12529                    "Black offers a draw" : "White offers a draw")) {
12530             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12531             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12532         } else if (currentMove == cmailOldMove + 1) {
12533             char *offer = WhiteOnMove(cmailOldMove) ?
12534               "White offers a draw" : "Black offers a draw";
12535             AppendComment(currentMove, offer, TRUE);
12536             DisplayComment(currentMove - 1, offer);
12537             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12538         } else {
12539             DisplayError(_("You must make your move before offering a draw"), 0);
12540             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12541         }
12542     } else if (first.offeredDraw) {
12543         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12544     } else {
12545         if (first.sendDrawOffers) {
12546             SendToProgram("draw\n", &first);
12547             userOfferedDraw = TRUE;
12548         }
12549     }
12550 }
12551
12552 void
12553 AdjournEvent()
12554 {
12555     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12556     
12557     if (appData.icsActive) {
12558         SendToICS(ics_prefix);
12559         SendToICS("adjourn\n");
12560     } else {
12561         /* Currently GNU Chess doesn't offer or accept Adjourns */
12562     }
12563 }
12564
12565
12566 void
12567 AbortEvent()
12568 {
12569     /* Offer Abort or accept pending Abort offer from opponent */
12570     
12571     if (appData.icsActive) {
12572         SendToICS(ics_prefix);
12573         SendToICS("abort\n");
12574     } else {
12575         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12576     }
12577 }
12578
12579 void
12580 ResignEvent()
12581 {
12582     /* Resign.  You can do this even if it's not your turn. */
12583     
12584     if (appData.icsActive) {
12585         SendToICS(ics_prefix);
12586         SendToICS("resign\n");
12587     } else {
12588         switch (gameMode) {
12589           case MachinePlaysWhite:
12590             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12591             break;
12592           case MachinePlaysBlack:
12593             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12594             break;
12595           case EditGame:
12596             if (cmailMsgLoaded) {
12597                 TruncateGame();
12598                 if (WhiteOnMove(cmailOldMove)) {
12599                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12600                 } else {
12601                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12602                 }
12603                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12604             }
12605             break;
12606           default:
12607             break;
12608         }
12609     }
12610 }
12611
12612
12613 void
12614 StopObservingEvent()
12615 {
12616     /* Stop observing current games */
12617     SendToICS(ics_prefix);
12618     SendToICS("unobserve\n");
12619 }
12620
12621 void
12622 StopExaminingEvent()
12623 {
12624     /* Stop observing current game */
12625     SendToICS(ics_prefix);
12626     SendToICS("unexamine\n");
12627 }
12628
12629 void
12630 ForwardInner(target)
12631      int target;
12632 {
12633     int limit;
12634
12635     if (appData.debugMode)
12636         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12637                 target, currentMove, forwardMostMove);
12638
12639     if (gameMode == EditPosition)
12640       return;
12641
12642     if (gameMode == PlayFromGameFile && !pausing)
12643       PauseEvent();
12644     
12645     if (gameMode == IcsExamining && pausing)
12646       limit = pauseExamForwardMostMove;
12647     else
12648       limit = forwardMostMove;
12649     
12650     if (target > limit) target = limit;
12651
12652     if (target > 0 && moveList[target - 1][0]) {
12653         int fromX, fromY, toX, toY;
12654         toX = moveList[target - 1][2] - AAA;
12655         toY = moveList[target - 1][3] - ONE;
12656         if (moveList[target - 1][1] == '@') {
12657             if (appData.highlightLastMove) {
12658                 SetHighlights(-1, -1, toX, toY);
12659             }
12660         } else {
12661             fromX = moveList[target - 1][0] - AAA;
12662             fromY = moveList[target - 1][1] - ONE;
12663             if (target == currentMove + 1) {
12664                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12665             }
12666             if (appData.highlightLastMove) {
12667                 SetHighlights(fromX, fromY, toX, toY);
12668             }
12669         }
12670     }
12671     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12672         gameMode == Training || gameMode == PlayFromGameFile || 
12673         gameMode == AnalyzeFile) {
12674         while (currentMove < target) {
12675             SendMoveToProgram(currentMove++, &first);
12676         }
12677     } else {
12678         currentMove = target;
12679     }
12680     
12681     if (gameMode == EditGame || gameMode == EndOfGame) {
12682         whiteTimeRemaining = timeRemaining[0][currentMove];
12683         blackTimeRemaining = timeRemaining[1][currentMove];
12684     }
12685     DisplayBothClocks();
12686     DisplayMove(currentMove - 1);
12687     DrawPosition(FALSE, boards[currentMove]);
12688     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12689     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12690         DisplayComment(currentMove - 1, commentList[currentMove]);
12691     }
12692 }
12693
12694
12695 void
12696 ForwardEvent()
12697 {
12698     if (gameMode == IcsExamining && !pausing) {
12699         SendToICS(ics_prefix);
12700         SendToICS("forward\n");
12701     } else {
12702         ForwardInner(currentMove + 1);
12703     }
12704 }
12705
12706 void
12707 ToEndEvent()
12708 {
12709     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12710         /* to optimze, we temporarily turn off analysis mode while we feed
12711          * the remaining moves to the engine. Otherwise we get analysis output
12712          * after each move.
12713          */ 
12714         if (first.analysisSupport) {
12715           SendToProgram("exit\nforce\n", &first);
12716           first.analyzing = FALSE;
12717         }
12718     }
12719         
12720     if (gameMode == IcsExamining && !pausing) {
12721         SendToICS(ics_prefix);
12722         SendToICS("forward 999999\n");
12723     } else {
12724         ForwardInner(forwardMostMove);
12725     }
12726
12727     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12728         /* we have fed all the moves, so reactivate analysis mode */
12729         SendToProgram("analyze\n", &first);
12730         first.analyzing = TRUE;
12731         /*first.maybeThinking = TRUE;*/
12732         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12733     }
12734 }
12735
12736 void
12737 BackwardInner(target)
12738      int target;
12739 {
12740     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12741
12742     if (appData.debugMode)
12743         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12744                 target, currentMove, forwardMostMove);
12745
12746     if (gameMode == EditPosition) return;
12747     if (currentMove <= backwardMostMove) {
12748         ClearHighlights();
12749         DrawPosition(full_redraw, boards[currentMove]);
12750         return;
12751     }
12752     if (gameMode == PlayFromGameFile && !pausing)
12753       PauseEvent();
12754     
12755     if (moveList[target][0]) {
12756         int fromX, fromY, toX, toY;
12757         toX = moveList[target][2] - AAA;
12758         toY = moveList[target][3] - ONE;
12759         if (moveList[target][1] == '@') {
12760             if (appData.highlightLastMove) {
12761                 SetHighlights(-1, -1, toX, toY);
12762             }
12763         } else {
12764             fromX = moveList[target][0] - AAA;
12765             fromY = moveList[target][1] - ONE;
12766             if (target == currentMove - 1) {
12767                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12768             }
12769             if (appData.highlightLastMove) {
12770                 SetHighlights(fromX, fromY, toX, toY);
12771             }
12772         }
12773     }
12774     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12775         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12776         while (currentMove > target) {
12777             SendToProgram("undo\n", &first);
12778             currentMove--;
12779         }
12780     } else {
12781         currentMove = target;
12782     }
12783     
12784     if (gameMode == EditGame || gameMode == EndOfGame) {
12785         whiteTimeRemaining = timeRemaining[0][currentMove];
12786         blackTimeRemaining = timeRemaining[1][currentMove];
12787     }
12788     DisplayBothClocks();
12789     DisplayMove(currentMove - 1);
12790     DrawPosition(full_redraw, boards[currentMove]);
12791     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12792     // [HGM] PV info: routine tests if comment empty
12793     DisplayComment(currentMove - 1, commentList[currentMove]);
12794 }
12795
12796 void
12797 BackwardEvent()
12798 {
12799     if (gameMode == IcsExamining && !pausing) {
12800         SendToICS(ics_prefix);
12801         SendToICS("backward\n");
12802     } else {
12803         BackwardInner(currentMove - 1);
12804     }
12805 }
12806
12807 void
12808 ToStartEvent()
12809 {
12810     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12811         /* to optimize, we temporarily turn off analysis mode while we undo
12812          * all the moves. Otherwise we get analysis output after each undo.
12813          */ 
12814         if (first.analysisSupport) {
12815           SendToProgram("exit\nforce\n", &first);
12816           first.analyzing = FALSE;
12817         }
12818     }
12819
12820     if (gameMode == IcsExamining && !pausing) {
12821         SendToICS(ics_prefix);
12822         SendToICS("backward 999999\n");
12823     } else {
12824         BackwardInner(backwardMostMove);
12825     }
12826
12827     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12828         /* we have fed all the moves, so reactivate analysis mode */
12829         SendToProgram("analyze\n", &first);
12830         first.analyzing = TRUE;
12831         /*first.maybeThinking = TRUE;*/
12832         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12833     }
12834 }
12835
12836 void
12837 ToNrEvent(int to)
12838 {
12839   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12840   if (to >= forwardMostMove) to = forwardMostMove;
12841   if (to <= backwardMostMove) to = backwardMostMove;
12842   if (to < currentMove) {
12843     BackwardInner(to);
12844   } else {
12845     ForwardInner(to);
12846   }
12847 }
12848
12849 void
12850 RevertEvent(Boolean annotate)
12851 {
12852     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12853         return;
12854     }
12855     if (gameMode != IcsExamining) {
12856         DisplayError(_("You are not examining a game"), 0);
12857         return;
12858     }
12859     if (pausing) {
12860         DisplayError(_("You can't revert while pausing"), 0);
12861         return;
12862     }
12863     SendToICS(ics_prefix);
12864     SendToICS("revert\n");
12865 }
12866
12867 void
12868 RetractMoveEvent()
12869 {
12870     switch (gameMode) {
12871       case MachinePlaysWhite:
12872       case MachinePlaysBlack:
12873         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12874             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12875             return;
12876         }
12877         if (forwardMostMove < 2) return;
12878         currentMove = forwardMostMove = forwardMostMove - 2;
12879         whiteTimeRemaining = timeRemaining[0][currentMove];
12880         blackTimeRemaining = timeRemaining[1][currentMove];
12881         DisplayBothClocks();
12882         DisplayMove(currentMove - 1);
12883         ClearHighlights();/*!! could figure this out*/
12884         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12885         SendToProgram("remove\n", &first);
12886         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12887         break;
12888
12889       case BeginningOfGame:
12890       default:
12891         break;
12892
12893       case IcsPlayingWhite:
12894       case IcsPlayingBlack:
12895         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12896             SendToICS(ics_prefix);
12897             SendToICS("takeback 2\n");
12898         } else {
12899             SendToICS(ics_prefix);
12900             SendToICS("takeback 1\n");
12901         }
12902         break;
12903     }
12904 }
12905
12906 void
12907 MoveNowEvent()
12908 {
12909     ChessProgramState *cps;
12910
12911     switch (gameMode) {
12912       case MachinePlaysWhite:
12913         if (!WhiteOnMove(forwardMostMove)) {
12914             DisplayError(_("It is your turn"), 0);
12915             return;
12916         }
12917         cps = &first;
12918         break;
12919       case MachinePlaysBlack:
12920         if (WhiteOnMove(forwardMostMove)) {
12921             DisplayError(_("It is your turn"), 0);
12922             return;
12923         }
12924         cps = &first;
12925         break;
12926       case TwoMachinesPlay:
12927         if (WhiteOnMove(forwardMostMove) ==
12928             (first.twoMachinesColor[0] == 'w')) {
12929             cps = &first;
12930         } else {
12931             cps = &second;
12932         }
12933         break;
12934       case BeginningOfGame:
12935       default:
12936         return;
12937     }
12938     SendToProgram("?\n", cps);
12939 }
12940
12941 void
12942 TruncateGameEvent()
12943 {
12944     EditGameEvent();
12945     if (gameMode != EditGame) return;
12946     TruncateGame();
12947 }
12948
12949 void
12950 TruncateGame()
12951 {
12952     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12953     if (forwardMostMove > currentMove) {
12954         if (gameInfo.resultDetails != NULL) {
12955             free(gameInfo.resultDetails);
12956             gameInfo.resultDetails = NULL;
12957             gameInfo.result = GameUnfinished;
12958         }
12959         forwardMostMove = currentMove;
12960         HistorySet(parseList, backwardMostMove, forwardMostMove,
12961                    currentMove-1);
12962     }
12963 }
12964
12965 void
12966 HintEvent()
12967 {
12968     if (appData.noChessProgram) return;
12969     switch (gameMode) {
12970       case MachinePlaysWhite:
12971         if (WhiteOnMove(forwardMostMove)) {
12972             DisplayError(_("Wait until your turn"), 0);
12973             return;
12974         }
12975         break;
12976       case BeginningOfGame:
12977       case MachinePlaysBlack:
12978         if (!WhiteOnMove(forwardMostMove)) {
12979             DisplayError(_("Wait until your turn"), 0);
12980             return;
12981         }
12982         break;
12983       default:
12984         DisplayError(_("No hint available"), 0);
12985         return;
12986     }
12987     SendToProgram("hint\n", &first);
12988     hintRequested = TRUE;
12989 }
12990
12991 void
12992 BookEvent()
12993 {
12994     if (appData.noChessProgram) return;
12995     switch (gameMode) {
12996       case MachinePlaysWhite:
12997         if (WhiteOnMove(forwardMostMove)) {
12998             DisplayError(_("Wait until your turn"), 0);
12999             return;
13000         }
13001         break;
13002       case BeginningOfGame:
13003       case MachinePlaysBlack:
13004         if (!WhiteOnMove(forwardMostMove)) {
13005             DisplayError(_("Wait until your turn"), 0);
13006             return;
13007         }
13008         break;
13009       case EditPosition:
13010         EditPositionDone(TRUE);
13011         break;
13012       case TwoMachinesPlay:
13013         return;
13014       default:
13015         break;
13016     }
13017     SendToProgram("bk\n", &first);
13018     bookOutput[0] = NULLCHAR;
13019     bookRequested = TRUE;
13020 }
13021
13022 void
13023 AboutGameEvent()
13024 {
13025     char *tags = PGNTags(&gameInfo);
13026     TagsPopUp(tags, CmailMsg());
13027     free(tags);
13028 }
13029
13030 /* end button procedures */
13031
13032 void
13033 PrintPosition(fp, move)
13034      FILE *fp;
13035      int move;
13036 {
13037     int i, j;
13038     
13039     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13040         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13041             char c = PieceToChar(boards[move][i][j]);
13042             fputc(c == 'x' ? '.' : c, fp);
13043             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13044         }
13045     }
13046     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13047       fprintf(fp, "white to play\n");
13048     else
13049       fprintf(fp, "black to play\n");
13050 }
13051
13052 void
13053 PrintOpponents(fp)
13054      FILE *fp;
13055 {
13056     if (gameInfo.white != NULL) {
13057         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13058     } else {
13059         fprintf(fp, "\n");
13060     }
13061 }
13062
13063 /* Find last component of program's own name, using some heuristics */
13064 void
13065 TidyProgramName(prog, host, buf)
13066      char *prog, *host, buf[MSG_SIZ];
13067 {
13068     char *p, *q;
13069     int local = (strcmp(host, "localhost") == 0);
13070     while (!local && (p = strchr(prog, ';')) != NULL) {
13071         p++;
13072         while (*p == ' ') p++;
13073         prog = p;
13074     }
13075     if (*prog == '"' || *prog == '\'') {
13076         q = strchr(prog + 1, *prog);
13077     } else {
13078         q = strchr(prog, ' ');
13079     }
13080     if (q == NULL) q = prog + strlen(prog);
13081     p = q;
13082     while (p >= prog && *p != '/' && *p != '\\') p--;
13083     p++;
13084     if(p == prog && *p == '"') p++;
13085     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13086     memcpy(buf, p, q - p);
13087     buf[q - p] = NULLCHAR;
13088     if (!local) {
13089         strcat(buf, "@");
13090         strcat(buf, host);
13091     }
13092 }
13093
13094 char *
13095 TimeControlTagValue()
13096 {
13097     char buf[MSG_SIZ];
13098     if (!appData.clockMode) {
13099         strcpy(buf, "-");
13100     } else if (movesPerSession > 0) {
13101         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13102     } else if (timeIncrement == 0) {
13103         sprintf(buf, "%ld", timeControl/1000);
13104     } else {
13105         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13106     }
13107     return StrSave(buf);
13108 }
13109
13110 void
13111 SetGameInfo()
13112 {
13113     /* This routine is used only for certain modes */
13114     VariantClass v = gameInfo.variant;
13115     ChessMove r = GameUnfinished;
13116     char *p = NULL;
13117
13118     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13119         r = gameInfo.result; 
13120         p = gameInfo.resultDetails; 
13121         gameInfo.resultDetails = NULL;
13122     }
13123     ClearGameInfo(&gameInfo);
13124     gameInfo.variant = v;
13125
13126     switch (gameMode) {
13127       case MachinePlaysWhite:
13128         gameInfo.event = StrSave( appData.pgnEventHeader );
13129         gameInfo.site = StrSave(HostName());
13130         gameInfo.date = PGNDate();
13131         gameInfo.round = StrSave("-");
13132         gameInfo.white = StrSave(first.tidy);
13133         gameInfo.black = StrSave(UserName());
13134         gameInfo.timeControl = TimeControlTagValue();
13135         break;
13136
13137       case MachinePlaysBlack:
13138         gameInfo.event = StrSave( appData.pgnEventHeader );
13139         gameInfo.site = StrSave(HostName());
13140         gameInfo.date = PGNDate();
13141         gameInfo.round = StrSave("-");
13142         gameInfo.white = StrSave(UserName());
13143         gameInfo.black = StrSave(first.tidy);
13144         gameInfo.timeControl = TimeControlTagValue();
13145         break;
13146
13147       case TwoMachinesPlay:
13148         gameInfo.event = StrSave( appData.pgnEventHeader );
13149         gameInfo.site = StrSave(HostName());
13150         gameInfo.date = PGNDate();
13151         if (matchGame > 0) {
13152             char buf[MSG_SIZ];
13153             sprintf(buf, "%d", matchGame);
13154             gameInfo.round = StrSave(buf);
13155         } else {
13156             gameInfo.round = StrSave("-");
13157         }
13158         if (first.twoMachinesColor[0] == 'w') {
13159             gameInfo.white = StrSave(first.tidy);
13160             gameInfo.black = StrSave(second.tidy);
13161         } else {
13162             gameInfo.white = StrSave(second.tidy);
13163             gameInfo.black = StrSave(first.tidy);
13164         }
13165         gameInfo.timeControl = TimeControlTagValue();
13166         break;
13167
13168       case EditGame:
13169         gameInfo.event = StrSave("Edited game");
13170         gameInfo.site = StrSave(HostName());
13171         gameInfo.date = PGNDate();
13172         gameInfo.round = StrSave("-");
13173         gameInfo.white = StrSave("-");
13174         gameInfo.black = StrSave("-");
13175         gameInfo.result = r;
13176         gameInfo.resultDetails = p;
13177         break;
13178
13179       case EditPosition:
13180         gameInfo.event = StrSave("Edited position");
13181         gameInfo.site = StrSave(HostName());
13182         gameInfo.date = PGNDate();
13183         gameInfo.round = StrSave("-");
13184         gameInfo.white = StrSave("-");
13185         gameInfo.black = StrSave("-");
13186         break;
13187
13188       case IcsPlayingWhite:
13189       case IcsPlayingBlack:
13190       case IcsObserving:
13191       case IcsExamining:
13192         break;
13193
13194       case PlayFromGameFile:
13195         gameInfo.event = StrSave("Game from non-PGN file");
13196         gameInfo.site = StrSave(HostName());
13197         gameInfo.date = PGNDate();
13198         gameInfo.round = StrSave("-");
13199         gameInfo.white = StrSave("?");
13200         gameInfo.black = StrSave("?");
13201         break;
13202
13203       default:
13204         break;
13205     }
13206 }
13207
13208 void
13209 ReplaceComment(index, text)
13210      int index;
13211      char *text;
13212 {
13213     int len;
13214
13215     while (*text == '\n') text++;
13216     len = strlen(text);
13217     while (len > 0 && text[len - 1] == '\n') len--;
13218
13219     if (commentList[index] != NULL)
13220       free(commentList[index]);
13221
13222     if (len == 0) {
13223         commentList[index] = NULL;
13224         return;
13225     }
13226   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13227       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13228       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13229     commentList[index] = (char *) malloc(len + 2);
13230     strncpy(commentList[index], text, len);
13231     commentList[index][len] = '\n';
13232     commentList[index][len + 1] = NULLCHAR;
13233   } else { 
13234     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13235     char *p;
13236     commentList[index] = (char *) malloc(len + 6);
13237     strcpy(commentList[index], "{\n");
13238     strncpy(commentList[index]+2, text, len);
13239     commentList[index][len+2] = NULLCHAR;
13240     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13241     strcat(commentList[index], "\n}\n");
13242   }
13243 }
13244
13245 void
13246 CrushCRs(text)
13247      char *text;
13248 {
13249   char *p = text;
13250   char *q = text;
13251   char ch;
13252
13253   do {
13254     ch = *p++;
13255     if (ch == '\r') continue;
13256     *q++ = ch;
13257   } while (ch != '\0');
13258 }
13259
13260 void
13261 AppendComment(index, text, addBraces)
13262      int index;
13263      char *text;
13264      Boolean addBraces; // [HGM] braces: tells if we should add {}
13265 {
13266     int oldlen, len;
13267     char *old;
13268
13269 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13270     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13271
13272     CrushCRs(text);
13273     while (*text == '\n') text++;
13274     len = strlen(text);
13275     while (len > 0 && text[len - 1] == '\n') len--;
13276
13277     if (len == 0) return;
13278
13279     if (commentList[index] != NULL) {
13280         old = commentList[index];
13281         oldlen = strlen(old);
13282         while(commentList[index][oldlen-1] ==  '\n')
13283           commentList[index][--oldlen] = NULLCHAR;
13284         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13285         strcpy(commentList[index], old);
13286         free(old);
13287         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13288         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13289           if(addBraces) addBraces = FALSE; else { text++; len--; }
13290           while (*text == '\n') { text++; len--; }
13291           commentList[index][--oldlen] = NULLCHAR;
13292       }
13293         if(addBraces) strcat(commentList[index], "\n{\n");
13294         else          strcat(commentList[index], "\n");
13295         strcat(commentList[index], text);
13296         if(addBraces) strcat(commentList[index], "\n}\n");
13297         else          strcat(commentList[index], "\n");
13298     } else {
13299         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13300         if(addBraces)
13301              strcpy(commentList[index], "{\n");
13302         else commentList[index][0] = NULLCHAR;
13303         strcat(commentList[index], text);
13304         strcat(commentList[index], "\n");
13305         if(addBraces) strcat(commentList[index], "}\n");
13306     }
13307 }
13308
13309 static char * FindStr( char * text, char * sub_text )
13310 {
13311     char * result = strstr( text, sub_text );
13312
13313     if( result != NULL ) {
13314         result += strlen( sub_text );
13315     }
13316
13317     return result;
13318 }
13319
13320 /* [AS] Try to extract PV info from PGN comment */
13321 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13322 char *GetInfoFromComment( int index, char * text )
13323 {
13324     char * sep = text;
13325
13326     if( text != NULL && index > 0 ) {
13327         int score = 0;
13328         int depth = 0;
13329         int time = -1, sec = 0, deci;
13330         char * s_eval = FindStr( text, "[%eval " );
13331         char * s_emt = FindStr( text, "[%emt " );
13332
13333         if( s_eval != NULL || s_emt != NULL ) {
13334             /* New style */
13335             char delim;
13336
13337             if( s_eval != NULL ) {
13338                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13339                     return text;
13340                 }
13341
13342                 if( delim != ']' ) {
13343                     return text;
13344                 }
13345             }
13346
13347             if( s_emt != NULL ) {
13348             }
13349                 return text;
13350         }
13351         else {
13352             /* We expect something like: [+|-]nnn.nn/dd */
13353             int score_lo = 0;
13354
13355             if(*text != '{') return text; // [HGM] braces: must be normal comment
13356
13357             sep = strchr( text, '/' );
13358             if( sep == NULL || sep < (text+4) ) {
13359                 return text;
13360             }
13361
13362             time = -1; sec = -1; deci = -1;
13363             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13364                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13365                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13366                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13367                 return text;
13368             }
13369
13370             if( score_lo < 0 || score_lo >= 100 ) {
13371                 return text;
13372             }
13373
13374             if(sec >= 0) time = 600*time + 10*sec; else
13375             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13376
13377             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13378
13379             /* [HGM] PV time: now locate end of PV info */
13380             while( *++sep >= '0' && *sep <= '9'); // strip depth
13381             if(time >= 0)
13382             while( *++sep >= '0' && *sep <= '9'); // strip time
13383             if(sec >= 0)
13384             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13385             if(deci >= 0)
13386             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13387             while(*sep == ' ') sep++;
13388         }
13389
13390         if( depth <= 0 ) {
13391             return text;
13392         }
13393
13394         if( time < 0 ) {
13395             time = -1;
13396         }
13397
13398         pvInfoList[index-1].depth = depth;
13399         pvInfoList[index-1].score = score;
13400         pvInfoList[index-1].time  = 10*time; // centi-sec
13401         if(*sep == '}') *sep = 0; else *--sep = '{';
13402     }
13403     return sep;
13404 }
13405
13406 void
13407 SendToProgram(message, cps)
13408      char *message;
13409      ChessProgramState *cps;
13410 {
13411     int count, outCount, error;
13412     char buf[MSG_SIZ];
13413
13414     if (cps->pr == NULL) return;
13415     Attention(cps);
13416     
13417     if (appData.debugMode) {
13418         TimeMark now;
13419         GetTimeMark(&now);
13420         fprintf(debugFP, "%ld >%-6s: %s", 
13421                 SubtractTimeMarks(&now, &programStartTime),
13422                 cps->which, message);
13423     }
13424     
13425     count = strlen(message);
13426     outCount = OutputToProcess(cps->pr, message, count, &error);
13427     if (outCount < count && !exiting 
13428                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13429         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13430         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13431             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13432                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13433                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13434             } else {
13435                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13436             }
13437             gameInfo.resultDetails = StrSave(buf);
13438         }
13439         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13440     }
13441 }
13442
13443 void
13444 ReceiveFromProgram(isr, closure, message, count, error)
13445      InputSourceRef isr;
13446      VOIDSTAR closure;
13447      char *message;
13448      int count;
13449      int error;
13450 {
13451     char *end_str;
13452     char buf[MSG_SIZ];
13453     ChessProgramState *cps = (ChessProgramState *)closure;
13454
13455     if (isr != cps->isr) return; /* Killed intentionally */
13456     if (count <= 0) {
13457         if (count == 0) {
13458             sprintf(buf,
13459                     _("Error: %s chess program (%s) exited unexpectedly"),
13460                     cps->which, cps->program);
13461         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13462                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13463                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13464                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13465                 } else {
13466                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13467                 }
13468                 gameInfo.resultDetails = StrSave(buf);
13469             }
13470             RemoveInputSource(cps->isr);
13471             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13472         } else {
13473             sprintf(buf,
13474                     _("Error reading from %s chess program (%s)"),
13475                     cps->which, cps->program);
13476             RemoveInputSource(cps->isr);
13477
13478             /* [AS] Program is misbehaving badly... kill it */
13479             if( count == -2 ) {
13480                 DestroyChildProcess( cps->pr, 9 );
13481                 cps->pr = NoProc;
13482             }
13483
13484             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13485         }
13486         return;
13487     }
13488     
13489     if ((end_str = strchr(message, '\r')) != NULL)
13490       *end_str = NULLCHAR;
13491     if ((end_str = strchr(message, '\n')) != NULL)
13492       *end_str = NULLCHAR;
13493     
13494     if (appData.debugMode) {
13495         TimeMark now; int print = 1;
13496         char *quote = ""; char c; int i;
13497
13498         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13499                 char start = message[0];
13500                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13501                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13502                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13503                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13504                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13505                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13506                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13507                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13508                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13509                     print = (appData.engineComments >= 2);
13510                 }
13511                 message[0] = start; // restore original message
13512         }
13513         if(print) {
13514                 GetTimeMark(&now);
13515                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13516                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13517                         quote,
13518                         message);
13519         }
13520     }
13521
13522     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13523     if (appData.icsEngineAnalyze) {
13524         if (strstr(message, "whisper") != NULL ||
13525              strstr(message, "kibitz") != NULL || 
13526             strstr(message, "tellics") != NULL) return;
13527     }
13528
13529     HandleMachineMove(message, cps);
13530 }
13531
13532
13533 void
13534 SendTimeControl(cps, mps, tc, inc, sd, st)
13535      ChessProgramState *cps;
13536      int mps, inc, sd, st;
13537      long tc;
13538 {
13539     char buf[MSG_SIZ];
13540     int seconds;
13541
13542     if( timeControl_2 > 0 ) {
13543         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13544             tc = timeControl_2;
13545         }
13546     }
13547     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13548     inc /= cps->timeOdds;
13549     st  /= cps->timeOdds;
13550
13551     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13552
13553     if (st > 0) {
13554       /* Set exact time per move, normally using st command */
13555       if (cps->stKludge) {
13556         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13557         seconds = st % 60;
13558         if (seconds == 0) {
13559           sprintf(buf, "level 1 %d\n", st/60);
13560         } else {
13561           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13562         }
13563       } else {
13564         sprintf(buf, "st %d\n", st);
13565       }
13566     } else {
13567       /* Set conventional or incremental time control, using level command */
13568       if (seconds == 0) {
13569         /* Note old gnuchess bug -- minutes:seconds used to not work.
13570            Fixed in later versions, but still avoid :seconds
13571            when seconds is 0. */
13572         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13573       } else {
13574         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13575                 seconds, inc/1000);
13576       }
13577     }
13578     SendToProgram(buf, cps);
13579
13580     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13581     /* Orthogonally, limit search to given depth */
13582     if (sd > 0) {
13583       if (cps->sdKludge) {
13584         sprintf(buf, "depth\n%d\n", sd);
13585       } else {
13586         sprintf(buf, "sd %d\n", sd);
13587       }
13588       SendToProgram(buf, cps);
13589     }
13590
13591     if(cps->nps > 0) { /* [HGM] nps */
13592         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13593         else {
13594                 sprintf(buf, "nps %d\n", cps->nps);
13595               SendToProgram(buf, cps);
13596         }
13597     }
13598 }
13599
13600 ChessProgramState *WhitePlayer()
13601 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13602 {
13603     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13604        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13605         return &second;
13606     return &first;
13607 }
13608
13609 void
13610 SendTimeRemaining(cps, machineWhite)
13611      ChessProgramState *cps;
13612      int /*boolean*/ machineWhite;
13613 {
13614     char message[MSG_SIZ];
13615     long time, otime;
13616
13617     /* Note: this routine must be called when the clocks are stopped
13618        or when they have *just* been set or switched; otherwise
13619        it will be off by the time since the current tick started.
13620     */
13621     if (machineWhite) {
13622         time = whiteTimeRemaining / 10;
13623         otime = blackTimeRemaining / 10;
13624     } else {
13625         time = blackTimeRemaining / 10;
13626         otime = whiteTimeRemaining / 10;
13627     }
13628     /* [HGM] translate opponent's time by time-odds factor */
13629     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13630     if (appData.debugMode) {
13631         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13632     }
13633
13634     if (time <= 0) time = 1;
13635     if (otime <= 0) otime = 1;
13636     
13637     sprintf(message, "time %ld\n", time);
13638     SendToProgram(message, cps);
13639
13640     sprintf(message, "otim %ld\n", otime);
13641     SendToProgram(message, cps);
13642 }
13643
13644 int
13645 BoolFeature(p, name, loc, cps)
13646      char **p;
13647      char *name;
13648      int *loc;
13649      ChessProgramState *cps;
13650 {
13651   char buf[MSG_SIZ];
13652   int len = strlen(name);
13653   int val;
13654   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13655     (*p) += len + 1;
13656     sscanf(*p, "%d", &val);
13657     *loc = (val != 0);
13658     while (**p && **p != ' ') (*p)++;
13659     sprintf(buf, "accepted %s\n", name);
13660     SendToProgram(buf, cps);
13661     return TRUE;
13662   }
13663   return FALSE;
13664 }
13665
13666 int
13667 IntFeature(p, name, loc, cps)
13668      char **p;
13669      char *name;
13670      int *loc;
13671      ChessProgramState *cps;
13672 {
13673   char buf[MSG_SIZ];
13674   int len = strlen(name);
13675   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13676     (*p) += len + 1;
13677     sscanf(*p, "%d", loc);
13678     while (**p && **p != ' ') (*p)++;
13679     sprintf(buf, "accepted %s\n", name);
13680     SendToProgram(buf, cps);
13681     return TRUE;
13682   }
13683   return FALSE;
13684 }
13685
13686 int
13687 StringFeature(p, name, loc, cps)
13688      char **p;
13689      char *name;
13690      char loc[];
13691      ChessProgramState *cps;
13692 {
13693   char buf[MSG_SIZ];
13694   int len = strlen(name);
13695   if (strncmp((*p), name, len) == 0
13696       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13697     (*p) += len + 2;
13698     sscanf(*p, "%[^\"]", loc);
13699     while (**p && **p != '\"') (*p)++;
13700     if (**p == '\"') (*p)++;
13701     sprintf(buf, "accepted %s\n", name);
13702     SendToProgram(buf, cps);
13703     return TRUE;
13704   }
13705   return FALSE;
13706 }
13707
13708 int 
13709 ParseOption(Option *opt, ChessProgramState *cps)
13710 // [HGM] options: process the string that defines an engine option, and determine
13711 // name, type, default value, and allowed value range
13712 {
13713         char *p, *q, buf[MSG_SIZ];
13714         int n, min = (-1)<<31, max = 1<<31, def;
13715
13716         if(p = strstr(opt->name, " -spin ")) {
13717             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13718             if(max < min) max = min; // enforce consistency
13719             if(def < min) def = min;
13720             if(def > max) def = max;
13721             opt->value = def;
13722             opt->min = min;
13723             opt->max = max;
13724             opt->type = Spin;
13725         } else if((p = strstr(opt->name, " -slider "))) {
13726             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13727             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13728             if(max < min) max = min; // enforce consistency
13729             if(def < min) def = min;
13730             if(def > max) def = max;
13731             opt->value = def;
13732             opt->min = min;
13733             opt->max = max;
13734             opt->type = Spin; // Slider;
13735         } else if((p = strstr(opt->name, " -string "))) {
13736             opt->textValue = p+9;
13737             opt->type = TextBox;
13738         } else if((p = strstr(opt->name, " -file "))) {
13739             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13740             opt->textValue = p+7;
13741             opt->type = TextBox; // FileName;
13742         } else if((p = strstr(opt->name, " -path "))) {
13743             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13744             opt->textValue = p+7;
13745             opt->type = TextBox; // PathName;
13746         } else if(p = strstr(opt->name, " -check ")) {
13747             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13748             opt->value = (def != 0);
13749             opt->type = CheckBox;
13750         } else if(p = strstr(opt->name, " -combo ")) {
13751             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13752             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13753             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13754             opt->value = n = 0;
13755             while(q = StrStr(q, " /// ")) {
13756                 n++; *q = 0;    // count choices, and null-terminate each of them
13757                 q += 5;
13758                 if(*q == '*') { // remember default, which is marked with * prefix
13759                     q++;
13760                     opt->value = n;
13761                 }
13762                 cps->comboList[cps->comboCnt++] = q;
13763             }
13764             cps->comboList[cps->comboCnt++] = NULL;
13765             opt->max = n + 1;
13766             opt->type = ComboBox;
13767         } else if(p = strstr(opt->name, " -button")) {
13768             opt->type = Button;
13769         } else if(p = strstr(opt->name, " -save")) {
13770             opt->type = SaveButton;
13771         } else return FALSE;
13772         *p = 0; // terminate option name
13773         // now look if the command-line options define a setting for this engine option.
13774         if(cps->optionSettings && cps->optionSettings[0])
13775             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13776         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13777                 sprintf(buf, "option %s", p);
13778                 if(p = strstr(buf, ",")) *p = 0;
13779                 strcat(buf, "\n");
13780                 SendToProgram(buf, cps);
13781         }
13782         return TRUE;
13783 }
13784
13785 void
13786 FeatureDone(cps, val)
13787      ChessProgramState* cps;
13788      int val;
13789 {
13790   DelayedEventCallback cb = GetDelayedEvent();
13791   if ((cb == InitBackEnd3 && cps == &first) ||
13792       (cb == TwoMachinesEventIfReady && cps == &second)) {
13793     CancelDelayedEvent();
13794     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13795   }
13796   cps->initDone = val;
13797 }
13798
13799 /* Parse feature command from engine */
13800 void
13801 ParseFeatures(args, cps)
13802      char* args;
13803      ChessProgramState *cps;  
13804 {
13805   char *p = args;
13806   char *q;
13807   int val;
13808   char buf[MSG_SIZ];
13809
13810   for (;;) {
13811     while (*p == ' ') p++;
13812     if (*p == NULLCHAR) return;
13813
13814     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13815     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13816     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13817     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13818     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13819     if (BoolFeature(&p, "reuse", &val, cps)) {
13820       /* Engine can disable reuse, but can't enable it if user said no */
13821       if (!val) cps->reuse = FALSE;
13822       continue;
13823     }
13824     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13825     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13826       if (gameMode == TwoMachinesPlay) {
13827         DisplayTwoMachinesTitle();
13828       } else {
13829         DisplayTitle("");
13830       }
13831       continue;
13832     }
13833     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13834     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13835     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13836     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13837     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13838     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13839     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13840     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13841     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13842     if (IntFeature(&p, "done", &val, cps)) {
13843       FeatureDone(cps, val);
13844       continue;
13845     }
13846     /* Added by Tord: */
13847     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13848     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13849     /* End of additions by Tord */
13850
13851     /* [HGM] added features: */
13852     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13853     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13854     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13855     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13856     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13857     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13858     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13859         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13860             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13861             SendToProgram(buf, cps);
13862             continue;
13863         }
13864         if(cps->nrOptions >= MAX_OPTIONS) {
13865             cps->nrOptions--;
13866             sprintf(buf, "%s engine has too many options\n", cps->which);
13867             DisplayError(buf, 0);
13868         }
13869         continue;
13870     }
13871     /* End of additions by HGM */
13872
13873     /* unknown feature: complain and skip */
13874     q = p;
13875     while (*q && *q != '=') q++;
13876     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13877     SendToProgram(buf, cps);
13878     p = q;
13879     if (*p == '=') {
13880       p++;
13881       if (*p == '\"') {
13882         p++;
13883         while (*p && *p != '\"') p++;
13884         if (*p == '\"') p++;
13885       } else {
13886         while (*p && *p != ' ') p++;
13887       }
13888     }
13889   }
13890
13891 }
13892
13893 void
13894 PeriodicUpdatesEvent(newState)
13895      int newState;
13896 {
13897     if (newState == appData.periodicUpdates)
13898       return;
13899
13900     appData.periodicUpdates=newState;
13901
13902     /* Display type changes, so update it now */
13903 //    DisplayAnalysis();
13904
13905     /* Get the ball rolling again... */
13906     if (newState) {
13907         AnalysisPeriodicEvent(1);
13908         StartAnalysisClock();
13909     }
13910 }
13911
13912 void
13913 PonderNextMoveEvent(newState)
13914      int newState;
13915 {
13916     if (newState == appData.ponderNextMove) return;
13917     if (gameMode == EditPosition) EditPositionDone(TRUE);
13918     if (newState) {
13919         SendToProgram("hard\n", &first);
13920         if (gameMode == TwoMachinesPlay) {
13921             SendToProgram("hard\n", &second);
13922         }
13923     } else {
13924         SendToProgram("easy\n", &first);
13925         thinkOutput[0] = NULLCHAR;
13926         if (gameMode == TwoMachinesPlay) {
13927             SendToProgram("easy\n", &second);
13928         }
13929     }
13930     appData.ponderNextMove = newState;
13931 }
13932
13933 void
13934 NewSettingEvent(option, feature, command, value)
13935      char *command;
13936      int option, value, *feature;
13937 {
13938     char buf[MSG_SIZ];
13939
13940     if (gameMode == EditPosition) EditPositionDone(TRUE);
13941     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13942     if(feature == NULL || *feature) SendToProgram(buf, &first);
13943     if (gameMode == TwoMachinesPlay) {
13944         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13945     }
13946 }
13947
13948 void
13949 ShowThinkingEvent()
13950 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13951 {
13952     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13953     int newState = appData.showThinking
13954         // [HGM] thinking: other features now need thinking output as well
13955         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13956     
13957     if (oldState == newState) return;
13958     oldState = newState;
13959     if (gameMode == EditPosition) EditPositionDone(TRUE);
13960     if (oldState) {
13961         SendToProgram("post\n", &first);
13962         if (gameMode == TwoMachinesPlay) {
13963             SendToProgram("post\n", &second);
13964         }
13965     } else {
13966         SendToProgram("nopost\n", &first);
13967         thinkOutput[0] = NULLCHAR;
13968         if (gameMode == TwoMachinesPlay) {
13969             SendToProgram("nopost\n", &second);
13970         }
13971     }
13972 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13973 }
13974
13975 void
13976 AskQuestionEvent(title, question, replyPrefix, which)
13977      char *title; char *question; char *replyPrefix; char *which;
13978 {
13979   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13980   if (pr == NoProc) return;
13981   AskQuestion(title, question, replyPrefix, pr);
13982 }
13983
13984 void
13985 DisplayMove(moveNumber)
13986      int moveNumber;
13987 {
13988     char message[MSG_SIZ];
13989     char res[MSG_SIZ];
13990     char cpThinkOutput[MSG_SIZ];
13991
13992     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13993     
13994     if (moveNumber == forwardMostMove - 1 || 
13995         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13996
13997         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13998
13999         if (strchr(cpThinkOutput, '\n')) {
14000             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14001         }
14002     } else {
14003         *cpThinkOutput = NULLCHAR;
14004     }
14005
14006     /* [AS] Hide thinking from human user */
14007     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14008         *cpThinkOutput = NULLCHAR;
14009         if( thinkOutput[0] != NULLCHAR ) {
14010             int i;
14011
14012             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14013                 cpThinkOutput[i] = '.';
14014             }
14015             cpThinkOutput[i] = NULLCHAR;
14016             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14017         }
14018     }
14019
14020     if (moveNumber == forwardMostMove - 1 &&
14021         gameInfo.resultDetails != NULL) {
14022         if (gameInfo.resultDetails[0] == NULLCHAR) {
14023             sprintf(res, " %s", PGNResult(gameInfo.result));
14024         } else {
14025             sprintf(res, " {%s} %s",
14026                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14027         }
14028     } else {
14029         res[0] = NULLCHAR;
14030     }
14031
14032     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14033         DisplayMessage(res, cpThinkOutput);
14034     } else {
14035         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
14036                 WhiteOnMove(moveNumber) ? " " : ".. ",
14037                 parseList[moveNumber], res);
14038         DisplayMessage(message, cpThinkOutput);
14039     }
14040 }
14041
14042 void
14043 DisplayComment(moveNumber, text)
14044      int moveNumber;
14045      char *text;
14046 {
14047     char title[MSG_SIZ];
14048     char buf[8000]; // comment can be long!
14049     int score, depth;
14050     
14051     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14052       strcpy(title, "Comment");
14053     } else {
14054       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14055               WhiteOnMove(moveNumber) ? " " : ".. ",
14056               parseList[moveNumber]);
14057     }
14058     // [HGM] PV info: display PV info together with (or as) comment
14059     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14060       if(text == NULL) text = "";                                           
14061       score = pvInfoList[moveNumber].score;
14062       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14063               depth, (pvInfoList[moveNumber].time+50)/100, text);
14064       text = buf;
14065     }
14066     if (text != NULL && (appData.autoDisplayComment || commentUp))
14067         CommentPopUp(title, text);
14068 }
14069
14070 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14071  * might be busy thinking or pondering.  It can be omitted if your
14072  * gnuchess is configured to stop thinking immediately on any user
14073  * input.  However, that gnuchess feature depends on the FIONREAD
14074  * ioctl, which does not work properly on some flavors of Unix.
14075  */
14076 void
14077 Attention(cps)
14078      ChessProgramState *cps;
14079 {
14080 #if ATTENTION
14081     if (!cps->useSigint) return;
14082     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14083     switch (gameMode) {
14084       case MachinePlaysWhite:
14085       case MachinePlaysBlack:
14086       case TwoMachinesPlay:
14087       case IcsPlayingWhite:
14088       case IcsPlayingBlack:
14089       case AnalyzeMode:
14090       case AnalyzeFile:
14091         /* Skip if we know it isn't thinking */
14092         if (!cps->maybeThinking) return;
14093         if (appData.debugMode)
14094           fprintf(debugFP, "Interrupting %s\n", cps->which);
14095         InterruptChildProcess(cps->pr);
14096         cps->maybeThinking = FALSE;
14097         break;
14098       default:
14099         break;
14100     }
14101 #endif /*ATTENTION*/
14102 }
14103
14104 int
14105 CheckFlags()
14106 {
14107     if (whiteTimeRemaining <= 0) {
14108         if (!whiteFlag) {
14109             whiteFlag = TRUE;
14110             if (appData.icsActive) {
14111                 if (appData.autoCallFlag &&
14112                     gameMode == IcsPlayingBlack && !blackFlag) {
14113                   SendToICS(ics_prefix);
14114                   SendToICS("flag\n");
14115                 }
14116             } else {
14117                 if (blackFlag) {
14118                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14119                 } else {
14120                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14121                     if (appData.autoCallFlag) {
14122                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14123                         return TRUE;
14124                     }
14125                 }
14126             }
14127         }
14128     }
14129     if (blackTimeRemaining <= 0) {
14130         if (!blackFlag) {
14131             blackFlag = TRUE;
14132             if (appData.icsActive) {
14133                 if (appData.autoCallFlag &&
14134                     gameMode == IcsPlayingWhite && !whiteFlag) {
14135                   SendToICS(ics_prefix);
14136                   SendToICS("flag\n");
14137                 }
14138             } else {
14139                 if (whiteFlag) {
14140                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14141                 } else {
14142                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14143                     if (appData.autoCallFlag) {
14144                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14145                         return TRUE;
14146                     }
14147                 }
14148             }
14149         }
14150     }
14151     return FALSE;
14152 }
14153
14154 void
14155 CheckTimeControl()
14156 {
14157     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14158         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14159
14160     /*
14161      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14162      */
14163     if ( !WhiteOnMove(forwardMostMove) )
14164         /* White made time control */
14165         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14166         /* [HGM] time odds: correct new time quota for time odds! */
14167                                             / WhitePlayer()->timeOdds;
14168       else
14169         /* Black made time control */
14170         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14171                                             / WhitePlayer()->other->timeOdds;
14172 }
14173
14174 void
14175 DisplayBothClocks()
14176 {
14177     int wom = gameMode == EditPosition ?
14178       !blackPlaysFirst : WhiteOnMove(currentMove);
14179     DisplayWhiteClock(whiteTimeRemaining, wom);
14180     DisplayBlackClock(blackTimeRemaining, !wom);
14181 }
14182
14183
14184 /* Timekeeping seems to be a portability nightmare.  I think everyone
14185    has ftime(), but I'm really not sure, so I'm including some ifdefs
14186    to use other calls if you don't.  Clocks will be less accurate if
14187    you have neither ftime nor gettimeofday.
14188 */
14189
14190 /* VS 2008 requires the #include outside of the function */
14191 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14192 #include <sys/timeb.h>
14193 #endif
14194
14195 /* Get the current time as a TimeMark */
14196 void
14197 GetTimeMark(tm)
14198      TimeMark *tm;
14199 {
14200 #if HAVE_GETTIMEOFDAY
14201
14202     struct timeval timeVal;
14203     struct timezone timeZone;
14204
14205     gettimeofday(&timeVal, &timeZone);
14206     tm->sec = (long) timeVal.tv_sec; 
14207     tm->ms = (int) (timeVal.tv_usec / 1000L);
14208
14209 #else /*!HAVE_GETTIMEOFDAY*/
14210 #if HAVE_FTIME
14211
14212 // include <sys/timeb.h> / moved to just above start of function
14213     struct timeb timeB;
14214
14215     ftime(&timeB);
14216     tm->sec = (long) timeB.time;
14217     tm->ms = (int) timeB.millitm;
14218
14219 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14220     tm->sec = (long) time(NULL);
14221     tm->ms = 0;
14222 #endif
14223 #endif
14224 }
14225
14226 /* Return the difference in milliseconds between two
14227    time marks.  We assume the difference will fit in a long!
14228 */
14229 long
14230 SubtractTimeMarks(tm2, tm1)
14231      TimeMark *tm2, *tm1;
14232 {
14233     return 1000L*(tm2->sec - tm1->sec) +
14234            (long) (tm2->ms - tm1->ms);
14235 }
14236
14237
14238 /*
14239  * Code to manage the game clocks.
14240  *
14241  * In tournament play, black starts the clock and then white makes a move.
14242  * We give the human user a slight advantage if he is playing white---the
14243  * clocks don't run until he makes his first move, so it takes zero time.
14244  * Also, we don't account for network lag, so we could get out of sync
14245  * with GNU Chess's clock -- but then, referees are always right.  
14246  */
14247
14248 static TimeMark tickStartTM;
14249 static long intendedTickLength;
14250
14251 long
14252 NextTickLength(timeRemaining)
14253      long timeRemaining;
14254 {
14255     long nominalTickLength, nextTickLength;
14256
14257     if (timeRemaining > 0L && timeRemaining <= 10000L)
14258       nominalTickLength = 100L;
14259     else
14260       nominalTickLength = 1000L;
14261     nextTickLength = timeRemaining % nominalTickLength;
14262     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14263
14264     return nextTickLength;
14265 }
14266
14267 /* Adjust clock one minute up or down */
14268 void
14269 AdjustClock(Boolean which, int dir)
14270 {
14271     if(which) blackTimeRemaining += 60000*dir;
14272     else      whiteTimeRemaining += 60000*dir;
14273     DisplayBothClocks();
14274 }
14275
14276 /* Stop clocks and reset to a fresh time control */
14277 void
14278 ResetClocks() 
14279 {
14280     (void) StopClockTimer();
14281     if (appData.icsActive) {
14282         whiteTimeRemaining = blackTimeRemaining = 0;
14283     } else if (searchTime) {
14284         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14285         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14286     } else { /* [HGM] correct new time quote for time odds */
14287         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14288         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14289     }
14290     if (whiteFlag || blackFlag) {
14291         DisplayTitle("");
14292         whiteFlag = blackFlag = FALSE;
14293     }
14294     DisplayBothClocks();
14295 }
14296
14297 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14298
14299 /* Decrement running clock by amount of time that has passed */
14300 void
14301 DecrementClocks()
14302 {
14303     long timeRemaining;
14304     long lastTickLength, fudge;
14305     TimeMark now;
14306
14307     if (!appData.clockMode) return;
14308     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14309         
14310     GetTimeMark(&now);
14311
14312     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14313
14314     /* Fudge if we woke up a little too soon */
14315     fudge = intendedTickLength - lastTickLength;
14316     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14317
14318     if (WhiteOnMove(forwardMostMove)) {
14319         if(whiteNPS >= 0) lastTickLength = 0;
14320         timeRemaining = whiteTimeRemaining -= lastTickLength;
14321         DisplayWhiteClock(whiteTimeRemaining - fudge,
14322                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14323     } else {
14324         if(blackNPS >= 0) lastTickLength = 0;
14325         timeRemaining = blackTimeRemaining -= lastTickLength;
14326         DisplayBlackClock(blackTimeRemaining - fudge,
14327                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14328     }
14329
14330     if (CheckFlags()) return;
14331         
14332     tickStartTM = now;
14333     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14334     StartClockTimer(intendedTickLength);
14335
14336     /* if the time remaining has fallen below the alarm threshold, sound the
14337      * alarm. if the alarm has sounded and (due to a takeback or time control
14338      * with increment) the time remaining has increased to a level above the
14339      * threshold, reset the alarm so it can sound again. 
14340      */
14341     
14342     if (appData.icsActive && appData.icsAlarm) {
14343
14344         /* make sure we are dealing with the user's clock */
14345         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14346                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14347            )) return;
14348
14349         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14350             alarmSounded = FALSE;
14351         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14352             PlayAlarmSound();
14353             alarmSounded = TRUE;
14354         }
14355     }
14356 }
14357
14358
14359 /* A player has just moved, so stop the previously running
14360    clock and (if in clock mode) start the other one.
14361    We redisplay both clocks in case we're in ICS mode, because
14362    ICS gives us an update to both clocks after every move.
14363    Note that this routine is called *after* forwardMostMove
14364    is updated, so the last fractional tick must be subtracted
14365    from the color that is *not* on move now.
14366 */
14367 void
14368 SwitchClocks(int newMoveNr)
14369 {
14370     long lastTickLength;
14371     TimeMark now;
14372     int flagged = FALSE;
14373
14374     GetTimeMark(&now);
14375
14376     if (StopClockTimer() && appData.clockMode) {
14377         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14378         if (!WhiteOnMove(forwardMostMove)) {
14379             if(blackNPS >= 0) lastTickLength = 0;
14380             blackTimeRemaining -= lastTickLength;
14381            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14382 //         if(pvInfoList[forwardMostMove-1].time == -1)
14383                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14384                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14385         } else {
14386            if(whiteNPS >= 0) lastTickLength = 0;
14387            whiteTimeRemaining -= lastTickLength;
14388            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14389 //         if(pvInfoList[forwardMostMove-1].time == -1)
14390                  pvInfoList[forwardMostMove-1].time = 
14391                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14392         }
14393         flagged = CheckFlags();
14394     }
14395     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14396     CheckTimeControl();
14397
14398     if (flagged || !appData.clockMode) return;
14399
14400     switch (gameMode) {
14401       case MachinePlaysBlack:
14402       case MachinePlaysWhite:
14403       case BeginningOfGame:
14404         if (pausing) return;
14405         break;
14406
14407       case EditGame:
14408       case PlayFromGameFile:
14409       case IcsExamining:
14410         return;
14411
14412       default:
14413         break;
14414     }
14415
14416     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14417         if(WhiteOnMove(forwardMostMove))
14418              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14419         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14420     }
14421
14422     tickStartTM = now;
14423     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14424       whiteTimeRemaining : blackTimeRemaining);
14425     StartClockTimer(intendedTickLength);
14426 }
14427         
14428
14429 /* Stop both clocks */
14430 void
14431 StopClocks()
14432 {       
14433     long lastTickLength;
14434     TimeMark now;
14435
14436     if (!StopClockTimer()) return;
14437     if (!appData.clockMode) return;
14438
14439     GetTimeMark(&now);
14440
14441     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14442     if (WhiteOnMove(forwardMostMove)) {
14443         if(whiteNPS >= 0) lastTickLength = 0;
14444         whiteTimeRemaining -= lastTickLength;
14445         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14446     } else {
14447         if(blackNPS >= 0) lastTickLength = 0;
14448         blackTimeRemaining -= lastTickLength;
14449         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14450     }
14451     CheckFlags();
14452 }
14453         
14454 /* Start clock of player on move.  Time may have been reset, so
14455    if clock is already running, stop and restart it. */
14456 void
14457 StartClocks()
14458 {
14459     (void) StopClockTimer(); /* in case it was running already */
14460     DisplayBothClocks();
14461     if (CheckFlags()) return;
14462
14463     if (!appData.clockMode) return;
14464     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14465
14466     GetTimeMark(&tickStartTM);
14467     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14468       whiteTimeRemaining : blackTimeRemaining);
14469
14470    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14471     whiteNPS = blackNPS = -1; 
14472     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14473        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14474         whiteNPS = first.nps;
14475     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14476        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14477         blackNPS = first.nps;
14478     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14479         whiteNPS = second.nps;
14480     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14481         blackNPS = second.nps;
14482     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14483
14484     StartClockTimer(intendedTickLength);
14485 }
14486
14487 char *
14488 TimeString(ms)
14489      long ms;
14490 {
14491     long second, minute, hour, day;
14492     char *sign = "";
14493     static char buf[32];
14494     
14495     if (ms > 0 && ms <= 9900) {
14496       /* convert milliseconds to tenths, rounding up */
14497       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14498
14499       sprintf(buf, " %03.1f ", tenths/10.0);
14500       return buf;
14501     }
14502
14503     /* convert milliseconds to seconds, rounding up */
14504     /* use floating point to avoid strangeness of integer division
14505        with negative dividends on many machines */
14506     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14507
14508     if (second < 0) {
14509         sign = "-";
14510         second = -second;
14511     }
14512     
14513     day = second / (60 * 60 * 24);
14514     second = second % (60 * 60 * 24);
14515     hour = second / (60 * 60);
14516     second = second % (60 * 60);
14517     minute = second / 60;
14518     second = second % 60;
14519     
14520     if (day > 0)
14521       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14522               sign, day, hour, minute, second);
14523     else if (hour > 0)
14524       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14525     else
14526       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14527     
14528     return buf;
14529 }
14530
14531
14532 /*
14533  * This is necessary because some C libraries aren't ANSI C compliant yet.
14534  */
14535 char *
14536 StrStr(string, match)
14537      char *string, *match;
14538 {
14539     int i, length;
14540     
14541     length = strlen(match);
14542     
14543     for (i = strlen(string) - length; i >= 0; i--, string++)
14544       if (!strncmp(match, string, length))
14545         return string;
14546     
14547     return NULL;
14548 }
14549
14550 char *
14551 StrCaseStr(string, match)
14552      char *string, *match;
14553 {
14554     int i, j, length;
14555     
14556     length = strlen(match);
14557     
14558     for (i = strlen(string) - length; i >= 0; i--, string++) {
14559         for (j = 0; j < length; j++) {
14560             if (ToLower(match[j]) != ToLower(string[j]))
14561               break;
14562         }
14563         if (j == length) return string;
14564     }
14565
14566     return NULL;
14567 }
14568
14569 #ifndef _amigados
14570 int
14571 StrCaseCmp(s1, s2)
14572      char *s1, *s2;
14573 {
14574     char c1, c2;
14575     
14576     for (;;) {
14577         c1 = ToLower(*s1++);
14578         c2 = ToLower(*s2++);
14579         if (c1 > c2) return 1;
14580         if (c1 < c2) return -1;
14581         if (c1 == NULLCHAR) return 0;
14582     }
14583 }
14584
14585
14586 int
14587 ToLower(c)
14588      int c;
14589 {
14590     return isupper(c) ? tolower(c) : c;
14591 }
14592
14593
14594 int
14595 ToUpper(c)
14596      int c;
14597 {
14598     return islower(c) ? toupper(c) : c;
14599 }
14600 #endif /* !_amigados    */
14601
14602 char *
14603 StrSave(s)
14604      char *s;
14605 {
14606     char *ret;
14607
14608     if ((ret = (char *) malloc(strlen(s) + 1))) {
14609         strcpy(ret, s);
14610     }
14611     return ret;
14612 }
14613
14614 char *
14615 StrSavePtr(s, savePtr)
14616      char *s, **savePtr;
14617 {
14618     if (*savePtr) {
14619         free(*savePtr);
14620     }
14621     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14622         strcpy(*savePtr, s);
14623     }
14624     return(*savePtr);
14625 }
14626
14627 char *
14628 PGNDate()
14629 {
14630     time_t clock;
14631     struct tm *tm;
14632     char buf[MSG_SIZ];
14633
14634     clock = time((time_t *)NULL);
14635     tm = localtime(&clock);
14636     sprintf(buf, "%04d.%02d.%02d",
14637             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14638     return StrSave(buf);
14639 }
14640
14641
14642 char *
14643 PositionToFEN(move, overrideCastling)
14644      int move;
14645      char *overrideCastling;
14646 {
14647     int i, j, fromX, fromY, toX, toY;
14648     int whiteToPlay;
14649     char buf[128];
14650     char *p, *q;
14651     int emptycount;
14652     ChessSquare piece;
14653
14654     whiteToPlay = (gameMode == EditPosition) ?
14655       !blackPlaysFirst : (move % 2 == 0);
14656     p = buf;
14657
14658     /* Piece placement data */
14659     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14660         emptycount = 0;
14661         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14662             if (boards[move][i][j] == EmptySquare) {
14663                 emptycount++;
14664             } else { ChessSquare piece = boards[move][i][j];
14665                 if (emptycount > 0) {
14666                     if(emptycount<10) /* [HGM] can be >= 10 */
14667                         *p++ = '0' + emptycount;
14668                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14669                     emptycount = 0;
14670                 }
14671                 if(PieceToChar(piece) == '+') {
14672                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14673                     *p++ = '+';
14674                     piece = (ChessSquare)(DEMOTED piece);
14675                 } 
14676                 *p++ = PieceToChar(piece);
14677                 if(p[-1] == '~') {
14678                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14679                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14680                     *p++ = '~';
14681                 }
14682             }
14683         }
14684         if (emptycount > 0) {
14685             if(emptycount<10) /* [HGM] can be >= 10 */
14686                 *p++ = '0' + emptycount;
14687             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14688             emptycount = 0;
14689         }
14690         *p++ = '/';
14691     }
14692     *(p - 1) = ' ';
14693
14694     /* [HGM] print Crazyhouse or Shogi holdings */
14695     if( gameInfo.holdingsWidth ) {
14696         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14697         q = p;
14698         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14699             piece = boards[move][i][BOARD_WIDTH-1];
14700             if( piece != EmptySquare )
14701               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14702                   *p++ = PieceToChar(piece);
14703         }
14704         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14705             piece = boards[move][BOARD_HEIGHT-i-1][0];
14706             if( piece != EmptySquare )
14707               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14708                   *p++ = PieceToChar(piece);
14709         }
14710
14711         if( q == p ) *p++ = '-';
14712         *p++ = ']';
14713         *p++ = ' ';
14714     }
14715
14716     /* Active color */
14717     *p++ = whiteToPlay ? 'w' : 'b';
14718     *p++ = ' ';
14719
14720   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14721     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14722   } else {
14723   if(nrCastlingRights) {
14724      q = p;
14725      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14726        /* [HGM] write directly from rights */
14727            if(boards[move][CASTLING][2] != NoRights &&
14728               boards[move][CASTLING][0] != NoRights   )
14729                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14730            if(boards[move][CASTLING][2] != NoRights &&
14731               boards[move][CASTLING][1] != NoRights   )
14732                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14733            if(boards[move][CASTLING][5] != NoRights &&
14734               boards[move][CASTLING][3] != NoRights   )
14735                 *p++ = boards[move][CASTLING][3] + AAA;
14736            if(boards[move][CASTLING][5] != NoRights &&
14737               boards[move][CASTLING][4] != NoRights   )
14738                 *p++ = boards[move][CASTLING][4] + AAA;
14739      } else {
14740
14741         /* [HGM] write true castling rights */
14742         if( nrCastlingRights == 6 ) {
14743             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14744                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14745             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14746                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14747             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14748                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14749             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14750                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14751         }
14752      }
14753      if (q == p) *p++ = '-'; /* No castling rights */
14754      *p++ = ' ';
14755   }
14756
14757   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14758      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14759     /* En passant target square */
14760     if (move > backwardMostMove) {
14761         fromX = moveList[move - 1][0] - AAA;
14762         fromY = moveList[move - 1][1] - ONE;
14763         toX = moveList[move - 1][2] - AAA;
14764         toY = moveList[move - 1][3] - ONE;
14765         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14766             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14767             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14768             fromX == toX) {
14769             /* 2-square pawn move just happened */
14770             *p++ = toX + AAA;
14771             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14772         } else {
14773             *p++ = '-';
14774         }
14775     } else if(move == backwardMostMove) {
14776         // [HGM] perhaps we should always do it like this, and forget the above?
14777         if((signed char)boards[move][EP_STATUS] >= 0) {
14778             *p++ = boards[move][EP_STATUS] + AAA;
14779             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14780         } else {
14781             *p++ = '-';
14782         }
14783     } else {
14784         *p++ = '-';
14785     }
14786     *p++ = ' ';
14787   }
14788   }
14789
14790     /* [HGM] find reversible plies */
14791     {   int i = 0, j=move;
14792
14793         if (appData.debugMode) { int k;
14794             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14795             for(k=backwardMostMove; k<=forwardMostMove; k++)
14796                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14797
14798         }
14799
14800         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14801         if( j == backwardMostMove ) i += initialRulePlies;
14802         sprintf(p, "%d ", i);
14803         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14804     }
14805     /* Fullmove number */
14806     sprintf(p, "%d", (move / 2) + 1);
14807     
14808     return StrSave(buf);
14809 }
14810
14811 Boolean
14812 ParseFEN(board, blackPlaysFirst, fen)
14813     Board board;
14814      int *blackPlaysFirst;
14815      char *fen;
14816 {
14817     int i, j;
14818     char *p, c;
14819     int emptycount;
14820     ChessSquare piece;
14821
14822     p = fen;
14823
14824     /* [HGM] by default clear Crazyhouse holdings, if present */
14825     if(gameInfo.holdingsWidth) {
14826        for(i=0; i<BOARD_HEIGHT; i++) {
14827            board[i][0]             = EmptySquare; /* black holdings */
14828            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14829            board[i][1]             = (ChessSquare) 0; /* black counts */
14830            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14831        }
14832     }
14833
14834     /* Piece placement data */
14835     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14836         j = 0;
14837         for (;;) {
14838             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14839                 if (*p == '/') p++;
14840                 emptycount = gameInfo.boardWidth - j;
14841                 while (emptycount--)
14842                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14843                 break;
14844 #if(BOARD_FILES >= 10)
14845             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14846                 p++; emptycount=10;
14847                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14848                 while (emptycount--)
14849                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14850 #endif
14851             } else if (isdigit(*p)) {
14852                 emptycount = *p++ - '0';
14853                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14854                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14855                 while (emptycount--)
14856                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14857             } else if (*p == '+' || isalpha(*p)) {
14858                 if (j >= gameInfo.boardWidth) return FALSE;
14859                 if(*p=='+') {
14860                     piece = CharToPiece(*++p);
14861                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14862                     piece = (ChessSquare) (PROMOTED piece ); p++;
14863                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14864                 } else piece = CharToPiece(*p++);
14865
14866                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14867                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14868                     piece = (ChessSquare) (PROMOTED piece);
14869                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14870                     p++;
14871                 }
14872                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14873             } else {
14874                 return FALSE;
14875             }
14876         }
14877     }
14878     while (*p == '/' || *p == ' ') p++;
14879
14880     /* [HGM] look for Crazyhouse holdings here */
14881     while(*p==' ') p++;
14882     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14883         if(*p == '[') p++;
14884         if(*p == '-' ) *p++; /* empty holdings */ else {
14885             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14886             /* if we would allow FEN reading to set board size, we would   */
14887             /* have to add holdings and shift the board read so far here   */
14888             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14889                 *p++;
14890                 if((int) piece >= (int) BlackPawn ) {
14891                     i = (int)piece - (int)BlackPawn;
14892                     i = PieceToNumber((ChessSquare)i);
14893                     if( i >= gameInfo.holdingsSize ) return FALSE;
14894                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14895                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14896                 } else {
14897                     i = (int)piece - (int)WhitePawn;
14898                     i = PieceToNumber((ChessSquare)i);
14899                     if( i >= gameInfo.holdingsSize ) return FALSE;
14900                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14901                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14902                 }
14903             }
14904         }
14905         if(*p == ']') *p++;
14906     }
14907
14908     while(*p == ' ') p++;
14909
14910     /* Active color */
14911     c = *p++;
14912     if(appData.colorNickNames) {
14913       if( c == appData.colorNickNames[0] ) c = 'w'; else
14914       if( c == appData.colorNickNames[1] ) c = 'b';
14915     }
14916     switch (c) {
14917       case 'w':
14918         *blackPlaysFirst = FALSE;
14919         break;
14920       case 'b': 
14921         *blackPlaysFirst = TRUE;
14922         break;
14923       default:
14924         return FALSE;
14925     }
14926
14927     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14928     /* return the extra info in global variiables             */
14929
14930     /* set defaults in case FEN is incomplete */
14931     board[EP_STATUS] = EP_UNKNOWN;
14932     for(i=0; i<nrCastlingRights; i++ ) {
14933         board[CASTLING][i] =
14934             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14935     }   /* assume possible unless obviously impossible */
14936     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14937     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14938     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14939                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14940     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14941     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14942     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14943                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14944     FENrulePlies = 0;
14945
14946     while(*p==' ') p++;
14947     if(nrCastlingRights) {
14948       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14949           /* castling indicator present, so default becomes no castlings */
14950           for(i=0; i<nrCastlingRights; i++ ) {
14951                  board[CASTLING][i] = NoRights;
14952           }
14953       }
14954       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14955              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14956              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14957              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14958         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14959
14960         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14961             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14962             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14963         }
14964         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14965             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14966         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14967                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14968         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14969                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14970         switch(c) {
14971           case'K':
14972               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14973               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14974               board[CASTLING][2] = whiteKingFile;
14975               break;
14976           case'Q':
14977               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14978               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14979               board[CASTLING][2] = whiteKingFile;
14980               break;
14981           case'k':
14982               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14983               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14984               board[CASTLING][5] = blackKingFile;
14985               break;
14986           case'q':
14987               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14988               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14989               board[CASTLING][5] = blackKingFile;
14990           case '-':
14991               break;
14992           default: /* FRC castlings */
14993               if(c >= 'a') { /* black rights */
14994                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14995                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14996                   if(i == BOARD_RGHT) break;
14997                   board[CASTLING][5] = i;
14998                   c -= AAA;
14999                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15000                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15001                   if(c > i)
15002                       board[CASTLING][3] = c;
15003                   else
15004                       board[CASTLING][4] = c;
15005               } else { /* white rights */
15006                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15007                     if(board[0][i] == WhiteKing) break;
15008                   if(i == BOARD_RGHT) break;
15009                   board[CASTLING][2] = i;
15010                   c -= AAA - 'a' + 'A';
15011                   if(board[0][c] >= WhiteKing) break;
15012                   if(c > i)
15013                       board[CASTLING][0] = c;
15014                   else
15015                       board[CASTLING][1] = c;
15016               }
15017         }
15018       }
15019       for(i=0; i<nrCastlingRights; i++)
15020         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15021     if (appData.debugMode) {
15022         fprintf(debugFP, "FEN castling rights:");
15023         for(i=0; i<nrCastlingRights; i++)
15024         fprintf(debugFP, " %d", board[CASTLING][i]);
15025         fprintf(debugFP, "\n");
15026     }
15027
15028       while(*p==' ') p++;
15029     }
15030
15031     /* read e.p. field in games that know e.p. capture */
15032     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15033        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
15034       if(*p=='-') {
15035         p++; board[EP_STATUS] = EP_NONE;
15036       } else {
15037          char c = *p++ - AAA;
15038
15039          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15040          if(*p >= '0' && *p <='9') *p++;
15041          board[EP_STATUS] = c;
15042       }
15043     }
15044
15045
15046     if(sscanf(p, "%d", &i) == 1) {
15047         FENrulePlies = i; /* 50-move ply counter */
15048         /* (The move number is still ignored)    */
15049     }
15050
15051     return TRUE;
15052 }
15053       
15054 void
15055 EditPositionPasteFEN(char *fen)
15056 {
15057   if (fen != NULL) {
15058     Board initial_position;
15059
15060     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15061       DisplayError(_("Bad FEN position in clipboard"), 0);
15062       return ;
15063     } else {
15064       int savedBlackPlaysFirst = blackPlaysFirst;
15065       EditPositionEvent();
15066       blackPlaysFirst = savedBlackPlaysFirst;
15067       CopyBoard(boards[0], initial_position);
15068       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15069       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15070       DisplayBothClocks();
15071       DrawPosition(FALSE, boards[currentMove]);
15072     }
15073   }
15074 }
15075
15076 static char cseq[12] = "\\   ";
15077
15078 Boolean set_cont_sequence(char *new_seq)
15079 {
15080     int len;
15081     Boolean ret;
15082
15083     // handle bad attempts to set the sequence
15084         if (!new_seq)
15085                 return 0; // acceptable error - no debug
15086
15087     len = strlen(new_seq);
15088     ret = (len > 0) && (len < sizeof(cseq));
15089     if (ret)
15090         strcpy(cseq, new_seq);
15091     else if (appData.debugMode)
15092         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15093     return ret;
15094 }
15095
15096 /*
15097     reformat a source message so words don't cross the width boundary.  internal
15098     newlines are not removed.  returns the wrapped size (no null character unless
15099     included in source message).  If dest is NULL, only calculate the size required
15100     for the dest buffer.  lp argument indicats line position upon entry, and it's
15101     passed back upon exit.
15102 */
15103 int wrap(char *dest, char *src, int count, int width, int *lp)
15104 {
15105     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15106
15107     cseq_len = strlen(cseq);
15108     old_line = line = *lp;
15109     ansi = len = clen = 0;
15110
15111     for (i=0; i < count; i++)
15112     {
15113         if (src[i] == '\033')
15114             ansi = 1;
15115
15116         // if we hit the width, back up
15117         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15118         {
15119             // store i & len in case the word is too long
15120             old_i = i, old_len = len;
15121
15122             // find the end of the last word
15123             while (i && src[i] != ' ' && src[i] != '\n')
15124             {
15125                 i--;
15126                 len--;
15127             }
15128
15129             // word too long?  restore i & len before splitting it
15130             if ((old_i-i+clen) >= width)
15131             {
15132                 i = old_i;
15133                 len = old_len;
15134             }
15135
15136             // extra space?
15137             if (i && src[i-1] == ' ')
15138                 len--;
15139
15140             if (src[i] != ' ' && src[i] != '\n')
15141             {
15142                 i--;
15143                 if (len)
15144                     len--;
15145             }
15146
15147             // now append the newline and continuation sequence
15148             if (dest)
15149                 dest[len] = '\n';
15150             len++;
15151             if (dest)
15152                 strncpy(dest+len, cseq, cseq_len);
15153             len += cseq_len;
15154             line = cseq_len;
15155             clen = cseq_len;
15156             continue;
15157         }
15158
15159         if (dest)
15160             dest[len] = src[i];
15161         len++;
15162         if (!ansi)
15163             line++;
15164         if (src[i] == '\n')
15165             line = 0;
15166         if (src[i] == 'm')
15167             ansi = 0;
15168     }
15169     if (dest && appData.debugMode)
15170     {
15171         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15172             count, width, line, len, *lp);
15173         show_bytes(debugFP, src, count);
15174         fprintf(debugFP, "\ndest: ");
15175         show_bytes(debugFP, dest, len);
15176         fprintf(debugFP, "\n");
15177     }
15178     *lp = dest ? line : old_line;
15179
15180     return len;
15181 }
15182
15183 // [HGM] vari: routines for shelving variations
15184
15185 void 
15186 PushTail(int firstMove, int lastMove)
15187 {
15188         int i, j, nrMoves = lastMove - firstMove;
15189
15190         if(appData.icsActive) { // only in local mode
15191                 forwardMostMove = currentMove; // mimic old ICS behavior
15192                 return;
15193         }
15194         if(storedGames >= MAX_VARIATIONS-1) return;
15195
15196         // push current tail of game on stack
15197         savedResult[storedGames] = gameInfo.result;
15198         savedDetails[storedGames] = gameInfo.resultDetails;
15199         gameInfo.resultDetails = NULL;
15200         savedFirst[storedGames] = firstMove;
15201         savedLast [storedGames] = lastMove;
15202         savedFramePtr[storedGames] = framePtr;
15203         framePtr -= nrMoves; // reserve space for the boards
15204         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15205             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15206             for(j=0; j<MOVE_LEN; j++)
15207                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15208             for(j=0; j<2*MOVE_LEN; j++)
15209                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15210             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15211             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15212             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15213             pvInfoList[firstMove+i-1].depth = 0;
15214             commentList[framePtr+i] = commentList[firstMove+i];
15215             commentList[firstMove+i] = NULL;
15216         }
15217
15218         storedGames++;
15219         forwardMostMove = firstMove; // truncate game so we can start variation
15220         if(storedGames == 1) GreyRevert(FALSE);
15221 }
15222
15223 Boolean
15224 PopTail(Boolean annotate)
15225 {
15226         int i, j, nrMoves;
15227         char buf[8000], moveBuf[20];
15228
15229         if(appData.icsActive) return FALSE; // only in local mode
15230         if(!storedGames) return FALSE; // sanity
15231         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15232
15233         storedGames--;
15234         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15235         nrMoves = savedLast[storedGames] - currentMove;
15236         if(annotate) {
15237                 int cnt = 10;
15238                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15239                 else strcpy(buf, "(");
15240                 for(i=currentMove; i<forwardMostMove; i++) {
15241                         if(WhiteOnMove(i))
15242                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15243                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15244                         strcat(buf, moveBuf);
15245                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15246                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15247                 }
15248                 strcat(buf, ")");
15249         }
15250         for(i=1; i<=nrMoves; i++) { // copy last variation back
15251             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15252             for(j=0; j<MOVE_LEN; j++)
15253                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15254             for(j=0; j<2*MOVE_LEN; j++)
15255                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15256             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15257             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15258             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15259             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15260             commentList[currentMove+i] = commentList[framePtr+i];
15261             commentList[framePtr+i] = NULL;
15262         }
15263         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15264         framePtr = savedFramePtr[storedGames];
15265         gameInfo.result = savedResult[storedGames];
15266         if(gameInfo.resultDetails != NULL) {
15267             free(gameInfo.resultDetails);
15268       }
15269         gameInfo.resultDetails = savedDetails[storedGames];
15270         forwardMostMove = currentMove + nrMoves;
15271         if(storedGames == 0) GreyRevert(TRUE);
15272         return TRUE;
15273 }
15274
15275 void 
15276 CleanupTail()
15277 {       // remove all shelved variations
15278         int i;
15279         for(i=0; i<storedGames; i++) {
15280             if(savedDetails[i])
15281                 free(savedDetails[i]);
15282             savedDetails[i] = NULL;
15283         }
15284         for(i=framePtr; i<MAX_MOVES; i++) {
15285                 if(commentList[i]) free(commentList[i]);
15286                 commentList[i] = NULL;
15287         }
15288         framePtr = MAX_MOVES-1;
15289         storedGames = 0;
15290 }
15291
15292 void
15293 LoadVariation(int index, char *text)
15294 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15295         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15296         int level = 0, move;
15297
15298         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15299         // first find outermost bracketing variation
15300         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15301             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15302                 if(*p == '{') wait = '}'; else
15303                 if(*p == '[') wait = ']'; else
15304                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15305                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15306             }
15307             if(*p == wait) wait = NULLCHAR; // closing ]} found
15308             p++;
15309         }
15310         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15311         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15312         end[1] = NULLCHAR; // clip off comment beyond variation
15313         ToNrEvent(currentMove-1);
15314         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15315         // kludge: use ParsePV() to append variation to game
15316         move = currentMove;
15317         ParsePV(start, TRUE);
15318         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15319         ClearPremoveHighlights();
15320         CommentPopDown();
15321         ToNrEvent(currentMove+1);
15322 }
15323