Prevent transmission of spurious promo char to other engine
[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     { int i;
8328
8329       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8330       oldEP = (signed char)board[EP_STATUS];
8331       board[EP_STATUS] = EP_NONE;
8332
8333       if( board[toY][toX] != EmptySquare ) 
8334            board[EP_STATUS] = EP_CAPTURE;  
8335
8336       if( board[fromY][fromX] == WhitePawn ) {
8337            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8338                board[EP_STATUS] = EP_PAWN_MOVE;
8339            if( toY-fromY==2) {
8340                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8341                         gameInfo.variant != VariantBerolina || toX < fromX)
8342                       board[EP_STATUS] = toX | berolina;
8343                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8344                         gameInfo.variant != VariantBerolina || toX > fromX) 
8345                       board[EP_STATUS] = toX;
8346            }
8347       } else 
8348       if( board[fromY][fromX] == BlackPawn ) {
8349            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8350                board[EP_STATUS] = EP_PAWN_MOVE; 
8351            if( toY-fromY== -2) {
8352                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8353                         gameInfo.variant != VariantBerolina || toX < fromX)
8354                       board[EP_STATUS] = toX | berolina;
8355                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8356                         gameInfo.variant != VariantBerolina || toX > fromX) 
8357                       board[EP_STATUS] = toX;
8358            }
8359        }
8360
8361        for(i=0; i<nrCastlingRights; i++) {
8362            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8363               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8364              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8365        }
8366
8367     }
8368
8369   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8370   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8371        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8372          
8373   if (fromX == toX && fromY == toY) return;
8374
8375   if (fromY == DROP_RANK) {
8376         /* must be first */
8377         piece = board[toY][toX] = (ChessSquare) fromX;
8378   } else {
8379      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8380      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8381      if(gameInfo.variant == VariantKnightmate)
8382          king += (int) WhiteUnicorn - (int) WhiteKing;
8383
8384     /* Code added by Tord: */
8385     /* FRC castling assumed when king captures friendly rook. */
8386     if (board[fromY][fromX] == WhiteKing &&
8387              board[toY][toX] == WhiteRook) {
8388       board[fromY][fromX] = EmptySquare;
8389       board[toY][toX] = EmptySquare;
8390       if(toX > fromX) {
8391         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8392       } else {
8393         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8394       }
8395     } else if (board[fromY][fromX] == BlackKing &&
8396                board[toY][toX] == BlackRook) {
8397       board[fromY][fromX] = EmptySquare;
8398       board[toY][toX] = EmptySquare;
8399       if(toX > fromX) {
8400         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8401       } else {
8402         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8403       }
8404     /* End of code added by Tord */
8405
8406     } else if (board[fromY][fromX] == king
8407         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8408         && toY == fromY && toX > fromX+1) {
8409         board[fromY][fromX] = EmptySquare;
8410         board[toY][toX] = king;
8411         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8412         board[fromY][BOARD_RGHT-1] = EmptySquare;
8413     } else if (board[fromY][fromX] == king
8414         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8415                && toY == fromY && toX < fromX-1) {
8416         board[fromY][fromX] = EmptySquare;
8417         board[toY][toX] = king;
8418         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8419         board[fromY][BOARD_LEFT] = EmptySquare;
8420     } else if (board[fromY][fromX] == WhitePawn
8421                && toY >= BOARD_HEIGHT-promoRank
8422                && gameInfo.variant != VariantXiangqi
8423                ) {
8424         /* white pawn promotion */
8425         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8426         if (board[toY][toX] == EmptySquare) {
8427             board[toY][toX] = WhiteQueen;
8428         }
8429         if(gameInfo.variant==VariantBughouse ||
8430            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8431             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8432         board[fromY][fromX] = EmptySquare;
8433     } else if ((fromY == BOARD_HEIGHT-4)
8434                && (toX != fromX)
8435                && gameInfo.variant != VariantXiangqi
8436                && gameInfo.variant != VariantBerolina
8437                && (board[fromY][fromX] == WhitePawn)
8438                && (board[toY][toX] == EmptySquare)) {
8439         board[fromY][fromX] = EmptySquare;
8440         board[toY][toX] = WhitePawn;
8441         captured = board[toY - 1][toX];
8442         board[toY - 1][toX] = EmptySquare;
8443     } else if ((fromY == BOARD_HEIGHT-4)
8444                && (toX == fromX)
8445                && gameInfo.variant == VariantBerolina
8446                && (board[fromY][fromX] == WhitePawn)
8447                && (board[toY][toX] == EmptySquare)) {
8448         board[fromY][fromX] = EmptySquare;
8449         board[toY][toX] = WhitePawn;
8450         if(oldEP & EP_BEROLIN_A) {
8451                 captured = board[fromY][fromX-1];
8452                 board[fromY][fromX-1] = EmptySquare;
8453         }else{  captured = board[fromY][fromX+1];
8454                 board[fromY][fromX+1] = EmptySquare;
8455         }
8456     } else if (board[fromY][fromX] == king
8457         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8458                && toY == fromY && toX > fromX+1) {
8459         board[fromY][fromX] = EmptySquare;
8460         board[toY][toX] = king;
8461         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8462         board[fromY][BOARD_RGHT-1] = EmptySquare;
8463     } else if (board[fromY][fromX] == king
8464         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8465                && toY == fromY && toX < fromX-1) {
8466         board[fromY][fromX] = EmptySquare;
8467         board[toY][toX] = king;
8468         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8469         board[fromY][BOARD_LEFT] = EmptySquare;
8470     } else if (fromY == 7 && fromX == 3
8471                && board[fromY][fromX] == BlackKing
8472                && toY == 7 && toX == 5) {
8473         board[fromY][fromX] = EmptySquare;
8474         board[toY][toX] = BlackKing;
8475         board[fromY][7] = EmptySquare;
8476         board[toY][4] = BlackRook;
8477     } else if (fromY == 7 && fromX == 3
8478                && board[fromY][fromX] == BlackKing
8479                && toY == 7 && toX == 1) {
8480         board[fromY][fromX] = EmptySquare;
8481         board[toY][toX] = BlackKing;
8482         board[fromY][0] = EmptySquare;
8483         board[toY][2] = BlackRook;
8484     } else if (board[fromY][fromX] == BlackPawn
8485                && toY < promoRank
8486                && gameInfo.variant != VariantXiangqi
8487                ) {
8488         /* black pawn promotion */
8489         board[toY][toX] = CharToPiece(ToLower(promoChar));
8490         if (board[toY][toX] == EmptySquare) {
8491             board[toY][toX] = BlackQueen;
8492         }
8493         if(gameInfo.variant==VariantBughouse ||
8494            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8495             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8496         board[fromY][fromX] = EmptySquare;
8497     } else if ((fromY == 3)
8498                && (toX != fromX)
8499                && gameInfo.variant != VariantXiangqi
8500                && gameInfo.variant != VariantBerolina
8501                && (board[fromY][fromX] == BlackPawn)
8502                && (board[toY][toX] == EmptySquare)) {
8503         board[fromY][fromX] = EmptySquare;
8504         board[toY][toX] = BlackPawn;
8505         captured = board[toY + 1][toX];
8506         board[toY + 1][toX] = EmptySquare;
8507     } else if ((fromY == 3)
8508                && (toX == fromX)
8509                && gameInfo.variant == VariantBerolina
8510                && (board[fromY][fromX] == BlackPawn)
8511                && (board[toY][toX] == EmptySquare)) {
8512         board[fromY][fromX] = EmptySquare;
8513         board[toY][toX] = BlackPawn;
8514         if(oldEP & EP_BEROLIN_A) {
8515                 captured = board[fromY][fromX-1];
8516                 board[fromY][fromX-1] = EmptySquare;
8517         }else{  captured = board[fromY][fromX+1];
8518                 board[fromY][fromX+1] = EmptySquare;
8519         }
8520     } else {
8521         board[toY][toX] = board[fromY][fromX];
8522         board[fromY][fromX] = EmptySquare;
8523     }
8524
8525     /* [HGM] now we promote for Shogi, if needed */
8526     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8527         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8528   }
8529
8530     if (gameInfo.holdingsWidth != 0) {
8531
8532       /* !!A lot more code needs to be written to support holdings  */
8533       /* [HGM] OK, so I have written it. Holdings are stored in the */
8534       /* penultimate board files, so they are automaticlly stored   */
8535       /* in the game history.                                       */
8536       if (fromY == DROP_RANK) {
8537         /* Delete from holdings, by decreasing count */
8538         /* and erasing image if necessary            */
8539         p = (int) fromX;
8540         if(p < (int) BlackPawn) { /* white drop */
8541              p -= (int)WhitePawn;
8542                  p = PieceToNumber((ChessSquare)p);
8543              if(p >= gameInfo.holdingsSize) p = 0;
8544              if(--board[p][BOARD_WIDTH-2] <= 0)
8545                   board[p][BOARD_WIDTH-1] = EmptySquare;
8546              if((int)board[p][BOARD_WIDTH-2] < 0)
8547                         board[p][BOARD_WIDTH-2] = 0;
8548         } else {                  /* black drop */
8549              p -= (int)BlackPawn;
8550                  p = PieceToNumber((ChessSquare)p);
8551              if(p >= gameInfo.holdingsSize) p = 0;
8552              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8553                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8554              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8555                         board[BOARD_HEIGHT-1-p][1] = 0;
8556         }
8557       }
8558       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8559           && gameInfo.variant != VariantBughouse        ) {
8560         /* [HGM] holdings: Add to holdings, if holdings exist */
8561         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8562                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8563                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8564         }
8565         p = (int) captured;
8566         if (p >= (int) BlackPawn) {
8567           p -= (int)BlackPawn;
8568           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8569                   /* in Shogi restore piece to its original  first */
8570                   captured = (ChessSquare) (DEMOTED captured);
8571                   p = DEMOTED p;
8572           }
8573           p = PieceToNumber((ChessSquare)p);
8574           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8575           board[p][BOARD_WIDTH-2]++;
8576           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8577         } else {
8578           p -= (int)WhitePawn;
8579           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8580                   captured = (ChessSquare) (DEMOTED captured);
8581                   p = DEMOTED p;
8582           }
8583           p = PieceToNumber((ChessSquare)p);
8584           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8585           board[BOARD_HEIGHT-1-p][1]++;
8586           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8587         }
8588       }
8589     } else if (gameInfo.variant == VariantAtomic) {
8590       if (captured != EmptySquare) {
8591         int y, x;
8592         for (y = toY-1; y <= toY+1; y++) {
8593           for (x = toX-1; x <= toX+1; x++) {
8594             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8595                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8596               board[y][x] = EmptySquare;
8597             }
8598           }
8599         }
8600         board[toY][toX] = EmptySquare;
8601       }
8602     }
8603     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8604         /* [HGM] Shogi promotions */
8605         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8606     }
8607
8608     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8609                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8610         // [HGM] superchess: take promotion piece out of holdings
8611         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8612         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8613             if(!--board[k][BOARD_WIDTH-2])
8614                 board[k][BOARD_WIDTH-1] = EmptySquare;
8615         } else {
8616             if(!--board[BOARD_HEIGHT-1-k][1])
8617                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8618         }
8619     }
8620
8621 }
8622
8623 /* Updates forwardMostMove */
8624 void
8625 MakeMove(fromX, fromY, toX, toY, promoChar)
8626      int fromX, fromY, toX, toY;
8627      int promoChar;
8628 {
8629 //    forwardMostMove++; // [HGM] bare: moved downstream
8630
8631     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8632         int timeLeft; static int lastLoadFlag=0; int king, piece;
8633         piece = boards[forwardMostMove][fromY][fromX];
8634         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8635         if(gameInfo.variant == VariantKnightmate)
8636             king += (int) WhiteUnicorn - (int) WhiteKing;
8637         if(forwardMostMove == 0) {
8638             if(blackPlaysFirst) 
8639                 fprintf(serverMoves, "%s;", second.tidy);
8640             fprintf(serverMoves, "%s;", first.tidy);
8641             if(!blackPlaysFirst) 
8642                 fprintf(serverMoves, "%s;", second.tidy);
8643         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8644         lastLoadFlag = loadFlag;
8645         // print base move
8646         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8647         // print castling suffix
8648         if( toY == fromY && piece == king ) {
8649             if(toX-fromX > 1)
8650                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8651             if(fromX-toX >1)
8652                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8653         }
8654         // e.p. suffix
8655         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8656              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8657              boards[forwardMostMove][toY][toX] == EmptySquare
8658              && fromX != toX && fromY != toY)
8659                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8660         // promotion suffix
8661         if(promoChar != NULLCHAR)
8662                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8663         if(!loadFlag) {
8664             fprintf(serverMoves, "/%d/%d",
8665                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8666             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8667             else                      timeLeft = blackTimeRemaining/1000;
8668             fprintf(serverMoves, "/%d", timeLeft);
8669         }
8670         fflush(serverMoves);
8671     }
8672
8673     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8674       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8675                         0, 1);
8676       return;
8677     }
8678     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8679     if (commentList[forwardMostMove+1] != NULL) {
8680         free(commentList[forwardMostMove+1]);
8681         commentList[forwardMostMove+1] = NULL;
8682     }
8683     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8684     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8685     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8686     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8687     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8688     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8689     gameInfo.result = GameUnfinished;
8690     if (gameInfo.resultDetails != NULL) {
8691         free(gameInfo.resultDetails);
8692         gameInfo.resultDetails = NULL;
8693     }
8694     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8695                               moveList[forwardMostMove - 1]);
8696     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8697                              PosFlags(forwardMostMove - 1),
8698                              fromY, fromX, toY, toX, promoChar,
8699                              parseList[forwardMostMove - 1]);
8700     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8701       case MT_NONE:
8702       case MT_STALEMATE:
8703       default:
8704         break;
8705       case MT_CHECK:
8706         if(gameInfo.variant != VariantShogi)
8707             strcat(parseList[forwardMostMove - 1], "+");
8708         break;
8709       case MT_CHECKMATE:
8710       case MT_STAINMATE:
8711         strcat(parseList[forwardMostMove - 1], "#");
8712         break;
8713     }
8714     if (appData.debugMode) {
8715         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8716     }
8717
8718 }
8719
8720 /* Updates currentMove if not pausing */
8721 void
8722 ShowMove(fromX, fromY, toX, toY)
8723 {
8724     int instant = (gameMode == PlayFromGameFile) ?
8725         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8726     if(appData.noGUI) return;
8727     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8728         if (!instant) {
8729             if (forwardMostMove == currentMove + 1) {
8730                 AnimateMove(boards[forwardMostMove - 1],
8731                             fromX, fromY, toX, toY);
8732             }
8733             if (appData.highlightLastMove) {
8734                 SetHighlights(fromX, fromY, toX, toY);
8735             }
8736         }
8737         currentMove = forwardMostMove;
8738     }
8739
8740     if (instant) return;
8741
8742     DisplayMove(currentMove - 1);
8743     DrawPosition(FALSE, boards[currentMove]);
8744     DisplayBothClocks();
8745     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8746 }
8747
8748 void SendEgtPath(ChessProgramState *cps)
8749 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8750         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8751
8752         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8753
8754         while(*p) {
8755             char c, *q = name+1, *r, *s;
8756
8757             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8758             while(*p && *p != ',') *q++ = *p++;
8759             *q++ = ':'; *q = 0;
8760             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8761                 strcmp(name, ",nalimov:") == 0 ) {
8762                 // take nalimov path from the menu-changeable option first, if it is defined
8763                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8764                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8765             } else
8766             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8767                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8768                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8769                 s = r = StrStr(s, ":") + 1; // beginning of path info
8770                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8771                 c = *r; *r = 0;             // temporarily null-terminate path info
8772                     *--q = 0;               // strip of trailig ':' from name
8773                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8774                 *r = c;
8775                 SendToProgram(buf,cps);     // send egtbpath command for this format
8776             }
8777             if(*p == ',') p++; // read away comma to position for next format name
8778         }
8779 }
8780
8781 void
8782 InitChessProgram(cps, setup)
8783      ChessProgramState *cps;
8784      int setup; /* [HGM] needed to setup FRC opening position */
8785 {
8786     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8787     if (appData.noChessProgram) return;
8788     hintRequested = FALSE;
8789     bookRequested = FALSE;
8790
8791     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8792     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8793     if(cps->memSize) { /* [HGM] memory */
8794         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8795         SendToProgram(buf, cps);
8796     }
8797     SendEgtPath(cps); /* [HGM] EGT */
8798     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8799         sprintf(buf, "cores %d\n", appData.smpCores);
8800         SendToProgram(buf, cps);
8801     }
8802
8803     SendToProgram(cps->initString, cps);
8804     if (gameInfo.variant != VariantNormal &&
8805         gameInfo.variant != VariantLoadable
8806         /* [HGM] also send variant if board size non-standard */
8807         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8808                                             ) {
8809       char *v = VariantName(gameInfo.variant);
8810       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8811         /* [HGM] in protocol 1 we have to assume all variants valid */
8812         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8813         DisplayFatalError(buf, 0, 1);
8814         return;
8815       }
8816
8817       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8818       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8819       if( gameInfo.variant == VariantXiangqi )
8820            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8821       if( gameInfo.variant == VariantShogi )
8822            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8823       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8824            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8825       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8826                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8827            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8828       if( gameInfo.variant == VariantCourier )
8829            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8830       if( gameInfo.variant == VariantSuper )
8831            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8832       if( gameInfo.variant == VariantGreat )
8833            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8834
8835       if(overruled) {
8836            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8837                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8838            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8839            if(StrStr(cps->variants, b) == NULL) { 
8840                // specific sized variant not known, check if general sizing allowed
8841                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8842                    if(StrStr(cps->variants, "boardsize") == NULL) {
8843                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8844                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8845                        DisplayFatalError(buf, 0, 1);
8846                        return;
8847                    }
8848                    /* [HGM] here we really should compare with the maximum supported board size */
8849                }
8850            }
8851       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8852       sprintf(buf, "variant %s\n", b);
8853       SendToProgram(buf, cps);
8854     }
8855     currentlyInitializedVariant = gameInfo.variant;
8856
8857     /* [HGM] send opening position in FRC to first engine */
8858     if(setup) {
8859           SendToProgram("force\n", cps);
8860           SendBoard(cps, 0);
8861           /* engine is now in force mode! Set flag to wake it up after first move. */
8862           setboardSpoiledMachineBlack = 1;
8863     }
8864
8865     if (cps->sendICS) {
8866       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8867       SendToProgram(buf, cps);
8868     }
8869     cps->maybeThinking = FALSE;
8870     cps->offeredDraw = 0;
8871     if (!appData.icsActive) {
8872         SendTimeControl(cps, movesPerSession, timeControl,
8873                         timeIncrement, appData.searchDepth,
8874                         searchTime);
8875     }
8876     if (appData.showThinking 
8877         // [HGM] thinking: four options require thinking output to be sent
8878         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8879                                 ) {
8880         SendToProgram("post\n", cps);
8881     }
8882     SendToProgram("hard\n", cps);
8883     if (!appData.ponderNextMove) {
8884         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8885            it without being sure what state we are in first.  "hard"
8886            is not a toggle, so that one is OK.
8887          */
8888         SendToProgram("easy\n", cps);
8889     }
8890     if (cps->usePing) {
8891       sprintf(buf, "ping %d\n", ++cps->lastPing);
8892       SendToProgram(buf, cps);
8893     }
8894     cps->initDone = TRUE;
8895 }   
8896
8897
8898 void
8899 StartChessProgram(cps)
8900      ChessProgramState *cps;
8901 {
8902     char buf[MSG_SIZ];
8903     int err;
8904
8905     if (appData.noChessProgram) return;
8906     cps->initDone = FALSE;
8907
8908     if (strcmp(cps->host, "localhost") == 0) {
8909         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8910     } else if (*appData.remoteShell == NULLCHAR) {
8911         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8912     } else {
8913         if (*appData.remoteUser == NULLCHAR) {
8914           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8915                     cps->program);
8916         } else {
8917           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8918                     cps->host, appData.remoteUser, cps->program);
8919         }
8920         err = StartChildProcess(buf, "", &cps->pr);
8921     }
8922     
8923     if (err != 0) {
8924         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8925         DisplayFatalError(buf, err, 1);
8926         cps->pr = NoProc;
8927         cps->isr = NULL;
8928         return;
8929     }
8930     
8931     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8932     if (cps->protocolVersion > 1) {
8933       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8934       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8935       cps->comboCnt = 0;  //                and values of combo boxes
8936       SendToProgram(buf, cps);
8937     } else {
8938       SendToProgram("xboard\n", cps);
8939     }
8940 }
8941
8942
8943 void
8944 TwoMachinesEventIfReady P((void))
8945 {
8946   if (first.lastPing != first.lastPong) {
8947     DisplayMessage("", _("Waiting for first chess program"));
8948     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8949     return;
8950   }
8951   if (second.lastPing != second.lastPong) {
8952     DisplayMessage("", _("Waiting for second chess program"));
8953     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8954     return;
8955   }
8956   ThawUI();
8957   TwoMachinesEvent();
8958 }
8959
8960 void
8961 NextMatchGame P((void))
8962 {
8963     int index; /* [HGM] autoinc: step load index during match */
8964     Reset(FALSE, TRUE);
8965     if (*appData.loadGameFile != NULLCHAR) {
8966         index = appData.loadGameIndex;
8967         if(index < 0) { // [HGM] autoinc
8968             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8969             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8970         } 
8971         LoadGameFromFile(appData.loadGameFile,
8972                          index,
8973                          appData.loadGameFile, FALSE);
8974     } else if (*appData.loadPositionFile != NULLCHAR) {
8975         index = appData.loadPositionIndex;
8976         if(index < 0) { // [HGM] autoinc
8977             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8978             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8979         } 
8980         LoadPositionFromFile(appData.loadPositionFile,
8981                              index,
8982                              appData.loadPositionFile);
8983     }
8984     TwoMachinesEventIfReady();
8985 }
8986
8987 void UserAdjudicationEvent( int result )
8988 {
8989     ChessMove gameResult = GameIsDrawn;
8990
8991     if( result > 0 ) {
8992         gameResult = WhiteWins;
8993     }
8994     else if( result < 0 ) {
8995         gameResult = BlackWins;
8996     }
8997
8998     if( gameMode == TwoMachinesPlay ) {
8999         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9000     }
9001 }
9002
9003
9004 // [HGM] save: calculate checksum of game to make games easily identifiable
9005 int StringCheckSum(char *s)
9006 {
9007         int i = 0;
9008         if(s==NULL) return 0;
9009         while(*s) i = i*259 + *s++;
9010         return i;
9011 }
9012
9013 int GameCheckSum()
9014 {
9015         int i, sum=0;
9016         for(i=backwardMostMove; i<forwardMostMove; i++) {
9017                 sum += pvInfoList[i].depth;
9018                 sum += StringCheckSum(parseList[i]);
9019                 sum += StringCheckSum(commentList[i]);
9020                 sum *= 261;
9021         }
9022         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9023         return sum + StringCheckSum(commentList[i]);
9024 } // end of save patch
9025
9026 void
9027 GameEnds(result, resultDetails, whosays)
9028      ChessMove result;
9029      char *resultDetails;
9030      int whosays;
9031 {
9032     GameMode nextGameMode;
9033     int isIcsGame;
9034     char buf[MSG_SIZ], popupRequested = 0;
9035
9036     if(endingGame) return; /* [HGM] crash: forbid recursion */
9037     endingGame = 1;
9038     if(twoBoards) { // [HGM] dual: switch back to one board
9039         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9040         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9041     }
9042     if (appData.debugMode) {
9043       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9044               result, resultDetails ? resultDetails : "(null)", whosays);
9045     }
9046
9047     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9048
9049     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9050         /* If we are playing on ICS, the server decides when the
9051            game is over, but the engine can offer to draw, claim 
9052            a draw, or resign. 
9053          */
9054 #if ZIPPY
9055         if (appData.zippyPlay && first.initDone) {
9056             if (result == GameIsDrawn) {
9057                 /* In case draw still needs to be claimed */
9058                 SendToICS(ics_prefix);
9059                 SendToICS("draw\n");
9060             } else if (StrCaseStr(resultDetails, "resign")) {
9061                 SendToICS(ics_prefix);
9062                 SendToICS("resign\n");
9063             }
9064         }
9065 #endif
9066         endingGame = 0; /* [HGM] crash */
9067         return;
9068     }
9069
9070     /* If we're loading the game from a file, stop */
9071     if (whosays == GE_FILE) {
9072       (void) StopLoadGameTimer();
9073       gameFileFP = NULL;
9074     }
9075
9076     /* Cancel draw offers */
9077     first.offeredDraw = second.offeredDraw = 0;
9078
9079     /* If this is an ICS game, only ICS can really say it's done;
9080        if not, anyone can. */
9081     isIcsGame = (gameMode == IcsPlayingWhite || 
9082                  gameMode == IcsPlayingBlack || 
9083                  gameMode == IcsObserving    || 
9084                  gameMode == IcsExamining);
9085
9086     if (!isIcsGame || whosays == GE_ICS) {
9087         /* OK -- not an ICS game, or ICS said it was done */
9088         StopClocks();
9089         if (!isIcsGame && !appData.noChessProgram) 
9090           SetUserThinkingEnables();
9091     
9092         /* [HGM] if a machine claims the game end we verify this claim */
9093         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9094             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9095                 char claimer;
9096                 ChessMove trueResult = (ChessMove) -1;
9097
9098                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9099                                             first.twoMachinesColor[0] :
9100                                             second.twoMachinesColor[0] ;
9101
9102                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9103                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9104                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9105                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9106                 } else
9107                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9108                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9109                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9110                 } else
9111                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9112                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9113                 }
9114
9115                 // now verify win claims, but not in drop games, as we don't understand those yet
9116                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9117                                                  || gameInfo.variant == VariantGreat) &&
9118                     (result == WhiteWins && claimer == 'w' ||
9119                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9120                       if (appData.debugMode) {
9121                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9122                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9123                       }
9124                       if(result != trueResult) {
9125                               sprintf(buf, "False win claim: '%s'", resultDetails);
9126                               result = claimer == 'w' ? BlackWins : WhiteWins;
9127                               resultDetails = buf;
9128                       }
9129                 } else
9130                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9131                     && (forwardMostMove <= backwardMostMove ||
9132                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9133                         (claimer=='b')==(forwardMostMove&1))
9134                                                                                   ) {
9135                       /* [HGM] verify: draws that were not flagged are false claims */
9136                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9137                       result = claimer == 'w' ? BlackWins : WhiteWins;
9138                       resultDetails = buf;
9139                 }
9140                 /* (Claiming a loss is accepted no questions asked!) */
9141             }
9142             /* [HGM] bare: don't allow bare King to win */
9143             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9144                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9145                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9146                && result != GameIsDrawn)
9147             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9148                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9149                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9150                         if(p >= 0 && p <= (int)WhiteKing) k++;
9151                 }
9152                 if (appData.debugMode) {
9153                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9154                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9155                 }
9156                 if(k <= 1) {
9157                         result = GameIsDrawn;
9158                         sprintf(buf, "%s but bare king", resultDetails);
9159                         resultDetails = buf;
9160                 }
9161             }
9162         }
9163
9164
9165         if(serverMoves != NULL && !loadFlag) { char c = '=';
9166             if(result==WhiteWins) c = '+';
9167             if(result==BlackWins) c = '-';
9168             if(resultDetails != NULL)
9169                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9170         }
9171         if (resultDetails != NULL) {
9172             gameInfo.result = result;
9173             gameInfo.resultDetails = StrSave(resultDetails);
9174
9175             /* display last move only if game was not loaded from file */
9176             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9177                 DisplayMove(currentMove - 1);
9178     
9179             if (forwardMostMove != 0) {
9180                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9181                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9182                                                                 ) {
9183                     if (*appData.saveGameFile != NULLCHAR) {
9184                         SaveGameToFile(appData.saveGameFile, TRUE);
9185                     } else if (appData.autoSaveGames) {
9186                         AutoSaveGame();
9187                     }
9188                     if (*appData.savePositionFile != NULLCHAR) {
9189                         SavePositionToFile(appData.savePositionFile);
9190                     }
9191                 }
9192             }
9193
9194             /* Tell program how game ended in case it is learning */
9195             /* [HGM] Moved this to after saving the PGN, just in case */
9196             /* engine died and we got here through time loss. In that */
9197             /* case we will get a fatal error writing the pipe, which */
9198             /* would otherwise lose us the PGN.                       */
9199             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9200             /* output during GameEnds should never be fatal anymore   */
9201             if (gameMode == MachinePlaysWhite ||
9202                 gameMode == MachinePlaysBlack ||
9203                 gameMode == TwoMachinesPlay ||
9204                 gameMode == IcsPlayingWhite ||
9205                 gameMode == IcsPlayingBlack ||
9206                 gameMode == BeginningOfGame) {
9207                 char buf[MSG_SIZ];
9208                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9209                         resultDetails);
9210                 if (first.pr != NoProc) {
9211                     SendToProgram(buf, &first);
9212                 }
9213                 if (second.pr != NoProc &&
9214                     gameMode == TwoMachinesPlay) {
9215                     SendToProgram(buf, &second);
9216                 }
9217             }
9218         }
9219
9220         if (appData.icsActive) {
9221             if (appData.quietPlay &&
9222                 (gameMode == IcsPlayingWhite ||
9223                  gameMode == IcsPlayingBlack)) {
9224                 SendToICS(ics_prefix);
9225                 SendToICS("set shout 1\n");
9226             }
9227             nextGameMode = IcsIdle;
9228             ics_user_moved = FALSE;
9229             /* clean up premove.  It's ugly when the game has ended and the
9230              * premove highlights are still on the board.
9231              */
9232             if (gotPremove) {
9233               gotPremove = FALSE;
9234               ClearPremoveHighlights();
9235               DrawPosition(FALSE, boards[currentMove]);
9236             }
9237             if (whosays == GE_ICS) {
9238                 switch (result) {
9239                 case WhiteWins:
9240                     if (gameMode == IcsPlayingWhite)
9241                         PlayIcsWinSound();
9242                     else if(gameMode == IcsPlayingBlack)
9243                         PlayIcsLossSound();
9244                     break;
9245                 case BlackWins:
9246                     if (gameMode == IcsPlayingBlack)
9247                         PlayIcsWinSound();
9248                     else if(gameMode == IcsPlayingWhite)
9249                         PlayIcsLossSound();
9250                     break;
9251                 case GameIsDrawn:
9252                     PlayIcsDrawSound();
9253                     break;
9254                 default:
9255                     PlayIcsUnfinishedSound();
9256                 }
9257             }
9258         } else if (gameMode == EditGame ||
9259                    gameMode == PlayFromGameFile || 
9260                    gameMode == AnalyzeMode || 
9261                    gameMode == AnalyzeFile) {
9262             nextGameMode = gameMode;
9263         } else {
9264             nextGameMode = EndOfGame;
9265         }
9266         pausing = FALSE;
9267         ModeHighlight();
9268     } else {
9269         nextGameMode = gameMode;
9270     }
9271
9272     if (appData.noChessProgram) {
9273         gameMode = nextGameMode;
9274         ModeHighlight();
9275         endingGame = 0; /* [HGM] crash */
9276         return;
9277     }
9278
9279     if (first.reuse) {
9280         /* Put first chess program into idle state */
9281         if (first.pr != NoProc &&
9282             (gameMode == MachinePlaysWhite ||
9283              gameMode == MachinePlaysBlack ||
9284              gameMode == TwoMachinesPlay ||
9285              gameMode == IcsPlayingWhite ||
9286              gameMode == IcsPlayingBlack ||
9287              gameMode == BeginningOfGame)) {
9288             SendToProgram("force\n", &first);
9289             if (first.usePing) {
9290               char buf[MSG_SIZ];
9291               sprintf(buf, "ping %d\n", ++first.lastPing);
9292               SendToProgram(buf, &first);
9293             }
9294         }
9295     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9296         /* Kill off first chess program */
9297         if (first.isr != NULL)
9298           RemoveInputSource(first.isr);
9299         first.isr = NULL;
9300     
9301         if (first.pr != NoProc) {
9302             ExitAnalyzeMode();
9303             DoSleep( appData.delayBeforeQuit );
9304             SendToProgram("quit\n", &first);
9305             DoSleep( appData.delayAfterQuit );
9306             DestroyChildProcess(first.pr, first.useSigterm);
9307         }
9308         first.pr = NoProc;
9309     }
9310     if (second.reuse) {
9311         /* Put second chess program into idle state */
9312         if (second.pr != NoProc &&
9313             gameMode == TwoMachinesPlay) {
9314             SendToProgram("force\n", &second);
9315             if (second.usePing) {
9316               char buf[MSG_SIZ];
9317               sprintf(buf, "ping %d\n", ++second.lastPing);
9318               SendToProgram(buf, &second);
9319             }
9320         }
9321     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9322         /* Kill off second chess program */
9323         if (second.isr != NULL)
9324           RemoveInputSource(second.isr);
9325         second.isr = NULL;
9326     
9327         if (second.pr != NoProc) {
9328             DoSleep( appData.delayBeforeQuit );
9329             SendToProgram("quit\n", &second);
9330             DoSleep( appData.delayAfterQuit );
9331             DestroyChildProcess(second.pr, second.useSigterm);
9332         }
9333         second.pr = NoProc;
9334     }
9335
9336     if (matchMode && gameMode == TwoMachinesPlay) {
9337         switch (result) {
9338         case WhiteWins:
9339           if (first.twoMachinesColor[0] == 'w') {
9340             first.matchWins++;
9341           } else {
9342             second.matchWins++;
9343           }
9344           break;
9345         case BlackWins:
9346           if (first.twoMachinesColor[0] == 'b') {
9347             first.matchWins++;
9348           } else {
9349             second.matchWins++;
9350           }
9351           break;
9352         default:
9353           break;
9354         }
9355         if (matchGame < appData.matchGames) {
9356             char *tmp;
9357             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9358                 tmp = first.twoMachinesColor;
9359                 first.twoMachinesColor = second.twoMachinesColor;
9360                 second.twoMachinesColor = tmp;
9361             }
9362             gameMode = nextGameMode;
9363             matchGame++;
9364             if(appData.matchPause>10000 || appData.matchPause<10)
9365                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9366             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9367             endingGame = 0; /* [HGM] crash */
9368             return;
9369         } else {
9370             gameMode = nextGameMode;
9371             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9372                     first.tidy, second.tidy,
9373                     first.matchWins, second.matchWins,
9374                     appData.matchGames - (first.matchWins + second.matchWins));
9375             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9376         }
9377     }
9378     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9379         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9380       ExitAnalyzeMode();
9381     gameMode = nextGameMode;
9382     ModeHighlight();
9383     endingGame = 0;  /* [HGM] crash */
9384     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9385       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9386         matchMode = FALSE; appData.matchGames = matchGame = 0;
9387         DisplayNote(buf);
9388       }
9389     }
9390 }
9391
9392 /* Assumes program was just initialized (initString sent).
9393    Leaves program in force mode. */
9394 void
9395 FeedMovesToProgram(cps, upto) 
9396      ChessProgramState *cps;
9397      int upto;
9398 {
9399     int i;
9400     
9401     if (appData.debugMode)
9402       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9403               startedFromSetupPosition ? "position and " : "",
9404               backwardMostMove, upto, cps->which);
9405     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9406         // [HGM] variantswitch: make engine aware of new variant
9407         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9408                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9409         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9410         SendToProgram(buf, cps);
9411         currentlyInitializedVariant = gameInfo.variant;
9412     }
9413     SendToProgram("force\n", cps);
9414     if (startedFromSetupPosition) {
9415         SendBoard(cps, backwardMostMove);
9416     if (appData.debugMode) {
9417         fprintf(debugFP, "feedMoves\n");
9418     }
9419     }
9420     for (i = backwardMostMove; i < upto; i++) {
9421         SendMoveToProgram(i, cps);
9422     }
9423 }
9424
9425
9426 void
9427 ResurrectChessProgram()
9428 {
9429      /* The chess program may have exited.
9430         If so, restart it and feed it all the moves made so far. */
9431
9432     if (appData.noChessProgram || first.pr != NoProc) return;
9433     
9434     StartChessProgram(&first);
9435     InitChessProgram(&first, FALSE);
9436     FeedMovesToProgram(&first, currentMove);
9437
9438     if (!first.sendTime) {
9439         /* can't tell gnuchess what its clock should read,
9440            so we bow to its notion. */
9441         ResetClocks();
9442         timeRemaining[0][currentMove] = whiteTimeRemaining;
9443         timeRemaining[1][currentMove] = blackTimeRemaining;
9444     }
9445
9446     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9447                 appData.icsEngineAnalyze) && first.analysisSupport) {
9448       SendToProgram("analyze\n", &first);
9449       first.analyzing = TRUE;
9450     }
9451 }
9452
9453 /*
9454  * Button procedures
9455  */
9456 void
9457 Reset(redraw, init)
9458      int redraw, init;
9459 {
9460     int i;
9461
9462     if (appData.debugMode) {
9463         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9464                 redraw, init, gameMode);
9465     }
9466     CleanupTail(); // [HGM] vari: delete any stored variations
9467     pausing = pauseExamInvalid = FALSE;
9468     startedFromSetupPosition = blackPlaysFirst = FALSE;
9469     firstMove = TRUE;
9470     whiteFlag = blackFlag = FALSE;
9471     userOfferedDraw = FALSE;
9472     hintRequested = bookRequested = FALSE;
9473     first.maybeThinking = FALSE;
9474     second.maybeThinking = FALSE;
9475     first.bookSuspend = FALSE; // [HGM] book
9476     second.bookSuspend = FALSE;
9477     thinkOutput[0] = NULLCHAR;
9478     lastHint[0] = NULLCHAR;
9479     ClearGameInfo(&gameInfo);
9480     gameInfo.variant = StringToVariant(appData.variant);
9481     ics_user_moved = ics_clock_paused = FALSE;
9482     ics_getting_history = H_FALSE;
9483     ics_gamenum = -1;
9484     white_holding[0] = black_holding[0] = NULLCHAR;
9485     ClearProgramStats();
9486     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9487     
9488     ResetFrontEnd();
9489     ClearHighlights();
9490     flipView = appData.flipView;
9491     ClearPremoveHighlights();
9492     gotPremove = FALSE;
9493     alarmSounded = FALSE;
9494
9495     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9496     if(appData.serverMovesName != NULL) {
9497         /* [HGM] prepare to make moves file for broadcasting */
9498         clock_t t = clock();
9499         if(serverMoves != NULL) fclose(serverMoves);
9500         serverMoves = fopen(appData.serverMovesName, "r");
9501         if(serverMoves != NULL) {
9502             fclose(serverMoves);
9503             /* delay 15 sec before overwriting, so all clients can see end */
9504             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9505         }
9506         serverMoves = fopen(appData.serverMovesName, "w");
9507     }
9508
9509     ExitAnalyzeMode();
9510     gameMode = BeginningOfGame;
9511     ModeHighlight();
9512     if(appData.icsActive) gameInfo.variant = VariantNormal;
9513     currentMove = forwardMostMove = backwardMostMove = 0;
9514     InitPosition(redraw);
9515     for (i = 0; i < MAX_MOVES; i++) {
9516         if (commentList[i] != NULL) {
9517             free(commentList[i]);
9518             commentList[i] = NULL;
9519         }
9520     }
9521     ResetClocks();
9522     timeRemaining[0][0] = whiteTimeRemaining;
9523     timeRemaining[1][0] = blackTimeRemaining;
9524     if (first.pr == NULL) {
9525         StartChessProgram(&first);
9526     }
9527     if (init) {
9528             InitChessProgram(&first, startedFromSetupPosition);
9529     }
9530     DisplayTitle("");
9531     DisplayMessage("", "");
9532     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9533     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9534 }
9535
9536 void
9537 AutoPlayGameLoop()
9538 {
9539     for (;;) {
9540         if (!AutoPlayOneMove())
9541           return;
9542         if (matchMode || appData.timeDelay == 0)
9543           continue;
9544         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9545           return;
9546         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9547         break;
9548     }
9549 }
9550
9551
9552 int
9553 AutoPlayOneMove()
9554 {
9555     int fromX, fromY, toX, toY;
9556
9557     if (appData.debugMode) {
9558       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9559     }
9560
9561     if (gameMode != PlayFromGameFile)
9562       return FALSE;
9563
9564     if (currentMove >= forwardMostMove) {
9565       gameMode = EditGame;
9566       ModeHighlight();
9567
9568       /* [AS] Clear current move marker at the end of a game */
9569       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9570
9571       return FALSE;
9572     }
9573     
9574     toX = moveList[currentMove][2] - AAA;
9575     toY = moveList[currentMove][3] - ONE;
9576
9577     if (moveList[currentMove][1] == '@') {
9578         if (appData.highlightLastMove) {
9579             SetHighlights(-1, -1, toX, toY);
9580         }
9581     } else {
9582         fromX = moveList[currentMove][0] - AAA;
9583         fromY = moveList[currentMove][1] - ONE;
9584
9585         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9586
9587         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9588
9589         if (appData.highlightLastMove) {
9590             SetHighlights(fromX, fromY, toX, toY);
9591         }
9592     }
9593     DisplayMove(currentMove);
9594     SendMoveToProgram(currentMove++, &first);
9595     DisplayBothClocks();
9596     DrawPosition(FALSE, boards[currentMove]);
9597     // [HGM] PV info: always display, routine tests if empty
9598     DisplayComment(currentMove - 1, commentList[currentMove]);
9599     return TRUE;
9600 }
9601
9602
9603 int
9604 LoadGameOneMove(readAhead)
9605      ChessMove readAhead;
9606 {
9607     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9608     char promoChar = NULLCHAR;
9609     ChessMove moveType;
9610     char move[MSG_SIZ];
9611     char *p, *q;
9612     
9613     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9614         gameMode != AnalyzeMode && gameMode != Training) {
9615         gameFileFP = NULL;
9616         return FALSE;
9617     }
9618     
9619     yyboardindex = forwardMostMove;
9620     if (readAhead != (ChessMove)0) {
9621       moveType = readAhead;
9622     } else {
9623       if (gameFileFP == NULL)
9624           return FALSE;
9625       moveType = (ChessMove) yylex();
9626     }
9627     
9628     done = FALSE;
9629     switch (moveType) {
9630       case Comment:
9631         if (appData.debugMode) 
9632           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9633         p = yy_text;
9634
9635         /* append the comment but don't display it */
9636         AppendComment(currentMove, p, FALSE);
9637         return TRUE;
9638
9639       case WhiteCapturesEnPassant:
9640       case BlackCapturesEnPassant:
9641       case WhitePromotionChancellor:
9642       case BlackPromotionChancellor:
9643       case WhitePromotionArchbishop:
9644       case BlackPromotionArchbishop:
9645       case WhitePromotionCentaur:
9646       case BlackPromotionCentaur:
9647       case WhitePromotionQueen:
9648       case BlackPromotionQueen:
9649       case WhitePromotionRook:
9650       case BlackPromotionRook:
9651       case WhitePromotionBishop:
9652       case BlackPromotionBishop:
9653       case WhitePromotionKnight:
9654       case BlackPromotionKnight:
9655       case WhitePromotionKing:
9656       case BlackPromotionKing:
9657       case WhiteNonPromotion:
9658       case BlackNonPromotion:
9659       case NormalMove:
9660       case WhiteKingSideCastle:
9661       case WhiteQueenSideCastle:
9662       case BlackKingSideCastle:
9663       case BlackQueenSideCastle:
9664       case WhiteKingSideCastleWild:
9665       case WhiteQueenSideCastleWild:
9666       case BlackKingSideCastleWild:
9667       case BlackQueenSideCastleWild:
9668       /* PUSH Fabien */
9669       case WhiteHSideCastleFR:
9670       case WhiteASideCastleFR:
9671       case BlackHSideCastleFR:
9672       case BlackASideCastleFR:
9673       /* POP Fabien */
9674         if (appData.debugMode)
9675           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9676         fromX = currentMoveString[0] - AAA;
9677         fromY = currentMoveString[1] - ONE;
9678         toX = currentMoveString[2] - AAA;
9679         toY = currentMoveString[3] - ONE;
9680         promoChar = currentMoveString[4];
9681         break;
9682
9683       case WhiteDrop:
9684       case BlackDrop:
9685         if (appData.debugMode)
9686           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9687         fromX = moveType == WhiteDrop ?
9688           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9689         (int) CharToPiece(ToLower(currentMoveString[0]));
9690         fromY = DROP_RANK;
9691         toX = currentMoveString[2] - AAA;
9692         toY = currentMoveString[3] - ONE;
9693         break;
9694
9695       case WhiteWins:
9696       case BlackWins:
9697       case GameIsDrawn:
9698       case GameUnfinished:
9699         if (appData.debugMode)
9700           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9701         p = strchr(yy_text, '{');
9702         if (p == NULL) p = strchr(yy_text, '(');
9703         if (p == NULL) {
9704             p = yy_text;
9705             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9706         } else {
9707             q = strchr(p, *p == '{' ? '}' : ')');
9708             if (q != NULL) *q = NULLCHAR;
9709             p++;
9710         }
9711         GameEnds(moveType, p, GE_FILE);
9712         done = TRUE;
9713         if (cmailMsgLoaded) {
9714             ClearHighlights();
9715             flipView = WhiteOnMove(currentMove);
9716             if (moveType == GameUnfinished) flipView = !flipView;
9717             if (appData.debugMode)
9718               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9719         }
9720         break;
9721
9722       case (ChessMove) 0:       /* end of file */
9723         if (appData.debugMode)
9724           fprintf(debugFP, "Parser hit end of file\n");
9725         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9726           case MT_NONE:
9727           case MT_CHECK:
9728             break;
9729           case MT_CHECKMATE:
9730           case MT_STAINMATE:
9731             if (WhiteOnMove(currentMove)) {
9732                 GameEnds(BlackWins, "Black mates", GE_FILE);
9733             } else {
9734                 GameEnds(WhiteWins, "White mates", GE_FILE);
9735             }
9736             break;
9737           case MT_STALEMATE:
9738             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9739             break;
9740         }
9741         done = TRUE;
9742         break;
9743
9744       case MoveNumberOne:
9745         if (lastLoadGameStart == GNUChessGame) {
9746             /* GNUChessGames have numbers, but they aren't move numbers */
9747             if (appData.debugMode)
9748               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9749                       yy_text, (int) moveType);
9750             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9751         }
9752         /* else fall thru */
9753
9754       case XBoardGame:
9755       case GNUChessGame:
9756       case PGNTag:
9757         /* Reached start of next game in file */
9758         if (appData.debugMode)
9759           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9760         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9761           case MT_NONE:
9762           case MT_CHECK:
9763             break;
9764           case MT_CHECKMATE:
9765           case MT_STAINMATE:
9766             if (WhiteOnMove(currentMove)) {
9767                 GameEnds(BlackWins, "Black mates", GE_FILE);
9768             } else {
9769                 GameEnds(WhiteWins, "White mates", GE_FILE);
9770             }
9771             break;
9772           case MT_STALEMATE:
9773             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9774             break;
9775         }
9776         done = TRUE;
9777         break;
9778
9779       case PositionDiagram:     /* should not happen; ignore */
9780       case ElapsedTime:         /* ignore */
9781       case NAG:                 /* ignore */
9782         if (appData.debugMode)
9783           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9784                   yy_text, (int) moveType);
9785         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9786
9787       case IllegalMove:
9788         if (appData.testLegality) {
9789             if (appData.debugMode)
9790               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9791             sprintf(move, _("Illegal move: %d.%s%s"),
9792                     (forwardMostMove / 2) + 1,
9793                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9794             DisplayError(move, 0);
9795             done = TRUE;
9796         } else {
9797             if (appData.debugMode)
9798               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9799                       yy_text, currentMoveString);
9800             fromX = currentMoveString[0] - AAA;
9801             fromY = currentMoveString[1] - ONE;
9802             toX = currentMoveString[2] - AAA;
9803             toY = currentMoveString[3] - ONE;
9804             promoChar = currentMoveString[4];
9805         }
9806         break;
9807
9808       case AmbiguousMove:
9809         if (appData.debugMode)
9810           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9811         sprintf(move, _("Ambiguous move: %d.%s%s"),
9812                 (forwardMostMove / 2) + 1,
9813                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9814         DisplayError(move, 0);
9815         done = TRUE;
9816         break;
9817
9818       default:
9819       case ImpossibleMove:
9820         if (appData.debugMode)
9821           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9822         sprintf(move, _("Illegal move: %d.%s%s"),
9823                 (forwardMostMove / 2) + 1,
9824                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9825         DisplayError(move, 0);
9826         done = TRUE;
9827         break;
9828     }
9829
9830     if (done) {
9831         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9832             DrawPosition(FALSE, boards[currentMove]);
9833             DisplayBothClocks();
9834             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9835               DisplayComment(currentMove - 1, commentList[currentMove]);
9836         }
9837         (void) StopLoadGameTimer();
9838         gameFileFP = NULL;
9839         cmailOldMove = forwardMostMove;
9840         return FALSE;
9841     } else {
9842         /* currentMoveString is set as a side-effect of yylex */
9843         strcat(currentMoveString, "\n");
9844         strcpy(moveList[forwardMostMove], currentMoveString);
9845         
9846         thinkOutput[0] = NULLCHAR;
9847         MakeMove(fromX, fromY, toX, toY, promoChar);
9848         currentMove = forwardMostMove;
9849         return TRUE;
9850     }
9851 }
9852
9853 /* Load the nth game from the given file */
9854 int
9855 LoadGameFromFile(filename, n, title, useList)
9856      char *filename;
9857      int n;
9858      char *title;
9859      /*Boolean*/ int useList;
9860 {
9861     FILE *f;
9862     char buf[MSG_SIZ];
9863
9864     if (strcmp(filename, "-") == 0) {
9865         f = stdin;
9866         title = "stdin";
9867     } else {
9868         f = fopen(filename, "rb");
9869         if (f == NULL) {
9870           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9871             DisplayError(buf, errno);
9872             return FALSE;
9873         }
9874     }
9875     if (fseek(f, 0, 0) == -1) {
9876         /* f is not seekable; probably a pipe */
9877         useList = FALSE;
9878     }
9879     if (useList && n == 0) {
9880         int error = GameListBuild(f);
9881         if (error) {
9882             DisplayError(_("Cannot build game list"), error);
9883         } else if (!ListEmpty(&gameList) &&
9884                    ((ListGame *) gameList.tailPred)->number > 1) {
9885             GameListPopUp(f, title);
9886             return TRUE;
9887         }
9888         GameListDestroy();
9889         n = 1;
9890     }
9891     if (n == 0) n = 1;
9892     return LoadGame(f, n, title, FALSE);
9893 }
9894
9895
9896 void
9897 MakeRegisteredMove()
9898 {
9899     int fromX, fromY, toX, toY;
9900     char promoChar;
9901     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9902         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9903           case CMAIL_MOVE:
9904           case CMAIL_DRAW:
9905             if (appData.debugMode)
9906               fprintf(debugFP, "Restoring %s for game %d\n",
9907                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9908     
9909             thinkOutput[0] = NULLCHAR;
9910             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9911             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9912             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9913             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9914             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9915             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9916             MakeMove(fromX, fromY, toX, toY, promoChar);
9917             ShowMove(fromX, fromY, toX, toY);
9918               
9919             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9920               case MT_NONE:
9921               case MT_CHECK:
9922                 break;
9923                 
9924               case MT_CHECKMATE:
9925               case MT_STAINMATE:
9926                 if (WhiteOnMove(currentMove)) {
9927                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9928                 } else {
9929                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9930                 }
9931                 break;
9932                 
9933               case MT_STALEMATE:
9934                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9935                 break;
9936             }
9937
9938             break;
9939             
9940           case CMAIL_RESIGN:
9941             if (WhiteOnMove(currentMove)) {
9942                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9943             } else {
9944                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9945             }
9946             break;
9947             
9948           case CMAIL_ACCEPT:
9949             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9950             break;
9951               
9952           default:
9953             break;
9954         }
9955     }
9956
9957     return;
9958 }
9959
9960 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9961 int
9962 CmailLoadGame(f, gameNumber, title, useList)
9963      FILE *f;
9964      int gameNumber;
9965      char *title;
9966      int useList;
9967 {
9968     int retVal;
9969
9970     if (gameNumber > nCmailGames) {
9971         DisplayError(_("No more games in this message"), 0);
9972         return FALSE;
9973     }
9974     if (f == lastLoadGameFP) {
9975         int offset = gameNumber - lastLoadGameNumber;
9976         if (offset == 0) {
9977             cmailMsg[0] = NULLCHAR;
9978             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9979                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9980                 nCmailMovesRegistered--;
9981             }
9982             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9983             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9984                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9985             }
9986         } else {
9987             if (! RegisterMove()) return FALSE;
9988         }
9989     }
9990
9991     retVal = LoadGame(f, gameNumber, title, useList);
9992
9993     /* Make move registered during previous look at this game, if any */
9994     MakeRegisteredMove();
9995
9996     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9997         commentList[currentMove]
9998           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9999         DisplayComment(currentMove - 1, commentList[currentMove]);
10000     }
10001
10002     return retVal;
10003 }
10004
10005 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10006 int
10007 ReloadGame(offset)
10008      int offset;
10009 {
10010     int gameNumber = lastLoadGameNumber + offset;
10011     if (lastLoadGameFP == NULL) {
10012         DisplayError(_("No game has been loaded yet"), 0);
10013         return FALSE;
10014     }
10015     if (gameNumber <= 0) {
10016         DisplayError(_("Can't back up any further"), 0);
10017         return FALSE;
10018     }
10019     if (cmailMsgLoaded) {
10020         return CmailLoadGame(lastLoadGameFP, gameNumber,
10021                              lastLoadGameTitle, lastLoadGameUseList);
10022     } else {
10023         return LoadGame(lastLoadGameFP, gameNumber,
10024                         lastLoadGameTitle, lastLoadGameUseList);
10025     }
10026 }
10027
10028
10029
10030 /* Load the nth game from open file f */
10031 int
10032 LoadGame(f, gameNumber, title, useList)
10033      FILE *f;
10034      int gameNumber;
10035      char *title;
10036      int useList;
10037 {
10038     ChessMove cm;
10039     char buf[MSG_SIZ];
10040     int gn = gameNumber;
10041     ListGame *lg = NULL;
10042     int numPGNTags = 0;
10043     int err;
10044     GameMode oldGameMode;
10045     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10046
10047     if (appData.debugMode) 
10048         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10049
10050     if (gameMode == Training )
10051         SetTrainingModeOff();
10052
10053     oldGameMode = gameMode;
10054     if (gameMode != BeginningOfGame) {
10055       Reset(FALSE, TRUE);
10056     }
10057
10058     gameFileFP = f;
10059     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10060         fclose(lastLoadGameFP);
10061     }
10062
10063     if (useList) {
10064         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10065         
10066         if (lg) {
10067             fseek(f, lg->offset, 0);
10068             GameListHighlight(gameNumber);
10069             gn = 1;
10070         }
10071         else {
10072             DisplayError(_("Game number out of range"), 0);
10073             return FALSE;
10074         }
10075     } else {
10076         GameListDestroy();
10077         if (fseek(f, 0, 0) == -1) {
10078             if (f == lastLoadGameFP ?
10079                 gameNumber == lastLoadGameNumber + 1 :
10080                 gameNumber == 1) {
10081                 gn = 1;
10082             } else {
10083                 DisplayError(_("Can't seek on game file"), 0);
10084                 return FALSE;
10085             }
10086         }
10087     }
10088     lastLoadGameFP = f;
10089     lastLoadGameNumber = gameNumber;
10090     strcpy(lastLoadGameTitle, title);
10091     lastLoadGameUseList = useList;
10092
10093     yynewfile(f);
10094
10095     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10096       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10097                 lg->gameInfo.black);
10098             DisplayTitle(buf);
10099     } else if (*title != NULLCHAR) {
10100         if (gameNumber > 1) {
10101             sprintf(buf, "%s %d", title, gameNumber);
10102             DisplayTitle(buf);
10103         } else {
10104             DisplayTitle(title);
10105         }
10106     }
10107
10108     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10109         gameMode = PlayFromGameFile;
10110         ModeHighlight();
10111     }
10112
10113     currentMove = forwardMostMove = backwardMostMove = 0;
10114     CopyBoard(boards[0], initialPosition);
10115     StopClocks();
10116
10117     /*
10118      * Skip the first gn-1 games in the file.
10119      * Also skip over anything that precedes an identifiable 
10120      * start of game marker, to avoid being confused by 
10121      * garbage at the start of the file.  Currently 
10122      * recognized start of game markers are the move number "1",
10123      * the pattern "gnuchess .* game", the pattern
10124      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10125      * A game that starts with one of the latter two patterns
10126      * will also have a move number 1, possibly
10127      * following a position diagram.
10128      * 5-4-02: Let's try being more lenient and allowing a game to
10129      * start with an unnumbered move.  Does that break anything?
10130      */
10131     cm = lastLoadGameStart = (ChessMove) 0;
10132     while (gn > 0) {
10133         yyboardindex = forwardMostMove;
10134         cm = (ChessMove) yylex();
10135         switch (cm) {
10136           case (ChessMove) 0:
10137             if (cmailMsgLoaded) {
10138                 nCmailGames = CMAIL_MAX_GAMES - gn;
10139             } else {
10140                 Reset(TRUE, TRUE);
10141                 DisplayError(_("Game not found in file"), 0);
10142             }
10143             return FALSE;
10144
10145           case GNUChessGame:
10146           case XBoardGame:
10147             gn--;
10148             lastLoadGameStart = cm;
10149             break;
10150             
10151           case MoveNumberOne:
10152             switch (lastLoadGameStart) {
10153               case GNUChessGame:
10154               case XBoardGame:
10155               case PGNTag:
10156                 break;
10157               case MoveNumberOne:
10158               case (ChessMove) 0:
10159                 gn--;           /* count this game */
10160                 lastLoadGameStart = cm;
10161                 break;
10162               default:
10163                 /* impossible */
10164                 break;
10165             }
10166             break;
10167
10168           case PGNTag:
10169             switch (lastLoadGameStart) {
10170               case GNUChessGame:
10171               case PGNTag:
10172               case MoveNumberOne:
10173               case (ChessMove) 0:
10174                 gn--;           /* count this game */
10175                 lastLoadGameStart = cm;
10176                 break;
10177               case XBoardGame:
10178                 lastLoadGameStart = cm; /* game counted already */
10179                 break;
10180               default:
10181                 /* impossible */
10182                 break;
10183             }
10184             if (gn > 0) {
10185                 do {
10186                     yyboardindex = forwardMostMove;
10187                     cm = (ChessMove) yylex();
10188                 } while (cm == PGNTag || cm == Comment);
10189             }
10190             break;
10191
10192           case WhiteWins:
10193           case BlackWins:
10194           case GameIsDrawn:
10195             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10196                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10197                     != CMAIL_OLD_RESULT) {
10198                     nCmailResults ++ ;
10199                     cmailResult[  CMAIL_MAX_GAMES
10200                                 - gn - 1] = CMAIL_OLD_RESULT;
10201                 }
10202             }
10203             break;
10204
10205           case NormalMove:
10206             /* Only a NormalMove can be at the start of a game
10207              * without a position diagram. */
10208             if (lastLoadGameStart == (ChessMove) 0) {
10209               gn--;
10210               lastLoadGameStart = MoveNumberOne;
10211             }
10212             break;
10213
10214           default:
10215             break;
10216         }
10217     }
10218     
10219     if (appData.debugMode)
10220       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10221
10222     if (cm == XBoardGame) {
10223         /* Skip any header junk before position diagram and/or move 1 */
10224         for (;;) {
10225             yyboardindex = forwardMostMove;
10226             cm = (ChessMove) yylex();
10227
10228             if (cm == (ChessMove) 0 ||
10229                 cm == GNUChessGame || cm == XBoardGame) {
10230                 /* Empty game; pretend end-of-file and handle later */
10231                 cm = (ChessMove) 0;
10232                 break;
10233             }
10234
10235             if (cm == MoveNumberOne || cm == PositionDiagram ||
10236                 cm == PGNTag || cm == Comment)
10237               break;
10238         }
10239     } else if (cm == GNUChessGame) {
10240         if (gameInfo.event != NULL) {
10241             free(gameInfo.event);
10242         }
10243         gameInfo.event = StrSave(yy_text);
10244     }   
10245
10246     startedFromSetupPosition = FALSE;
10247     while (cm == PGNTag) {
10248         if (appData.debugMode) 
10249           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10250         err = ParsePGNTag(yy_text, &gameInfo);
10251         if (!err) numPGNTags++;
10252
10253         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10254         if(gameInfo.variant != oldVariant) {
10255             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10256             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10257             InitPosition(TRUE);
10258             oldVariant = gameInfo.variant;
10259             if (appData.debugMode) 
10260               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10261         }
10262
10263
10264         if (gameInfo.fen != NULL) {
10265           Board initial_position;
10266           startedFromSetupPosition = TRUE;
10267           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10268             Reset(TRUE, TRUE);
10269             DisplayError(_("Bad FEN position in file"), 0);
10270             return FALSE;
10271           }
10272           CopyBoard(boards[0], initial_position);
10273           if (blackPlaysFirst) {
10274             currentMove = forwardMostMove = backwardMostMove = 1;
10275             CopyBoard(boards[1], initial_position);
10276             strcpy(moveList[0], "");
10277             strcpy(parseList[0], "");
10278             timeRemaining[0][1] = whiteTimeRemaining;
10279             timeRemaining[1][1] = blackTimeRemaining;
10280             if (commentList[0] != NULL) {
10281               commentList[1] = commentList[0];
10282               commentList[0] = NULL;
10283             }
10284           } else {
10285             currentMove = forwardMostMove = backwardMostMove = 0;
10286           }
10287           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10288           {   int i;
10289               initialRulePlies = FENrulePlies;
10290               for( i=0; i< nrCastlingRights; i++ )
10291                   initialRights[i] = initial_position[CASTLING][i];
10292           }
10293           yyboardindex = forwardMostMove;
10294           free(gameInfo.fen);
10295           gameInfo.fen = NULL;
10296         }
10297
10298         yyboardindex = forwardMostMove;
10299         cm = (ChessMove) yylex();
10300
10301         /* Handle comments interspersed among the tags */
10302         while (cm == Comment) {
10303             char *p;
10304             if (appData.debugMode) 
10305               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10306             p = yy_text;
10307             AppendComment(currentMove, p, FALSE);
10308             yyboardindex = forwardMostMove;
10309             cm = (ChessMove) yylex();
10310         }
10311     }
10312
10313     /* don't rely on existence of Event tag since if game was
10314      * pasted from clipboard the Event tag may not exist
10315      */
10316     if (numPGNTags > 0){
10317         char *tags;
10318         if (gameInfo.variant == VariantNormal) {
10319           VariantClass v = StringToVariant(gameInfo.event);
10320           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10321           if(v < VariantShogi) gameInfo.variant = v;
10322         }
10323         if (!matchMode) {
10324           if( appData.autoDisplayTags ) {
10325             tags = PGNTags(&gameInfo);
10326             TagsPopUp(tags, CmailMsg());
10327             free(tags);
10328           }
10329         }
10330     } else {
10331         /* Make something up, but don't display it now */
10332         SetGameInfo();
10333         TagsPopDown();
10334     }
10335
10336     if (cm == PositionDiagram) {
10337         int i, j;
10338         char *p;
10339         Board initial_position;
10340
10341         if (appData.debugMode)
10342           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10343
10344         if (!startedFromSetupPosition) {
10345             p = yy_text;
10346             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10347               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10348                 switch (*p) {
10349                   case '[':
10350                   case '-':
10351                   case ' ':
10352                   case '\t':
10353                   case '\n':
10354                   case '\r':
10355                     break;
10356                   default:
10357                     initial_position[i][j++] = CharToPiece(*p);
10358                     break;
10359                 }
10360             while (*p == ' ' || *p == '\t' ||
10361                    *p == '\n' || *p == '\r') p++;
10362         
10363             if (strncmp(p, "black", strlen("black"))==0)
10364               blackPlaysFirst = TRUE;
10365             else
10366               blackPlaysFirst = FALSE;
10367             startedFromSetupPosition = TRUE;
10368         
10369             CopyBoard(boards[0], initial_position);
10370             if (blackPlaysFirst) {
10371                 currentMove = forwardMostMove = backwardMostMove = 1;
10372                 CopyBoard(boards[1], initial_position);
10373                 strcpy(moveList[0], "");
10374                 strcpy(parseList[0], "");
10375                 timeRemaining[0][1] = whiteTimeRemaining;
10376                 timeRemaining[1][1] = blackTimeRemaining;
10377                 if (commentList[0] != NULL) {
10378                     commentList[1] = commentList[0];
10379                     commentList[0] = NULL;
10380                 }
10381             } else {
10382                 currentMove = forwardMostMove = backwardMostMove = 0;
10383             }
10384         }
10385         yyboardindex = forwardMostMove;
10386         cm = (ChessMove) yylex();
10387     }
10388
10389     if (first.pr == NoProc) {
10390         StartChessProgram(&first);
10391     }
10392     InitChessProgram(&first, FALSE);
10393     SendToProgram("force\n", &first);
10394     if (startedFromSetupPosition) {
10395         SendBoard(&first, forwardMostMove);
10396     if (appData.debugMode) {
10397         fprintf(debugFP, "Load Game\n");
10398     }
10399         DisplayBothClocks();
10400     }      
10401
10402     /* [HGM] server: flag to write setup moves in broadcast file as one */
10403     loadFlag = appData.suppressLoadMoves;
10404
10405     while (cm == Comment) {
10406         char *p;
10407         if (appData.debugMode) 
10408           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10409         p = yy_text;
10410         AppendComment(currentMove, p, FALSE);
10411         yyboardindex = forwardMostMove;
10412         cm = (ChessMove) yylex();
10413     }
10414
10415     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10416         cm == WhiteWins || cm == BlackWins ||
10417         cm == GameIsDrawn || cm == GameUnfinished) {
10418         DisplayMessage("", _("No moves in game"));
10419         if (cmailMsgLoaded) {
10420             if (appData.debugMode)
10421               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10422             ClearHighlights();
10423             flipView = FALSE;
10424         }
10425         DrawPosition(FALSE, boards[currentMove]);
10426         DisplayBothClocks();
10427         gameMode = EditGame;
10428         ModeHighlight();
10429         gameFileFP = NULL;
10430         cmailOldMove = 0;
10431         return TRUE;
10432     }
10433
10434     // [HGM] PV info: routine tests if comment empty
10435     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10436         DisplayComment(currentMove - 1, commentList[currentMove]);
10437     }
10438     if (!matchMode && appData.timeDelay != 0) 
10439       DrawPosition(FALSE, boards[currentMove]);
10440
10441     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10442       programStats.ok_to_send = 1;
10443     }
10444
10445     /* if the first token after the PGN tags is a move
10446      * and not move number 1, retrieve it from the parser 
10447      */
10448     if (cm != MoveNumberOne)
10449         LoadGameOneMove(cm);
10450
10451     /* load the remaining moves from the file */
10452     while (LoadGameOneMove((ChessMove)0)) {
10453       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10454       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10455     }
10456
10457     /* rewind to the start of the game */
10458     currentMove = backwardMostMove;
10459
10460     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10461
10462     if (oldGameMode == AnalyzeFile ||
10463         oldGameMode == AnalyzeMode) {
10464       AnalyzeFileEvent();
10465     }
10466
10467     if (matchMode || appData.timeDelay == 0) {
10468       ToEndEvent();
10469       gameMode = EditGame;
10470       ModeHighlight();
10471     } else if (appData.timeDelay > 0) {
10472       AutoPlayGameLoop();
10473     }
10474
10475     if (appData.debugMode) 
10476         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10477
10478     loadFlag = 0; /* [HGM] true game starts */
10479     return TRUE;
10480 }
10481
10482 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10483 int
10484 ReloadPosition(offset)
10485      int offset;
10486 {
10487     int positionNumber = lastLoadPositionNumber + offset;
10488     if (lastLoadPositionFP == NULL) {
10489         DisplayError(_("No position has been loaded yet"), 0);
10490         return FALSE;
10491     }
10492     if (positionNumber <= 0) {
10493         DisplayError(_("Can't back up any further"), 0);
10494         return FALSE;
10495     }
10496     return LoadPosition(lastLoadPositionFP, positionNumber,
10497                         lastLoadPositionTitle);
10498 }
10499
10500 /* Load the nth position from the given file */
10501 int
10502 LoadPositionFromFile(filename, n, title)
10503      char *filename;
10504      int n;
10505      char *title;
10506 {
10507     FILE *f;
10508     char buf[MSG_SIZ];
10509
10510     if (strcmp(filename, "-") == 0) {
10511         return LoadPosition(stdin, n, "stdin");
10512     } else {
10513         f = fopen(filename, "rb");
10514         if (f == NULL) {
10515             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10516             DisplayError(buf, errno);
10517             return FALSE;
10518         } else {
10519             return LoadPosition(f, n, title);
10520         }
10521     }
10522 }
10523
10524 /* Load the nth position from the given open file, and close it */
10525 int
10526 LoadPosition(f, positionNumber, title)
10527      FILE *f;
10528      int positionNumber;
10529      char *title;
10530 {
10531     char *p, line[MSG_SIZ];
10532     Board initial_position;
10533     int i, j, fenMode, pn;
10534     
10535     if (gameMode == Training )
10536         SetTrainingModeOff();
10537
10538     if (gameMode != BeginningOfGame) {
10539         Reset(FALSE, TRUE);
10540     }
10541     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10542         fclose(lastLoadPositionFP);
10543     }
10544     if (positionNumber == 0) positionNumber = 1;
10545     lastLoadPositionFP = f;
10546     lastLoadPositionNumber = positionNumber;
10547     strcpy(lastLoadPositionTitle, title);
10548     if (first.pr == NoProc) {
10549       StartChessProgram(&first);
10550       InitChessProgram(&first, FALSE);
10551     }    
10552     pn = positionNumber;
10553     if (positionNumber < 0) {
10554         /* Negative position number means to seek to that byte offset */
10555         if (fseek(f, -positionNumber, 0) == -1) {
10556             DisplayError(_("Can't seek on position file"), 0);
10557             return FALSE;
10558         };
10559         pn = 1;
10560     } else {
10561         if (fseek(f, 0, 0) == -1) {
10562             if (f == lastLoadPositionFP ?
10563                 positionNumber == lastLoadPositionNumber + 1 :
10564                 positionNumber == 1) {
10565                 pn = 1;
10566             } else {
10567                 DisplayError(_("Can't seek on position file"), 0);
10568                 return FALSE;
10569             }
10570         }
10571     }
10572     /* See if this file is FEN or old-style xboard */
10573     if (fgets(line, MSG_SIZ, f) == NULL) {
10574         DisplayError(_("Position not found in file"), 0);
10575         return FALSE;
10576     }
10577     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10578     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10579
10580     if (pn >= 2) {
10581         if (fenMode || line[0] == '#') pn--;
10582         while (pn > 0) {
10583             /* skip positions before number pn */
10584             if (fgets(line, MSG_SIZ, f) == NULL) {
10585                 Reset(TRUE, TRUE);
10586                 DisplayError(_("Position not found in file"), 0);
10587                 return FALSE;
10588             }
10589             if (fenMode || line[0] == '#') pn--;
10590         }
10591     }
10592
10593     if (fenMode) {
10594         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10595             DisplayError(_("Bad FEN position in file"), 0);
10596             return FALSE;
10597         }
10598     } else {
10599         (void) fgets(line, MSG_SIZ, f);
10600         (void) fgets(line, MSG_SIZ, f);
10601     
10602         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10603             (void) fgets(line, MSG_SIZ, f);
10604             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10605                 if (*p == ' ')
10606                   continue;
10607                 initial_position[i][j++] = CharToPiece(*p);
10608             }
10609         }
10610     
10611         blackPlaysFirst = FALSE;
10612         if (!feof(f)) {
10613             (void) fgets(line, MSG_SIZ, f);
10614             if (strncmp(line, "black", strlen("black"))==0)
10615               blackPlaysFirst = TRUE;
10616         }
10617     }
10618     startedFromSetupPosition = TRUE;
10619     
10620     SendToProgram("force\n", &first);
10621     CopyBoard(boards[0], initial_position);
10622     if (blackPlaysFirst) {
10623         currentMove = forwardMostMove = backwardMostMove = 1;
10624         strcpy(moveList[0], "");
10625         strcpy(parseList[0], "");
10626         CopyBoard(boards[1], initial_position);
10627         DisplayMessage("", _("Black to play"));
10628     } else {
10629         currentMove = forwardMostMove = backwardMostMove = 0;
10630         DisplayMessage("", _("White to play"));
10631     }
10632     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10633     SendBoard(&first, forwardMostMove);
10634     if (appData.debugMode) {
10635 int i, j;
10636   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10637   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10638         fprintf(debugFP, "Load Position\n");
10639     }
10640
10641     if (positionNumber > 1) {
10642         sprintf(line, "%s %d", title, positionNumber);
10643         DisplayTitle(line);
10644     } else {
10645         DisplayTitle(title);
10646     }
10647     gameMode = EditGame;
10648     ModeHighlight();
10649     ResetClocks();
10650     timeRemaining[0][1] = whiteTimeRemaining;
10651     timeRemaining[1][1] = blackTimeRemaining;
10652     DrawPosition(FALSE, boards[currentMove]);
10653    
10654     return TRUE;
10655 }
10656
10657
10658 void
10659 CopyPlayerNameIntoFileName(dest, src)
10660      char **dest, *src;
10661 {
10662     while (*src != NULLCHAR && *src != ',') {
10663         if (*src == ' ') {
10664             *(*dest)++ = '_';
10665             src++;
10666         } else {
10667             *(*dest)++ = *src++;
10668         }
10669     }
10670 }
10671
10672 char *DefaultFileName(ext)
10673      char *ext;
10674 {
10675     static char def[MSG_SIZ];
10676     char *p;
10677
10678     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10679         p = def;
10680         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10681         *p++ = '-';
10682         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10683         *p++ = '.';
10684         strcpy(p, ext);
10685     } else {
10686         def[0] = NULLCHAR;
10687     }
10688     return def;
10689 }
10690
10691 /* Save the current game to the given file */
10692 int
10693 SaveGameToFile(filename, append)
10694      char *filename;
10695      int append;
10696 {
10697     FILE *f;
10698     char buf[MSG_SIZ];
10699
10700     if (strcmp(filename, "-") == 0) {
10701         return SaveGame(stdout, 0, NULL);
10702     } else {
10703         f = fopen(filename, append ? "a" : "w");
10704         if (f == NULL) {
10705             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10706             DisplayError(buf, errno);
10707             return FALSE;
10708         } else {
10709             return SaveGame(f, 0, NULL);
10710         }
10711     }
10712 }
10713
10714 char *
10715 SavePart(str)
10716      char *str;
10717 {
10718     static char buf[MSG_SIZ];
10719     char *p;
10720     
10721     p = strchr(str, ' ');
10722     if (p == NULL) return str;
10723     strncpy(buf, str, p - str);
10724     buf[p - str] = NULLCHAR;
10725     return buf;
10726 }
10727
10728 #define PGN_MAX_LINE 75
10729
10730 #define PGN_SIDE_WHITE  0
10731 #define PGN_SIDE_BLACK  1
10732
10733 /* [AS] */
10734 static int FindFirstMoveOutOfBook( int side )
10735 {
10736     int result = -1;
10737
10738     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10739         int index = backwardMostMove;
10740         int has_book_hit = 0;
10741
10742         if( (index % 2) != side ) {
10743             index++;
10744         }
10745
10746         while( index < forwardMostMove ) {
10747             /* Check to see if engine is in book */
10748             int depth = pvInfoList[index].depth;
10749             int score = pvInfoList[index].score;
10750             int in_book = 0;
10751
10752             if( depth <= 2 ) {
10753                 in_book = 1;
10754             }
10755             else if( score == 0 && depth == 63 ) {
10756                 in_book = 1; /* Zappa */
10757             }
10758             else if( score == 2 && depth == 99 ) {
10759                 in_book = 1; /* Abrok */
10760             }
10761
10762             has_book_hit += in_book;
10763
10764             if( ! in_book ) {
10765                 result = index;
10766
10767                 break;
10768             }
10769
10770             index += 2;
10771         }
10772     }
10773
10774     return result;
10775 }
10776
10777 /* [AS] */
10778 void GetOutOfBookInfo( char * buf )
10779 {
10780     int oob[2];
10781     int i;
10782     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10783
10784     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10785     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10786
10787     *buf = '\0';
10788
10789     if( oob[0] >= 0 || oob[1] >= 0 ) {
10790         for( i=0; i<2; i++ ) {
10791             int idx = oob[i];
10792
10793             if( idx >= 0 ) {
10794                 if( i > 0 && oob[0] >= 0 ) {
10795                     strcat( buf, "   " );
10796                 }
10797
10798                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10799                 sprintf( buf+strlen(buf), "%s%.2f", 
10800                     pvInfoList[idx].score >= 0 ? "+" : "",
10801                     pvInfoList[idx].score / 100.0 );
10802             }
10803         }
10804     }
10805 }
10806
10807 /* Save game in PGN style and close the file */
10808 int
10809 SaveGamePGN(f)
10810      FILE *f;
10811 {
10812     int i, offset, linelen, newblock;
10813     time_t tm;
10814 //    char *movetext;
10815     char numtext[32];
10816     int movelen, numlen, blank;
10817     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10818
10819     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10820     
10821     tm = time((time_t *) NULL);
10822     
10823     PrintPGNTags(f, &gameInfo);
10824     
10825     if (backwardMostMove > 0 || startedFromSetupPosition) {
10826         char *fen = PositionToFEN(backwardMostMove, NULL);
10827         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10828         fprintf(f, "\n{--------------\n");
10829         PrintPosition(f, backwardMostMove);
10830         fprintf(f, "--------------}\n");
10831         free(fen);
10832     }
10833     else {
10834         /* [AS] Out of book annotation */
10835         if( appData.saveOutOfBookInfo ) {
10836             char buf[64];
10837
10838             GetOutOfBookInfo( buf );
10839
10840             if( buf[0] != '\0' ) {
10841                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10842             }
10843         }
10844
10845         fprintf(f, "\n");
10846     }
10847
10848     i = backwardMostMove;
10849     linelen = 0;
10850     newblock = TRUE;
10851
10852     while (i < forwardMostMove) {
10853         /* Print comments preceding this move */
10854         if (commentList[i] != NULL) {
10855             if (linelen > 0) fprintf(f, "\n");
10856             fprintf(f, "%s", commentList[i]);
10857             linelen = 0;
10858             newblock = TRUE;
10859         }
10860
10861         /* Format move number */
10862         if ((i % 2) == 0) {
10863             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10864         } else {
10865             if (newblock) {
10866                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10867             } else {
10868                 numtext[0] = NULLCHAR;
10869             }
10870         }
10871         numlen = strlen(numtext);
10872         newblock = FALSE;
10873
10874         /* Print move number */
10875         blank = linelen > 0 && numlen > 0;
10876         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10877             fprintf(f, "\n");
10878             linelen = 0;
10879             blank = 0;
10880         }
10881         if (blank) {
10882             fprintf(f, " ");
10883             linelen++;
10884         }
10885         fprintf(f, "%s", numtext);
10886         linelen += numlen;
10887
10888         /* Get move */
10889         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10890         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10891
10892         /* Print move */
10893         blank = linelen > 0 && movelen > 0;
10894         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10895             fprintf(f, "\n");
10896             linelen = 0;
10897             blank = 0;
10898         }
10899         if (blank) {
10900             fprintf(f, " ");
10901             linelen++;
10902         }
10903         fprintf(f, "%s", move_buffer);
10904         linelen += movelen;
10905
10906         /* [AS] Add PV info if present */
10907         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10908             /* [HGM] add time */
10909             char buf[MSG_SIZ]; int seconds;
10910
10911             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10912
10913             if( seconds <= 0) buf[0] = 0; else
10914             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10915                 seconds = (seconds + 4)/10; // round to full seconds
10916                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10917                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10918             }
10919
10920             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10921                 pvInfoList[i].score >= 0 ? "+" : "",
10922                 pvInfoList[i].score / 100.0,
10923                 pvInfoList[i].depth,
10924                 buf );
10925
10926             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10927
10928             /* Print score/depth */
10929             blank = linelen > 0 && movelen > 0;
10930             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10931                 fprintf(f, "\n");
10932                 linelen = 0;
10933                 blank = 0;
10934             }
10935             if (blank) {
10936                 fprintf(f, " ");
10937                 linelen++;
10938             }
10939             fprintf(f, "%s", move_buffer);
10940             linelen += movelen;
10941         }
10942
10943         i++;
10944     }
10945     
10946     /* Start a new line */
10947     if (linelen > 0) fprintf(f, "\n");
10948
10949     /* Print comments after last move */
10950     if (commentList[i] != NULL) {
10951         fprintf(f, "%s\n", commentList[i]);
10952     }
10953
10954     /* Print result */
10955     if (gameInfo.resultDetails != NULL &&
10956         gameInfo.resultDetails[0] != NULLCHAR) {
10957         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10958                 PGNResult(gameInfo.result));
10959     } else {
10960         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10961     }
10962
10963     fclose(f);
10964     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10965     return TRUE;
10966 }
10967
10968 /* Save game in old style and close the file */
10969 int
10970 SaveGameOldStyle(f)
10971      FILE *f;
10972 {
10973     int i, offset;
10974     time_t tm;
10975     
10976     tm = time((time_t *) NULL);
10977     
10978     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10979     PrintOpponents(f);
10980     
10981     if (backwardMostMove > 0 || startedFromSetupPosition) {
10982         fprintf(f, "\n[--------------\n");
10983         PrintPosition(f, backwardMostMove);
10984         fprintf(f, "--------------]\n");
10985     } else {
10986         fprintf(f, "\n");
10987     }
10988
10989     i = backwardMostMove;
10990     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10991
10992     while (i < forwardMostMove) {
10993         if (commentList[i] != NULL) {
10994             fprintf(f, "[%s]\n", commentList[i]);
10995         }
10996
10997         if ((i % 2) == 1) {
10998             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10999             i++;
11000         } else {
11001             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11002             i++;
11003             if (commentList[i] != NULL) {
11004                 fprintf(f, "\n");
11005                 continue;
11006             }
11007             if (i >= forwardMostMove) {
11008                 fprintf(f, "\n");
11009                 break;
11010             }
11011             fprintf(f, "%s\n", parseList[i]);
11012             i++;
11013         }
11014     }
11015     
11016     if (commentList[i] != NULL) {
11017         fprintf(f, "[%s]\n", commentList[i]);
11018     }
11019
11020     /* This isn't really the old style, but it's close enough */
11021     if (gameInfo.resultDetails != NULL &&
11022         gameInfo.resultDetails[0] != NULLCHAR) {
11023         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11024                 gameInfo.resultDetails);
11025     } else {
11026         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11027     }
11028
11029     fclose(f);
11030     return TRUE;
11031 }
11032
11033 /* Save the current game to open file f and close the file */
11034 int
11035 SaveGame(f, dummy, dummy2)
11036      FILE *f;
11037      int dummy;
11038      char *dummy2;
11039 {
11040     if (gameMode == EditPosition) EditPositionDone(TRUE);
11041     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11042     if (appData.oldSaveStyle)
11043       return SaveGameOldStyle(f);
11044     else
11045       return SaveGamePGN(f);
11046 }
11047
11048 /* Save the current position to the given file */
11049 int
11050 SavePositionToFile(filename)
11051      char *filename;
11052 {
11053     FILE *f;
11054     char buf[MSG_SIZ];
11055
11056     if (strcmp(filename, "-") == 0) {
11057         return SavePosition(stdout, 0, NULL);
11058     } else {
11059         f = fopen(filename, "a");
11060         if (f == NULL) {
11061             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11062             DisplayError(buf, errno);
11063             return FALSE;
11064         } else {
11065             SavePosition(f, 0, NULL);
11066             return TRUE;
11067         }
11068     }
11069 }
11070
11071 /* Save the current position to the given open file and close the file */
11072 int
11073 SavePosition(f, dummy, dummy2)
11074      FILE *f;
11075      int dummy;
11076      char *dummy2;
11077 {
11078     time_t tm;
11079     char *fen;
11080     
11081     if (gameMode == EditPosition) EditPositionDone(TRUE);
11082     if (appData.oldSaveStyle) {
11083         tm = time((time_t *) NULL);
11084     
11085         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11086         PrintOpponents(f);
11087         fprintf(f, "[--------------\n");
11088         PrintPosition(f, currentMove);
11089         fprintf(f, "--------------]\n");
11090     } else {
11091         fen = PositionToFEN(currentMove, NULL);
11092         fprintf(f, "%s\n", fen);
11093         free(fen);
11094     }
11095     fclose(f);
11096     return TRUE;
11097 }
11098
11099 void
11100 ReloadCmailMsgEvent(unregister)
11101      int unregister;
11102 {
11103 #if !WIN32
11104     static char *inFilename = NULL;
11105     static char *outFilename;
11106     int i;
11107     struct stat inbuf, outbuf;
11108     int status;
11109     
11110     /* Any registered moves are unregistered if unregister is set, */
11111     /* i.e. invoked by the signal handler */
11112     if (unregister) {
11113         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11114             cmailMoveRegistered[i] = FALSE;
11115             if (cmailCommentList[i] != NULL) {
11116                 free(cmailCommentList[i]);
11117                 cmailCommentList[i] = NULL;
11118             }
11119         }
11120         nCmailMovesRegistered = 0;
11121     }
11122
11123     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11124         cmailResult[i] = CMAIL_NOT_RESULT;
11125     }
11126     nCmailResults = 0;
11127
11128     if (inFilename == NULL) {
11129         /* Because the filenames are static they only get malloced once  */
11130         /* and they never get freed                                      */
11131         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11132         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11133
11134         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11135         sprintf(outFilename, "%s.out", appData.cmailGameName);
11136     }
11137     
11138     status = stat(outFilename, &outbuf);
11139     if (status < 0) {
11140         cmailMailedMove = FALSE;
11141     } else {
11142         status = stat(inFilename, &inbuf);
11143         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11144     }
11145     
11146     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11147        counts the games, notes how each one terminated, etc.
11148        
11149        It would be nice to remove this kludge and instead gather all
11150        the information while building the game list.  (And to keep it
11151        in the game list nodes instead of having a bunch of fixed-size
11152        parallel arrays.)  Note this will require getting each game's
11153        termination from the PGN tags, as the game list builder does
11154        not process the game moves.  --mann
11155        */
11156     cmailMsgLoaded = TRUE;
11157     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11158     
11159     /* Load first game in the file or popup game menu */
11160     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11161
11162 #endif /* !WIN32 */
11163     return;
11164 }
11165
11166 int
11167 RegisterMove()
11168 {
11169     FILE *f;
11170     char string[MSG_SIZ];
11171
11172     if (   cmailMailedMove
11173         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11174         return TRUE;            /* Allow free viewing  */
11175     }
11176
11177     /* Unregister move to ensure that we don't leave RegisterMove        */
11178     /* with the move registered when the conditions for registering no   */
11179     /* longer hold                                                       */
11180     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11181         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11182         nCmailMovesRegistered --;
11183
11184         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11185           {
11186               free(cmailCommentList[lastLoadGameNumber - 1]);
11187               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11188           }
11189     }
11190
11191     if (cmailOldMove == -1) {
11192         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11193         return FALSE;
11194     }
11195
11196     if (currentMove > cmailOldMove + 1) {
11197         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11198         return FALSE;
11199     }
11200
11201     if (currentMove < cmailOldMove) {
11202         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11203         return FALSE;
11204     }
11205
11206     if (forwardMostMove > currentMove) {
11207         /* Silently truncate extra moves */
11208         TruncateGame();
11209     }
11210
11211     if (   (currentMove == cmailOldMove + 1)
11212         || (   (currentMove == cmailOldMove)
11213             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11214                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11215         if (gameInfo.result != GameUnfinished) {
11216             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11217         }
11218
11219         if (commentList[currentMove] != NULL) {
11220             cmailCommentList[lastLoadGameNumber - 1]
11221               = StrSave(commentList[currentMove]);
11222         }
11223         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11224
11225         if (appData.debugMode)
11226           fprintf(debugFP, "Saving %s for game %d\n",
11227                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11228
11229         sprintf(string,
11230                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11231         
11232         f = fopen(string, "w");
11233         if (appData.oldSaveStyle) {
11234             SaveGameOldStyle(f); /* also closes the file */
11235             
11236             sprintf(string, "%s.pos.out", appData.cmailGameName);
11237             f = fopen(string, "w");
11238             SavePosition(f, 0, NULL); /* also closes the file */
11239         } else {
11240             fprintf(f, "{--------------\n");
11241             PrintPosition(f, currentMove);
11242             fprintf(f, "--------------}\n\n");
11243             
11244             SaveGame(f, 0, NULL); /* also closes the file*/
11245         }
11246         
11247         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11248         nCmailMovesRegistered ++;
11249     } else if (nCmailGames == 1) {
11250         DisplayError(_("You have not made a move yet"), 0);
11251         return FALSE;
11252     }
11253
11254     return TRUE;
11255 }
11256
11257 void
11258 MailMoveEvent()
11259 {
11260 #if !WIN32
11261     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11262     FILE *commandOutput;
11263     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11264     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11265     int nBuffers;
11266     int i;
11267     int archived;
11268     char *arcDir;
11269
11270     if (! cmailMsgLoaded) {
11271         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11272         return;
11273     }
11274
11275     if (nCmailGames == nCmailResults) {
11276         DisplayError(_("No unfinished games"), 0);
11277         return;
11278     }
11279
11280 #if CMAIL_PROHIBIT_REMAIL
11281     if (cmailMailedMove) {
11282         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);
11283         DisplayError(msg, 0);
11284         return;
11285     }
11286 #endif
11287
11288     if (! (cmailMailedMove || RegisterMove())) return;
11289     
11290     if (   cmailMailedMove
11291         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11292         sprintf(string, partCommandString,
11293                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11294         commandOutput = popen(string, "r");
11295
11296         if (commandOutput == NULL) {
11297             DisplayError(_("Failed to invoke cmail"), 0);
11298         } else {
11299             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11300                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11301             }
11302             if (nBuffers > 1) {
11303                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11304                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11305                 nBytes = MSG_SIZ - 1;
11306             } else {
11307                 (void) memcpy(msg, buffer, nBytes);
11308             }
11309             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11310
11311             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11312                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11313
11314                 archived = TRUE;
11315                 for (i = 0; i < nCmailGames; i ++) {
11316                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11317                         archived = FALSE;
11318                     }
11319                 }
11320                 if (   archived
11321                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11322                         != NULL)) {
11323                     sprintf(buffer, "%s/%s.%s.archive",
11324                             arcDir,
11325                             appData.cmailGameName,
11326                             gameInfo.date);
11327                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11328                     cmailMsgLoaded = FALSE;
11329                 }
11330             }
11331
11332             DisplayInformation(msg);
11333             pclose(commandOutput);
11334         }
11335     } else {
11336         if ((*cmailMsg) != '\0') {
11337             DisplayInformation(cmailMsg);
11338         }
11339     }
11340
11341     return;
11342 #endif /* !WIN32 */
11343 }
11344
11345 char *
11346 CmailMsg()
11347 {
11348 #if WIN32
11349     return NULL;
11350 #else
11351     int  prependComma = 0;
11352     char number[5];
11353     char string[MSG_SIZ];       /* Space for game-list */
11354     int  i;
11355     
11356     if (!cmailMsgLoaded) return "";
11357
11358     if (cmailMailedMove) {
11359         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11360     } else {
11361         /* Create a list of games left */
11362         sprintf(string, "[");
11363         for (i = 0; i < nCmailGames; i ++) {
11364             if (! (   cmailMoveRegistered[i]
11365                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11366                 if (prependComma) {
11367                     sprintf(number, ",%d", i + 1);
11368                 } else {
11369                     sprintf(number, "%d", i + 1);
11370                     prependComma = 1;
11371                 }
11372                 
11373                 strcat(string, number);
11374             }
11375         }
11376         strcat(string, "]");
11377
11378         if (nCmailMovesRegistered + nCmailResults == 0) {
11379             switch (nCmailGames) {
11380               case 1:
11381                 sprintf(cmailMsg,
11382                         _("Still need to make move for game\n"));
11383                 break;
11384                 
11385               case 2:
11386                 sprintf(cmailMsg,
11387                         _("Still need to make moves for both games\n"));
11388                 break;
11389                 
11390               default:
11391                 sprintf(cmailMsg,
11392                         _("Still need to make moves for all %d games\n"),
11393                         nCmailGames);
11394                 break;
11395             }
11396         } else {
11397             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11398               case 1:
11399                 sprintf(cmailMsg,
11400                         _("Still need to make a move for game %s\n"),
11401                         string);
11402                 break;
11403                 
11404               case 0:
11405                 if (nCmailResults == nCmailGames) {
11406                     sprintf(cmailMsg, _("No unfinished games\n"));
11407                 } else {
11408                     sprintf(cmailMsg, _("Ready to send mail\n"));
11409                 }
11410                 break;
11411                 
11412               default:
11413                 sprintf(cmailMsg,
11414                         _("Still need to make moves for games %s\n"),
11415                         string);
11416             }
11417         }
11418     }
11419     return cmailMsg;
11420 #endif /* WIN32 */
11421 }
11422
11423 void
11424 ResetGameEvent()
11425 {
11426     if (gameMode == Training)
11427       SetTrainingModeOff();
11428
11429     Reset(TRUE, TRUE);
11430     cmailMsgLoaded = FALSE;
11431     if (appData.icsActive) {
11432       SendToICS(ics_prefix);
11433       SendToICS("refresh\n");
11434     }
11435 }
11436
11437 void
11438 ExitEvent(status)
11439      int status;
11440 {
11441     exiting++;
11442     if (exiting > 2) {
11443       /* Give up on clean exit */
11444       exit(status);
11445     }
11446     if (exiting > 1) {
11447       /* Keep trying for clean exit */
11448       return;
11449     }
11450
11451     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11452
11453     if (telnetISR != NULL) {
11454       RemoveInputSource(telnetISR);
11455     }
11456     if (icsPR != NoProc) {
11457       DestroyChildProcess(icsPR, TRUE);
11458     }
11459
11460     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11461     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11462
11463     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11464     /* make sure this other one finishes before killing it!                  */
11465     if(endingGame) { int count = 0;
11466         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11467         while(endingGame && count++ < 10) DoSleep(1);
11468         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11469     }
11470
11471     /* Kill off chess programs */
11472     if (first.pr != NoProc) {
11473         ExitAnalyzeMode();
11474         
11475         DoSleep( appData.delayBeforeQuit );
11476         SendToProgram("quit\n", &first);
11477         DoSleep( appData.delayAfterQuit );
11478         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11479     }
11480     if (second.pr != NoProc) {
11481         DoSleep( appData.delayBeforeQuit );
11482         SendToProgram("quit\n", &second);
11483         DoSleep( appData.delayAfterQuit );
11484         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11485     }
11486     if (first.isr != NULL) {
11487         RemoveInputSource(first.isr);
11488     }
11489     if (second.isr != NULL) {
11490         RemoveInputSource(second.isr);
11491     }
11492
11493     ShutDownFrontEnd();
11494     exit(status);
11495 }
11496
11497 void
11498 PauseEvent()
11499 {
11500     if (appData.debugMode)
11501         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11502     if (pausing) {
11503         pausing = FALSE;
11504         ModeHighlight();
11505         if (gameMode == MachinePlaysWhite ||
11506             gameMode == MachinePlaysBlack) {
11507             StartClocks();
11508         } else {
11509             DisplayBothClocks();
11510         }
11511         if (gameMode == PlayFromGameFile) {
11512             if (appData.timeDelay >= 0) 
11513                 AutoPlayGameLoop();
11514         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11515             Reset(FALSE, TRUE);
11516             SendToICS(ics_prefix);
11517             SendToICS("refresh\n");
11518         } else if (currentMove < forwardMostMove) {
11519             ForwardInner(forwardMostMove);
11520         }
11521         pauseExamInvalid = FALSE;
11522     } else {
11523         switch (gameMode) {
11524           default:
11525             return;
11526           case IcsExamining:
11527             pauseExamForwardMostMove = forwardMostMove;
11528             pauseExamInvalid = FALSE;
11529             /* fall through */
11530           case IcsObserving:
11531           case IcsPlayingWhite:
11532           case IcsPlayingBlack:
11533             pausing = TRUE;
11534             ModeHighlight();
11535             return;
11536           case PlayFromGameFile:
11537             (void) StopLoadGameTimer();
11538             pausing = TRUE;
11539             ModeHighlight();
11540             break;
11541           case BeginningOfGame:
11542             if (appData.icsActive) return;
11543             /* else fall through */
11544           case MachinePlaysWhite:
11545           case MachinePlaysBlack:
11546           case TwoMachinesPlay:
11547             if (forwardMostMove == 0)
11548               return;           /* don't pause if no one has moved */
11549             if ((gameMode == MachinePlaysWhite &&
11550                  !WhiteOnMove(forwardMostMove)) ||
11551                 (gameMode == MachinePlaysBlack &&
11552                  WhiteOnMove(forwardMostMove))) {
11553                 StopClocks();
11554             }
11555             pausing = TRUE;
11556             ModeHighlight();
11557             break;
11558         }
11559     }
11560 }
11561
11562 void
11563 EditCommentEvent()
11564 {
11565     char title[MSG_SIZ];
11566
11567     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11568         strcpy(title, _("Edit comment"));
11569     } else {
11570         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11571                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11572                 parseList[currentMove - 1]);
11573     }
11574
11575     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11576 }
11577
11578
11579 void
11580 EditTagsEvent()
11581 {
11582     char *tags = PGNTags(&gameInfo);
11583     EditTagsPopUp(tags);
11584     free(tags);
11585 }
11586
11587 void
11588 AnalyzeModeEvent()
11589 {
11590     if (appData.noChessProgram || gameMode == AnalyzeMode)
11591       return;
11592
11593     if (gameMode != AnalyzeFile) {
11594         if (!appData.icsEngineAnalyze) {
11595                EditGameEvent();
11596                if (gameMode != EditGame) return;
11597         }
11598         ResurrectChessProgram();
11599         SendToProgram("analyze\n", &first);
11600         first.analyzing = TRUE;
11601         /*first.maybeThinking = TRUE;*/
11602         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11603         EngineOutputPopUp();
11604     }
11605     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11606     pausing = FALSE;
11607     ModeHighlight();
11608     SetGameInfo();
11609
11610     StartAnalysisClock();
11611     GetTimeMark(&lastNodeCountTime);
11612     lastNodeCount = 0;
11613 }
11614
11615 void
11616 AnalyzeFileEvent()
11617 {
11618     if (appData.noChessProgram || gameMode == AnalyzeFile)
11619       return;
11620
11621     if (gameMode != AnalyzeMode) {
11622         EditGameEvent();
11623         if (gameMode != EditGame) return;
11624         ResurrectChessProgram();
11625         SendToProgram("analyze\n", &first);
11626         first.analyzing = TRUE;
11627         /*first.maybeThinking = TRUE;*/
11628         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11629         EngineOutputPopUp();
11630     }
11631     gameMode = AnalyzeFile;
11632     pausing = FALSE;
11633     ModeHighlight();
11634     SetGameInfo();
11635
11636     StartAnalysisClock();
11637     GetTimeMark(&lastNodeCountTime);
11638     lastNodeCount = 0;
11639 }
11640
11641 void
11642 MachineWhiteEvent()
11643 {
11644     char buf[MSG_SIZ];
11645     char *bookHit = NULL;
11646
11647     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11648       return;
11649
11650
11651     if (gameMode == PlayFromGameFile || 
11652         gameMode == TwoMachinesPlay  || 
11653         gameMode == Training         || 
11654         gameMode == AnalyzeMode      || 
11655         gameMode == EndOfGame)
11656         EditGameEvent();
11657
11658     if (gameMode == EditPosition) 
11659         EditPositionDone(TRUE);
11660
11661     if (!WhiteOnMove(currentMove)) {
11662         DisplayError(_("It is not White's turn"), 0);
11663         return;
11664     }
11665   
11666     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11667       ExitAnalyzeMode();
11668
11669     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11670         gameMode == AnalyzeFile)
11671         TruncateGame();
11672
11673     ResurrectChessProgram();    /* in case it isn't running */
11674     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11675         gameMode = MachinePlaysWhite;
11676         ResetClocks();
11677     } else
11678     gameMode = MachinePlaysWhite;
11679     pausing = FALSE;
11680     ModeHighlight();
11681     SetGameInfo();
11682     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11683     DisplayTitle(buf);
11684     if (first.sendName) {
11685       sprintf(buf, "name %s\n", gameInfo.black);
11686       SendToProgram(buf, &first);
11687     }
11688     if (first.sendTime) {
11689       if (first.useColors) {
11690         SendToProgram("black\n", &first); /*gnu kludge*/
11691       }
11692       SendTimeRemaining(&first, TRUE);
11693     }
11694     if (first.useColors) {
11695       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11696     }
11697     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11698     SetMachineThinkingEnables();
11699     first.maybeThinking = TRUE;
11700     StartClocks();
11701     firstMove = FALSE;
11702
11703     if (appData.autoFlipView && !flipView) {
11704       flipView = !flipView;
11705       DrawPosition(FALSE, NULL);
11706       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11707     }
11708
11709     if(bookHit) { // [HGM] book: simulate book reply
11710         static char bookMove[MSG_SIZ]; // a bit generous?
11711
11712         programStats.nodes = programStats.depth = programStats.time = 
11713         programStats.score = programStats.got_only_move = 0;
11714         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11715
11716         strcpy(bookMove, "move ");
11717         strcat(bookMove, bookHit);
11718         HandleMachineMove(bookMove, &first);
11719     }
11720 }
11721
11722 void
11723 MachineBlackEvent()
11724 {
11725     char buf[MSG_SIZ];
11726    char *bookHit = NULL;
11727
11728     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11729         return;
11730
11731
11732     if (gameMode == PlayFromGameFile || 
11733         gameMode == TwoMachinesPlay  || 
11734         gameMode == Training         || 
11735         gameMode == AnalyzeMode      || 
11736         gameMode == EndOfGame)
11737         EditGameEvent();
11738
11739     if (gameMode == EditPosition) 
11740         EditPositionDone(TRUE);
11741
11742     if (WhiteOnMove(currentMove)) {
11743         DisplayError(_("It is not Black's turn"), 0);
11744         return;
11745     }
11746     
11747     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11748       ExitAnalyzeMode();
11749
11750     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11751         gameMode == AnalyzeFile)
11752         TruncateGame();
11753
11754     ResurrectChessProgram();    /* in case it isn't running */
11755     gameMode = MachinePlaysBlack;
11756     pausing = FALSE;
11757     ModeHighlight();
11758     SetGameInfo();
11759     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11760     DisplayTitle(buf);
11761     if (first.sendName) {
11762       sprintf(buf, "name %s\n", gameInfo.white);
11763       SendToProgram(buf, &first);
11764     }
11765     if (first.sendTime) {
11766       if (first.useColors) {
11767         SendToProgram("white\n", &first); /*gnu kludge*/
11768       }
11769       SendTimeRemaining(&first, FALSE);
11770     }
11771     if (first.useColors) {
11772       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11773     }
11774     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11775     SetMachineThinkingEnables();
11776     first.maybeThinking = TRUE;
11777     StartClocks();
11778
11779     if (appData.autoFlipView && flipView) {
11780       flipView = !flipView;
11781       DrawPosition(FALSE, NULL);
11782       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11783     }
11784     if(bookHit) { // [HGM] book: simulate book reply
11785         static char bookMove[MSG_SIZ]; // a bit generous?
11786
11787         programStats.nodes = programStats.depth = programStats.time = 
11788         programStats.score = programStats.got_only_move = 0;
11789         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11790
11791         strcpy(bookMove, "move ");
11792         strcat(bookMove, bookHit);
11793         HandleMachineMove(bookMove, &first);
11794     }
11795 }
11796
11797
11798 void
11799 DisplayTwoMachinesTitle()
11800 {
11801     char buf[MSG_SIZ];
11802     if (appData.matchGames > 0) {
11803         if (first.twoMachinesColor[0] == 'w') {
11804             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11805                     gameInfo.white, gameInfo.black,
11806                     first.matchWins, second.matchWins,
11807                     matchGame - 1 - (first.matchWins + second.matchWins));
11808         } else {
11809             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11810                     gameInfo.white, gameInfo.black,
11811                     second.matchWins, first.matchWins,
11812                     matchGame - 1 - (first.matchWins + second.matchWins));
11813         }
11814     } else {
11815         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11816     }
11817     DisplayTitle(buf);
11818 }
11819
11820 void
11821 TwoMachinesEvent P((void))
11822 {
11823     int i;
11824     char buf[MSG_SIZ];
11825     ChessProgramState *onmove;
11826     char *bookHit = NULL;
11827     
11828     if (appData.noChessProgram) return;
11829
11830     switch (gameMode) {
11831       case TwoMachinesPlay:
11832         return;
11833       case MachinePlaysWhite:
11834       case MachinePlaysBlack:
11835         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11836             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11837             return;
11838         }
11839         /* fall through */
11840       case BeginningOfGame:
11841       case PlayFromGameFile:
11842       case EndOfGame:
11843         EditGameEvent();
11844         if (gameMode != EditGame) return;
11845         break;
11846       case EditPosition:
11847         EditPositionDone(TRUE);
11848         break;
11849       case AnalyzeMode:
11850       case AnalyzeFile:
11851         ExitAnalyzeMode();
11852         break;
11853       case EditGame:
11854       default:
11855         break;
11856     }
11857
11858 //    forwardMostMove = currentMove;
11859     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11860     ResurrectChessProgram();    /* in case first program isn't running */
11861
11862     if (second.pr == NULL) {
11863         StartChessProgram(&second);
11864         if (second.protocolVersion == 1) {
11865           TwoMachinesEventIfReady();
11866         } else {
11867           /* kludge: allow timeout for initial "feature" command */
11868           FreezeUI();
11869           DisplayMessage("", _("Starting second chess program"));
11870           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11871         }
11872         return;
11873     }
11874     DisplayMessage("", "");
11875     InitChessProgram(&second, FALSE);
11876     SendToProgram("force\n", &second);
11877     if (startedFromSetupPosition) {
11878         SendBoard(&second, backwardMostMove);
11879     if (appData.debugMode) {
11880         fprintf(debugFP, "Two Machines\n");
11881     }
11882     }
11883     for (i = backwardMostMove; i < forwardMostMove; i++) {
11884         SendMoveToProgram(i, &second);
11885     }
11886
11887     gameMode = TwoMachinesPlay;
11888     pausing = FALSE;
11889     ModeHighlight();
11890     SetGameInfo();
11891     DisplayTwoMachinesTitle();
11892     firstMove = TRUE;
11893     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11894         onmove = &first;
11895     } else {
11896         onmove = &second;
11897     }
11898
11899     SendToProgram(first.computerString, &first);
11900     if (first.sendName) {
11901       sprintf(buf, "name %s\n", second.tidy);
11902       SendToProgram(buf, &first);
11903     }
11904     SendToProgram(second.computerString, &second);
11905     if (second.sendName) {
11906       sprintf(buf, "name %s\n", first.tidy);
11907       SendToProgram(buf, &second);
11908     }
11909
11910     ResetClocks();
11911     if (!first.sendTime || !second.sendTime) {
11912         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11913         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11914     }
11915     if (onmove->sendTime) {
11916       if (onmove->useColors) {
11917         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11918       }
11919       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11920     }
11921     if (onmove->useColors) {
11922       SendToProgram(onmove->twoMachinesColor, onmove);
11923     }
11924     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11925 //    SendToProgram("go\n", onmove);
11926     onmove->maybeThinking = TRUE;
11927     SetMachineThinkingEnables();
11928
11929     StartClocks();
11930
11931     if(bookHit) { // [HGM] book: simulate book reply
11932         static char bookMove[MSG_SIZ]; // a bit generous?
11933
11934         programStats.nodes = programStats.depth = programStats.time = 
11935         programStats.score = programStats.got_only_move = 0;
11936         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11937
11938         strcpy(bookMove, "move ");
11939         strcat(bookMove, bookHit);
11940         savedMessage = bookMove; // args for deferred call
11941         savedState = onmove;
11942         ScheduleDelayedEvent(DeferredBookMove, 1);
11943     }
11944 }
11945
11946 void
11947 TrainingEvent()
11948 {
11949     if (gameMode == Training) {
11950       SetTrainingModeOff();
11951       gameMode = PlayFromGameFile;
11952       DisplayMessage("", _("Training mode off"));
11953     } else {
11954       gameMode = Training;
11955       animateTraining = appData.animate;
11956
11957       /* make sure we are not already at the end of the game */
11958       if (currentMove < forwardMostMove) {
11959         SetTrainingModeOn();
11960         DisplayMessage("", _("Training mode on"));
11961       } else {
11962         gameMode = PlayFromGameFile;
11963         DisplayError(_("Already at end of game"), 0);
11964       }
11965     }
11966     ModeHighlight();
11967 }
11968
11969 void
11970 IcsClientEvent()
11971 {
11972     if (!appData.icsActive) return;
11973     switch (gameMode) {
11974       case IcsPlayingWhite:
11975       case IcsPlayingBlack:
11976       case IcsObserving:
11977       case IcsIdle:
11978       case BeginningOfGame:
11979       case IcsExamining:
11980         return;
11981
11982       case EditGame:
11983         break;
11984
11985       case EditPosition:
11986         EditPositionDone(TRUE);
11987         break;
11988
11989       case AnalyzeMode:
11990       case AnalyzeFile:
11991         ExitAnalyzeMode();
11992         break;
11993         
11994       default:
11995         EditGameEvent();
11996         break;
11997     }
11998
11999     gameMode = IcsIdle;
12000     ModeHighlight();
12001     return;
12002 }
12003
12004
12005 void
12006 EditGameEvent()
12007 {
12008     int i;
12009
12010     switch (gameMode) {
12011       case Training:
12012         SetTrainingModeOff();
12013         break;
12014       case MachinePlaysWhite:
12015       case MachinePlaysBlack:
12016       case BeginningOfGame:
12017         SendToProgram("force\n", &first);
12018         SetUserThinkingEnables();
12019         break;
12020       case PlayFromGameFile:
12021         (void) StopLoadGameTimer();
12022         if (gameFileFP != NULL) {
12023             gameFileFP = NULL;
12024         }
12025         break;
12026       case EditPosition:
12027         EditPositionDone(TRUE);
12028         break;
12029       case AnalyzeMode:
12030       case AnalyzeFile:
12031         ExitAnalyzeMode();
12032         SendToProgram("force\n", &first);
12033         break;
12034       case TwoMachinesPlay:
12035         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
12036         ResurrectChessProgram();
12037         SetUserThinkingEnables();
12038         break;
12039       case EndOfGame:
12040         ResurrectChessProgram();
12041         break;
12042       case IcsPlayingBlack:
12043       case IcsPlayingWhite:
12044         DisplayError(_("Warning: You are still playing a game"), 0);
12045         break;
12046       case IcsObserving:
12047         DisplayError(_("Warning: You are still observing a game"), 0);
12048         break;
12049       case IcsExamining:
12050         DisplayError(_("Warning: You are still examining a game"), 0);
12051         break;
12052       case IcsIdle:
12053         break;
12054       case EditGame:
12055       default:
12056         return;
12057     }
12058     
12059     pausing = FALSE;
12060     StopClocks();
12061     first.offeredDraw = second.offeredDraw = 0;
12062
12063     if (gameMode == PlayFromGameFile) {
12064         whiteTimeRemaining = timeRemaining[0][currentMove];
12065         blackTimeRemaining = timeRemaining[1][currentMove];
12066         DisplayTitle("");
12067     }
12068
12069     if (gameMode == MachinePlaysWhite ||
12070         gameMode == MachinePlaysBlack ||
12071         gameMode == TwoMachinesPlay ||
12072         gameMode == EndOfGame) {
12073         i = forwardMostMove;
12074         while (i > currentMove) {
12075             SendToProgram("undo\n", &first);
12076             i--;
12077         }
12078         whiteTimeRemaining = timeRemaining[0][currentMove];
12079         blackTimeRemaining = timeRemaining[1][currentMove];
12080         DisplayBothClocks();
12081         if (whiteFlag || blackFlag) {
12082             whiteFlag = blackFlag = 0;
12083         }
12084         DisplayTitle("");
12085     }           
12086     
12087     gameMode = EditGame;
12088     ModeHighlight();
12089     SetGameInfo();
12090 }
12091
12092
12093 void
12094 EditPositionEvent()
12095 {
12096     if (gameMode == EditPosition) {
12097         EditGameEvent();
12098         return;
12099     }
12100     
12101     EditGameEvent();
12102     if (gameMode != EditGame) return;
12103     
12104     gameMode = EditPosition;
12105     ModeHighlight();
12106     SetGameInfo();
12107     if (currentMove > 0)
12108       CopyBoard(boards[0], boards[currentMove]);
12109     
12110     blackPlaysFirst = !WhiteOnMove(currentMove);
12111     ResetClocks();
12112     currentMove = forwardMostMove = backwardMostMove = 0;
12113     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12114     DisplayMove(-1);
12115 }
12116
12117 void
12118 ExitAnalyzeMode()
12119 {
12120     /* [DM] icsEngineAnalyze - possible call from other functions */
12121     if (appData.icsEngineAnalyze) {
12122         appData.icsEngineAnalyze = FALSE;
12123
12124         DisplayMessage("",_("Close ICS engine analyze..."));
12125     }
12126     if (first.analysisSupport && first.analyzing) {
12127       SendToProgram("exit\n", &first);
12128       first.analyzing = FALSE;
12129     }
12130     thinkOutput[0] = NULLCHAR;
12131 }
12132
12133 void
12134 EditPositionDone(Boolean fakeRights)
12135 {
12136     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12137
12138     startedFromSetupPosition = TRUE;
12139     InitChessProgram(&first, FALSE);
12140     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12141       boards[0][EP_STATUS] = EP_NONE;
12142       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12143     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12144         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12145         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12146       } else boards[0][CASTLING][2] = NoRights;
12147     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12148         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12149         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12150       } else boards[0][CASTLING][5] = NoRights;
12151     }
12152     SendToProgram("force\n", &first);
12153     if (blackPlaysFirst) {
12154         strcpy(moveList[0], "");
12155         strcpy(parseList[0], "");
12156         currentMove = forwardMostMove = backwardMostMove = 1;
12157         CopyBoard(boards[1], boards[0]);
12158     } else {
12159         currentMove = forwardMostMove = backwardMostMove = 0;
12160     }
12161     SendBoard(&first, forwardMostMove);
12162     if (appData.debugMode) {
12163         fprintf(debugFP, "EditPosDone\n");
12164     }
12165     DisplayTitle("");
12166     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12167     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12168     gameMode = EditGame;
12169     ModeHighlight();
12170     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12171     ClearHighlights(); /* [AS] */
12172 }
12173
12174 /* Pause for `ms' milliseconds */
12175 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12176 void
12177 TimeDelay(ms)
12178      long ms;
12179 {
12180     TimeMark m1, m2;
12181
12182     GetTimeMark(&m1);
12183     do {
12184         GetTimeMark(&m2);
12185     } while (SubtractTimeMarks(&m2, &m1) < ms);
12186 }
12187
12188 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12189 void
12190 SendMultiLineToICS(buf)
12191      char *buf;
12192 {
12193     char temp[MSG_SIZ+1], *p;
12194     int len;
12195
12196     len = strlen(buf);
12197     if (len > MSG_SIZ)
12198       len = MSG_SIZ;
12199   
12200     strncpy(temp, buf, len);
12201     temp[len] = 0;
12202
12203     p = temp;
12204     while (*p) {
12205         if (*p == '\n' || *p == '\r')
12206           *p = ' ';
12207         ++p;
12208     }
12209
12210     strcat(temp, "\n");
12211     SendToICS(temp);
12212     SendToPlayer(temp, strlen(temp));
12213 }
12214
12215 void
12216 SetWhiteToPlayEvent()
12217 {
12218     if (gameMode == EditPosition) {
12219         blackPlaysFirst = FALSE;
12220         DisplayBothClocks();    /* works because currentMove is 0 */
12221     } else if (gameMode == IcsExamining) {
12222         SendToICS(ics_prefix);
12223         SendToICS("tomove white\n");
12224     }
12225 }
12226
12227 void
12228 SetBlackToPlayEvent()
12229 {
12230     if (gameMode == EditPosition) {
12231         blackPlaysFirst = TRUE;
12232         currentMove = 1;        /* kludge */
12233         DisplayBothClocks();
12234         currentMove = 0;
12235     } else if (gameMode == IcsExamining) {
12236         SendToICS(ics_prefix);
12237         SendToICS("tomove black\n");
12238     }
12239 }
12240
12241 void
12242 EditPositionMenuEvent(selection, x, y)
12243      ChessSquare selection;
12244      int x, y;
12245 {
12246     char buf[MSG_SIZ];
12247     ChessSquare piece = boards[0][y][x];
12248
12249     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12250
12251     switch (selection) {
12252       case ClearBoard:
12253         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12254             SendToICS(ics_prefix);
12255             SendToICS("bsetup clear\n");
12256         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12257             SendToICS(ics_prefix);
12258             SendToICS("clearboard\n");
12259         } else {
12260             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12261                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12262                 for (y = 0; y < BOARD_HEIGHT; y++) {
12263                     if (gameMode == IcsExamining) {
12264                         if (boards[currentMove][y][x] != EmptySquare) {
12265                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12266                                     AAA + x, ONE + y);
12267                             SendToICS(buf);
12268                         }
12269                     } else {
12270                         boards[0][y][x] = p;
12271                     }
12272                 }
12273             }
12274         }
12275         if (gameMode == EditPosition) {
12276             DrawPosition(FALSE, boards[0]);
12277         }
12278         break;
12279
12280       case WhitePlay:
12281         SetWhiteToPlayEvent();
12282         break;
12283
12284       case BlackPlay:
12285         SetBlackToPlayEvent();
12286         break;
12287
12288       case EmptySquare:
12289         if (gameMode == IcsExamining) {
12290             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12291             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12292             SendToICS(buf);
12293         } else {
12294             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12295                 if(x == BOARD_LEFT-2) {
12296                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12297                     boards[0][y][1] = 0;
12298                 } else
12299                 if(x == BOARD_RGHT+1) {
12300                     if(y >= gameInfo.holdingsSize) break;
12301                     boards[0][y][BOARD_WIDTH-2] = 0;
12302                 } else break;
12303             }
12304             boards[0][y][x] = EmptySquare;
12305             DrawPosition(FALSE, boards[0]);
12306         }
12307         break;
12308
12309       case PromotePiece:
12310         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12311            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12312             selection = (ChessSquare) (PROMOTED piece);
12313         } else if(piece == EmptySquare) selection = WhiteSilver;
12314         else selection = (ChessSquare)((int)piece - 1);
12315         goto defaultlabel;
12316
12317       case DemotePiece:
12318         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12319            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12320             selection = (ChessSquare) (DEMOTED piece);
12321         } else if(piece == EmptySquare) selection = BlackSilver;
12322         else selection = (ChessSquare)((int)piece + 1);       
12323         goto defaultlabel;
12324
12325       case WhiteQueen:
12326       case BlackQueen:
12327         if(gameInfo.variant == VariantShatranj ||
12328            gameInfo.variant == VariantXiangqi  ||
12329            gameInfo.variant == VariantCourier  ||
12330            gameInfo.variant == VariantMakruk     )
12331             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12332         goto defaultlabel;
12333
12334       case WhiteKing:
12335       case BlackKing:
12336         if(gameInfo.variant == VariantXiangqi)
12337             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12338         if(gameInfo.variant == VariantKnightmate)
12339             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12340       default:
12341         defaultlabel:
12342         if (gameMode == IcsExamining) {
12343             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12344             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12345                     PieceToChar(selection), AAA + x, ONE + y);
12346             SendToICS(buf);
12347         } else {
12348             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12349                 int n;
12350                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12351                     n = PieceToNumber(selection - BlackPawn);
12352                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12353                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12354                     boards[0][BOARD_HEIGHT-1-n][1]++;
12355                 } else
12356                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12357                     n = PieceToNumber(selection);
12358                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12359                     boards[0][n][BOARD_WIDTH-1] = selection;
12360                     boards[0][n][BOARD_WIDTH-2]++;
12361                 }
12362             } else
12363             boards[0][y][x] = selection;
12364             DrawPosition(TRUE, boards[0]);
12365         }
12366         break;
12367     }
12368 }
12369
12370
12371 void
12372 DropMenuEvent(selection, x, y)
12373      ChessSquare selection;
12374      int x, y;
12375 {
12376     ChessMove moveType;
12377
12378     switch (gameMode) {
12379       case IcsPlayingWhite:
12380       case MachinePlaysBlack:
12381         if (!WhiteOnMove(currentMove)) {
12382             DisplayMoveError(_("It is Black's turn"));
12383             return;
12384         }
12385         moveType = WhiteDrop;
12386         break;
12387       case IcsPlayingBlack:
12388       case MachinePlaysWhite:
12389         if (WhiteOnMove(currentMove)) {
12390             DisplayMoveError(_("It is White's turn"));
12391             return;
12392         }
12393         moveType = BlackDrop;
12394         break;
12395       case EditGame:
12396         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12397         break;
12398       default:
12399         return;
12400     }
12401
12402     if (moveType == BlackDrop && selection < BlackPawn) {
12403       selection = (ChessSquare) ((int) selection
12404                                  + (int) BlackPawn - (int) WhitePawn);
12405     }
12406     if (boards[currentMove][y][x] != EmptySquare) {
12407         DisplayMoveError(_("That square is occupied"));
12408         return;
12409     }
12410
12411     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12412 }
12413
12414 void
12415 AcceptEvent()
12416 {
12417     /* Accept a pending offer of any kind from opponent */
12418     
12419     if (appData.icsActive) {
12420         SendToICS(ics_prefix);
12421         SendToICS("accept\n");
12422     } else if (cmailMsgLoaded) {
12423         if (currentMove == cmailOldMove &&
12424             commentList[cmailOldMove] != NULL &&
12425             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12426                    "Black offers a draw" : "White offers a draw")) {
12427             TruncateGame();
12428             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12429             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12430         } else {
12431             DisplayError(_("There is no pending offer on this move"), 0);
12432             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12433         }
12434     } else {
12435         /* Not used for offers from chess program */
12436     }
12437 }
12438
12439 void
12440 DeclineEvent()
12441 {
12442     /* Decline a pending offer of any kind from opponent */
12443     
12444     if (appData.icsActive) {
12445         SendToICS(ics_prefix);
12446         SendToICS("decline\n");
12447     } else if (cmailMsgLoaded) {
12448         if (currentMove == cmailOldMove &&
12449             commentList[cmailOldMove] != NULL &&
12450             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12451                    "Black offers a draw" : "White offers a draw")) {
12452 #ifdef NOTDEF
12453             AppendComment(cmailOldMove, "Draw declined", TRUE);
12454             DisplayComment(cmailOldMove - 1, "Draw declined");
12455 #endif /*NOTDEF*/
12456         } else {
12457             DisplayError(_("There is no pending offer on this move"), 0);
12458         }
12459     } else {
12460         /* Not used for offers from chess program */
12461     }
12462 }
12463
12464 void
12465 RematchEvent()
12466 {
12467     /* Issue ICS rematch command */
12468     if (appData.icsActive) {
12469         SendToICS(ics_prefix);
12470         SendToICS("rematch\n");
12471     }
12472 }
12473
12474 void
12475 CallFlagEvent()
12476 {
12477     /* Call your opponent's flag (claim a win on time) */
12478     if (appData.icsActive) {
12479         SendToICS(ics_prefix);
12480         SendToICS("flag\n");
12481     } else {
12482         switch (gameMode) {
12483           default:
12484             return;
12485           case MachinePlaysWhite:
12486             if (whiteFlag) {
12487                 if (blackFlag)
12488                   GameEnds(GameIsDrawn, "Both players ran out of time",
12489                            GE_PLAYER);
12490                 else
12491                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12492             } else {
12493                 DisplayError(_("Your opponent is not out of time"), 0);
12494             }
12495             break;
12496           case MachinePlaysBlack:
12497             if (blackFlag) {
12498                 if (whiteFlag)
12499                   GameEnds(GameIsDrawn, "Both players ran out of time",
12500                            GE_PLAYER);
12501                 else
12502                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12503             } else {
12504                 DisplayError(_("Your opponent is not out of time"), 0);
12505             }
12506             break;
12507         }
12508     }
12509 }
12510
12511 void
12512 DrawEvent()
12513 {
12514     /* Offer draw or accept pending draw offer from opponent */
12515     
12516     if (appData.icsActive) {
12517         /* Note: tournament rules require draw offers to be
12518            made after you make your move but before you punch
12519            your clock.  Currently ICS doesn't let you do that;
12520            instead, you immediately punch your clock after making
12521            a move, but you can offer a draw at any time. */
12522         
12523         SendToICS(ics_prefix);
12524         SendToICS("draw\n");
12525         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12526     } else if (cmailMsgLoaded) {
12527         if (currentMove == cmailOldMove &&
12528             commentList[cmailOldMove] != NULL &&
12529             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12530                    "Black offers a draw" : "White offers a draw")) {
12531             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12532             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12533         } else if (currentMove == cmailOldMove + 1) {
12534             char *offer = WhiteOnMove(cmailOldMove) ?
12535               "White offers a draw" : "Black offers a draw";
12536             AppendComment(currentMove, offer, TRUE);
12537             DisplayComment(currentMove - 1, offer);
12538             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12539         } else {
12540             DisplayError(_("You must make your move before offering a draw"), 0);
12541             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12542         }
12543     } else if (first.offeredDraw) {
12544         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12545     } else {
12546         if (first.sendDrawOffers) {
12547             SendToProgram("draw\n", &first);
12548             userOfferedDraw = TRUE;
12549         }
12550     }
12551 }
12552
12553 void
12554 AdjournEvent()
12555 {
12556     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12557     
12558     if (appData.icsActive) {
12559         SendToICS(ics_prefix);
12560         SendToICS("adjourn\n");
12561     } else {
12562         /* Currently GNU Chess doesn't offer or accept Adjourns */
12563     }
12564 }
12565
12566
12567 void
12568 AbortEvent()
12569 {
12570     /* Offer Abort or accept pending Abort offer from opponent */
12571     
12572     if (appData.icsActive) {
12573         SendToICS(ics_prefix);
12574         SendToICS("abort\n");
12575     } else {
12576         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12577     }
12578 }
12579
12580 void
12581 ResignEvent()
12582 {
12583     /* Resign.  You can do this even if it's not your turn. */
12584     
12585     if (appData.icsActive) {
12586         SendToICS(ics_prefix);
12587         SendToICS("resign\n");
12588     } else {
12589         switch (gameMode) {
12590           case MachinePlaysWhite:
12591             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12592             break;
12593           case MachinePlaysBlack:
12594             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12595             break;
12596           case EditGame:
12597             if (cmailMsgLoaded) {
12598                 TruncateGame();
12599                 if (WhiteOnMove(cmailOldMove)) {
12600                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12601                 } else {
12602                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12603                 }
12604                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12605             }
12606             break;
12607           default:
12608             break;
12609         }
12610     }
12611 }
12612
12613
12614 void
12615 StopObservingEvent()
12616 {
12617     /* Stop observing current games */
12618     SendToICS(ics_prefix);
12619     SendToICS("unobserve\n");
12620 }
12621
12622 void
12623 StopExaminingEvent()
12624 {
12625     /* Stop observing current game */
12626     SendToICS(ics_prefix);
12627     SendToICS("unexamine\n");
12628 }
12629
12630 void
12631 ForwardInner(target)
12632      int target;
12633 {
12634     int limit;
12635
12636     if (appData.debugMode)
12637         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12638                 target, currentMove, forwardMostMove);
12639
12640     if (gameMode == EditPosition)
12641       return;
12642
12643     if (gameMode == PlayFromGameFile && !pausing)
12644       PauseEvent();
12645     
12646     if (gameMode == IcsExamining && pausing)
12647       limit = pauseExamForwardMostMove;
12648     else
12649       limit = forwardMostMove;
12650     
12651     if (target > limit) target = limit;
12652
12653     if (target > 0 && moveList[target - 1][0]) {
12654         int fromX, fromY, toX, toY;
12655         toX = moveList[target - 1][2] - AAA;
12656         toY = moveList[target - 1][3] - ONE;
12657         if (moveList[target - 1][1] == '@') {
12658             if (appData.highlightLastMove) {
12659                 SetHighlights(-1, -1, toX, toY);
12660             }
12661         } else {
12662             fromX = moveList[target - 1][0] - AAA;
12663             fromY = moveList[target - 1][1] - ONE;
12664             if (target == currentMove + 1) {
12665                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12666             }
12667             if (appData.highlightLastMove) {
12668                 SetHighlights(fromX, fromY, toX, toY);
12669             }
12670         }
12671     }
12672     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12673         gameMode == Training || gameMode == PlayFromGameFile || 
12674         gameMode == AnalyzeFile) {
12675         while (currentMove < target) {
12676             SendMoveToProgram(currentMove++, &first);
12677         }
12678     } else {
12679         currentMove = target;
12680     }
12681     
12682     if (gameMode == EditGame || gameMode == EndOfGame) {
12683         whiteTimeRemaining = timeRemaining[0][currentMove];
12684         blackTimeRemaining = timeRemaining[1][currentMove];
12685     }
12686     DisplayBothClocks();
12687     DisplayMove(currentMove - 1);
12688     DrawPosition(FALSE, boards[currentMove]);
12689     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12690     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12691         DisplayComment(currentMove - 1, commentList[currentMove]);
12692     }
12693 }
12694
12695
12696 void
12697 ForwardEvent()
12698 {
12699     if (gameMode == IcsExamining && !pausing) {
12700         SendToICS(ics_prefix);
12701         SendToICS("forward\n");
12702     } else {
12703         ForwardInner(currentMove + 1);
12704     }
12705 }
12706
12707 void
12708 ToEndEvent()
12709 {
12710     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12711         /* to optimze, we temporarily turn off analysis mode while we feed
12712          * the remaining moves to the engine. Otherwise we get analysis output
12713          * after each move.
12714          */ 
12715         if (first.analysisSupport) {
12716           SendToProgram("exit\nforce\n", &first);
12717           first.analyzing = FALSE;
12718         }
12719     }
12720         
12721     if (gameMode == IcsExamining && !pausing) {
12722         SendToICS(ics_prefix);
12723         SendToICS("forward 999999\n");
12724     } else {
12725         ForwardInner(forwardMostMove);
12726     }
12727
12728     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12729         /* we have fed all the moves, so reactivate analysis mode */
12730         SendToProgram("analyze\n", &first);
12731         first.analyzing = TRUE;
12732         /*first.maybeThinking = TRUE;*/
12733         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12734     }
12735 }
12736
12737 void
12738 BackwardInner(target)
12739      int target;
12740 {
12741     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12742
12743     if (appData.debugMode)
12744         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12745                 target, currentMove, forwardMostMove);
12746
12747     if (gameMode == EditPosition) return;
12748     if (currentMove <= backwardMostMove) {
12749         ClearHighlights();
12750         DrawPosition(full_redraw, boards[currentMove]);
12751         return;
12752     }
12753     if (gameMode == PlayFromGameFile && !pausing)
12754       PauseEvent();
12755     
12756     if (moveList[target][0]) {
12757         int fromX, fromY, toX, toY;
12758         toX = moveList[target][2] - AAA;
12759         toY = moveList[target][3] - ONE;
12760         if (moveList[target][1] == '@') {
12761             if (appData.highlightLastMove) {
12762                 SetHighlights(-1, -1, toX, toY);
12763             }
12764         } else {
12765             fromX = moveList[target][0] - AAA;
12766             fromY = moveList[target][1] - ONE;
12767             if (target == currentMove - 1) {
12768                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12769             }
12770             if (appData.highlightLastMove) {
12771                 SetHighlights(fromX, fromY, toX, toY);
12772             }
12773         }
12774     }
12775     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12776         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12777         while (currentMove > target) {
12778             SendToProgram("undo\n", &first);
12779             currentMove--;
12780         }
12781     } else {
12782         currentMove = target;
12783     }
12784     
12785     if (gameMode == EditGame || gameMode == EndOfGame) {
12786         whiteTimeRemaining = timeRemaining[0][currentMove];
12787         blackTimeRemaining = timeRemaining[1][currentMove];
12788     }
12789     DisplayBothClocks();
12790     DisplayMove(currentMove - 1);
12791     DrawPosition(full_redraw, boards[currentMove]);
12792     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12793     // [HGM] PV info: routine tests if comment empty
12794     DisplayComment(currentMove - 1, commentList[currentMove]);
12795 }
12796
12797 void
12798 BackwardEvent()
12799 {
12800     if (gameMode == IcsExamining && !pausing) {
12801         SendToICS(ics_prefix);
12802         SendToICS("backward\n");
12803     } else {
12804         BackwardInner(currentMove - 1);
12805     }
12806 }
12807
12808 void
12809 ToStartEvent()
12810 {
12811     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12812         /* to optimize, we temporarily turn off analysis mode while we undo
12813          * all the moves. Otherwise we get analysis output after each undo.
12814          */ 
12815         if (first.analysisSupport) {
12816           SendToProgram("exit\nforce\n", &first);
12817           first.analyzing = FALSE;
12818         }
12819     }
12820
12821     if (gameMode == IcsExamining && !pausing) {
12822         SendToICS(ics_prefix);
12823         SendToICS("backward 999999\n");
12824     } else {
12825         BackwardInner(backwardMostMove);
12826     }
12827
12828     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12829         /* we have fed all the moves, so reactivate analysis mode */
12830         SendToProgram("analyze\n", &first);
12831         first.analyzing = TRUE;
12832         /*first.maybeThinking = TRUE;*/
12833         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12834     }
12835 }
12836
12837 void
12838 ToNrEvent(int to)
12839 {
12840   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12841   if (to >= forwardMostMove) to = forwardMostMove;
12842   if (to <= backwardMostMove) to = backwardMostMove;
12843   if (to < currentMove) {
12844     BackwardInner(to);
12845   } else {
12846     ForwardInner(to);
12847   }
12848 }
12849
12850 void
12851 RevertEvent(Boolean annotate)
12852 {
12853     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12854         return;
12855     }
12856     if (gameMode != IcsExamining) {
12857         DisplayError(_("You are not examining a game"), 0);
12858         return;
12859     }
12860     if (pausing) {
12861         DisplayError(_("You can't revert while pausing"), 0);
12862         return;
12863     }
12864     SendToICS(ics_prefix);
12865     SendToICS("revert\n");
12866 }
12867
12868 void
12869 RetractMoveEvent()
12870 {
12871     switch (gameMode) {
12872       case MachinePlaysWhite:
12873       case MachinePlaysBlack:
12874         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12875             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12876             return;
12877         }
12878         if (forwardMostMove < 2) return;
12879         currentMove = forwardMostMove = forwardMostMove - 2;
12880         whiteTimeRemaining = timeRemaining[0][currentMove];
12881         blackTimeRemaining = timeRemaining[1][currentMove];
12882         DisplayBothClocks();
12883         DisplayMove(currentMove - 1);
12884         ClearHighlights();/*!! could figure this out*/
12885         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12886         SendToProgram("remove\n", &first);
12887         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12888         break;
12889
12890       case BeginningOfGame:
12891       default:
12892         break;
12893
12894       case IcsPlayingWhite:
12895       case IcsPlayingBlack:
12896         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12897             SendToICS(ics_prefix);
12898             SendToICS("takeback 2\n");
12899         } else {
12900             SendToICS(ics_prefix);
12901             SendToICS("takeback 1\n");
12902         }
12903         break;
12904     }
12905 }
12906
12907 void
12908 MoveNowEvent()
12909 {
12910     ChessProgramState *cps;
12911
12912     switch (gameMode) {
12913       case MachinePlaysWhite:
12914         if (!WhiteOnMove(forwardMostMove)) {
12915             DisplayError(_("It is your turn"), 0);
12916             return;
12917         }
12918         cps = &first;
12919         break;
12920       case MachinePlaysBlack:
12921         if (WhiteOnMove(forwardMostMove)) {
12922             DisplayError(_("It is your turn"), 0);
12923             return;
12924         }
12925         cps = &first;
12926         break;
12927       case TwoMachinesPlay:
12928         if (WhiteOnMove(forwardMostMove) ==
12929             (first.twoMachinesColor[0] == 'w')) {
12930             cps = &first;
12931         } else {
12932             cps = &second;
12933         }
12934         break;
12935       case BeginningOfGame:
12936       default:
12937         return;
12938     }
12939     SendToProgram("?\n", cps);
12940 }
12941
12942 void
12943 TruncateGameEvent()
12944 {
12945     EditGameEvent();
12946     if (gameMode != EditGame) return;
12947     TruncateGame();
12948 }
12949
12950 void
12951 TruncateGame()
12952 {
12953     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12954     if (forwardMostMove > currentMove) {
12955         if (gameInfo.resultDetails != NULL) {
12956             free(gameInfo.resultDetails);
12957             gameInfo.resultDetails = NULL;
12958             gameInfo.result = GameUnfinished;
12959         }
12960         forwardMostMove = currentMove;
12961         HistorySet(parseList, backwardMostMove, forwardMostMove,
12962                    currentMove-1);
12963     }
12964 }
12965
12966 void
12967 HintEvent()
12968 {
12969     if (appData.noChessProgram) return;
12970     switch (gameMode) {
12971       case MachinePlaysWhite:
12972         if (WhiteOnMove(forwardMostMove)) {
12973             DisplayError(_("Wait until your turn"), 0);
12974             return;
12975         }
12976         break;
12977       case BeginningOfGame:
12978       case MachinePlaysBlack:
12979         if (!WhiteOnMove(forwardMostMove)) {
12980             DisplayError(_("Wait until your turn"), 0);
12981             return;
12982         }
12983         break;
12984       default:
12985         DisplayError(_("No hint available"), 0);
12986         return;
12987     }
12988     SendToProgram("hint\n", &first);
12989     hintRequested = TRUE;
12990 }
12991
12992 void
12993 BookEvent()
12994 {
12995     if (appData.noChessProgram) return;
12996     switch (gameMode) {
12997       case MachinePlaysWhite:
12998         if (WhiteOnMove(forwardMostMove)) {
12999             DisplayError(_("Wait until your turn"), 0);
13000             return;
13001         }
13002         break;
13003       case BeginningOfGame:
13004       case MachinePlaysBlack:
13005         if (!WhiteOnMove(forwardMostMove)) {
13006             DisplayError(_("Wait until your turn"), 0);
13007             return;
13008         }
13009         break;
13010       case EditPosition:
13011         EditPositionDone(TRUE);
13012         break;
13013       case TwoMachinesPlay:
13014         return;
13015       default:
13016         break;
13017     }
13018     SendToProgram("bk\n", &first);
13019     bookOutput[0] = NULLCHAR;
13020     bookRequested = TRUE;
13021 }
13022
13023 void
13024 AboutGameEvent()
13025 {
13026     char *tags = PGNTags(&gameInfo);
13027     TagsPopUp(tags, CmailMsg());
13028     free(tags);
13029 }
13030
13031 /* end button procedures */
13032
13033 void
13034 PrintPosition(fp, move)
13035      FILE *fp;
13036      int move;
13037 {
13038     int i, j;
13039     
13040     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13041         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13042             char c = PieceToChar(boards[move][i][j]);
13043             fputc(c == 'x' ? '.' : c, fp);
13044             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13045         }
13046     }
13047     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13048       fprintf(fp, "white to play\n");
13049     else
13050       fprintf(fp, "black to play\n");
13051 }
13052
13053 void
13054 PrintOpponents(fp)
13055      FILE *fp;
13056 {
13057     if (gameInfo.white != NULL) {
13058         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13059     } else {
13060         fprintf(fp, "\n");
13061     }
13062 }
13063
13064 /* Find last component of program's own name, using some heuristics */
13065 void
13066 TidyProgramName(prog, host, buf)
13067      char *prog, *host, buf[MSG_SIZ];
13068 {
13069     char *p, *q;
13070     int local = (strcmp(host, "localhost") == 0);
13071     while (!local && (p = strchr(prog, ';')) != NULL) {
13072         p++;
13073         while (*p == ' ') p++;
13074         prog = p;
13075     }
13076     if (*prog == '"' || *prog == '\'') {
13077         q = strchr(prog + 1, *prog);
13078     } else {
13079         q = strchr(prog, ' ');
13080     }
13081     if (q == NULL) q = prog + strlen(prog);
13082     p = q;
13083     while (p >= prog && *p != '/' && *p != '\\') p--;
13084     p++;
13085     if(p == prog && *p == '"') p++;
13086     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13087     memcpy(buf, p, q - p);
13088     buf[q - p] = NULLCHAR;
13089     if (!local) {
13090         strcat(buf, "@");
13091         strcat(buf, host);
13092     }
13093 }
13094
13095 char *
13096 TimeControlTagValue()
13097 {
13098     char buf[MSG_SIZ];
13099     if (!appData.clockMode) {
13100         strcpy(buf, "-");
13101     } else if (movesPerSession > 0) {
13102         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13103     } else if (timeIncrement == 0) {
13104         sprintf(buf, "%ld", timeControl/1000);
13105     } else {
13106         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13107     }
13108     return StrSave(buf);
13109 }
13110
13111 void
13112 SetGameInfo()
13113 {
13114     /* This routine is used only for certain modes */
13115     VariantClass v = gameInfo.variant;
13116     ChessMove r = GameUnfinished;
13117     char *p = NULL;
13118
13119     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13120         r = gameInfo.result; 
13121         p = gameInfo.resultDetails; 
13122         gameInfo.resultDetails = NULL;
13123     }
13124     ClearGameInfo(&gameInfo);
13125     gameInfo.variant = v;
13126
13127     switch (gameMode) {
13128       case MachinePlaysWhite:
13129         gameInfo.event = StrSave( appData.pgnEventHeader );
13130         gameInfo.site = StrSave(HostName());
13131         gameInfo.date = PGNDate();
13132         gameInfo.round = StrSave("-");
13133         gameInfo.white = StrSave(first.tidy);
13134         gameInfo.black = StrSave(UserName());
13135         gameInfo.timeControl = TimeControlTagValue();
13136         break;
13137
13138       case MachinePlaysBlack:
13139         gameInfo.event = StrSave( appData.pgnEventHeader );
13140         gameInfo.site = StrSave(HostName());
13141         gameInfo.date = PGNDate();
13142         gameInfo.round = StrSave("-");
13143         gameInfo.white = StrSave(UserName());
13144         gameInfo.black = StrSave(first.tidy);
13145         gameInfo.timeControl = TimeControlTagValue();
13146         break;
13147
13148       case TwoMachinesPlay:
13149         gameInfo.event = StrSave( appData.pgnEventHeader );
13150         gameInfo.site = StrSave(HostName());
13151         gameInfo.date = PGNDate();
13152         if (matchGame > 0) {
13153             char buf[MSG_SIZ];
13154             sprintf(buf, "%d", matchGame);
13155             gameInfo.round = StrSave(buf);
13156         } else {
13157             gameInfo.round = StrSave("-");
13158         }
13159         if (first.twoMachinesColor[0] == 'w') {
13160             gameInfo.white = StrSave(first.tidy);
13161             gameInfo.black = StrSave(second.tidy);
13162         } else {
13163             gameInfo.white = StrSave(second.tidy);
13164             gameInfo.black = StrSave(first.tidy);
13165         }
13166         gameInfo.timeControl = TimeControlTagValue();
13167         break;
13168
13169       case EditGame:
13170         gameInfo.event = StrSave("Edited game");
13171         gameInfo.site = StrSave(HostName());
13172         gameInfo.date = PGNDate();
13173         gameInfo.round = StrSave("-");
13174         gameInfo.white = StrSave("-");
13175         gameInfo.black = StrSave("-");
13176         gameInfo.result = r;
13177         gameInfo.resultDetails = p;
13178         break;
13179
13180       case EditPosition:
13181         gameInfo.event = StrSave("Edited position");
13182         gameInfo.site = StrSave(HostName());
13183         gameInfo.date = PGNDate();
13184         gameInfo.round = StrSave("-");
13185         gameInfo.white = StrSave("-");
13186         gameInfo.black = StrSave("-");
13187         break;
13188
13189       case IcsPlayingWhite:
13190       case IcsPlayingBlack:
13191       case IcsObserving:
13192       case IcsExamining:
13193         break;
13194
13195       case PlayFromGameFile:
13196         gameInfo.event = StrSave("Game from non-PGN file");
13197         gameInfo.site = StrSave(HostName());
13198         gameInfo.date = PGNDate();
13199         gameInfo.round = StrSave("-");
13200         gameInfo.white = StrSave("?");
13201         gameInfo.black = StrSave("?");
13202         break;
13203
13204       default:
13205         break;
13206     }
13207 }
13208
13209 void
13210 ReplaceComment(index, text)
13211      int index;
13212      char *text;
13213 {
13214     int len;
13215
13216     while (*text == '\n') text++;
13217     len = strlen(text);
13218     while (len > 0 && text[len - 1] == '\n') len--;
13219
13220     if (commentList[index] != NULL)
13221       free(commentList[index]);
13222
13223     if (len == 0) {
13224         commentList[index] = NULL;
13225         return;
13226     }
13227   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13228       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13229       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13230     commentList[index] = (char *) malloc(len + 2);
13231     strncpy(commentList[index], text, len);
13232     commentList[index][len] = '\n';
13233     commentList[index][len + 1] = NULLCHAR;
13234   } else { 
13235     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13236     char *p;
13237     commentList[index] = (char *) malloc(len + 6);
13238     strcpy(commentList[index], "{\n");
13239     strncpy(commentList[index]+2, text, len);
13240     commentList[index][len+2] = NULLCHAR;
13241     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13242     strcat(commentList[index], "\n}\n");
13243   }
13244 }
13245
13246 void
13247 CrushCRs(text)
13248      char *text;
13249 {
13250   char *p = text;
13251   char *q = text;
13252   char ch;
13253
13254   do {
13255     ch = *p++;
13256     if (ch == '\r') continue;
13257     *q++ = ch;
13258   } while (ch != '\0');
13259 }
13260
13261 void
13262 AppendComment(index, text, addBraces)
13263      int index;
13264      char *text;
13265      Boolean addBraces; // [HGM] braces: tells if we should add {}
13266 {
13267     int oldlen, len;
13268     char *old;
13269
13270 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13271     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13272
13273     CrushCRs(text);
13274     while (*text == '\n') text++;
13275     len = strlen(text);
13276     while (len > 0 && text[len - 1] == '\n') len--;
13277
13278     if (len == 0) return;
13279
13280     if (commentList[index] != NULL) {
13281         old = commentList[index];
13282         oldlen = strlen(old);
13283         while(commentList[index][oldlen-1] ==  '\n')
13284           commentList[index][--oldlen] = NULLCHAR;
13285         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13286         strcpy(commentList[index], old);
13287         free(old);
13288         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13289         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13290           if(addBraces) addBraces = FALSE; else { text++; len--; }
13291           while (*text == '\n') { text++; len--; }
13292           commentList[index][--oldlen] = NULLCHAR;
13293       }
13294         if(addBraces) strcat(commentList[index], "\n{\n");
13295         else          strcat(commentList[index], "\n");
13296         strcat(commentList[index], text);
13297         if(addBraces) strcat(commentList[index], "\n}\n");
13298         else          strcat(commentList[index], "\n");
13299     } else {
13300         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13301         if(addBraces)
13302              strcpy(commentList[index], "{\n");
13303         else commentList[index][0] = NULLCHAR;
13304         strcat(commentList[index], text);
13305         strcat(commentList[index], "\n");
13306         if(addBraces) strcat(commentList[index], "}\n");
13307     }
13308 }
13309
13310 static char * FindStr( char * text, char * sub_text )
13311 {
13312     char * result = strstr( text, sub_text );
13313
13314     if( result != NULL ) {
13315         result += strlen( sub_text );
13316     }
13317
13318     return result;
13319 }
13320
13321 /* [AS] Try to extract PV info from PGN comment */
13322 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13323 char *GetInfoFromComment( int index, char * text )
13324 {
13325     char * sep = text;
13326
13327     if( text != NULL && index > 0 ) {
13328         int score = 0;
13329         int depth = 0;
13330         int time = -1, sec = 0, deci;
13331         char * s_eval = FindStr( text, "[%eval " );
13332         char * s_emt = FindStr( text, "[%emt " );
13333
13334         if( s_eval != NULL || s_emt != NULL ) {
13335             /* New style */
13336             char delim;
13337
13338             if( s_eval != NULL ) {
13339                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13340                     return text;
13341                 }
13342
13343                 if( delim != ']' ) {
13344                     return text;
13345                 }
13346             }
13347
13348             if( s_emt != NULL ) {
13349             }
13350                 return text;
13351         }
13352         else {
13353             /* We expect something like: [+|-]nnn.nn/dd */
13354             int score_lo = 0;
13355
13356             if(*text != '{') return text; // [HGM] braces: must be normal comment
13357
13358             sep = strchr( text, '/' );
13359             if( sep == NULL || sep < (text+4) ) {
13360                 return text;
13361             }
13362
13363             time = -1; sec = -1; deci = -1;
13364             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13365                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13366                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13367                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13368                 return text;
13369             }
13370
13371             if( score_lo < 0 || score_lo >= 100 ) {
13372                 return text;
13373             }
13374
13375             if(sec >= 0) time = 600*time + 10*sec; else
13376             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13377
13378             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13379
13380             /* [HGM] PV time: now locate end of PV info */
13381             while( *++sep >= '0' && *sep <= '9'); // strip depth
13382             if(time >= 0)
13383             while( *++sep >= '0' && *sep <= '9'); // strip time
13384             if(sec >= 0)
13385             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13386             if(deci >= 0)
13387             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13388             while(*sep == ' ') sep++;
13389         }
13390
13391         if( depth <= 0 ) {
13392             return text;
13393         }
13394
13395         if( time < 0 ) {
13396             time = -1;
13397         }
13398
13399         pvInfoList[index-1].depth = depth;
13400         pvInfoList[index-1].score = score;
13401         pvInfoList[index-1].time  = 10*time; // centi-sec
13402         if(*sep == '}') *sep = 0; else *--sep = '{';
13403     }
13404     return sep;
13405 }
13406
13407 void
13408 SendToProgram(message, cps)
13409      char *message;
13410      ChessProgramState *cps;
13411 {
13412     int count, outCount, error;
13413     char buf[MSG_SIZ];
13414
13415     if (cps->pr == NULL) return;
13416     Attention(cps);
13417     
13418     if (appData.debugMode) {
13419         TimeMark now;
13420         GetTimeMark(&now);
13421         fprintf(debugFP, "%ld >%-6s: %s", 
13422                 SubtractTimeMarks(&now, &programStartTime),
13423                 cps->which, message);
13424     }
13425     
13426     count = strlen(message);
13427     outCount = OutputToProcess(cps->pr, message, count, &error);
13428     if (outCount < count && !exiting 
13429                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13430         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13431         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13432             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13433                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13434                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13435             } else {
13436                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13437             }
13438             gameInfo.resultDetails = StrSave(buf);
13439         }
13440         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13441     }
13442 }
13443
13444 void
13445 ReceiveFromProgram(isr, closure, message, count, error)
13446      InputSourceRef isr;
13447      VOIDSTAR closure;
13448      char *message;
13449      int count;
13450      int error;
13451 {
13452     char *end_str;
13453     char buf[MSG_SIZ];
13454     ChessProgramState *cps = (ChessProgramState *)closure;
13455
13456     if (isr != cps->isr) return; /* Killed intentionally */
13457     if (count <= 0) {
13458         if (count == 0) {
13459             sprintf(buf,
13460                     _("Error: %s chess program (%s) exited unexpectedly"),
13461                     cps->which, cps->program);
13462         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13463                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13464                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13465                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13466                 } else {
13467                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13468                 }
13469                 gameInfo.resultDetails = StrSave(buf);
13470             }
13471             RemoveInputSource(cps->isr);
13472             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13473         } else {
13474             sprintf(buf,
13475                     _("Error reading from %s chess program (%s)"),
13476                     cps->which, cps->program);
13477             RemoveInputSource(cps->isr);
13478
13479             /* [AS] Program is misbehaving badly... kill it */
13480             if( count == -2 ) {
13481                 DestroyChildProcess( cps->pr, 9 );
13482                 cps->pr = NoProc;
13483             }
13484
13485             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13486         }
13487         return;
13488     }
13489     
13490     if ((end_str = strchr(message, '\r')) != NULL)
13491       *end_str = NULLCHAR;
13492     if ((end_str = strchr(message, '\n')) != NULL)
13493       *end_str = NULLCHAR;
13494     
13495     if (appData.debugMode) {
13496         TimeMark now; int print = 1;
13497         char *quote = ""; char c; int i;
13498
13499         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13500                 char start = message[0];
13501                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13502                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13503                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13504                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13505                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13506                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13507                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13508                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13509                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13510                     print = (appData.engineComments >= 2);
13511                 }
13512                 message[0] = start; // restore original message
13513         }
13514         if(print) {
13515                 GetTimeMark(&now);
13516                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13517                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13518                         quote,
13519                         message);
13520         }
13521     }
13522
13523     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13524     if (appData.icsEngineAnalyze) {
13525         if (strstr(message, "whisper") != NULL ||
13526              strstr(message, "kibitz") != NULL || 
13527             strstr(message, "tellics") != NULL) return;
13528     }
13529
13530     HandleMachineMove(message, cps);
13531 }
13532
13533
13534 void
13535 SendTimeControl(cps, mps, tc, inc, sd, st)
13536      ChessProgramState *cps;
13537      int mps, inc, sd, st;
13538      long tc;
13539 {
13540     char buf[MSG_SIZ];
13541     int seconds;
13542
13543     if( timeControl_2 > 0 ) {
13544         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13545             tc = timeControl_2;
13546         }
13547     }
13548     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13549     inc /= cps->timeOdds;
13550     st  /= cps->timeOdds;
13551
13552     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13553
13554     if (st > 0) {
13555       /* Set exact time per move, normally using st command */
13556       if (cps->stKludge) {
13557         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13558         seconds = st % 60;
13559         if (seconds == 0) {
13560           sprintf(buf, "level 1 %d\n", st/60);
13561         } else {
13562           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13563         }
13564       } else {
13565         sprintf(buf, "st %d\n", st);
13566       }
13567     } else {
13568       /* Set conventional or incremental time control, using level command */
13569       if (seconds == 0) {
13570         /* Note old gnuchess bug -- minutes:seconds used to not work.
13571            Fixed in later versions, but still avoid :seconds
13572            when seconds is 0. */
13573         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13574       } else {
13575         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13576                 seconds, inc/1000);
13577       }
13578     }
13579     SendToProgram(buf, cps);
13580
13581     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13582     /* Orthogonally, limit search to given depth */
13583     if (sd > 0) {
13584       if (cps->sdKludge) {
13585         sprintf(buf, "depth\n%d\n", sd);
13586       } else {
13587         sprintf(buf, "sd %d\n", sd);
13588       }
13589       SendToProgram(buf, cps);
13590     }
13591
13592     if(cps->nps > 0) { /* [HGM] nps */
13593         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13594         else {
13595                 sprintf(buf, "nps %d\n", cps->nps);
13596               SendToProgram(buf, cps);
13597         }
13598     }
13599 }
13600
13601 ChessProgramState *WhitePlayer()
13602 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13603 {
13604     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13605        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13606         return &second;
13607     return &first;
13608 }
13609
13610 void
13611 SendTimeRemaining(cps, machineWhite)
13612      ChessProgramState *cps;
13613      int /*boolean*/ machineWhite;
13614 {
13615     char message[MSG_SIZ];
13616     long time, otime;
13617
13618     /* Note: this routine must be called when the clocks are stopped
13619        or when they have *just* been set or switched; otherwise
13620        it will be off by the time since the current tick started.
13621     */
13622     if (machineWhite) {
13623         time = whiteTimeRemaining / 10;
13624         otime = blackTimeRemaining / 10;
13625     } else {
13626         time = blackTimeRemaining / 10;
13627         otime = whiteTimeRemaining / 10;
13628     }
13629     /* [HGM] translate opponent's time by time-odds factor */
13630     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13631     if (appData.debugMode) {
13632         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13633     }
13634
13635     if (time <= 0) time = 1;
13636     if (otime <= 0) otime = 1;
13637     
13638     sprintf(message, "time %ld\n", time);
13639     SendToProgram(message, cps);
13640
13641     sprintf(message, "otim %ld\n", otime);
13642     SendToProgram(message, cps);
13643 }
13644
13645 int
13646 BoolFeature(p, name, loc, cps)
13647      char **p;
13648      char *name;
13649      int *loc;
13650      ChessProgramState *cps;
13651 {
13652   char buf[MSG_SIZ];
13653   int len = strlen(name);
13654   int val;
13655   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13656     (*p) += len + 1;
13657     sscanf(*p, "%d", &val);
13658     *loc = (val != 0);
13659     while (**p && **p != ' ') (*p)++;
13660     sprintf(buf, "accepted %s\n", name);
13661     SendToProgram(buf, cps);
13662     return TRUE;
13663   }
13664   return FALSE;
13665 }
13666
13667 int
13668 IntFeature(p, name, loc, cps)
13669      char **p;
13670      char *name;
13671      int *loc;
13672      ChessProgramState *cps;
13673 {
13674   char buf[MSG_SIZ];
13675   int len = strlen(name);
13676   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13677     (*p) += len + 1;
13678     sscanf(*p, "%d", loc);
13679     while (**p && **p != ' ') (*p)++;
13680     sprintf(buf, "accepted %s\n", name);
13681     SendToProgram(buf, cps);
13682     return TRUE;
13683   }
13684   return FALSE;
13685 }
13686
13687 int
13688 StringFeature(p, name, loc, cps)
13689      char **p;
13690      char *name;
13691      char loc[];
13692      ChessProgramState *cps;
13693 {
13694   char buf[MSG_SIZ];
13695   int len = strlen(name);
13696   if (strncmp((*p), name, len) == 0
13697       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13698     (*p) += len + 2;
13699     sscanf(*p, "%[^\"]", loc);
13700     while (**p && **p != '\"') (*p)++;
13701     if (**p == '\"') (*p)++;
13702     sprintf(buf, "accepted %s\n", name);
13703     SendToProgram(buf, cps);
13704     return TRUE;
13705   }
13706   return FALSE;
13707 }
13708
13709 int 
13710 ParseOption(Option *opt, ChessProgramState *cps)
13711 // [HGM] options: process the string that defines an engine option, and determine
13712 // name, type, default value, and allowed value range
13713 {
13714         char *p, *q, buf[MSG_SIZ];
13715         int n, min = (-1)<<31, max = 1<<31, def;
13716
13717         if(p = strstr(opt->name, " -spin ")) {
13718             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13719             if(max < min) max = min; // enforce consistency
13720             if(def < min) def = min;
13721             if(def > max) def = max;
13722             opt->value = def;
13723             opt->min = min;
13724             opt->max = max;
13725             opt->type = Spin;
13726         } else if((p = strstr(opt->name, " -slider "))) {
13727             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13728             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13729             if(max < min) max = min; // enforce consistency
13730             if(def < min) def = min;
13731             if(def > max) def = max;
13732             opt->value = def;
13733             opt->min = min;
13734             opt->max = max;
13735             opt->type = Spin; // Slider;
13736         } else if((p = strstr(opt->name, " -string "))) {
13737             opt->textValue = p+9;
13738             opt->type = TextBox;
13739         } else if((p = strstr(opt->name, " -file "))) {
13740             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13741             opt->textValue = p+7;
13742             opt->type = TextBox; // FileName;
13743         } else if((p = strstr(opt->name, " -path "))) {
13744             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13745             opt->textValue = p+7;
13746             opt->type = TextBox; // PathName;
13747         } else if(p = strstr(opt->name, " -check ")) {
13748             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13749             opt->value = (def != 0);
13750             opt->type = CheckBox;
13751         } else if(p = strstr(opt->name, " -combo ")) {
13752             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13753             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13754             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13755             opt->value = n = 0;
13756             while(q = StrStr(q, " /// ")) {
13757                 n++; *q = 0;    // count choices, and null-terminate each of them
13758                 q += 5;
13759                 if(*q == '*') { // remember default, which is marked with * prefix
13760                     q++;
13761                     opt->value = n;
13762                 }
13763                 cps->comboList[cps->comboCnt++] = q;
13764             }
13765             cps->comboList[cps->comboCnt++] = NULL;
13766             opt->max = n + 1;
13767             opt->type = ComboBox;
13768         } else if(p = strstr(opt->name, " -button")) {
13769             opt->type = Button;
13770         } else if(p = strstr(opt->name, " -save")) {
13771             opt->type = SaveButton;
13772         } else return FALSE;
13773         *p = 0; // terminate option name
13774         // now look if the command-line options define a setting for this engine option.
13775         if(cps->optionSettings && cps->optionSettings[0])
13776             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13777         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13778                 sprintf(buf, "option %s", p);
13779                 if(p = strstr(buf, ",")) *p = 0;
13780                 strcat(buf, "\n");
13781                 SendToProgram(buf, cps);
13782         }
13783         return TRUE;
13784 }
13785
13786 void
13787 FeatureDone(cps, val)
13788      ChessProgramState* cps;
13789      int val;
13790 {
13791   DelayedEventCallback cb = GetDelayedEvent();
13792   if ((cb == InitBackEnd3 && cps == &first) ||
13793       (cb == TwoMachinesEventIfReady && cps == &second)) {
13794     CancelDelayedEvent();
13795     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13796   }
13797   cps->initDone = val;
13798 }
13799
13800 /* Parse feature command from engine */
13801 void
13802 ParseFeatures(args, cps)
13803      char* args;
13804      ChessProgramState *cps;  
13805 {
13806   char *p = args;
13807   char *q;
13808   int val;
13809   char buf[MSG_SIZ];
13810
13811   for (;;) {
13812     while (*p == ' ') p++;
13813     if (*p == NULLCHAR) return;
13814
13815     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13816     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13817     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13818     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13819     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13820     if (BoolFeature(&p, "reuse", &val, cps)) {
13821       /* Engine can disable reuse, but can't enable it if user said no */
13822       if (!val) cps->reuse = FALSE;
13823       continue;
13824     }
13825     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13826     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13827       if (gameMode == TwoMachinesPlay) {
13828         DisplayTwoMachinesTitle();
13829       } else {
13830         DisplayTitle("");
13831       }
13832       continue;
13833     }
13834     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13835     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13836     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13837     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13838     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13839     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13840     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13841     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13842     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13843     if (IntFeature(&p, "done", &val, cps)) {
13844       FeatureDone(cps, val);
13845       continue;
13846     }
13847     /* Added by Tord: */
13848     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13849     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13850     /* End of additions by Tord */
13851
13852     /* [HGM] added features: */
13853     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13854     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13855     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13856     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13857     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13858     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13859     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13860         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13861             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13862             SendToProgram(buf, cps);
13863             continue;
13864         }
13865         if(cps->nrOptions >= MAX_OPTIONS) {
13866             cps->nrOptions--;
13867             sprintf(buf, "%s engine has too many options\n", cps->which);
13868             DisplayError(buf, 0);
13869         }
13870         continue;
13871     }
13872     /* End of additions by HGM */
13873
13874     /* unknown feature: complain and skip */
13875     q = p;
13876     while (*q && *q != '=') q++;
13877     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13878     SendToProgram(buf, cps);
13879     p = q;
13880     if (*p == '=') {
13881       p++;
13882       if (*p == '\"') {
13883         p++;
13884         while (*p && *p != '\"') p++;
13885         if (*p == '\"') p++;
13886       } else {
13887         while (*p && *p != ' ') p++;
13888       }
13889     }
13890   }
13891
13892 }
13893
13894 void
13895 PeriodicUpdatesEvent(newState)
13896      int newState;
13897 {
13898     if (newState == appData.periodicUpdates)
13899       return;
13900
13901     appData.periodicUpdates=newState;
13902
13903     /* Display type changes, so update it now */
13904 //    DisplayAnalysis();
13905
13906     /* Get the ball rolling again... */
13907     if (newState) {
13908         AnalysisPeriodicEvent(1);
13909         StartAnalysisClock();
13910     }
13911 }
13912
13913 void
13914 PonderNextMoveEvent(newState)
13915      int newState;
13916 {
13917     if (newState == appData.ponderNextMove) return;
13918     if (gameMode == EditPosition) EditPositionDone(TRUE);
13919     if (newState) {
13920         SendToProgram("hard\n", &first);
13921         if (gameMode == TwoMachinesPlay) {
13922             SendToProgram("hard\n", &second);
13923         }
13924     } else {
13925         SendToProgram("easy\n", &first);
13926         thinkOutput[0] = NULLCHAR;
13927         if (gameMode == TwoMachinesPlay) {
13928             SendToProgram("easy\n", &second);
13929         }
13930     }
13931     appData.ponderNextMove = newState;
13932 }
13933
13934 void
13935 NewSettingEvent(option, feature, command, value)
13936      char *command;
13937      int option, value, *feature;
13938 {
13939     char buf[MSG_SIZ];
13940
13941     if (gameMode == EditPosition) EditPositionDone(TRUE);
13942     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13943     if(feature == NULL || *feature) SendToProgram(buf, &first);
13944     if (gameMode == TwoMachinesPlay) {
13945         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13946     }
13947 }
13948
13949 void
13950 ShowThinkingEvent()
13951 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13952 {
13953     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13954     int newState = appData.showThinking
13955         // [HGM] thinking: other features now need thinking output as well
13956         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13957     
13958     if (oldState == newState) return;
13959     oldState = newState;
13960     if (gameMode == EditPosition) EditPositionDone(TRUE);
13961     if (oldState) {
13962         SendToProgram("post\n", &first);
13963         if (gameMode == TwoMachinesPlay) {
13964             SendToProgram("post\n", &second);
13965         }
13966     } else {
13967         SendToProgram("nopost\n", &first);
13968         thinkOutput[0] = NULLCHAR;
13969         if (gameMode == TwoMachinesPlay) {
13970             SendToProgram("nopost\n", &second);
13971         }
13972     }
13973 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13974 }
13975
13976 void
13977 AskQuestionEvent(title, question, replyPrefix, which)
13978      char *title; char *question; char *replyPrefix; char *which;
13979 {
13980   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13981   if (pr == NoProc) return;
13982   AskQuestion(title, question, replyPrefix, pr);
13983 }
13984
13985 void
13986 DisplayMove(moveNumber)
13987      int moveNumber;
13988 {
13989     char message[MSG_SIZ];
13990     char res[MSG_SIZ];
13991     char cpThinkOutput[MSG_SIZ];
13992
13993     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13994     
13995     if (moveNumber == forwardMostMove - 1 || 
13996         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13997
13998         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13999
14000         if (strchr(cpThinkOutput, '\n')) {
14001             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14002         }
14003     } else {
14004         *cpThinkOutput = NULLCHAR;
14005     }
14006
14007     /* [AS] Hide thinking from human user */
14008     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14009         *cpThinkOutput = NULLCHAR;
14010         if( thinkOutput[0] != NULLCHAR ) {
14011             int i;
14012
14013             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14014                 cpThinkOutput[i] = '.';
14015             }
14016             cpThinkOutput[i] = NULLCHAR;
14017             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14018         }
14019     }
14020
14021     if (moveNumber == forwardMostMove - 1 &&
14022         gameInfo.resultDetails != NULL) {
14023         if (gameInfo.resultDetails[0] == NULLCHAR) {
14024             sprintf(res, " %s", PGNResult(gameInfo.result));
14025         } else {
14026             sprintf(res, " {%s} %s",
14027                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14028         }
14029     } else {
14030         res[0] = NULLCHAR;
14031     }
14032
14033     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14034         DisplayMessage(res, cpThinkOutput);
14035     } else {
14036         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
14037                 WhiteOnMove(moveNumber) ? " " : ".. ",
14038                 parseList[moveNumber], res);
14039         DisplayMessage(message, cpThinkOutput);
14040     }
14041 }
14042
14043 void
14044 DisplayComment(moveNumber, text)
14045      int moveNumber;
14046      char *text;
14047 {
14048     char title[MSG_SIZ];
14049     char buf[8000]; // comment can be long!
14050     int score, depth;
14051     
14052     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14053       strcpy(title, "Comment");
14054     } else {
14055       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14056               WhiteOnMove(moveNumber) ? " " : ".. ",
14057               parseList[moveNumber]);
14058     }
14059     // [HGM] PV info: display PV info together with (or as) comment
14060     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14061       if(text == NULL) text = "";                                           
14062       score = pvInfoList[moveNumber].score;
14063       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14064               depth, (pvInfoList[moveNumber].time+50)/100, text);
14065       text = buf;
14066     }
14067     if (text != NULL && (appData.autoDisplayComment || commentUp))
14068         CommentPopUp(title, text);
14069 }
14070
14071 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14072  * might be busy thinking or pondering.  It can be omitted if your
14073  * gnuchess is configured to stop thinking immediately on any user
14074  * input.  However, that gnuchess feature depends on the FIONREAD
14075  * ioctl, which does not work properly on some flavors of Unix.
14076  */
14077 void
14078 Attention(cps)
14079      ChessProgramState *cps;
14080 {
14081 #if ATTENTION
14082     if (!cps->useSigint) return;
14083     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14084     switch (gameMode) {
14085       case MachinePlaysWhite:
14086       case MachinePlaysBlack:
14087       case TwoMachinesPlay:
14088       case IcsPlayingWhite:
14089       case IcsPlayingBlack:
14090       case AnalyzeMode:
14091       case AnalyzeFile:
14092         /* Skip if we know it isn't thinking */
14093         if (!cps->maybeThinking) return;
14094         if (appData.debugMode)
14095           fprintf(debugFP, "Interrupting %s\n", cps->which);
14096         InterruptChildProcess(cps->pr);
14097         cps->maybeThinking = FALSE;
14098         break;
14099       default:
14100         break;
14101     }
14102 #endif /*ATTENTION*/
14103 }
14104
14105 int
14106 CheckFlags()
14107 {
14108     if (whiteTimeRemaining <= 0) {
14109         if (!whiteFlag) {
14110             whiteFlag = TRUE;
14111             if (appData.icsActive) {
14112                 if (appData.autoCallFlag &&
14113                     gameMode == IcsPlayingBlack && !blackFlag) {
14114                   SendToICS(ics_prefix);
14115                   SendToICS("flag\n");
14116                 }
14117             } else {
14118                 if (blackFlag) {
14119                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14120                 } else {
14121                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14122                     if (appData.autoCallFlag) {
14123                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14124                         return TRUE;
14125                     }
14126                 }
14127             }
14128         }
14129     }
14130     if (blackTimeRemaining <= 0) {
14131         if (!blackFlag) {
14132             blackFlag = TRUE;
14133             if (appData.icsActive) {
14134                 if (appData.autoCallFlag &&
14135                     gameMode == IcsPlayingWhite && !whiteFlag) {
14136                   SendToICS(ics_prefix);
14137                   SendToICS("flag\n");
14138                 }
14139             } else {
14140                 if (whiteFlag) {
14141                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14142                 } else {
14143                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14144                     if (appData.autoCallFlag) {
14145                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14146                         return TRUE;
14147                     }
14148                 }
14149             }
14150         }
14151     }
14152     return FALSE;
14153 }
14154
14155 void
14156 CheckTimeControl()
14157 {
14158     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14159         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14160
14161     /*
14162      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14163      */
14164     if ( !WhiteOnMove(forwardMostMove) )
14165         /* White made time control */
14166         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14167         /* [HGM] time odds: correct new time quota for time odds! */
14168                                             / WhitePlayer()->timeOdds;
14169       else
14170         /* Black made time control */
14171         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14172                                             / WhitePlayer()->other->timeOdds;
14173 }
14174
14175 void
14176 DisplayBothClocks()
14177 {
14178     int wom = gameMode == EditPosition ?
14179       !blackPlaysFirst : WhiteOnMove(currentMove);
14180     DisplayWhiteClock(whiteTimeRemaining, wom);
14181     DisplayBlackClock(blackTimeRemaining, !wom);
14182 }
14183
14184
14185 /* Timekeeping seems to be a portability nightmare.  I think everyone
14186    has ftime(), but I'm really not sure, so I'm including some ifdefs
14187    to use other calls if you don't.  Clocks will be less accurate if
14188    you have neither ftime nor gettimeofday.
14189 */
14190
14191 /* VS 2008 requires the #include outside of the function */
14192 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14193 #include <sys/timeb.h>
14194 #endif
14195
14196 /* Get the current time as a TimeMark */
14197 void
14198 GetTimeMark(tm)
14199      TimeMark *tm;
14200 {
14201 #if HAVE_GETTIMEOFDAY
14202
14203     struct timeval timeVal;
14204     struct timezone timeZone;
14205
14206     gettimeofday(&timeVal, &timeZone);
14207     tm->sec = (long) timeVal.tv_sec; 
14208     tm->ms = (int) (timeVal.tv_usec / 1000L);
14209
14210 #else /*!HAVE_GETTIMEOFDAY*/
14211 #if HAVE_FTIME
14212
14213 // include <sys/timeb.h> / moved to just above start of function
14214     struct timeb timeB;
14215
14216     ftime(&timeB);
14217     tm->sec = (long) timeB.time;
14218     tm->ms = (int) timeB.millitm;
14219
14220 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14221     tm->sec = (long) time(NULL);
14222     tm->ms = 0;
14223 #endif
14224 #endif
14225 }
14226
14227 /* Return the difference in milliseconds between two
14228    time marks.  We assume the difference will fit in a long!
14229 */
14230 long
14231 SubtractTimeMarks(tm2, tm1)
14232      TimeMark *tm2, *tm1;
14233 {
14234     return 1000L*(tm2->sec - tm1->sec) +
14235            (long) (tm2->ms - tm1->ms);
14236 }
14237
14238
14239 /*
14240  * Code to manage the game clocks.
14241  *
14242  * In tournament play, black starts the clock and then white makes a move.
14243  * We give the human user a slight advantage if he is playing white---the
14244  * clocks don't run until he makes his first move, so it takes zero time.
14245  * Also, we don't account for network lag, so we could get out of sync
14246  * with GNU Chess's clock -- but then, referees are always right.  
14247  */
14248
14249 static TimeMark tickStartTM;
14250 static long intendedTickLength;
14251
14252 long
14253 NextTickLength(timeRemaining)
14254      long timeRemaining;
14255 {
14256     long nominalTickLength, nextTickLength;
14257
14258     if (timeRemaining > 0L && timeRemaining <= 10000L)
14259       nominalTickLength = 100L;
14260     else
14261       nominalTickLength = 1000L;
14262     nextTickLength = timeRemaining % nominalTickLength;
14263     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14264
14265     return nextTickLength;
14266 }
14267
14268 /* Adjust clock one minute up or down */
14269 void
14270 AdjustClock(Boolean which, int dir)
14271 {
14272     if(which) blackTimeRemaining += 60000*dir;
14273     else      whiteTimeRemaining += 60000*dir;
14274     DisplayBothClocks();
14275 }
14276
14277 /* Stop clocks and reset to a fresh time control */
14278 void
14279 ResetClocks() 
14280 {
14281     (void) StopClockTimer();
14282     if (appData.icsActive) {
14283         whiteTimeRemaining = blackTimeRemaining = 0;
14284     } else if (searchTime) {
14285         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14286         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14287     } else { /* [HGM] correct new time quote for time odds */
14288         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14289         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14290     }
14291     if (whiteFlag || blackFlag) {
14292         DisplayTitle("");
14293         whiteFlag = blackFlag = FALSE;
14294     }
14295     DisplayBothClocks();
14296 }
14297
14298 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14299
14300 /* Decrement running clock by amount of time that has passed */
14301 void
14302 DecrementClocks()
14303 {
14304     long timeRemaining;
14305     long lastTickLength, fudge;
14306     TimeMark now;
14307
14308     if (!appData.clockMode) return;
14309     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14310         
14311     GetTimeMark(&now);
14312
14313     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14314
14315     /* Fudge if we woke up a little too soon */
14316     fudge = intendedTickLength - lastTickLength;
14317     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14318
14319     if (WhiteOnMove(forwardMostMove)) {
14320         if(whiteNPS >= 0) lastTickLength = 0;
14321         timeRemaining = whiteTimeRemaining -= lastTickLength;
14322         DisplayWhiteClock(whiteTimeRemaining - fudge,
14323                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14324     } else {
14325         if(blackNPS >= 0) lastTickLength = 0;
14326         timeRemaining = blackTimeRemaining -= lastTickLength;
14327         DisplayBlackClock(blackTimeRemaining - fudge,
14328                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14329     }
14330
14331     if (CheckFlags()) return;
14332         
14333     tickStartTM = now;
14334     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14335     StartClockTimer(intendedTickLength);
14336
14337     /* if the time remaining has fallen below the alarm threshold, sound the
14338      * alarm. if the alarm has sounded and (due to a takeback or time control
14339      * with increment) the time remaining has increased to a level above the
14340      * threshold, reset the alarm so it can sound again. 
14341      */
14342     
14343     if (appData.icsActive && appData.icsAlarm) {
14344
14345         /* make sure we are dealing with the user's clock */
14346         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14347                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14348            )) return;
14349
14350         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14351             alarmSounded = FALSE;
14352         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14353             PlayAlarmSound();
14354             alarmSounded = TRUE;
14355         }
14356     }
14357 }
14358
14359
14360 /* A player has just moved, so stop the previously running
14361    clock and (if in clock mode) start the other one.
14362    We redisplay both clocks in case we're in ICS mode, because
14363    ICS gives us an update to both clocks after every move.
14364    Note that this routine is called *after* forwardMostMove
14365    is updated, so the last fractional tick must be subtracted
14366    from the color that is *not* on move now.
14367 */
14368 void
14369 SwitchClocks(int newMoveNr)
14370 {
14371     long lastTickLength;
14372     TimeMark now;
14373     int flagged = FALSE;
14374
14375     GetTimeMark(&now);
14376
14377     if (StopClockTimer() && appData.clockMode) {
14378         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14379         if (!WhiteOnMove(forwardMostMove)) {
14380             if(blackNPS >= 0) lastTickLength = 0;
14381             blackTimeRemaining -= lastTickLength;
14382            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14383 //         if(pvInfoList[forwardMostMove-1].time == -1)
14384                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14385                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14386         } else {
14387            if(whiteNPS >= 0) lastTickLength = 0;
14388            whiteTimeRemaining -= lastTickLength;
14389            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14390 //         if(pvInfoList[forwardMostMove-1].time == -1)
14391                  pvInfoList[forwardMostMove-1].time = 
14392                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14393         }
14394         flagged = CheckFlags();
14395     }
14396     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14397     CheckTimeControl();
14398
14399     if (flagged || !appData.clockMode) return;
14400
14401     switch (gameMode) {
14402       case MachinePlaysBlack:
14403       case MachinePlaysWhite:
14404       case BeginningOfGame:
14405         if (pausing) return;
14406         break;
14407
14408       case EditGame:
14409       case PlayFromGameFile:
14410       case IcsExamining:
14411         return;
14412
14413       default:
14414         break;
14415     }
14416
14417     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14418         if(WhiteOnMove(forwardMostMove))
14419              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14420         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14421     }
14422
14423     tickStartTM = now;
14424     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14425       whiteTimeRemaining : blackTimeRemaining);
14426     StartClockTimer(intendedTickLength);
14427 }
14428         
14429
14430 /* Stop both clocks */
14431 void
14432 StopClocks()
14433 {       
14434     long lastTickLength;
14435     TimeMark now;
14436
14437     if (!StopClockTimer()) return;
14438     if (!appData.clockMode) return;
14439
14440     GetTimeMark(&now);
14441
14442     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14443     if (WhiteOnMove(forwardMostMove)) {
14444         if(whiteNPS >= 0) lastTickLength = 0;
14445         whiteTimeRemaining -= lastTickLength;
14446         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14447     } else {
14448         if(blackNPS >= 0) lastTickLength = 0;
14449         blackTimeRemaining -= lastTickLength;
14450         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14451     }
14452     CheckFlags();
14453 }
14454         
14455 /* Start clock of player on move.  Time may have been reset, so
14456    if clock is already running, stop and restart it. */
14457 void
14458 StartClocks()
14459 {
14460     (void) StopClockTimer(); /* in case it was running already */
14461     DisplayBothClocks();
14462     if (CheckFlags()) return;
14463
14464     if (!appData.clockMode) return;
14465     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14466
14467     GetTimeMark(&tickStartTM);
14468     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14469       whiteTimeRemaining : blackTimeRemaining);
14470
14471    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14472     whiteNPS = blackNPS = -1; 
14473     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14474        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14475         whiteNPS = first.nps;
14476     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14477        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14478         blackNPS = first.nps;
14479     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14480         whiteNPS = second.nps;
14481     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14482         blackNPS = second.nps;
14483     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14484
14485     StartClockTimer(intendedTickLength);
14486 }
14487
14488 char *
14489 TimeString(ms)
14490      long ms;
14491 {
14492     long second, minute, hour, day;
14493     char *sign = "";
14494     static char buf[32];
14495     
14496     if (ms > 0 && ms <= 9900) {
14497       /* convert milliseconds to tenths, rounding up */
14498       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14499
14500       sprintf(buf, " %03.1f ", tenths/10.0);
14501       return buf;
14502     }
14503
14504     /* convert milliseconds to seconds, rounding up */
14505     /* use floating point to avoid strangeness of integer division
14506        with negative dividends on many machines */
14507     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14508
14509     if (second < 0) {
14510         sign = "-";
14511         second = -second;
14512     }
14513     
14514     day = second / (60 * 60 * 24);
14515     second = second % (60 * 60 * 24);
14516     hour = second / (60 * 60);
14517     second = second % (60 * 60);
14518     minute = second / 60;
14519     second = second % 60;
14520     
14521     if (day > 0)
14522       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14523               sign, day, hour, minute, second);
14524     else if (hour > 0)
14525       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14526     else
14527       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14528     
14529     return buf;
14530 }
14531
14532
14533 /*
14534  * This is necessary because some C libraries aren't ANSI C compliant yet.
14535  */
14536 char *
14537 StrStr(string, match)
14538      char *string, *match;
14539 {
14540     int i, length;
14541     
14542     length = strlen(match);
14543     
14544     for (i = strlen(string) - length; i >= 0; i--, string++)
14545       if (!strncmp(match, string, length))
14546         return string;
14547     
14548     return NULL;
14549 }
14550
14551 char *
14552 StrCaseStr(string, match)
14553      char *string, *match;
14554 {
14555     int i, j, length;
14556     
14557     length = strlen(match);
14558     
14559     for (i = strlen(string) - length; i >= 0; i--, string++) {
14560         for (j = 0; j < length; j++) {
14561             if (ToLower(match[j]) != ToLower(string[j]))
14562               break;
14563         }
14564         if (j == length) return string;
14565     }
14566
14567     return NULL;
14568 }
14569
14570 #ifndef _amigados
14571 int
14572 StrCaseCmp(s1, s2)
14573      char *s1, *s2;
14574 {
14575     char c1, c2;
14576     
14577     for (;;) {
14578         c1 = ToLower(*s1++);
14579         c2 = ToLower(*s2++);
14580         if (c1 > c2) return 1;
14581         if (c1 < c2) return -1;
14582         if (c1 == NULLCHAR) return 0;
14583     }
14584 }
14585
14586
14587 int
14588 ToLower(c)
14589      int c;
14590 {
14591     return isupper(c) ? tolower(c) : c;
14592 }
14593
14594
14595 int
14596 ToUpper(c)
14597      int c;
14598 {
14599     return islower(c) ? toupper(c) : c;
14600 }
14601 #endif /* !_amigados    */
14602
14603 char *
14604 StrSave(s)
14605      char *s;
14606 {
14607     char *ret;
14608
14609     if ((ret = (char *) malloc(strlen(s) + 1))) {
14610         strcpy(ret, s);
14611     }
14612     return ret;
14613 }
14614
14615 char *
14616 StrSavePtr(s, savePtr)
14617      char *s, **savePtr;
14618 {
14619     if (*savePtr) {
14620         free(*savePtr);
14621     }
14622     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14623         strcpy(*savePtr, s);
14624     }
14625     return(*savePtr);
14626 }
14627
14628 char *
14629 PGNDate()
14630 {
14631     time_t clock;
14632     struct tm *tm;
14633     char buf[MSG_SIZ];
14634
14635     clock = time((time_t *)NULL);
14636     tm = localtime(&clock);
14637     sprintf(buf, "%04d.%02d.%02d",
14638             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14639     return StrSave(buf);
14640 }
14641
14642
14643 char *
14644 PositionToFEN(move, overrideCastling)
14645      int move;
14646      char *overrideCastling;
14647 {
14648     int i, j, fromX, fromY, toX, toY;
14649     int whiteToPlay;
14650     char buf[128];
14651     char *p, *q;
14652     int emptycount;
14653     ChessSquare piece;
14654
14655     whiteToPlay = (gameMode == EditPosition) ?
14656       !blackPlaysFirst : (move % 2 == 0);
14657     p = buf;
14658
14659     /* Piece placement data */
14660     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14661         emptycount = 0;
14662         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14663             if (boards[move][i][j] == EmptySquare) {
14664                 emptycount++;
14665             } else { ChessSquare piece = boards[move][i][j];
14666                 if (emptycount > 0) {
14667                     if(emptycount<10) /* [HGM] can be >= 10 */
14668                         *p++ = '0' + emptycount;
14669                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14670                     emptycount = 0;
14671                 }
14672                 if(PieceToChar(piece) == '+') {
14673                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14674                     *p++ = '+';
14675                     piece = (ChessSquare)(DEMOTED piece);
14676                 } 
14677                 *p++ = PieceToChar(piece);
14678                 if(p[-1] == '~') {
14679                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14680                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14681                     *p++ = '~';
14682                 }
14683             }
14684         }
14685         if (emptycount > 0) {
14686             if(emptycount<10) /* [HGM] can be >= 10 */
14687                 *p++ = '0' + emptycount;
14688             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14689             emptycount = 0;
14690         }
14691         *p++ = '/';
14692     }
14693     *(p - 1) = ' ';
14694
14695     /* [HGM] print Crazyhouse or Shogi holdings */
14696     if( gameInfo.holdingsWidth ) {
14697         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14698         q = p;
14699         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14700             piece = boards[move][i][BOARD_WIDTH-1];
14701             if( piece != EmptySquare )
14702               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14703                   *p++ = PieceToChar(piece);
14704         }
14705         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14706             piece = boards[move][BOARD_HEIGHT-i-1][0];
14707             if( piece != EmptySquare )
14708               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14709                   *p++ = PieceToChar(piece);
14710         }
14711
14712         if( q == p ) *p++ = '-';
14713         *p++ = ']';
14714         *p++ = ' ';
14715     }
14716
14717     /* Active color */
14718     *p++ = whiteToPlay ? 'w' : 'b';
14719     *p++ = ' ';
14720
14721   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14722     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14723   } else {
14724   if(nrCastlingRights) {
14725      q = p;
14726      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14727        /* [HGM] write directly from rights */
14728            if(boards[move][CASTLING][2] != NoRights &&
14729               boards[move][CASTLING][0] != NoRights   )
14730                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14731            if(boards[move][CASTLING][2] != NoRights &&
14732               boards[move][CASTLING][1] != NoRights   )
14733                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14734            if(boards[move][CASTLING][5] != NoRights &&
14735               boards[move][CASTLING][3] != NoRights   )
14736                 *p++ = boards[move][CASTLING][3] + AAA;
14737            if(boards[move][CASTLING][5] != NoRights &&
14738               boards[move][CASTLING][4] != NoRights   )
14739                 *p++ = boards[move][CASTLING][4] + AAA;
14740      } else {
14741
14742         /* [HGM] write true castling rights */
14743         if( nrCastlingRights == 6 ) {
14744             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14745                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14746             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14747                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14748             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14749                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14750             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14751                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14752         }
14753      }
14754      if (q == p) *p++ = '-'; /* No castling rights */
14755      *p++ = ' ';
14756   }
14757
14758   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14759      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14760     /* En passant target square */
14761     if (move > backwardMostMove) {
14762         fromX = moveList[move - 1][0] - AAA;
14763         fromY = moveList[move - 1][1] - ONE;
14764         toX = moveList[move - 1][2] - AAA;
14765         toY = moveList[move - 1][3] - ONE;
14766         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14767             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14768             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14769             fromX == toX) {
14770             /* 2-square pawn move just happened */
14771             *p++ = toX + AAA;
14772             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14773         } else {
14774             *p++ = '-';
14775         }
14776     } else if(move == backwardMostMove) {
14777         // [HGM] perhaps we should always do it like this, and forget the above?
14778         if((signed char)boards[move][EP_STATUS] >= 0) {
14779             *p++ = boards[move][EP_STATUS] + AAA;
14780             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14781         } else {
14782             *p++ = '-';
14783         }
14784     } else {
14785         *p++ = '-';
14786     }
14787     *p++ = ' ';
14788   }
14789   }
14790
14791     /* [HGM] find reversible plies */
14792     {   int i = 0, j=move;
14793
14794         if (appData.debugMode) { int k;
14795             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14796             for(k=backwardMostMove; k<=forwardMostMove; k++)
14797                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14798
14799         }
14800
14801         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14802         if( j == backwardMostMove ) i += initialRulePlies;
14803         sprintf(p, "%d ", i);
14804         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14805     }
14806     /* Fullmove number */
14807     sprintf(p, "%d", (move / 2) + 1);
14808     
14809     return StrSave(buf);
14810 }
14811
14812 Boolean
14813 ParseFEN(board, blackPlaysFirst, fen)
14814     Board board;
14815      int *blackPlaysFirst;
14816      char *fen;
14817 {
14818     int i, j;
14819     char *p, c;
14820     int emptycount;
14821     ChessSquare piece;
14822
14823     p = fen;
14824
14825     /* [HGM] by default clear Crazyhouse holdings, if present */
14826     if(gameInfo.holdingsWidth) {
14827        for(i=0; i<BOARD_HEIGHT; i++) {
14828            board[i][0]             = EmptySquare; /* black holdings */
14829            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14830            board[i][1]             = (ChessSquare) 0; /* black counts */
14831            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14832        }
14833     }
14834
14835     /* Piece placement data */
14836     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14837         j = 0;
14838         for (;;) {
14839             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14840                 if (*p == '/') p++;
14841                 emptycount = gameInfo.boardWidth - j;
14842                 while (emptycount--)
14843                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14844                 break;
14845 #if(BOARD_FILES >= 10)
14846             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14847                 p++; emptycount=10;
14848                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14849                 while (emptycount--)
14850                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14851 #endif
14852             } else if (isdigit(*p)) {
14853                 emptycount = *p++ - '0';
14854                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14855                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14856                 while (emptycount--)
14857                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14858             } else if (*p == '+' || isalpha(*p)) {
14859                 if (j >= gameInfo.boardWidth) return FALSE;
14860                 if(*p=='+') {
14861                     piece = CharToPiece(*++p);
14862                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14863                     piece = (ChessSquare) (PROMOTED piece ); p++;
14864                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14865                 } else piece = CharToPiece(*p++);
14866
14867                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14868                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14869                     piece = (ChessSquare) (PROMOTED piece);
14870                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14871                     p++;
14872                 }
14873                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14874             } else {
14875                 return FALSE;
14876             }
14877         }
14878     }
14879     while (*p == '/' || *p == ' ') p++;
14880
14881     /* [HGM] look for Crazyhouse holdings here */
14882     while(*p==' ') p++;
14883     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14884         if(*p == '[') p++;
14885         if(*p == '-' ) *p++; /* empty holdings */ else {
14886             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14887             /* if we would allow FEN reading to set board size, we would   */
14888             /* have to add holdings and shift the board read so far here   */
14889             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14890                 *p++;
14891                 if((int) piece >= (int) BlackPawn ) {
14892                     i = (int)piece - (int)BlackPawn;
14893                     i = PieceToNumber((ChessSquare)i);
14894                     if( i >= gameInfo.holdingsSize ) return FALSE;
14895                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14896                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14897                 } else {
14898                     i = (int)piece - (int)WhitePawn;
14899                     i = PieceToNumber((ChessSquare)i);
14900                     if( i >= gameInfo.holdingsSize ) return FALSE;
14901                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14902                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14903                 }
14904             }
14905         }
14906         if(*p == ']') *p++;
14907     }
14908
14909     while(*p == ' ') p++;
14910
14911     /* Active color */
14912     c = *p++;
14913     if(appData.colorNickNames) {
14914       if( c == appData.colorNickNames[0] ) c = 'w'; else
14915       if( c == appData.colorNickNames[1] ) c = 'b';
14916     }
14917     switch (c) {
14918       case 'w':
14919         *blackPlaysFirst = FALSE;
14920         break;
14921       case 'b': 
14922         *blackPlaysFirst = TRUE;
14923         break;
14924       default:
14925         return FALSE;
14926     }
14927
14928     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14929     /* return the extra info in global variiables             */
14930
14931     /* set defaults in case FEN is incomplete */
14932     board[EP_STATUS] = EP_UNKNOWN;
14933     for(i=0; i<nrCastlingRights; i++ ) {
14934         board[CASTLING][i] =
14935             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14936     }   /* assume possible unless obviously impossible */
14937     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14938     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14939     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14940                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14941     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14942     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14943     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14944                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14945     FENrulePlies = 0;
14946
14947     while(*p==' ') p++;
14948     if(nrCastlingRights) {
14949       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14950           /* castling indicator present, so default becomes no castlings */
14951           for(i=0; i<nrCastlingRights; i++ ) {
14952                  board[CASTLING][i] = NoRights;
14953           }
14954       }
14955       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14956              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14957              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14958              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14959         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14960
14961         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14962             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14963             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14964         }
14965         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14966             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14967         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14968                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14969         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14970                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14971         switch(c) {
14972           case'K':
14973               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14974               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14975               board[CASTLING][2] = whiteKingFile;
14976               break;
14977           case'Q':
14978               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14979               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14980               board[CASTLING][2] = whiteKingFile;
14981               break;
14982           case'k':
14983               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14984               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14985               board[CASTLING][5] = blackKingFile;
14986               break;
14987           case'q':
14988               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14989               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14990               board[CASTLING][5] = blackKingFile;
14991           case '-':
14992               break;
14993           default: /* FRC castlings */
14994               if(c >= 'a') { /* black rights */
14995                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14996                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14997                   if(i == BOARD_RGHT) break;
14998                   board[CASTLING][5] = i;
14999                   c -= AAA;
15000                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15001                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15002                   if(c > i)
15003                       board[CASTLING][3] = c;
15004                   else
15005                       board[CASTLING][4] = c;
15006               } else { /* white rights */
15007                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15008                     if(board[0][i] == WhiteKing) break;
15009                   if(i == BOARD_RGHT) break;
15010                   board[CASTLING][2] = i;
15011                   c -= AAA - 'a' + 'A';
15012                   if(board[0][c] >= WhiteKing) break;
15013                   if(c > i)
15014                       board[CASTLING][0] = c;
15015                   else
15016                       board[CASTLING][1] = c;
15017               }
15018         }
15019       }
15020       for(i=0; i<nrCastlingRights; i++)
15021         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15022     if (appData.debugMode) {
15023         fprintf(debugFP, "FEN castling rights:");
15024         for(i=0; i<nrCastlingRights; i++)
15025         fprintf(debugFP, " %d", board[CASTLING][i]);
15026         fprintf(debugFP, "\n");
15027     }
15028
15029       while(*p==' ') p++;
15030     }
15031
15032     /* read e.p. field in games that know e.p. capture */
15033     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15034        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
15035       if(*p=='-') {
15036         p++; board[EP_STATUS] = EP_NONE;
15037       } else {
15038          char c = *p++ - AAA;
15039
15040          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15041          if(*p >= '0' && *p <='9') *p++;
15042          board[EP_STATUS] = c;
15043       }
15044     }
15045
15046
15047     if(sscanf(p, "%d", &i) == 1) {
15048         FENrulePlies = i; /* 50-move ply counter */
15049         /* (The move number is still ignored)    */
15050     }
15051
15052     return TRUE;
15053 }
15054       
15055 void
15056 EditPositionPasteFEN(char *fen)
15057 {
15058   if (fen != NULL) {
15059     Board initial_position;
15060
15061     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15062       DisplayError(_("Bad FEN position in clipboard"), 0);
15063       return ;
15064     } else {
15065       int savedBlackPlaysFirst = blackPlaysFirst;
15066       EditPositionEvent();
15067       blackPlaysFirst = savedBlackPlaysFirst;
15068       CopyBoard(boards[0], initial_position);
15069       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15070       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15071       DisplayBothClocks();
15072       DrawPosition(FALSE, boards[currentMove]);
15073     }
15074   }
15075 }
15076
15077 static char cseq[12] = "\\   ";
15078
15079 Boolean set_cont_sequence(char *new_seq)
15080 {
15081     int len;
15082     Boolean ret;
15083
15084     // handle bad attempts to set the sequence
15085         if (!new_seq)
15086                 return 0; // acceptable error - no debug
15087
15088     len = strlen(new_seq);
15089     ret = (len > 0) && (len < sizeof(cseq));
15090     if (ret)
15091         strcpy(cseq, new_seq);
15092     else if (appData.debugMode)
15093         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15094     return ret;
15095 }
15096
15097 /*
15098     reformat a source message so words don't cross the width boundary.  internal
15099     newlines are not removed.  returns the wrapped size (no null character unless
15100     included in source message).  If dest is NULL, only calculate the size required
15101     for the dest buffer.  lp argument indicats line position upon entry, and it's
15102     passed back upon exit.
15103 */
15104 int wrap(char *dest, char *src, int count, int width, int *lp)
15105 {
15106     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15107
15108     cseq_len = strlen(cseq);
15109     old_line = line = *lp;
15110     ansi = len = clen = 0;
15111
15112     for (i=0; i < count; i++)
15113     {
15114         if (src[i] == '\033')
15115             ansi = 1;
15116
15117         // if we hit the width, back up
15118         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15119         {
15120             // store i & len in case the word is too long
15121             old_i = i, old_len = len;
15122
15123             // find the end of the last word
15124             while (i && src[i] != ' ' && src[i] != '\n')
15125             {
15126                 i--;
15127                 len--;
15128             }
15129
15130             // word too long?  restore i & len before splitting it
15131             if ((old_i-i+clen) >= width)
15132             {
15133                 i = old_i;
15134                 len = old_len;
15135             }
15136
15137             // extra space?
15138             if (i && src[i-1] == ' ')
15139                 len--;
15140
15141             if (src[i] != ' ' && src[i] != '\n')
15142             {
15143                 i--;
15144                 if (len)
15145                     len--;
15146             }
15147
15148             // now append the newline and continuation sequence
15149             if (dest)
15150                 dest[len] = '\n';
15151             len++;
15152             if (dest)
15153                 strncpy(dest+len, cseq, cseq_len);
15154             len += cseq_len;
15155             line = cseq_len;
15156             clen = cseq_len;
15157             continue;
15158         }
15159
15160         if (dest)
15161             dest[len] = src[i];
15162         len++;
15163         if (!ansi)
15164             line++;
15165         if (src[i] == '\n')
15166             line = 0;
15167         if (src[i] == 'm')
15168             ansi = 0;
15169     }
15170     if (dest && appData.debugMode)
15171     {
15172         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15173             count, width, line, len, *lp);
15174         show_bytes(debugFP, src, count);
15175         fprintf(debugFP, "\ndest: ");
15176         show_bytes(debugFP, dest, len);
15177         fprintf(debugFP, "\n");
15178     }
15179     *lp = dest ? line : old_line;
15180
15181     return len;
15182 }
15183
15184 // [HGM] vari: routines for shelving variations
15185
15186 void 
15187 PushTail(int firstMove, int lastMove)
15188 {
15189         int i, j, nrMoves = lastMove - firstMove;
15190
15191         if(appData.icsActive) { // only in local mode
15192                 forwardMostMove = currentMove; // mimic old ICS behavior
15193                 return;
15194         }
15195         if(storedGames >= MAX_VARIATIONS-1) return;
15196
15197         // push current tail of game on stack
15198         savedResult[storedGames] = gameInfo.result;
15199         savedDetails[storedGames] = gameInfo.resultDetails;
15200         gameInfo.resultDetails = NULL;
15201         savedFirst[storedGames] = firstMove;
15202         savedLast [storedGames] = lastMove;
15203         savedFramePtr[storedGames] = framePtr;
15204         framePtr -= nrMoves; // reserve space for the boards
15205         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15206             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15207             for(j=0; j<MOVE_LEN; j++)
15208                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15209             for(j=0; j<2*MOVE_LEN; j++)
15210                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15211             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15212             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15213             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15214             pvInfoList[firstMove+i-1].depth = 0;
15215             commentList[framePtr+i] = commentList[firstMove+i];
15216             commentList[firstMove+i] = NULL;
15217         }
15218
15219         storedGames++;
15220         forwardMostMove = firstMove; // truncate game so we can start variation
15221         if(storedGames == 1) GreyRevert(FALSE);
15222 }
15223
15224 Boolean
15225 PopTail(Boolean annotate)
15226 {
15227         int i, j, nrMoves;
15228         char buf[8000], moveBuf[20];
15229
15230         if(appData.icsActive) return FALSE; // only in local mode
15231         if(!storedGames) return FALSE; // sanity
15232         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15233
15234         storedGames--;
15235         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15236         nrMoves = savedLast[storedGames] - currentMove;
15237         if(annotate) {
15238                 int cnt = 10;
15239                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15240                 else strcpy(buf, "(");
15241                 for(i=currentMove; i<forwardMostMove; i++) {
15242                         if(WhiteOnMove(i))
15243                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15244                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15245                         strcat(buf, moveBuf);
15246                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15247                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15248                 }
15249                 strcat(buf, ")");
15250         }
15251         for(i=1; i<=nrMoves; i++) { // copy last variation back
15252             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15253             for(j=0; j<MOVE_LEN; j++)
15254                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15255             for(j=0; j<2*MOVE_LEN; j++)
15256                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15257             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15258             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15259             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15260             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15261             commentList[currentMove+i] = commentList[framePtr+i];
15262             commentList[framePtr+i] = NULL;
15263         }
15264         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15265         framePtr = savedFramePtr[storedGames];
15266         gameInfo.result = savedResult[storedGames];
15267         if(gameInfo.resultDetails != NULL) {
15268             free(gameInfo.resultDetails);
15269       }
15270         gameInfo.resultDetails = savedDetails[storedGames];
15271         forwardMostMove = currentMove + nrMoves;
15272         if(storedGames == 0) GreyRevert(TRUE);
15273         return TRUE;
15274 }
15275
15276 void 
15277 CleanupTail()
15278 {       // remove all shelved variations
15279         int i;
15280         for(i=0; i<storedGames; i++) {
15281             if(savedDetails[i])
15282                 free(savedDetails[i]);
15283             savedDetails[i] = NULL;
15284         }
15285         for(i=framePtr; i<MAX_MOVES; i++) {
15286                 if(commentList[i]) free(commentList[i]);
15287                 commentList[i] = NULL;
15288         }
15289         framePtr = MAX_MOVES-1;
15290         storedGames = 0;
15291 }
15292
15293 void
15294 LoadVariation(int index, char *text)
15295 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15296         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15297         int level = 0, move;
15298
15299         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15300         // first find outermost bracketing variation
15301         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15302             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15303                 if(*p == '{') wait = '}'; else
15304                 if(*p == '[') wait = ']'; else
15305                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15306                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15307             }
15308             if(*p == wait) wait = NULLCHAR; // closing ]} found
15309             p++;
15310         }
15311         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15312         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15313         end[1] = NULLCHAR; // clip off comment beyond variation
15314         ToNrEvent(currentMove-1);
15315         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15316         // kludge: use ParsePV() to append variation to game
15317         move = currentMove;
15318         ParsePV(start, TRUE);
15319         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15320         ClearPremoveHighlights();
15321         CommentPopDown();
15322         ToNrEvent(currentMove+1);
15323 }
15324