Add string option /pieceNickNames
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237 VariantClass startVariant; /* [HGM] nicks: initial variant */
238
239 extern int tinyLayout, smallLayout;
240 ChessProgramStats programStats;
241 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
242 int endPV = -1;
243 static int exiting = 0; /* [HGM] moved to top */
244 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
245 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
246 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
247 int partnerHighlight[2];
248 Boolean partnerBoardValid = 0;
249 char partnerStatus[MSG_SIZ];
250 Boolean partnerUp;
251 Boolean originalFlip;
252 Boolean twoBoards = 0;
253 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
254 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
255 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
256 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
257 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
258 int opponentKibitzes;
259 int lastSavedGame; /* [HGM] save: ID of game */
260 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
261 extern int chatCount;
262 int chattingPartner;
263 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
264
265 /* States for ics_getting_history */
266 #define H_FALSE 0
267 #define H_REQUESTED 1
268 #define H_GOT_REQ_HEADER 2
269 #define H_GOT_UNREQ_HEADER 3
270 #define H_GETTING_MOVES 4
271 #define H_GOT_UNWANTED_HEADER 5
272
273 /* whosays values for GameEnds */
274 #define GE_ICS 0
275 #define GE_ENGINE 1
276 #define GE_PLAYER 2
277 #define GE_FILE 3
278 #define GE_XBOARD 4
279 #define GE_ENGINE1 5
280 #define GE_ENGINE2 6
281
282 /* Maximum number of games in a cmail message */
283 #define CMAIL_MAX_GAMES 20
284
285 /* Different types of move when calling RegisterMove */
286 #define CMAIL_MOVE   0
287 #define CMAIL_RESIGN 1
288 #define CMAIL_DRAW   2
289 #define CMAIL_ACCEPT 3
290
291 /* Different types of result to remember for each game */
292 #define CMAIL_NOT_RESULT 0
293 #define CMAIL_OLD_RESULT 1
294 #define CMAIL_NEW_RESULT 2
295
296 /* Telnet protocol constants */
297 #define TN_WILL 0373
298 #define TN_WONT 0374
299 #define TN_DO   0375
300 #define TN_DONT 0376
301 #define TN_IAC  0377
302 #define TN_ECHO 0001
303 #define TN_SGA  0003
304 #define TN_PORT 23
305
306 /* [AS] */
307 static char * safeStrCpy( char * dst, const char * src, size_t count )
308 {
309     assert( dst != NULL );
310     assert( src != NULL );
311     assert( count > 0 );
312
313     strncpy( dst, src, count );
314     dst[ count-1 ] = '\0';
315     return dst;
316 }
317
318 /* Some compiler can't cast u64 to double
319  * This function do the job for us:
320
321  * We use the highest bit for cast, this only
322  * works if the highest bit is not
323  * in use (This should not happen)
324  *
325  * We used this for all compiler
326  */
327 double
328 u64ToDouble(u64 value)
329 {
330   double r;
331   u64 tmp = value & u64Const(0x7fffffffffffffff);
332   r = (double)(s64)tmp;
333   if (value & u64Const(0x8000000000000000))
334        r +=  9.2233720368547758080e18; /* 2^63 */
335  return r;
336 }
337
338 /* Fake up flags for now, as we aren't keeping track of castling
339    availability yet. [HGM] Change of logic: the flag now only
340    indicates the type of castlings allowed by the rule of the game.
341    The actual rights themselves are maintained in the array
342    castlingRights, as part of the game history, and are not probed
343    by this function.
344  */
345 int
346 PosFlags(index)
347 {
348   int flags = F_ALL_CASTLE_OK;
349   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
350   switch (gameInfo.variant) {
351   case VariantSuicide:
352     flags &= ~F_ALL_CASTLE_OK;
353   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
354     flags |= F_IGNORE_CHECK;
355   case VariantLosers:
356     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
357     break;
358   case VariantAtomic:
359     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
360     break;
361   case VariantKriegspiel:
362     flags |= F_KRIEGSPIEL_CAPTURE;
363     break;
364   case VariantCapaRandom: 
365   case VariantFischeRandom:
366     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
367   case VariantNoCastle:
368   case VariantShatranj:
369   case VariantCourier:
370   case VariantMakruk:
371     flags &= ~F_ALL_CASTLE_OK;
372     break;
373   default:
374     break;
375   }
376   return flags;
377 }
378
379 FILE *gameFileFP, *debugFP;
380
381 /* 
382     [AS] Note: sometimes, the sscanf() function is used to parse the input
383     into a fixed-size buffer. Because of this, we must be prepared to
384     receive strings as long as the size of the input buffer, which is currently
385     set to 4K for Windows and 8K for the rest.
386     So, we must either allocate sufficiently large buffers here, or
387     reduce the size of the input buffer in the input reading part.
388 */
389
390 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
391 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
392 char thinkOutput1[MSG_SIZ*10];
393
394 ChessProgramState first, second;
395
396 /* premove variables */
397 int premoveToX = 0;
398 int premoveToY = 0;
399 int premoveFromX = 0;
400 int premoveFromY = 0;
401 int premovePromoChar = 0;
402 int gotPremove = 0;
403 Boolean alarmSounded;
404 /* end premove variables */
405
406 char *ics_prefix = "$";
407 int ics_type = ICS_GENERIC;
408
409 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
410 int pauseExamForwardMostMove = 0;
411 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
412 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
413 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
414 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
415 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
416 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
417 int whiteFlag = FALSE, blackFlag = FALSE;
418 int userOfferedDraw = FALSE;
419 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
420 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
421 int cmailMoveType[CMAIL_MAX_GAMES];
422 long ics_clock_paused = 0;
423 ProcRef icsPR = NoProc, cmailPR = NoProc;
424 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
425 GameMode gameMode = BeginningOfGame;
426 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
427 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
428 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
429 int hiddenThinkOutputState = 0; /* [AS] */
430 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
431 int adjudicateLossPlies = 6;
432 char white_holding[64], black_holding[64];
433 TimeMark lastNodeCountTime;
434 long lastNodeCount=0;
435 int have_sent_ICS_logon = 0;
436 int movesPerSession;
437 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
438 long timeControl_2; /* [AS] Allow separate time controls */
439 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
440 long timeRemaining[2][MAX_MOVES];
441 int matchGame = 0;
442 TimeMark programStartTime;
443 char ics_handle[MSG_SIZ];
444 int have_set_title = 0;
445
446 /* animateTraining preserves the state of appData.animate
447  * when Training mode is activated. This allows the
448  * response to be animated when appData.animate == TRUE and
449  * appData.animateDragging == TRUE.
450  */
451 Boolean animateTraining;
452
453 GameInfo gameInfo;
454
455 AppData appData;
456
457 Board boards[MAX_MOVES];
458 /* [HGM] Following 7 needed for accurate legality tests: */
459 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
460 signed char  initialRights[BOARD_FILES];
461 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
462 int   initialRulePlies, FENrulePlies;
463 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
464 int loadFlag = 0; 
465 int shuffleOpenings;
466 int mute; // mute all sounds
467
468 // [HGM] vari: next 12 to save and restore variations
469 #define MAX_VARIATIONS 10
470 int framePtr = MAX_MOVES-1; // points to free stack entry
471 int storedGames = 0;
472 int savedFirst[MAX_VARIATIONS];
473 int savedLast[MAX_VARIATIONS];
474 int savedFramePtr[MAX_VARIATIONS];
475 char *savedDetails[MAX_VARIATIONS];
476 ChessMove savedResult[MAX_VARIATIONS];
477
478 void PushTail P((int firstMove, int lastMove));
479 Boolean PopTail P((Boolean annotate));
480 void CleanupTail P((void));
481
482 ChessSquare  FIDEArray[2][BOARD_FILES] = {
483     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
484         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
485     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
486         BlackKing, BlackBishop, BlackKnight, BlackRook }
487 };
488
489 ChessSquare twoKingsArray[2][BOARD_FILES] = {
490     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
491         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
492     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
493         BlackKing, BlackKing, BlackKnight, BlackRook }
494 };
495
496 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
497     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
498         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
499     { BlackRook, BlackMan, BlackBishop, BlackQueen,
500         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
501 };
502
503 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
504     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
505         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
506     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
507         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
508 };
509
510 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
511     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
512         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
513     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
514         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
515 };
516
517 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
518     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
519         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackMan, BlackFerz,
521         BlackKing, BlackMan, BlackKnight, BlackRook }
522 };
523
524
525 #if (BOARD_FILES>=10)
526 ChessSquare ShogiArray[2][BOARD_FILES] = {
527     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
528         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
529     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
530         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
531 };
532
533 ChessSquare XiangqiArray[2][BOARD_FILES] = {
534     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
535         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
537         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
538 };
539
540 ChessSquare CapablancaArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
542         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
544         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
545 };
546
547 ChessSquare GreatArray[2][BOARD_FILES] = {
548     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
549         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
550     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
551         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
552 };
553
554 ChessSquare JanusArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
556         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
557     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
558         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
559 };
560
561 #ifdef GOTHIC
562 ChessSquare GothicArray[2][BOARD_FILES] = {
563     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
564         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
566         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
567 };
568 #else // !GOTHIC
569 #define GothicArray CapablancaArray
570 #endif // !GOTHIC
571
572 #ifdef FALCON
573 ChessSquare FalconArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
575         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
577         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
578 };
579 #else // !FALCON
580 #define FalconArray CapablancaArray
581 #endif // !FALCON
582
583 #else // !(BOARD_FILES>=10)
584 #define XiangqiPosition FIDEArray
585 #define CapablancaArray FIDEArray
586 #define GothicArray FIDEArray
587 #define GreatArray FIDEArray
588 #endif // !(BOARD_FILES>=10)
589
590 #if (BOARD_FILES>=12)
591 ChessSquare CourierArray[2][BOARD_FILES] = {
592     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
593         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
594     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
595         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
596 };
597 #else // !(BOARD_FILES>=12)
598 #define CourierArray CapablancaArray
599 #endif // !(BOARD_FILES>=12)
600
601
602 Board initialPosition;
603
604
605 /* Convert str to a rating. Checks for special cases of "----",
606
607    "++++", etc. Also strips ()'s */
608 int
609 string_to_rating(str)
610   char *str;
611 {
612   while(*str && !isdigit(*str)) ++str;
613   if (!*str)
614     return 0;   /* One of the special "no rating" cases */
615   else
616     return atoi(str);
617 }
618
619 void
620 ClearProgramStats()
621 {
622     /* Init programStats */
623     programStats.movelist[0] = 0;
624     programStats.depth = 0;
625     programStats.nr_moves = 0;
626     programStats.moves_left = 0;
627     programStats.nodes = 0;
628     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
629     programStats.score = 0;
630     programStats.got_only_move = 0;
631     programStats.got_fail = 0;
632     programStats.line_is_book = 0;
633 }
634
635 void
636 InitBackEnd1()
637 {
638     int matched, min, sec;
639
640     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
641     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
642
643     GetTimeMark(&programStartTime);
644     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
645
646     ClearProgramStats();
647     programStats.ok_to_send = 1;
648     programStats.seen_stat = 0;
649
650     /*
651      * Initialize game list
652      */
653     ListNew(&gameList);
654
655
656     /*
657      * Internet chess server status
658      */
659     if (appData.icsActive) {
660         appData.matchMode = FALSE;
661         appData.matchGames = 0;
662 #if ZIPPY       
663         appData.noChessProgram = !appData.zippyPlay;
664 #else
665         appData.zippyPlay = FALSE;
666         appData.zippyTalk = FALSE;
667         appData.noChessProgram = TRUE;
668 #endif
669         if (*appData.icsHelper != NULLCHAR) {
670             appData.useTelnet = TRUE;
671             appData.telnetProgram = appData.icsHelper;
672         }
673     } else {
674         appData.zippyTalk = appData.zippyPlay = FALSE;
675     }
676
677     /* [AS] Initialize pv info list [HGM] and game state */
678     {
679         int i, j;
680
681         for( i=0; i<=framePtr; i++ ) {
682             pvInfoList[i].depth = -1;
683             boards[i][EP_STATUS] = EP_NONE;
684             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
685         }
686     }
687
688     /*
689      * Parse timeControl resource
690      */
691     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
692                           appData.movesPerSession)) {
693         char buf[MSG_SIZ];
694         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
695         DisplayFatalError(buf, 0, 2);
696     }
697
698     /*
699      * Parse searchTime resource
700      */
701     if (*appData.searchTime != NULLCHAR) {
702         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
703         if (matched == 1) {
704             searchTime = min * 60;
705         } else if (matched == 2) {
706             searchTime = min * 60 + sec;
707         } else {
708             char buf[MSG_SIZ];
709             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
710             DisplayFatalError(buf, 0, 2);
711         }
712     }
713
714     /* [AS] Adjudication threshold */
715     adjudicateLossThreshold = appData.adjudicateLossThreshold;
716     
717     first.which = "first";
718     second.which = "second";
719     first.maybeThinking = second.maybeThinking = FALSE;
720     first.pr = second.pr = NoProc;
721     first.isr = second.isr = NULL;
722     first.sendTime = second.sendTime = 2;
723     first.sendDrawOffers = 1;
724     if (appData.firstPlaysBlack) {
725         first.twoMachinesColor = "black\n";
726         second.twoMachinesColor = "white\n";
727     } else {
728         first.twoMachinesColor = "white\n";
729         second.twoMachinesColor = "black\n";
730     }
731     first.program = appData.firstChessProgram;
732     second.program = appData.secondChessProgram;
733     first.host = appData.firstHost;
734     second.host = appData.secondHost;
735     first.dir = appData.firstDirectory;
736     second.dir = appData.secondDirectory;
737     first.other = &second;
738     second.other = &first;
739     first.initString = appData.initString;
740     second.initString = appData.secondInitString;
741     first.computerString = appData.firstComputerString;
742     second.computerString = appData.secondComputerString;
743     first.useSigint = second.useSigint = TRUE;
744     first.useSigterm = second.useSigterm = TRUE;
745     first.reuse = appData.reuseFirst;
746     second.reuse = appData.reuseSecond;
747     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
748     second.nps = appData.secondNPS;
749     first.useSetboard = second.useSetboard = FALSE;
750     first.useSAN = second.useSAN = FALSE;
751     first.usePing = second.usePing = FALSE;
752     first.lastPing = second.lastPing = 0;
753     first.lastPong = second.lastPong = 0;
754     first.usePlayother = second.usePlayother = FALSE;
755     first.useColors = second.useColors = TRUE;
756     first.useUsermove = second.useUsermove = FALSE;
757     first.sendICS = second.sendICS = FALSE;
758     first.sendName = second.sendName = appData.icsActive;
759     first.sdKludge = second.sdKludge = FALSE;
760     first.stKludge = second.stKludge = FALSE;
761     TidyProgramName(first.program, first.host, first.tidy);
762     TidyProgramName(second.program, second.host, second.tidy);
763     first.matchWins = second.matchWins = 0;
764     strcpy(first.variants, appData.variant);
765     strcpy(second.variants, appData.variant);
766     first.analysisSupport = second.analysisSupport = 2; /* detect */
767     first.analyzing = second.analyzing = FALSE;
768     first.initDone = second.initDone = FALSE;
769
770     /* New features added by Tord: */
771     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
772     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
773     /* End of new features added by Tord. */
774     first.fenOverride  = appData.fenOverride1;
775     second.fenOverride = appData.fenOverride2;
776
777     /* [HGM] time odds: set factor for each machine */
778     first.timeOdds  = appData.firstTimeOdds;
779     second.timeOdds = appData.secondTimeOdds;
780     { float norm = 1;
781         if(appData.timeOddsMode) {
782             norm = first.timeOdds;
783             if(norm > second.timeOdds) norm = second.timeOdds;
784         }
785         first.timeOdds /= norm;
786         second.timeOdds /= norm;
787     }
788
789     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
790     first.accumulateTC = appData.firstAccumulateTC;
791     second.accumulateTC = appData.secondAccumulateTC;
792     first.maxNrOfSessions = second.maxNrOfSessions = 1;
793
794     /* [HGM] debug */
795     first.debug = second.debug = FALSE;
796     first.supportsNPS = second.supportsNPS = UNKNOWN;
797
798     /* [HGM] options */
799     first.optionSettings  = appData.firstOptions;
800     second.optionSettings = appData.secondOptions;
801
802     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
803     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
804     first.isUCI = appData.firstIsUCI; /* [AS] */
805     second.isUCI = appData.secondIsUCI; /* [AS] */
806     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
807     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
808
809     if (appData.firstProtocolVersion > PROTOVER ||
810         appData.firstProtocolVersion < 1) {
811       char buf[MSG_SIZ];
812       sprintf(buf, _("protocol version %d not supported"),
813               appData.firstProtocolVersion);
814       DisplayFatalError(buf, 0, 2);
815     } else {
816       first.protocolVersion = appData.firstProtocolVersion;
817     }
818
819     if (appData.secondProtocolVersion > PROTOVER ||
820         appData.secondProtocolVersion < 1) {
821       char buf[MSG_SIZ];
822       sprintf(buf, _("protocol version %d not supported"),
823               appData.secondProtocolVersion);
824       DisplayFatalError(buf, 0, 2);
825     } else {
826       second.protocolVersion = appData.secondProtocolVersion;
827     }
828
829     if (appData.icsActive) {
830         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
831 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
832     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
833         appData.clockMode = FALSE;
834         first.sendTime = second.sendTime = 0;
835     }
836     
837 #if ZIPPY
838     /* Override some settings from environment variables, for backward
839        compatibility.  Unfortunately it's not feasible to have the env
840        vars just set defaults, at least in xboard.  Ugh.
841     */
842     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
843       ZippyInit();
844     }
845 #endif
846     
847     if (appData.noChessProgram) {
848         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
849         sprintf(programVersion, "%s", PACKAGE_STRING);
850     } else {
851       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
852       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
853       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
854     }
855
856     if (!appData.icsActive) {
857       char buf[MSG_SIZ];
858       /* Check for variants that are supported only in ICS mode,
859          or not at all.  Some that are accepted here nevertheless
860          have bugs; see comments below.
861       */
862       VariantClass variant = StringToVariant(appData.variant);
863       switch (variant) {
864       case VariantBughouse:     /* need four players and two boards */
865       case VariantKriegspiel:   /* need to hide pieces and move details */
866       /* case VariantFischeRandom: (Fabien: moved below) */
867         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
868         DisplayFatalError(buf, 0, 2);
869         return;
870
871       case VariantUnknown:
872       case VariantLoadable:
873       case Variant29:
874       case Variant30:
875       case Variant31:
876       case Variant32:
877       case Variant33:
878       case Variant34:
879       case Variant35:
880       case Variant36:
881       default:
882         sprintf(buf, _("Unknown variant name %s"), appData.variant);
883         DisplayFatalError(buf, 0, 2);
884         return;
885
886       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
887       case VariantFairy:      /* [HGM] TestLegality definitely off! */
888       case VariantGothic:     /* [HGM] should work */
889       case VariantCapablanca: /* [HGM] should work */
890       case VariantCourier:    /* [HGM] initial forced moves not implemented */
891       case VariantShogi:      /* [HGM] drops not tested for legality */
892       case VariantKnightmate: /* [HGM] should work */
893       case VariantCylinder:   /* [HGM] untested */
894       case VariantFalcon:     /* [HGM] untested */
895       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
896                                  offboard interposition not understood */
897       case VariantNormal:     /* definitely works! */
898       case VariantWildCastle: /* pieces not automatically shuffled */
899       case VariantNoCastle:   /* pieces not automatically shuffled */
900       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
901       case VariantLosers:     /* should work except for win condition,
902                                  and doesn't know captures are mandatory */
903       case VariantSuicide:    /* should work except for win condition,
904                                  and doesn't know captures are mandatory */
905       case VariantGiveaway:   /* should work except for win condition,
906                                  and doesn't know captures are mandatory */
907       case VariantTwoKings:   /* should work */
908       case VariantAtomic:     /* should work except for win condition */
909       case Variant3Check:     /* should work except for win condition */
910       case VariantShatranj:   /* should work except for all win conditions */
911       case VariantMakruk:     /* should work except for daw countdown */
912       case VariantBerolina:   /* might work if TestLegality is off */
913       case VariantCapaRandom: /* should work */
914       case VariantJanus:      /* should work */
915       case VariantSuper:      /* experimental */
916       case VariantGreat:      /* experimental, requires legality testing to be off */
917         break;
918       }
919     }
920
921     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
922     InitEngineUCI( installDir, &second );
923 }
924
925 int NextIntegerFromString( char ** str, long * value )
926 {
927     int result = -1;
928     char * s = *str;
929
930     while( *s == ' ' || *s == '\t' ) {
931         s++;
932     }
933
934     *value = 0;
935
936     if( *s >= '0' && *s <= '9' ) {
937         while( *s >= '0' && *s <= '9' ) {
938             *value = *value * 10 + (*s - '0');
939             s++;
940         }
941
942         result = 0;
943     }
944
945     *str = s;
946
947     return result;
948 }
949
950 int NextTimeControlFromString( char ** str, long * value )
951 {
952     long temp;
953     int result = NextIntegerFromString( str, &temp );
954
955     if( result == 0 ) {
956         *value = temp * 60; /* Minutes */
957         if( **str == ':' ) {
958             (*str)++;
959             result = NextIntegerFromString( str, &temp );
960             *value += temp; /* Seconds */
961         }
962     }
963
964     return result;
965 }
966
967 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
968 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
969     int result = -1; long temp, temp2;
970
971     if(**str != '+') return -1; // old params remain in force!
972     (*str)++;
973     if( NextTimeControlFromString( str, &temp ) ) return -1;
974
975     if(**str != '/') {
976         /* time only: incremental or sudden-death time control */
977         if(**str == '+') { /* increment follows; read it */
978             (*str)++;
979             if(result = NextIntegerFromString( str, &temp2)) return -1;
980             *inc = temp2 * 1000;
981         } else *inc = 0;
982         *moves = 0; *tc = temp * 1000; 
983         return 0;
984     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
985
986     (*str)++; /* classical time control */
987     result = NextTimeControlFromString( str, &temp2);
988     if(result == 0) {
989         *moves = temp/60;
990         *tc    = temp2 * 1000;
991         *inc   = 0;
992     }
993     return result;
994 }
995
996 int GetTimeQuota(int movenr)
997 {   /* [HGM] get time to add from the multi-session time-control string */
998     int moves=1; /* kludge to force reading of first session */
999     long time, increment;
1000     char *s = fullTimeControlString;
1001
1002     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1003     do {
1004         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1005         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1006         if(movenr == -1) return time;    /* last move before new session     */
1007         if(!moves) return increment;     /* current session is incremental   */
1008         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1009     } while(movenr >= -1);               /* try again for next session       */
1010
1011     return 0; // no new time quota on this move
1012 }
1013
1014 int
1015 ParseTimeControl(tc, ti, mps)
1016      char *tc;
1017      int ti;
1018      int mps;
1019 {
1020   long tc1;
1021   long tc2;
1022   char buf[MSG_SIZ];
1023   
1024   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1025   if(ti > 0) {
1026     if(mps)
1027       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1028     else sprintf(buf, "+%s+%d", tc, ti);
1029   } else {
1030     if(mps)
1031              sprintf(buf, "+%d/%s", mps, tc);
1032     else sprintf(buf, "+%s", tc);
1033   }
1034   fullTimeControlString = StrSave(buf);
1035   
1036   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1037     return FALSE;
1038   }
1039   
1040   if( *tc == '/' ) {
1041     /* Parse second time control */
1042     tc++;
1043     
1044     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1045       return FALSE;
1046     }
1047     
1048     if( tc2 == 0 ) {
1049       return FALSE;
1050     }
1051     
1052     timeControl_2 = tc2 * 1000;
1053   }
1054   else {
1055     timeControl_2 = 0;
1056   }
1057   
1058   if( tc1 == 0 ) {
1059     return FALSE;
1060   }
1061   
1062   timeControl = tc1 * 1000;
1063   
1064   if (ti >= 0) {
1065     timeIncrement = ti * 1000;  /* convert to ms */
1066     movesPerSession = 0;
1067   } else {
1068     timeIncrement = 0;
1069     movesPerSession = mps;
1070   }
1071   return TRUE;
1072 }
1073
1074 void
1075 InitBackEnd2()
1076 {
1077     if (appData.debugMode) {
1078         fprintf(debugFP, "%s\n", programVersion);
1079     }
1080
1081     set_cont_sequence(appData.wrapContSeq);
1082     if (appData.matchGames > 0) {
1083         appData.matchMode = TRUE;
1084     } else if (appData.matchMode) {
1085         appData.matchGames = 1;
1086     }
1087     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1088         appData.matchGames = appData.sameColorGames;
1089     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1090         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1091         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1092     }
1093     Reset(TRUE, FALSE);
1094     if (appData.noChessProgram || first.protocolVersion == 1) {
1095       InitBackEnd3();
1096     } else {
1097       /* kludge: allow timeout for initial "feature" commands */
1098       FreezeUI();
1099       DisplayMessage("", _("Starting chess program"));
1100       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1101     }
1102 }
1103
1104 void
1105 InitBackEnd3 P((void))
1106 {
1107     GameMode initialMode;
1108     char buf[MSG_SIZ];
1109     int err;
1110
1111     InitChessProgram(&first, startedFromSetupPosition);
1112
1113     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1114         free(programVersion);
1115         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1116         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1117     }
1118
1119     if (appData.icsActive) {
1120 #ifdef WIN32
1121         /* [DM] Make a console window if needed [HGM] merged ifs */
1122         ConsoleCreate(); 
1123 #endif
1124         err = establish();
1125         if (err != 0) {
1126             if (*appData.icsCommPort != NULLCHAR) {
1127                 sprintf(buf, _("Could not open comm port %s"),  
1128                         appData.icsCommPort);
1129             } else {
1130                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1131                         appData.icsHost, appData.icsPort);
1132             }
1133             DisplayFatalError(buf, err, 1);
1134             return;
1135         }
1136         SetICSMode();
1137         telnetISR =
1138           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1139         fromUserISR =
1140           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1141         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1142             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1143     } else if (appData.noChessProgram) {
1144         SetNCPMode();
1145     } else {
1146         SetGNUMode();
1147     }
1148
1149     if (*appData.cmailGameName != NULLCHAR) {
1150         SetCmailMode();
1151         OpenLoopback(&cmailPR);
1152         cmailISR =
1153           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1154     }
1155     
1156     ThawUI();
1157     DisplayMessage("", "");
1158     if (StrCaseCmp(appData.initialMode, "") == 0) {
1159       initialMode = BeginningOfGame;
1160     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1161       initialMode = TwoMachinesPlay;
1162     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1163       initialMode = AnalyzeFile; 
1164     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1165       initialMode = AnalyzeMode;
1166     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1167       initialMode = MachinePlaysWhite;
1168     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1169       initialMode = MachinePlaysBlack;
1170     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1171       initialMode = EditGame;
1172     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1173       initialMode = EditPosition;
1174     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1175       initialMode = Training;
1176     } else {
1177       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1178       DisplayFatalError(buf, 0, 2);
1179       return;
1180     }
1181
1182     if (appData.matchMode) {
1183         /* Set up machine vs. machine match */
1184         if (appData.noChessProgram) {
1185             DisplayFatalError(_("Can't have a match with no chess programs"),
1186                               0, 2);
1187             return;
1188         }
1189         matchMode = TRUE;
1190         matchGame = 1;
1191         if (*appData.loadGameFile != NULLCHAR) {
1192             int index = appData.loadGameIndex; // [HGM] autoinc
1193             if(index<0) lastIndex = index = 1;
1194             if (!LoadGameFromFile(appData.loadGameFile,
1195                                   index,
1196                                   appData.loadGameFile, FALSE)) {
1197                 DisplayFatalError(_("Bad game file"), 0, 1);
1198                 return;
1199             }
1200         } else if (*appData.loadPositionFile != NULLCHAR) {
1201             int index = appData.loadPositionIndex; // [HGM] autoinc
1202             if(index<0) lastIndex = index = 1;
1203             if (!LoadPositionFromFile(appData.loadPositionFile,
1204                                       index,
1205                                       appData.loadPositionFile)) {
1206                 DisplayFatalError(_("Bad position file"), 0, 1);
1207                 return;
1208             }
1209         }
1210         TwoMachinesEvent();
1211     } else if (*appData.cmailGameName != NULLCHAR) {
1212         /* Set up cmail mode */
1213         ReloadCmailMsgEvent(TRUE);
1214     } else {
1215         /* Set up other modes */
1216         if (initialMode == AnalyzeFile) {
1217           if (*appData.loadGameFile == NULLCHAR) {
1218             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1219             return;
1220           }
1221         }
1222         if (*appData.loadGameFile != NULLCHAR) {
1223             (void) LoadGameFromFile(appData.loadGameFile,
1224                                     appData.loadGameIndex,
1225                                     appData.loadGameFile, TRUE);
1226         } else if (*appData.loadPositionFile != NULLCHAR) {
1227             (void) LoadPositionFromFile(appData.loadPositionFile,
1228                                         appData.loadPositionIndex,
1229                                         appData.loadPositionFile);
1230             /* [HGM] try to make self-starting even after FEN load */
1231             /* to allow automatic setup of fairy variants with wtm */
1232             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1233                 gameMode = BeginningOfGame;
1234                 setboardSpoiledMachineBlack = 1;
1235             }
1236             /* [HGM] loadPos: make that every new game uses the setup */
1237             /* from file as long as we do not switch variant          */
1238             if(!blackPlaysFirst) {
1239                 startedFromPositionFile = TRUE;
1240                 CopyBoard(filePosition, boards[0]);
1241             }
1242         }
1243         if (initialMode == AnalyzeMode) {
1244           if (appData.noChessProgram) {
1245             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1246             return;
1247           }
1248           if (appData.icsActive) {
1249             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1250             return;
1251           }
1252           AnalyzeModeEvent();
1253         } else if (initialMode == AnalyzeFile) {
1254           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1255           ShowThinkingEvent();
1256           AnalyzeFileEvent();
1257           AnalysisPeriodicEvent(1);
1258         } else if (initialMode == MachinePlaysWhite) {
1259           if (appData.noChessProgram) {
1260             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1261                               0, 2);
1262             return;
1263           }
1264           if (appData.icsActive) {
1265             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1266                               0, 2);
1267             return;
1268           }
1269           MachineWhiteEvent();
1270         } else if (initialMode == MachinePlaysBlack) {
1271           if (appData.noChessProgram) {
1272             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1273                               0, 2);
1274             return;
1275           }
1276           if (appData.icsActive) {
1277             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1278                               0, 2);
1279             return;
1280           }
1281           MachineBlackEvent();
1282         } else if (initialMode == TwoMachinesPlay) {
1283           if (appData.noChessProgram) {
1284             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1285                               0, 2);
1286             return;
1287           }
1288           if (appData.icsActive) {
1289             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1290                               0, 2);
1291             return;
1292           }
1293           TwoMachinesEvent();
1294         } else if (initialMode == EditGame) {
1295           EditGameEvent();
1296         } else if (initialMode == EditPosition) {
1297           EditPositionEvent();
1298         } else if (initialMode == Training) {
1299           if (*appData.loadGameFile == NULLCHAR) {
1300             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1301             return;
1302           }
1303           TrainingEvent();
1304         }
1305     }
1306 }
1307
1308 /*
1309  * Establish will establish a contact to a remote host.port.
1310  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1311  *  used to talk to the host.
1312  * Returns 0 if okay, error code if not.
1313  */
1314 int
1315 establish()
1316 {
1317     char buf[MSG_SIZ];
1318
1319     if (*appData.icsCommPort != NULLCHAR) {
1320         /* Talk to the host through a serial comm port */
1321         return OpenCommPort(appData.icsCommPort, &icsPR);
1322
1323     } else if (*appData.gateway != NULLCHAR) {
1324         if (*appData.remoteShell == NULLCHAR) {
1325             /* Use the rcmd protocol to run telnet program on a gateway host */
1326             snprintf(buf, sizeof(buf), "%s %s %s",
1327                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1328             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1329
1330         } else {
1331             /* Use the rsh program to run telnet program on a gateway host */
1332             if (*appData.remoteUser == NULLCHAR) {
1333                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1334                         appData.gateway, appData.telnetProgram,
1335                         appData.icsHost, appData.icsPort);
1336             } else {
1337                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1338                         appData.remoteShell, appData.gateway, 
1339                         appData.remoteUser, appData.telnetProgram,
1340                         appData.icsHost, appData.icsPort);
1341             }
1342             return StartChildProcess(buf, "", &icsPR);
1343
1344         }
1345     } else if (appData.useTelnet) {
1346         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1347
1348     } else {
1349         /* TCP socket interface differs somewhat between
1350            Unix and NT; handle details in the front end.
1351            */
1352         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1353     }
1354 }
1355
1356 void EscapeExpand(char *p, char *q)
1357 {       // [HGM] initstring: routine to shape up string arguments
1358         while(*p++ = *q++) if(p[-1] == '\\')
1359             switch(*q++) {
1360                 case 'n': p[-1] = '\n'; break;
1361                 case 'r': p[-1] = '\r'; break;
1362                 case 't': p[-1] = '\t'; break;
1363                 case '\\': p[-1] = '\\'; break;
1364                 case 0: *p = 0; return;
1365                 default: p[-1] = q[-1]; break;
1366             }
1367 }
1368
1369 void
1370 show_bytes(fp, buf, count)
1371      FILE *fp;
1372      char *buf;
1373      int count;
1374 {
1375     while (count--) {
1376         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1377             fprintf(fp, "\\%03o", *buf & 0xff);
1378         } else {
1379             putc(*buf, fp);
1380         }
1381         buf++;
1382     }
1383     fflush(fp);
1384 }
1385
1386 /* Returns an errno value */
1387 int
1388 OutputMaybeTelnet(pr, message, count, outError)
1389      ProcRef pr;
1390      char *message;
1391      int count;
1392      int *outError;
1393 {
1394     char buf[8192], *p, *q, *buflim;
1395     int left, newcount, outcount;
1396
1397     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1398         *appData.gateway != NULLCHAR) {
1399         if (appData.debugMode) {
1400             fprintf(debugFP, ">ICS: ");
1401             show_bytes(debugFP, message, count);
1402             fprintf(debugFP, "\n");
1403         }
1404         return OutputToProcess(pr, message, count, outError);
1405     }
1406
1407     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1408     p = message;
1409     q = buf;
1410     left = count;
1411     newcount = 0;
1412     while (left) {
1413         if (q >= buflim) {
1414             if (appData.debugMode) {
1415                 fprintf(debugFP, ">ICS: ");
1416                 show_bytes(debugFP, buf, newcount);
1417                 fprintf(debugFP, "\n");
1418             }
1419             outcount = OutputToProcess(pr, buf, newcount, outError);
1420             if (outcount < newcount) return -1; /* to be sure */
1421             q = buf;
1422             newcount = 0;
1423         }
1424         if (*p == '\n') {
1425             *q++ = '\r';
1426             newcount++;
1427         } else if (((unsigned char) *p) == TN_IAC) {
1428             *q++ = (char) TN_IAC;
1429             newcount ++;
1430         }
1431         *q++ = *p++;
1432         newcount++;
1433         left--;
1434     }
1435     if (appData.debugMode) {
1436         fprintf(debugFP, ">ICS: ");
1437         show_bytes(debugFP, buf, newcount);
1438         fprintf(debugFP, "\n");
1439     }
1440     outcount = OutputToProcess(pr, buf, newcount, outError);
1441     if (outcount < newcount) return -1; /* to be sure */
1442     return count;
1443 }
1444
1445 void
1446 read_from_player(isr, closure, message, count, error)
1447      InputSourceRef isr;
1448      VOIDSTAR closure;
1449      char *message;
1450      int count;
1451      int error;
1452 {
1453     int outError, outCount;
1454     static int gotEof = 0;
1455
1456     /* Pass data read from player on to ICS */
1457     if (count > 0) {
1458         gotEof = 0;
1459         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1460         if (outCount < count) {
1461             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1462         }
1463     } else if (count < 0) {
1464         RemoveInputSource(isr);
1465         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1466     } else if (gotEof++ > 0) {
1467         RemoveInputSource(isr);
1468         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1469     }
1470 }
1471
1472 void
1473 KeepAlive()
1474 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1475     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1476     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1477     SendToICS("date\n");
1478     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1479 }
1480
1481 /* added routine for printf style output to ics */
1482 void ics_printf(char *format, ...)
1483 {
1484     char buffer[MSG_SIZ];
1485     va_list args;
1486
1487     va_start(args, format);
1488     vsnprintf(buffer, sizeof(buffer), format, args);
1489     buffer[sizeof(buffer)-1] = '\0';
1490     SendToICS(buffer);
1491     va_end(args);
1492 }
1493
1494 void
1495 SendToICS(s)
1496      char *s;
1497 {
1498     int count, outCount, outError;
1499
1500     if (icsPR == NULL) return;
1501
1502     count = strlen(s);
1503     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1504     if (outCount < count) {
1505         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506     }
1507 }
1508
1509 /* This is used for sending logon scripts to the ICS. Sending
1510    without a delay causes problems when using timestamp on ICC
1511    (at least on my machine). */
1512 void
1513 SendToICSDelayed(s,msdelay)
1514      char *s;
1515      long msdelay;
1516 {
1517     int count, outCount, outError;
1518
1519     if (icsPR == NULL) return;
1520
1521     count = strlen(s);
1522     if (appData.debugMode) {
1523         fprintf(debugFP, ">ICS: ");
1524         show_bytes(debugFP, s, count);
1525         fprintf(debugFP, "\n");
1526     }
1527     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1528                                       msdelay);
1529     if (outCount < count) {
1530         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1531     }
1532 }
1533
1534
1535 /* Remove all highlighting escape sequences in s
1536    Also deletes any suffix starting with '(' 
1537    */
1538 char *
1539 StripHighlightAndTitle(s)
1540      char *s;
1541 {
1542     static char retbuf[MSG_SIZ];
1543     char *p = retbuf;
1544
1545     while (*s != NULLCHAR) {
1546         while (*s == '\033') {
1547             while (*s != NULLCHAR && !isalpha(*s)) s++;
1548             if (*s != NULLCHAR) s++;
1549         }
1550         while (*s != NULLCHAR && *s != '\033') {
1551             if (*s == '(' || *s == '[') {
1552                 *p = NULLCHAR;
1553                 return retbuf;
1554             }
1555             *p++ = *s++;
1556         }
1557     }
1558     *p = NULLCHAR;
1559     return retbuf;
1560 }
1561
1562 /* Remove all highlighting escape sequences in s */
1563 char *
1564 StripHighlight(s)
1565      char *s;
1566 {
1567     static char retbuf[MSG_SIZ];
1568     char *p = retbuf;
1569
1570     while (*s != NULLCHAR) {
1571         while (*s == '\033') {
1572             while (*s != NULLCHAR && !isalpha(*s)) s++;
1573             if (*s != NULLCHAR) s++;
1574         }
1575         while (*s != NULLCHAR && *s != '\033') {
1576             *p++ = *s++;
1577         }
1578     }
1579     *p = NULLCHAR;
1580     return retbuf;
1581 }
1582
1583 char *variantNames[] = VARIANT_NAMES;
1584 char *
1585 VariantName(v)
1586      VariantClass v;
1587 {
1588     return variantNames[v];
1589 }
1590
1591
1592 /* Identify a variant from the strings the chess servers use or the
1593    PGN Variant tag names we use. */
1594 VariantClass
1595 StringToVariant(e)
1596      char *e;
1597 {
1598     char *p;
1599     int wnum = -1;
1600     VariantClass v = VariantNormal;
1601     int i, found = FALSE;
1602     char buf[MSG_SIZ];
1603
1604     if (!e) return v;
1605
1606     /* [HGM] skip over optional board-size prefixes */
1607     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1608         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1609         while( *e++ != '_');
1610     }
1611
1612     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1613         v = VariantNormal;
1614         found = TRUE;
1615     } else
1616     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1617       if (StrCaseStr(e, variantNames[i])) {
1618         v = (VariantClass) i;
1619         found = TRUE;
1620         break;
1621       }
1622     }
1623
1624     if (!found) {
1625       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1626           || StrCaseStr(e, "wild/fr") 
1627           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1628         v = VariantFischeRandom;
1629       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1630                  (i = 1, p = StrCaseStr(e, "w"))) {
1631         p += i;
1632         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1633         if (isdigit(*p)) {
1634           wnum = atoi(p);
1635         } else {
1636           wnum = -1;
1637         }
1638         switch (wnum) {
1639         case 0: /* FICS only, actually */
1640         case 1:
1641           /* Castling legal even if K starts on d-file */
1642           v = VariantWildCastle;
1643           break;
1644         case 2:
1645         case 3:
1646         case 4:
1647           /* Castling illegal even if K & R happen to start in
1648              normal positions. */
1649           v = VariantNoCastle;
1650           break;
1651         case 5:
1652         case 7:
1653         case 8:
1654         case 10:
1655         case 11:
1656         case 12:
1657         case 13:
1658         case 14:
1659         case 15:
1660         case 18:
1661         case 19:
1662           /* Castling legal iff K & R start in normal positions */
1663           v = VariantNormal;
1664           break;
1665         case 6:
1666         case 20:
1667         case 21:
1668           /* Special wilds for position setup; unclear what to do here */
1669           v = VariantLoadable;
1670           break;
1671         case 9:
1672           /* Bizarre ICC game */
1673           v = VariantTwoKings;
1674           break;
1675         case 16:
1676           v = VariantKriegspiel;
1677           break;
1678         case 17:
1679           v = VariantLosers;
1680           break;
1681         case 22:
1682           v = VariantFischeRandom;
1683           break;
1684         case 23:
1685           v = VariantCrazyhouse;
1686           break;
1687         case 24:
1688           v = VariantBughouse;
1689           break;
1690         case 25:
1691           v = Variant3Check;
1692           break;
1693         case 26:
1694           /* Not quite the same as FICS suicide! */
1695           v = VariantGiveaway;
1696           break;
1697         case 27:
1698           v = VariantAtomic;
1699           break;
1700         case 28:
1701           v = VariantShatranj;
1702           break;
1703
1704         /* Temporary names for future ICC types.  The name *will* change in 
1705            the next xboard/WinBoard release after ICC defines it. */
1706         case 29:
1707           v = Variant29;
1708           break;
1709         case 30:
1710           v = Variant30;
1711           break;
1712         case 31:
1713           v = Variant31;
1714           break;
1715         case 32:
1716           v = Variant32;
1717           break;
1718         case 33:
1719           v = Variant33;
1720           break;
1721         case 34:
1722           v = Variant34;
1723           break;
1724         case 35:
1725           v = Variant35;
1726           break;
1727         case 36:
1728           v = Variant36;
1729           break;
1730         case 37:
1731           v = VariantShogi;
1732           break;
1733         case 38:
1734           v = VariantXiangqi;
1735           break;
1736         case 39:
1737           v = VariantCourier;
1738           break;
1739         case 40:
1740           v = VariantGothic;
1741           break;
1742         case 41:
1743           v = VariantCapablanca;
1744           break;
1745         case 42:
1746           v = VariantKnightmate;
1747           break;
1748         case 43:
1749           v = VariantFairy;
1750           break;
1751         case 44:
1752           v = VariantCylinder;
1753           break;
1754         case 45:
1755           v = VariantFalcon;
1756           break;
1757         case 46:
1758           v = VariantCapaRandom;
1759           break;
1760         case 47:
1761           v = VariantBerolina;
1762           break;
1763         case 48:
1764           v = VariantJanus;
1765           break;
1766         case 49:
1767           v = VariantSuper;
1768           break;
1769         case 50:
1770           v = VariantGreat;
1771           break;
1772         case -1:
1773           /* Found "wild" or "w" in the string but no number;
1774              must assume it's normal chess. */
1775           v = VariantNormal;
1776           break;
1777         default:
1778           sprintf(buf, _("Unknown wild type %d"), wnum);
1779           DisplayError(buf, 0);
1780           v = VariantUnknown;
1781           break;
1782         }
1783       }
1784     }
1785     if (appData.debugMode) {
1786       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1787               e, wnum, VariantName(v));
1788     }
1789     return v;
1790 }
1791
1792 static int leftover_start = 0, leftover_len = 0;
1793 char star_match[STAR_MATCH_N][MSG_SIZ];
1794
1795 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1796    advance *index beyond it, and set leftover_start to the new value of
1797    *index; else return FALSE.  If pattern contains the character '*', it
1798    matches any sequence of characters not containing '\r', '\n', or the
1799    character following the '*' (if any), and the matched sequence(s) are
1800    copied into star_match.
1801    */
1802 int
1803 looking_at(buf, index, pattern)
1804      char *buf;
1805      int *index;
1806      char *pattern;
1807 {
1808     char *bufp = &buf[*index], *patternp = pattern;
1809     int star_count = 0;
1810     char *matchp = star_match[0];
1811     
1812     for (;;) {
1813         if (*patternp == NULLCHAR) {
1814             *index = leftover_start = bufp - buf;
1815             *matchp = NULLCHAR;
1816             return TRUE;
1817         }
1818         if (*bufp == NULLCHAR) return FALSE;
1819         if (*patternp == '*') {
1820             if (*bufp == *(patternp + 1)) {
1821                 *matchp = NULLCHAR;
1822                 matchp = star_match[++star_count];
1823                 patternp += 2;
1824                 bufp++;
1825                 continue;
1826             } else if (*bufp == '\n' || *bufp == '\r') {
1827                 patternp++;
1828                 if (*patternp == NULLCHAR)
1829                   continue;
1830                 else
1831                   return FALSE;
1832             } else {
1833                 *matchp++ = *bufp++;
1834                 continue;
1835             }
1836         }
1837         if (*patternp != *bufp) return FALSE;
1838         patternp++;
1839         bufp++;
1840     }
1841 }
1842
1843 void
1844 SendToPlayer(data, length)
1845      char *data;
1846      int length;
1847 {
1848     int error, outCount;
1849     outCount = OutputToProcess(NoProc, data, length, &error);
1850     if (outCount < length) {
1851         DisplayFatalError(_("Error writing to display"), error, 1);
1852     }
1853 }
1854
1855 void
1856 PackHolding(packed, holding)
1857      char packed[];
1858      char *holding;
1859 {
1860     char *p = holding;
1861     char *q = packed;
1862     int runlength = 0;
1863     int curr = 9999;
1864     do {
1865         if (*p == curr) {
1866             runlength++;
1867         } else {
1868             switch (runlength) {
1869               case 0:
1870                 break;
1871               case 1:
1872                 *q++ = curr;
1873                 break;
1874               case 2:
1875                 *q++ = curr;
1876                 *q++ = curr;
1877                 break;
1878               default:
1879                 sprintf(q, "%d", runlength);
1880                 while (*q) q++;
1881                 *q++ = curr;
1882                 break;
1883             }
1884             runlength = 1;
1885             curr = *p;
1886         }
1887     } while (*p++);
1888     *q = NULLCHAR;
1889 }
1890
1891 /* Telnet protocol requests from the front end */
1892 void
1893 TelnetRequest(ddww, option)
1894      unsigned char ddww, option;
1895 {
1896     unsigned char msg[3];
1897     int outCount, outError;
1898
1899     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1900
1901     if (appData.debugMode) {
1902         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1903         switch (ddww) {
1904           case TN_DO:
1905             ddwwStr = "DO";
1906             break;
1907           case TN_DONT:
1908             ddwwStr = "DONT";
1909             break;
1910           case TN_WILL:
1911             ddwwStr = "WILL";
1912             break;
1913           case TN_WONT:
1914             ddwwStr = "WONT";
1915             break;
1916           default:
1917             ddwwStr = buf1;
1918             sprintf(buf1, "%d", ddww);
1919             break;
1920         }
1921         switch (option) {
1922           case TN_ECHO:
1923             optionStr = "ECHO";
1924             break;
1925           default:
1926             optionStr = buf2;
1927             sprintf(buf2, "%d", option);
1928             break;
1929         }
1930         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1931     }
1932     msg[0] = TN_IAC;
1933     msg[1] = ddww;
1934     msg[2] = option;
1935     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1936     if (outCount < 3) {
1937         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1938     }
1939 }
1940
1941 void
1942 DoEcho()
1943 {
1944     if (!appData.icsActive) return;
1945     TelnetRequest(TN_DO, TN_ECHO);
1946 }
1947
1948 void
1949 DontEcho()
1950 {
1951     if (!appData.icsActive) return;
1952     TelnetRequest(TN_DONT, TN_ECHO);
1953 }
1954
1955 void
1956 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1957 {
1958     /* put the holdings sent to us by the server on the board holdings area */
1959     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1960     char p;
1961     ChessSquare piece;
1962
1963     if(gameInfo.holdingsWidth < 2)  return;
1964     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1965         return; // prevent overwriting by pre-board holdings
1966
1967     if( (int)lowestPiece >= BlackPawn ) {
1968         holdingsColumn = 0;
1969         countsColumn = 1;
1970         holdingsStartRow = BOARD_HEIGHT-1;
1971         direction = -1;
1972     } else {
1973         holdingsColumn = BOARD_WIDTH-1;
1974         countsColumn = BOARD_WIDTH-2;
1975         holdingsStartRow = 0;
1976         direction = 1;
1977     }
1978
1979     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1980         board[i][holdingsColumn] = EmptySquare;
1981         board[i][countsColumn]   = (ChessSquare) 0;
1982     }
1983     while( (p=*holdings++) != NULLCHAR ) {
1984         piece = CharToPiece( ToUpper(p) );
1985         if(piece == EmptySquare) continue;
1986         /*j = (int) piece - (int) WhitePawn;*/
1987         j = PieceToNumber(piece);
1988         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1989         if(j < 0) continue;               /* should not happen */
1990         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1991         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1992         board[holdingsStartRow+j*direction][countsColumn]++;
1993     }
1994 }
1995
1996
1997 void
1998 VariantSwitch(Board board, VariantClass newVariant)
1999 {
2000    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2001    static Board oldBoard;
2002
2003    startedFromPositionFile = FALSE;
2004    if(gameInfo.variant == newVariant) return;
2005
2006    /* [HGM] This routine is called each time an assignment is made to
2007     * gameInfo.variant during a game, to make sure the board sizes
2008     * are set to match the new variant. If that means adding or deleting
2009     * holdings, we shift the playing board accordingly
2010     * This kludge is needed because in ICS observe mode, we get boards
2011     * of an ongoing game without knowing the variant, and learn about the
2012     * latter only later. This can be because of the move list we requested,
2013     * in which case the game history is refilled from the beginning anyway,
2014     * but also when receiving holdings of a crazyhouse game. In the latter
2015     * case we want to add those holdings to the already received position.
2016     */
2017
2018    
2019    if (appData.debugMode) {
2020      fprintf(debugFP, "Switch board from %s to %s\n",
2021              VariantName(gameInfo.variant), VariantName(newVariant));
2022      setbuf(debugFP, NULL);
2023    }
2024    shuffleOpenings = 0;       /* [HGM] shuffle */
2025    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2026    switch(newVariant) 
2027      {
2028      case VariantShogi:
2029        newWidth = 9;  newHeight = 9;
2030        gameInfo.holdingsSize = 7;
2031      case VariantBughouse:
2032      case VariantCrazyhouse:
2033        newHoldingsWidth = 2; break;
2034      case VariantGreat:
2035        newWidth = 10;
2036      case VariantSuper:
2037        newHoldingsWidth = 2;
2038        gameInfo.holdingsSize = 8;
2039        break;
2040      case VariantGothic:
2041      case VariantCapablanca:
2042      case VariantCapaRandom:
2043        newWidth = 10;
2044      default:
2045        newHoldingsWidth = gameInfo.holdingsSize = 0;
2046      };
2047    
2048    if(newWidth  != gameInfo.boardWidth  ||
2049       newHeight != gameInfo.boardHeight ||
2050       newHoldingsWidth != gameInfo.holdingsWidth ) {
2051      
2052      /* shift position to new playing area, if needed */
2053      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2054        for(i=0; i<BOARD_HEIGHT; i++) 
2055          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2056            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2057              board[i][j];
2058        for(i=0; i<newHeight; i++) {
2059          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2060          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2061        }
2062      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2063        for(i=0; i<BOARD_HEIGHT; i++)
2064          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2065            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2066              board[i][j];
2067      }
2068      gameInfo.boardWidth  = newWidth;
2069      gameInfo.boardHeight = newHeight;
2070      gameInfo.holdingsWidth = newHoldingsWidth;
2071      gameInfo.variant = newVariant;
2072      InitDrawingSizes(-2, 0);
2073    } else gameInfo.variant = newVariant;
2074    CopyBoard(oldBoard, board);   // remember correctly formatted board
2075      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2076    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2077 }
2078
2079 static int loggedOn = FALSE;
2080
2081 /*-- Game start info cache: --*/
2082 int gs_gamenum;
2083 char gs_kind[MSG_SIZ];
2084 static char player1Name[128] = "";
2085 static char player2Name[128] = "";
2086 static char cont_seq[] = "\n\\   ";
2087 static int player1Rating = -1;
2088 static int player2Rating = -1;
2089 /*----------------------------*/
2090
2091 ColorClass curColor = ColorNormal;
2092 int suppressKibitz = 0;
2093
2094 // [HGM] seekgraph
2095 Boolean soughtPending = FALSE;
2096 Boolean seekGraphUp;
2097 #define MAX_SEEK_ADS 200
2098 #define SQUARE 0x80
2099 char *seekAdList[MAX_SEEK_ADS];
2100 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2101 float tcList[MAX_SEEK_ADS];
2102 char colorList[MAX_SEEK_ADS];
2103 int nrOfSeekAds = 0;
2104 int minRating = 1010, maxRating = 2800;
2105 int hMargin = 10, vMargin = 20, h, w;
2106 extern int squareSize, lineGap;
2107
2108 void
2109 PlotSeekAd(int i)
2110 {
2111         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2112         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2113         if(r < minRating+100 && r >=0 ) r = minRating+100;
2114         if(r > maxRating) r = maxRating;
2115         if(tc < 1.) tc = 1.;
2116         if(tc > 95.) tc = 95.;
2117         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2118         y = ((double)r - minRating)/(maxRating - minRating)
2119             * (h-vMargin-squareSize/8-1) + vMargin;
2120         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2121         if(strstr(seekAdList[i], " u ")) color = 1;
2122         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2123            !strstr(seekAdList[i], "bullet") &&
2124            !strstr(seekAdList[i], "blitz") &&
2125            !strstr(seekAdList[i], "standard") ) color = 2;
2126         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2127         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2128 }
2129
2130 void
2131 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2132 {
2133         char buf[MSG_SIZ], *ext = "";
2134         VariantClass v = StringToVariant(type);
2135         if(strstr(type, "wild")) {
2136             ext = type + 4; // append wild number
2137             if(v == VariantFischeRandom) type = "chess960"; else
2138             if(v == VariantLoadable) type = "setup"; else
2139             type = VariantName(v);
2140         }
2141         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2142         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2143             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2144             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2145             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2146             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2147             seekNrList[nrOfSeekAds] = nr;
2148             zList[nrOfSeekAds] = 0;
2149             seekAdList[nrOfSeekAds++] = StrSave(buf);
2150             if(plot) PlotSeekAd(nrOfSeekAds-1);
2151         }
2152 }
2153
2154 void
2155 EraseSeekDot(int i)
2156 {
2157     int x = xList[i], y = yList[i], d=squareSize/4, k;
2158     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2159     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2160     // now replot every dot that overlapped
2161     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2162         int xx = xList[k], yy = yList[k];
2163         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2164             DrawSeekDot(xx, yy, colorList[k]);
2165     }
2166 }
2167
2168 void
2169 RemoveSeekAd(int nr)
2170 {
2171         int i;
2172         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2173             EraseSeekDot(i);
2174             if(seekAdList[i]) free(seekAdList[i]);
2175             seekAdList[i] = seekAdList[--nrOfSeekAds];
2176             seekNrList[i] = seekNrList[nrOfSeekAds];
2177             ratingList[i] = ratingList[nrOfSeekAds];
2178             colorList[i]  = colorList[nrOfSeekAds];
2179             tcList[i] = tcList[nrOfSeekAds];
2180             xList[i]  = xList[nrOfSeekAds];
2181             yList[i]  = yList[nrOfSeekAds];
2182             zList[i]  = zList[nrOfSeekAds];
2183             seekAdList[nrOfSeekAds] = NULL;
2184             break;
2185         }
2186 }
2187
2188 Boolean
2189 MatchSoughtLine(char *line)
2190 {
2191     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2192     int nr, base, inc, u=0; char dummy;
2193
2194     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2195        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2196        (u=1) &&
2197        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2198         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2199         // match: compact and save the line
2200         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2201         return TRUE;
2202     }
2203     return FALSE;
2204 }
2205
2206 int
2207 DrawSeekGraph()
2208 {
2209     if(!seekGraphUp) return FALSE;
2210     int i;
2211     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2212     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2213
2214     DrawSeekBackground(0, 0, w, h);
2215     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2216     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2217     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2218         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2219         yy = h-1-yy;
2220         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2221         if(i%500 == 0) {
2222             char buf[MSG_SIZ];
2223             sprintf(buf, "%d", i);
2224             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2225         }
2226     }
2227     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2228     for(i=1; i<100; i+=(i<10?1:5)) {
2229         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2230         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2231         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2232             char buf[MSG_SIZ];
2233             sprintf(buf, "%d", i);
2234             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2235         }
2236     }
2237     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2238     return TRUE;
2239 }
2240
2241 int SeekGraphClick(ClickType click, int x, int y, int moving)
2242 {
2243     static int lastDown = 0, displayed = 0, lastSecond;
2244     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2245         if(click == Release || moving) return FALSE;
2246         nrOfSeekAds = 0;
2247         soughtPending = TRUE;
2248         SendToICS(ics_prefix);
2249         SendToICS("sought\n"); // should this be "sought all"?
2250     } else { // issue challenge based on clicked ad
2251         int dist = 10000; int i, closest = 0, second = 0;
2252         for(i=0; i<nrOfSeekAds; i++) {
2253             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2254             if(d < dist) { dist = d; closest = i; }
2255             second += (d - zList[i] < 120); // count in-range ads
2256             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2257         }
2258         if(dist < 120) {
2259             char buf[MSG_SIZ];
2260             second = (second > 1);
2261             if(displayed != closest || second != lastSecond) {
2262                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2263                 lastSecond = second; displayed = closest;
2264             }
2265             if(click == Press) {
2266                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2267                 lastDown = closest;
2268                 return TRUE;
2269             } // on press 'hit', only show info
2270             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2271             sprintf(buf, "play %d\n", seekNrList[closest]);
2272             SendToICS(ics_prefix);
2273             SendToICS(buf);
2274             return TRUE; // let incoming board of started game pop down the graph
2275         } else if(click == Release) { // release 'miss' is ignored
2276             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2277             if(moving == 2) { // right up-click
2278                 nrOfSeekAds = 0; // refresh graph
2279                 soughtPending = TRUE;
2280                 SendToICS(ics_prefix);
2281                 SendToICS("sought\n"); // should this be "sought all"?
2282             }
2283             return TRUE;
2284         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2285         // press miss or release hit 'pop down' seek graph
2286         seekGraphUp = FALSE;
2287         DrawPosition(TRUE, NULL);
2288     }
2289     return TRUE;
2290 }
2291
2292 void
2293 read_from_ics(isr, closure, data, count, error)
2294      InputSourceRef isr;
2295      VOIDSTAR closure;
2296      char *data;
2297      int count;
2298      int error;
2299 {
2300 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2301 #define STARTED_NONE 0
2302 #define STARTED_MOVES 1
2303 #define STARTED_BOARD 2
2304 #define STARTED_OBSERVE 3
2305 #define STARTED_HOLDINGS 4
2306 #define STARTED_CHATTER 5
2307 #define STARTED_COMMENT 6
2308 #define STARTED_MOVES_NOHIDE 7
2309     
2310     static int started = STARTED_NONE;
2311     static char parse[20000];
2312     static int parse_pos = 0;
2313     static char buf[BUF_SIZE + 1];
2314     static int firstTime = TRUE, intfSet = FALSE;
2315     static ColorClass prevColor = ColorNormal;
2316     static int savingComment = FALSE;
2317     static int cmatch = 0; // continuation sequence match
2318     char *bp;
2319     char str[500];
2320     int i, oldi;
2321     int buf_len;
2322     int next_out;
2323     int tkind;
2324     int backup;    /* [DM] For zippy color lines */
2325     char *p;
2326     char talker[MSG_SIZ]; // [HGM] chat
2327     int channel;
2328
2329     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2330
2331     if (appData.debugMode) {
2332       if (!error) {
2333         fprintf(debugFP, "<ICS: ");
2334         show_bytes(debugFP, data, count);
2335         fprintf(debugFP, "\n");
2336       }
2337     }
2338
2339     if (appData.debugMode) { int f = forwardMostMove;
2340         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2341                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2342                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2343     }
2344     if (count > 0) {
2345         /* If last read ended with a partial line that we couldn't parse,
2346            prepend it to the new read and try again. */
2347         if (leftover_len > 0) {
2348             for (i=0; i<leftover_len; i++)
2349               buf[i] = buf[leftover_start + i];
2350         }
2351
2352     /* copy new characters into the buffer */
2353     bp = buf + leftover_len;
2354     buf_len=leftover_len;
2355     for (i=0; i<count; i++)
2356     {
2357         // ignore these
2358         if (data[i] == '\r')
2359             continue;
2360
2361         // join lines split by ICS?
2362         if (!appData.noJoin)
2363         {
2364             /*
2365                 Joining just consists of finding matches against the
2366                 continuation sequence, and discarding that sequence
2367                 if found instead of copying it.  So, until a match
2368                 fails, there's nothing to do since it might be the
2369                 complete sequence, and thus, something we don't want
2370                 copied.
2371             */
2372             if (data[i] == cont_seq[cmatch])
2373             {
2374                 cmatch++;
2375                 if (cmatch == strlen(cont_seq))
2376                 {
2377                     cmatch = 0; // complete match.  just reset the counter
2378
2379                     /*
2380                         it's possible for the ICS to not include the space
2381                         at the end of the last word, making our [correct]
2382                         join operation fuse two separate words.  the server
2383                         does this when the space occurs at the width setting.
2384                     */
2385                     if (!buf_len || buf[buf_len-1] != ' ')
2386                     {
2387                         *bp++ = ' ';
2388                         buf_len++;
2389                     }
2390                 }
2391                 continue;
2392             }
2393             else if (cmatch)
2394             {
2395                 /*
2396                     match failed, so we have to copy what matched before
2397                     falling through and copying this character.  In reality,
2398                     this will only ever be just the newline character, but
2399                     it doesn't hurt to be precise.
2400                 */
2401                 strncpy(bp, cont_seq, cmatch);
2402                 bp += cmatch;
2403                 buf_len += cmatch;
2404                 cmatch = 0;
2405             }
2406         }
2407
2408         // copy this char
2409         *bp++ = data[i];
2410         buf_len++;
2411     }
2412
2413         buf[buf_len] = NULLCHAR;
2414 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2415         next_out = 0;
2416         leftover_start = 0;
2417         
2418         i = 0;
2419         while (i < buf_len) {
2420             /* Deal with part of the TELNET option negotiation
2421                protocol.  We refuse to do anything beyond the
2422                defaults, except that we allow the WILL ECHO option,
2423                which ICS uses to turn off password echoing when we are
2424                directly connected to it.  We reject this option
2425                if localLineEditing mode is on (always on in xboard)
2426                and we are talking to port 23, which might be a real
2427                telnet server that will try to keep WILL ECHO on permanently.
2428              */
2429             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2430                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2431                 unsigned char option;
2432                 oldi = i;
2433                 switch ((unsigned char) buf[++i]) {
2434                   case TN_WILL:
2435                     if (appData.debugMode)
2436                       fprintf(debugFP, "\n<WILL ");
2437                     switch (option = (unsigned char) buf[++i]) {
2438                       case TN_ECHO:
2439                         if (appData.debugMode)
2440                           fprintf(debugFP, "ECHO ");
2441                         /* Reply only if this is a change, according
2442                            to the protocol rules. */
2443                         if (remoteEchoOption) break;
2444                         if (appData.localLineEditing &&
2445                             atoi(appData.icsPort) == TN_PORT) {
2446                             TelnetRequest(TN_DONT, TN_ECHO);
2447                         } else {
2448                             EchoOff();
2449                             TelnetRequest(TN_DO, TN_ECHO);
2450                             remoteEchoOption = TRUE;
2451                         }
2452                         break;
2453                       default:
2454                         if (appData.debugMode)
2455                           fprintf(debugFP, "%d ", option);
2456                         /* Whatever this is, we don't want it. */
2457                         TelnetRequest(TN_DONT, option);
2458                         break;
2459                     }
2460                     break;
2461                   case TN_WONT:
2462                     if (appData.debugMode)
2463                       fprintf(debugFP, "\n<WONT ");
2464                     switch (option = (unsigned char) buf[++i]) {
2465                       case TN_ECHO:
2466                         if (appData.debugMode)
2467                           fprintf(debugFP, "ECHO ");
2468                         /* Reply only if this is a change, according
2469                            to the protocol rules. */
2470                         if (!remoteEchoOption) break;
2471                         EchoOn();
2472                         TelnetRequest(TN_DONT, TN_ECHO);
2473                         remoteEchoOption = FALSE;
2474                         break;
2475                       default:
2476                         if (appData.debugMode)
2477                           fprintf(debugFP, "%d ", (unsigned char) option);
2478                         /* Whatever this is, it must already be turned
2479                            off, because we never agree to turn on
2480                            anything non-default, so according to the
2481                            protocol rules, we don't reply. */
2482                         break;
2483                     }
2484                     break;
2485                   case TN_DO:
2486                     if (appData.debugMode)
2487                       fprintf(debugFP, "\n<DO ");
2488                     switch (option = (unsigned char) buf[++i]) {
2489                       default:
2490                         /* Whatever this is, we refuse to do it. */
2491                         if (appData.debugMode)
2492                           fprintf(debugFP, "%d ", option);
2493                         TelnetRequest(TN_WONT, option);
2494                         break;
2495                     }
2496                     break;
2497                   case TN_DONT:
2498                     if (appData.debugMode)
2499                       fprintf(debugFP, "\n<DONT ");
2500                     switch (option = (unsigned char) buf[++i]) {
2501                       default:
2502                         if (appData.debugMode)
2503                           fprintf(debugFP, "%d ", option);
2504                         /* Whatever this is, we are already not doing
2505                            it, because we never agree to do anything
2506                            non-default, so according to the protocol
2507                            rules, we don't reply. */
2508                         break;
2509                     }
2510                     break;
2511                   case TN_IAC:
2512                     if (appData.debugMode)
2513                       fprintf(debugFP, "\n<IAC ");
2514                     /* Doubled IAC; pass it through */
2515                     i--;
2516                     break;
2517                   default:
2518                     if (appData.debugMode)
2519                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2520                     /* Drop all other telnet commands on the floor */
2521                     break;
2522                 }
2523                 if (oldi > next_out)
2524                   SendToPlayer(&buf[next_out], oldi - next_out);
2525                 if (++i > next_out)
2526                   next_out = i;
2527                 continue;
2528             }
2529                 
2530             /* OK, this at least will *usually* work */
2531             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2532                 loggedOn = TRUE;
2533             }
2534             
2535             if (loggedOn && !intfSet) {
2536                 if (ics_type == ICS_ICC) {
2537                   sprintf(str,
2538                           "/set-quietly interface %s\n/set-quietly style 12\n",
2539                           programVersion);
2540                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2541                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2542                 } else if (ics_type == ICS_CHESSNET) {
2543                   sprintf(str, "/style 12\n");
2544                 } else {
2545                   strcpy(str, "alias $ @\n$set interface ");
2546                   strcat(str, programVersion);
2547                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2548                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2549                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2550 #ifdef WIN32
2551                   strcat(str, "$iset nohighlight 1\n");
2552 #endif
2553                   strcat(str, "$iset lock 1\n$style 12\n");
2554                 }
2555                 SendToICS(str);
2556                 NotifyFrontendLogin();
2557                 intfSet = TRUE;
2558             }
2559
2560             if (started == STARTED_COMMENT) {
2561                 /* Accumulate characters in comment */
2562                 parse[parse_pos++] = buf[i];
2563                 if (buf[i] == '\n') {
2564                     parse[parse_pos] = NULLCHAR;
2565                     if(chattingPartner>=0) {
2566                         char mess[MSG_SIZ];
2567                         sprintf(mess, "%s%s", talker, parse);
2568                         OutputChatMessage(chattingPartner, mess);
2569                         chattingPartner = -1;
2570                         next_out = i+1; // [HGM] suppress printing in ICS window
2571                     } else
2572                     if(!suppressKibitz) // [HGM] kibitz
2573                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2574                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2575                         int nrDigit = 0, nrAlph = 0, j;
2576                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2577                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2578                         parse[parse_pos] = NULLCHAR;
2579                         // try to be smart: if it does not look like search info, it should go to
2580                         // ICS interaction window after all, not to engine-output window.
2581                         for(j=0; j<parse_pos; j++) { // count letters and digits
2582                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2583                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2584                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2585                         }
2586                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2587                             int depth=0; float score;
2588                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2589                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2590                                 pvInfoList[forwardMostMove-1].depth = depth;
2591                                 pvInfoList[forwardMostMove-1].score = 100*score;
2592                             }
2593                             OutputKibitz(suppressKibitz, parse);
2594                         } else {
2595                             char tmp[MSG_SIZ];
2596                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2597                             SendToPlayer(tmp, strlen(tmp));
2598                         }
2599                         next_out = i+1; // [HGM] suppress printing in ICS window
2600                     }
2601                     started = STARTED_NONE;
2602                 } else {
2603                     /* Don't match patterns against characters in comment */
2604                     i++;
2605                     continue;
2606                 }
2607             }
2608             if (started == STARTED_CHATTER) {
2609                 if (buf[i] != '\n') {
2610                     /* Don't match patterns against characters in chatter */
2611                     i++;
2612                     continue;
2613                 }
2614                 started = STARTED_NONE;
2615                 if(suppressKibitz) next_out = i+1;
2616             }
2617
2618             /* Kludge to deal with rcmd protocol */
2619             if (firstTime && looking_at(buf, &i, "\001*")) {
2620                 DisplayFatalError(&buf[1], 0, 1);
2621                 continue;
2622             } else {
2623                 firstTime = FALSE;
2624             }
2625
2626             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2627                 ics_type = ICS_ICC;
2628                 ics_prefix = "/";
2629                 if (appData.debugMode)
2630                   fprintf(debugFP, "ics_type %d\n", ics_type);
2631                 continue;
2632             }
2633             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2634                 ics_type = ICS_FICS;
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, "chess.net")) {
2641                 ics_type = ICS_CHESSNET;
2642                 ics_prefix = "/";
2643                 if (appData.debugMode)
2644                   fprintf(debugFP, "ics_type %d\n", ics_type);
2645                 continue;
2646             }
2647
2648             if (!loggedOn &&
2649                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2650                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2651                  looking_at(buf, &i, "will be \"*\""))) {
2652               strcpy(ics_handle, star_match[0]);
2653               continue;
2654             }
2655
2656             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2657               char buf[MSG_SIZ];
2658               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2659               DisplayIcsInteractionTitle(buf);
2660               have_set_title = TRUE;
2661             }
2662
2663             /* skip finger notes */
2664             if (started == STARTED_NONE &&
2665                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2666                  (buf[i] == '1' && buf[i+1] == '0')) &&
2667                 buf[i+2] == ':' && buf[i+3] == ' ') {
2668               started = STARTED_CHATTER;
2669               i += 3;
2670               continue;
2671             }
2672
2673             oldi = i;
2674             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2675             if(appData.seekGraph) {
2676                 if(soughtPending && MatchSoughtLine(buf+i)) {
2677                     i = strstr(buf+i, "rated") - buf;
2678                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2679                     next_out = leftover_start = i;
2680                     started = STARTED_CHATTER;
2681                     suppressKibitz = TRUE;
2682                     continue;
2683                 }
2684                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2685                         && looking_at(buf, &i, "* ads displayed")) {
2686                     soughtPending = FALSE;
2687                     seekGraphUp = TRUE;
2688                     DrawSeekGraph();
2689                     continue;
2690                 }
2691                 if(appData.autoRefresh) {
2692                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2693                         int s = (ics_type == ICS_ICC); // ICC format differs
2694                         if(seekGraphUp)
2695                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2696                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2697                         looking_at(buf, &i, "*% "); // eat prompt
2698                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2699                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2700                         next_out = i; // suppress
2701                         continue;
2702                     }
2703                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2704                         char *p = star_match[0];
2705                         while(*p) {
2706                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2707                             while(*p && *p++ != ' '); // next
2708                         }
2709                         looking_at(buf, &i, "*% "); // eat prompt
2710                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2711                         next_out = i;
2712                         continue;
2713                     }
2714                 }
2715             }
2716
2717             /* skip formula vars */
2718             if (started == STARTED_NONE &&
2719                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2720               started = STARTED_CHATTER;
2721               i += 3;
2722               continue;
2723             }
2724
2725             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2726             if (appData.autoKibitz && started == STARTED_NONE && 
2727                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2728                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2729                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2730                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2731                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2732                         suppressKibitz = TRUE;
2733                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2734                         next_out = i;
2735                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2736                                 && (gameMode == IcsPlayingWhite)) ||
2737                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2738                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2739                             started = STARTED_CHATTER; // own kibitz we simply discard
2740                         else {
2741                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2742                             parse_pos = 0; parse[0] = NULLCHAR;
2743                             savingComment = TRUE;
2744                             suppressKibitz = gameMode != IcsObserving ? 2 :
2745                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2746                         } 
2747                         continue;
2748                 } else
2749                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2750                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2751                          && atoi(star_match[0])) {
2752                     // suppress the acknowledgements of our own autoKibitz
2753                     char *p;
2754                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2755                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2756                     SendToPlayer(star_match[0], strlen(star_match[0]));
2757                     if(looking_at(buf, &i, "*% ")) // eat prompt
2758                         suppressKibitz = FALSE;
2759                     next_out = i;
2760                     continue;
2761                 }
2762             } // [HGM] kibitz: end of patch
2763
2764             // [HGM] chat: intercept tells by users for which we have an open chat window
2765             channel = -1;
2766             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2767                                            looking_at(buf, &i, "* whispers:") ||
2768                                            looking_at(buf, &i, "* kibitzes:") ||
2769                                            looking_at(buf, &i, "* shouts:") ||
2770                                            looking_at(buf, &i, "* c-shouts:") ||
2771                                            looking_at(buf, &i, "--> * ") ||
2772                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2773                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2774                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2775                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2776                 int p;
2777                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2778                 chattingPartner = -1;
2779
2780                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2781                 for(p=0; p<MAX_CHAT; p++) {
2782                     if(channel == atoi(chatPartner[p])) {
2783                     talker[0] = '['; strcat(talker, "] ");
2784                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2785                     chattingPartner = p; break;
2786                     }
2787                 } else
2788                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2789                 for(p=0; p<MAX_CHAT; p++) {
2790                     if(!strcmp("kibitzes", chatPartner[p])) {
2791                         talker[0] = '['; strcat(talker, "] ");
2792                         chattingPartner = p; break;
2793                     }
2794                 } else
2795                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2796                 for(p=0; p<MAX_CHAT; p++) {
2797                     if(!strcmp("whispers", chatPartner[p])) {
2798                         talker[0] = '['; strcat(talker, "] ");
2799                         chattingPartner = p; break;
2800                     }
2801                 } else
2802                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2803                   if(buf[i-8] == '-' && buf[i-3] == 't')
2804                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2805                     if(!strcmp("c-shouts", chatPartner[p])) {
2806                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2807                         chattingPartner = p; break;
2808                     }
2809                   }
2810                   if(chattingPartner < 0)
2811                   for(p=0; p<MAX_CHAT; p++) {
2812                     if(!strcmp("shouts", chatPartner[p])) {
2813                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2814                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2815                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2816                         chattingPartner = p; break;
2817                     }
2818                   }
2819                 }
2820                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2821                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2822                     talker[0] = 0; Colorize(ColorTell, FALSE);
2823                     chattingPartner = p; break;
2824                 }
2825                 if(chattingPartner<0) i = oldi; else {
2826                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2827                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2828                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2829                     started = STARTED_COMMENT;
2830                     parse_pos = 0; parse[0] = NULLCHAR;
2831                     savingComment = 3 + chattingPartner; // counts as TRUE
2832                     suppressKibitz = TRUE;
2833                     continue;
2834                 }
2835             } // [HGM] chat: end of patch
2836
2837             if (appData.zippyTalk || appData.zippyPlay) {
2838                 /* [DM] Backup address for color zippy lines */
2839                 backup = i;
2840 #if ZIPPY
2841        #ifdef WIN32
2842                if (loggedOn == TRUE)
2843                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2844                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2845        #else
2846                 if (ZippyControl(buf, &i) ||
2847                     ZippyConverse(buf, &i) ||
2848                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2849                       loggedOn = TRUE;
2850                       if (!appData.colorize) continue;
2851                 }
2852        #endif
2853 #endif
2854             } // [DM] 'else { ' deleted
2855                 if (
2856                     /* Regular tells and says */
2857                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2858                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2859                     looking_at(buf, &i, "* says: ") ||
2860                     /* Don't color "message" or "messages" output */
2861                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2862                     looking_at(buf, &i, "*. * at *:*: ") ||
2863                     looking_at(buf, &i, "--* (*:*): ") ||
2864                     /* Message notifications (same color as tells) */
2865                     looking_at(buf, &i, "* has left a message ") ||
2866                     looking_at(buf, &i, "* just sent you a message:\n") ||
2867                     /* Whispers and kibitzes */
2868                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2869                     looking_at(buf, &i, "* kibitzes: ") ||
2870                     /* Channel tells */
2871                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2872
2873                   if (tkind == 1 && strchr(star_match[0], ':')) {
2874                       /* Avoid "tells you:" spoofs in channels */
2875                      tkind = 3;
2876                   }
2877                   if (star_match[0][0] == NULLCHAR ||
2878                       strchr(star_match[0], ' ') ||
2879                       (tkind == 3 && strchr(star_match[1], ' '))) {
2880                     /* Reject bogus matches */
2881                     i = oldi;
2882                   } else {
2883                     if (appData.colorize) {
2884                       if (oldi > next_out) {
2885                         SendToPlayer(&buf[next_out], oldi - next_out);
2886                         next_out = oldi;
2887                       }
2888                       switch (tkind) {
2889                       case 1:
2890                         Colorize(ColorTell, FALSE);
2891                         curColor = ColorTell;
2892                         break;
2893                       case 2:
2894                         Colorize(ColorKibitz, FALSE);
2895                         curColor = ColorKibitz;
2896                         break;
2897                       case 3:
2898                         p = strrchr(star_match[1], '(');
2899                         if (p == NULL) {
2900                           p = star_match[1];
2901                         } else {
2902                           p++;
2903                         }
2904                         if (atoi(p) == 1) {
2905                           Colorize(ColorChannel1, FALSE);
2906                           curColor = ColorChannel1;
2907                         } else {
2908                           Colorize(ColorChannel, FALSE);
2909                           curColor = ColorChannel;
2910                         }
2911                         break;
2912                       case 5:
2913                         curColor = ColorNormal;
2914                         break;
2915                       }
2916                     }
2917                     if (started == STARTED_NONE && appData.autoComment &&
2918                         (gameMode == IcsObserving ||
2919                          gameMode == IcsPlayingWhite ||
2920                          gameMode == IcsPlayingBlack)) {
2921                       parse_pos = i - oldi;
2922                       memcpy(parse, &buf[oldi], parse_pos);
2923                       parse[parse_pos] = NULLCHAR;
2924                       started = STARTED_COMMENT;
2925                       savingComment = TRUE;
2926                     } else {
2927                       started = STARTED_CHATTER;
2928                       savingComment = FALSE;
2929                     }
2930                     loggedOn = TRUE;
2931                     continue;
2932                   }
2933                 }
2934
2935                 if (looking_at(buf, &i, "* s-shouts: ") ||
2936                     looking_at(buf, &i, "* c-shouts: ")) {
2937                     if (appData.colorize) {
2938                         if (oldi > next_out) {
2939                             SendToPlayer(&buf[next_out], oldi - next_out);
2940                             next_out = oldi;
2941                         }
2942                         Colorize(ColorSShout, FALSE);
2943                         curColor = ColorSShout;
2944                     }
2945                     loggedOn = TRUE;
2946                     started = STARTED_CHATTER;
2947                     continue;
2948                 }
2949
2950                 if (looking_at(buf, &i, "--->")) {
2951                     loggedOn = TRUE;
2952                     continue;
2953                 }
2954
2955                 if (looking_at(buf, &i, "* shouts: ") ||
2956                     looking_at(buf, &i, "--> ")) {
2957                     if (appData.colorize) {
2958                         if (oldi > next_out) {
2959                             SendToPlayer(&buf[next_out], oldi - next_out);
2960                             next_out = oldi;
2961                         }
2962                         Colorize(ColorShout, FALSE);
2963                         curColor = ColorShout;
2964                     }
2965                     loggedOn = TRUE;
2966                     started = STARTED_CHATTER;
2967                     continue;
2968                 }
2969
2970                 if (looking_at( buf, &i, "Challenge:")) {
2971                     if (appData.colorize) {
2972                         if (oldi > next_out) {
2973                             SendToPlayer(&buf[next_out], oldi - next_out);
2974                             next_out = oldi;
2975                         }
2976                         Colorize(ColorChallenge, FALSE);
2977                         curColor = ColorChallenge;
2978                     }
2979                     loggedOn = TRUE;
2980                     continue;
2981                 }
2982
2983                 if (looking_at(buf, &i, "* offers you") ||
2984                     looking_at(buf, &i, "* offers to be") ||
2985                     looking_at(buf, &i, "* would like to") ||
2986                     looking_at(buf, &i, "* requests to") ||
2987                     looking_at(buf, &i, "Your opponent offers") ||
2988                     looking_at(buf, &i, "Your opponent requests")) {
2989
2990                     if (appData.colorize) {
2991                         if (oldi > next_out) {
2992                             SendToPlayer(&buf[next_out], oldi - next_out);
2993                             next_out = oldi;
2994                         }
2995                         Colorize(ColorRequest, FALSE);
2996                         curColor = ColorRequest;
2997                     }
2998                     continue;
2999                 }
3000
3001                 if (looking_at(buf, &i, "* (*) seeking")) {
3002                     if (appData.colorize) {
3003                         if (oldi > next_out) {
3004                             SendToPlayer(&buf[next_out], oldi - next_out);
3005                             next_out = oldi;
3006                         }
3007                         Colorize(ColorSeek, FALSE);
3008                         curColor = ColorSeek;
3009                     }
3010                     continue;
3011             }
3012
3013             if (looking_at(buf, &i, "\\   ")) {
3014                 if (prevColor != ColorNormal) {
3015                     if (oldi > next_out) {
3016                         SendToPlayer(&buf[next_out], oldi - next_out);
3017                         next_out = oldi;
3018                     }
3019                     Colorize(prevColor, TRUE);
3020                     curColor = prevColor;
3021                 }
3022                 if (savingComment) {
3023                     parse_pos = i - oldi;
3024                     memcpy(parse, &buf[oldi], parse_pos);
3025                     parse[parse_pos] = NULLCHAR;
3026                     started = STARTED_COMMENT;
3027                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3028                         chattingPartner = savingComment - 3; // kludge to remember the box
3029                 } else {
3030                     started = STARTED_CHATTER;
3031                 }
3032                 continue;
3033             }
3034
3035             if (looking_at(buf, &i, "Black Strength :") ||
3036                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3037                 looking_at(buf, &i, "<10>") ||
3038                 looking_at(buf, &i, "#@#")) {
3039                 /* Wrong board style */
3040                 loggedOn = TRUE;
3041                 SendToICS(ics_prefix);
3042                 SendToICS("set style 12\n");
3043                 SendToICS(ics_prefix);
3044                 SendToICS("refresh\n");
3045                 continue;
3046             }
3047             
3048             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3049                 ICSInitScript();
3050                 have_sent_ICS_logon = 1;
3051                 continue;
3052             }
3053               
3054             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3055                 (looking_at(buf, &i, "\n<12> ") ||
3056                  looking_at(buf, &i, "<12> "))) {
3057                 loggedOn = TRUE;
3058                 if (oldi > next_out) {
3059                     SendToPlayer(&buf[next_out], oldi - next_out);
3060                 }
3061                 next_out = i;
3062                 started = STARTED_BOARD;
3063                 parse_pos = 0;
3064                 continue;
3065             }
3066
3067             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3068                 looking_at(buf, &i, "<b1> ")) {
3069                 if (oldi > next_out) {
3070                     SendToPlayer(&buf[next_out], oldi - next_out);
3071                 }
3072                 next_out = i;
3073                 started = STARTED_HOLDINGS;
3074                 parse_pos = 0;
3075                 continue;
3076             }
3077
3078             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3079                 loggedOn = TRUE;
3080                 /* Header for a move list -- first line */
3081
3082                 switch (ics_getting_history) {
3083                   case H_FALSE:
3084                     switch (gameMode) {
3085                       case IcsIdle:
3086                       case BeginningOfGame:
3087                         /* User typed "moves" or "oldmoves" while we
3088                            were idle.  Pretend we asked for these
3089                            moves and soak them up so user can step
3090                            through them and/or save them.
3091                            */
3092                         Reset(FALSE, TRUE);
3093                         gameMode = IcsObserving;
3094                         ModeHighlight();
3095                         ics_gamenum = -1;
3096                         ics_getting_history = H_GOT_UNREQ_HEADER;
3097                         break;
3098                       case EditGame: /*?*/
3099                       case EditPosition: /*?*/
3100                         /* Should above feature work in these modes too? */
3101                         /* For now it doesn't */
3102                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3103                         break;
3104                       default:
3105                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3106                         break;
3107                     }
3108                     break;
3109                   case H_REQUESTED:
3110                     /* Is this the right one? */
3111                     if (gameInfo.white && gameInfo.black &&
3112                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3113                         strcmp(gameInfo.black, star_match[2]) == 0) {
3114                         /* All is well */
3115                         ics_getting_history = H_GOT_REQ_HEADER;
3116                     }
3117                     break;
3118                   case H_GOT_REQ_HEADER:
3119                   case H_GOT_UNREQ_HEADER:
3120                   case H_GOT_UNWANTED_HEADER:
3121                   case H_GETTING_MOVES:
3122                     /* Should not happen */
3123                     DisplayError(_("Error gathering move list: two headers"), 0);
3124                     ics_getting_history = H_FALSE;
3125                     break;
3126                 }
3127
3128                 /* Save player ratings into gameInfo if needed */
3129                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3130                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3131                     (gameInfo.whiteRating == -1 ||
3132                      gameInfo.blackRating == -1)) {
3133
3134                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3135                     gameInfo.blackRating = string_to_rating(star_match[3]);
3136                     if (appData.debugMode)
3137                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3138                               gameInfo.whiteRating, gameInfo.blackRating);
3139                 }
3140                 continue;
3141             }
3142
3143             if (looking_at(buf, &i,
3144               "* * match, initial time: * minute*, increment: * second")) {
3145                 /* Header for a move list -- second line */
3146                 /* Initial board will follow if this is a wild game */
3147                 if (gameInfo.event != NULL) free(gameInfo.event);
3148                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3149                 gameInfo.event = StrSave(str);
3150                 /* [HGM] we switched variant. Translate boards if needed. */
3151                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3152                 continue;
3153             }
3154
3155             if (looking_at(buf, &i, "Move  ")) {
3156                 /* Beginning of a move list */
3157                 switch (ics_getting_history) {
3158                   case H_FALSE:
3159                     /* Normally should not happen */
3160                     /* Maybe user hit reset while we were parsing */
3161                     break;
3162                   case H_REQUESTED:
3163                     /* Happens if we are ignoring a move list that is not
3164                      * the one we just requested.  Common if the user
3165                      * tries to observe two games without turning off
3166                      * getMoveList */
3167                     break;
3168                   case H_GETTING_MOVES:
3169                     /* Should not happen */
3170                     DisplayError(_("Error gathering move list: nested"), 0);
3171                     ics_getting_history = H_FALSE;
3172                     break;
3173                   case H_GOT_REQ_HEADER:
3174                     ics_getting_history = H_GETTING_MOVES;
3175                     started = STARTED_MOVES;
3176                     parse_pos = 0;
3177                     if (oldi > next_out) {
3178                         SendToPlayer(&buf[next_out], oldi - next_out);
3179                     }
3180                     break;
3181                   case H_GOT_UNREQ_HEADER:
3182                     ics_getting_history = H_GETTING_MOVES;
3183                     started = STARTED_MOVES_NOHIDE;
3184                     parse_pos = 0;
3185                     break;
3186                   case H_GOT_UNWANTED_HEADER:
3187                     ics_getting_history = H_FALSE;
3188                     break;
3189                 }
3190                 continue;
3191             }                           
3192             
3193             if (looking_at(buf, &i, "% ") ||
3194                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3195                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3196                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3197                     soughtPending = FALSE;
3198                     seekGraphUp = TRUE;
3199                     DrawSeekGraph();
3200                 }
3201                 if(suppressKibitz) next_out = i;
3202                 savingComment = FALSE;
3203                 suppressKibitz = 0;
3204                 switch (started) {
3205                   case STARTED_MOVES:
3206                   case STARTED_MOVES_NOHIDE:
3207                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3208                     parse[parse_pos + i - oldi] = NULLCHAR;
3209                     ParseGameHistory(parse);
3210 #if ZIPPY
3211                     if (appData.zippyPlay && first.initDone) {
3212                         FeedMovesToProgram(&first, forwardMostMove);
3213                         if (gameMode == IcsPlayingWhite) {
3214                             if (WhiteOnMove(forwardMostMove)) {
3215                                 if (first.sendTime) {
3216                                   if (first.useColors) {
3217                                     SendToProgram("black\n", &first); 
3218                                   }
3219                                   SendTimeRemaining(&first, TRUE);
3220                                 }
3221                                 if (first.useColors) {
3222                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3223                                 }
3224                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3225                                 first.maybeThinking = TRUE;
3226                             } else {
3227                                 if (first.usePlayother) {
3228                                   if (first.sendTime) {
3229                                     SendTimeRemaining(&first, TRUE);
3230                                   }
3231                                   SendToProgram("playother\n", &first);
3232                                   firstMove = FALSE;
3233                                 } else {
3234                                   firstMove = TRUE;
3235                                 }
3236                             }
3237                         } else if (gameMode == IcsPlayingBlack) {
3238                             if (!WhiteOnMove(forwardMostMove)) {
3239                                 if (first.sendTime) {
3240                                   if (first.useColors) {
3241                                     SendToProgram("white\n", &first);
3242                                   }
3243                                   SendTimeRemaining(&first, FALSE);
3244                                 }
3245                                 if (first.useColors) {
3246                                   SendToProgram("black\n", &first);
3247                                 }
3248                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3249                                 first.maybeThinking = TRUE;
3250                             } else {
3251                                 if (first.usePlayother) {
3252                                   if (first.sendTime) {
3253                                     SendTimeRemaining(&first, FALSE);
3254                                   }
3255                                   SendToProgram("playother\n", &first);
3256                                   firstMove = FALSE;
3257                                 } else {
3258                                   firstMove = TRUE;
3259                                 }
3260                             }
3261                         }                       
3262                     }
3263 #endif
3264                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3265                         /* Moves came from oldmoves or moves command
3266                            while we weren't doing anything else.
3267                            */
3268                         currentMove = forwardMostMove;
3269                         ClearHighlights();/*!!could figure this out*/
3270                         flipView = appData.flipView;
3271                         DrawPosition(TRUE, boards[currentMove]);
3272                         DisplayBothClocks();
3273                         sprintf(str, "%s vs. %s",
3274                                 gameInfo.white, gameInfo.black);
3275                         DisplayTitle(str);
3276                         gameMode = IcsIdle;
3277                     } else {
3278                         /* Moves were history of an active game */
3279                         if (gameInfo.resultDetails != NULL) {
3280                             free(gameInfo.resultDetails);
3281                             gameInfo.resultDetails = NULL;
3282                         }
3283                     }
3284                     HistorySet(parseList, backwardMostMove,
3285                                forwardMostMove, currentMove-1);
3286                     DisplayMove(currentMove - 1);
3287                     if (started == STARTED_MOVES) next_out = i;
3288                     started = STARTED_NONE;
3289                     ics_getting_history = H_FALSE;
3290                     break;
3291
3292                   case STARTED_OBSERVE:
3293                     started = STARTED_NONE;
3294                     SendToICS(ics_prefix);
3295                     SendToICS("refresh\n");
3296                     break;
3297
3298                   default:
3299                     break;
3300                 }
3301                 if(bookHit) { // [HGM] book: simulate book reply
3302                     static char bookMove[MSG_SIZ]; // a bit generous?
3303
3304                     programStats.nodes = programStats.depth = programStats.time = 
3305                     programStats.score = programStats.got_only_move = 0;
3306                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3307
3308                     strcpy(bookMove, "move ");
3309                     strcat(bookMove, bookHit);
3310                     HandleMachineMove(bookMove, &first);
3311                 }
3312                 continue;
3313             }
3314             
3315             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3316                  started == STARTED_HOLDINGS ||
3317                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3318                 /* Accumulate characters in move list or board */
3319                 parse[parse_pos++] = buf[i];
3320             }
3321             
3322             /* Start of game messages.  Mostly we detect start of game
3323                when the first board image arrives.  On some versions
3324                of the ICS, though, we need to do a "refresh" after starting
3325                to observe in order to get the current board right away. */
3326             if (looking_at(buf, &i, "Adding game * to observation list")) {
3327                 started = STARTED_OBSERVE;
3328                 continue;
3329             }
3330
3331             /* Handle auto-observe */
3332             if (appData.autoObserve &&
3333                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3334                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3335                 char *player;
3336                 /* Choose the player that was highlighted, if any. */
3337                 if (star_match[0][0] == '\033' ||
3338                     star_match[1][0] != '\033') {
3339                     player = star_match[0];
3340                 } else {
3341                     player = star_match[2];
3342                 }
3343                 sprintf(str, "%sobserve %s\n",
3344                         ics_prefix, StripHighlightAndTitle(player));
3345                 SendToICS(str);
3346
3347                 /* Save ratings from notify string */
3348                 strcpy(player1Name, star_match[0]);
3349                 player1Rating = string_to_rating(star_match[1]);
3350                 strcpy(player2Name, star_match[2]);
3351                 player2Rating = string_to_rating(star_match[3]);
3352
3353                 if (appData.debugMode)
3354                   fprintf(debugFP, 
3355                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3356                           player1Name, player1Rating,
3357                           player2Name, player2Rating);
3358
3359                 continue;
3360             }
3361
3362             /* Deal with automatic examine mode after a game,
3363                and with IcsObserving -> IcsExamining transition */
3364             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3365                 looking_at(buf, &i, "has made you an examiner of game *")) {
3366
3367                 int gamenum = atoi(star_match[0]);
3368                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3369                     gamenum == ics_gamenum) {
3370                     /* We were already playing or observing this game;
3371                        no need to refetch history */
3372                     gameMode = IcsExamining;
3373                     if (pausing) {
3374                         pauseExamForwardMostMove = forwardMostMove;
3375                     } else if (currentMove < forwardMostMove) {
3376                         ForwardInner(forwardMostMove);
3377                     }
3378                 } else {
3379                     /* I don't think this case really can happen */
3380                     SendToICS(ics_prefix);
3381                     SendToICS("refresh\n");
3382                 }
3383                 continue;
3384             }    
3385             
3386             /* Error messages */
3387 //          if (ics_user_moved) {
3388             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3389                 if (looking_at(buf, &i, "Illegal move") ||
3390                     looking_at(buf, &i, "Not a legal move") ||
3391                     looking_at(buf, &i, "Your king is in check") ||
3392                     looking_at(buf, &i, "It isn't your turn") ||
3393                     looking_at(buf, &i, "It is not your move")) {
3394                     /* Illegal move */
3395                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3396                         currentMove = forwardMostMove-1;
3397                         DisplayMove(currentMove - 1); /* before DMError */
3398                         DrawPosition(FALSE, boards[currentMove]);
3399                         SwitchClocks(forwardMostMove-1); // [HGM] race
3400                         DisplayBothClocks();
3401                     }
3402                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3403                     ics_user_moved = 0;
3404                     continue;
3405                 }
3406             }
3407
3408             if (looking_at(buf, &i, "still have time") ||
3409                 looking_at(buf, &i, "not out of time") ||
3410                 looking_at(buf, &i, "either player is out of time") ||
3411                 looking_at(buf, &i, "has timeseal; checking")) {
3412                 /* We must have called his flag a little too soon */
3413                 whiteFlag = blackFlag = FALSE;
3414                 continue;
3415             }
3416
3417             if (looking_at(buf, &i, "added * seconds to") ||
3418                 looking_at(buf, &i, "seconds were added to")) {
3419                 /* Update the clocks */
3420                 SendToICS(ics_prefix);
3421                 SendToICS("refresh\n");
3422                 continue;
3423             }
3424
3425             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3426                 ics_clock_paused = TRUE;
3427                 StopClocks();
3428                 continue;
3429             }
3430
3431             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3432                 ics_clock_paused = FALSE;
3433                 StartClocks();
3434                 continue;
3435             }
3436
3437             /* Grab player ratings from the Creating: message.
3438                Note we have to check for the special case when
3439                the ICS inserts things like [white] or [black]. */
3440             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3441                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3442                 /* star_matches:
3443                    0    player 1 name (not necessarily white)
3444                    1    player 1 rating
3445                    2    empty, white, or black (IGNORED)
3446                    3    player 2 name (not necessarily black)
3447                    4    player 2 rating
3448                    
3449                    The names/ratings are sorted out when the game
3450                    actually starts (below).
3451                 */
3452                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3453                 player1Rating = string_to_rating(star_match[1]);
3454                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3455                 player2Rating = string_to_rating(star_match[4]);
3456
3457                 if (appData.debugMode)
3458                   fprintf(debugFP, 
3459                           "Ratings from 'Creating:' %s %d, %s %d\n",
3460                           player1Name, player1Rating,
3461                           player2Name, player2Rating);
3462
3463                 continue;
3464             }
3465             
3466             /* Improved generic start/end-of-game messages */
3467             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3468                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3469                 /* If tkind == 0: */
3470                 /* star_match[0] is the game number */
3471                 /*           [1] is the white player's name */
3472                 /*           [2] is the black player's name */
3473                 /* For end-of-game: */
3474                 /*           [3] is the reason for the game end */
3475                 /*           [4] is a PGN end game-token, preceded by " " */
3476                 /* For start-of-game: */
3477                 /*           [3] begins with "Creating" or "Continuing" */
3478                 /*           [4] is " *" or empty (don't care). */
3479                 int gamenum = atoi(star_match[0]);
3480                 char *whitename, *blackname, *why, *endtoken;
3481                 ChessMove endtype = (ChessMove) 0;
3482
3483                 if (tkind == 0) {
3484                   whitename = star_match[1];
3485                   blackname = star_match[2];
3486                   why = star_match[3];
3487                   endtoken = star_match[4];
3488                 } else {
3489                   whitename = star_match[1];
3490                   blackname = star_match[3];
3491                   why = star_match[5];
3492                   endtoken = star_match[6];
3493                 }
3494
3495                 /* Game start messages */
3496                 if (strncmp(why, "Creating ", 9) == 0 ||
3497                     strncmp(why, "Continuing ", 11) == 0) {
3498                     gs_gamenum = gamenum;
3499                     strcpy(gs_kind, strchr(why, ' ') + 1);
3500                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3501 #if ZIPPY
3502                     if (appData.zippyPlay) {
3503                         ZippyGameStart(whitename, blackname);
3504                     }
3505 #endif /*ZIPPY*/
3506                     partnerBoardValid = FALSE; // [HGM] bughouse
3507                     continue;
3508                 }
3509
3510                 /* Game end messages */
3511                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3512                     ics_gamenum != gamenum) {
3513                     continue;
3514                 }
3515                 while (endtoken[0] == ' ') endtoken++;
3516                 switch (endtoken[0]) {
3517                   case '*':
3518                   default:
3519                     endtype = GameUnfinished;
3520                     break;
3521                   case '0':
3522                     endtype = BlackWins;
3523                     break;
3524                   case '1':
3525                     if (endtoken[1] == '/')
3526                       endtype = GameIsDrawn;
3527                     else
3528                       endtype = WhiteWins;
3529                     break;
3530                 }
3531                 GameEnds(endtype, why, GE_ICS);
3532 #if ZIPPY
3533                 if (appData.zippyPlay && first.initDone) {
3534                     ZippyGameEnd(endtype, why);
3535                     if (first.pr == NULL) {
3536                       /* Start the next process early so that we'll
3537                          be ready for the next challenge */
3538                       StartChessProgram(&first);
3539                     }
3540                     /* Send "new" early, in case this command takes
3541                        a long time to finish, so that we'll be ready
3542                        for the next challenge. */
3543                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3544                     Reset(TRUE, TRUE);
3545                 }
3546 #endif /*ZIPPY*/
3547                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3548                 continue;
3549             }
3550
3551             if (looking_at(buf, &i, "Removing game * from observation") ||
3552                 looking_at(buf, &i, "no longer observing game *") ||
3553                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3554                 if (gameMode == IcsObserving &&
3555                     atoi(star_match[0]) == ics_gamenum)
3556                   {
3557                       /* icsEngineAnalyze */
3558                       if (appData.icsEngineAnalyze) {
3559                             ExitAnalyzeMode();
3560                             ModeHighlight();
3561                       }
3562                       StopClocks();
3563                       gameMode = IcsIdle;
3564                       ics_gamenum = -1;
3565                       ics_user_moved = FALSE;
3566                   }
3567                 continue;
3568             }
3569
3570             if (looking_at(buf, &i, "no longer examining game *")) {
3571                 if (gameMode == IcsExamining &&
3572                     atoi(star_match[0]) == ics_gamenum)
3573                   {
3574                       gameMode = IcsIdle;
3575                       ics_gamenum = -1;
3576                       ics_user_moved = FALSE;
3577                   }
3578                 continue;
3579             }
3580
3581             /* Advance leftover_start past any newlines we find,
3582                so only partial lines can get reparsed */
3583             if (looking_at(buf, &i, "\n")) {
3584                 prevColor = curColor;
3585                 if (curColor != ColorNormal) {
3586                     if (oldi > next_out) {
3587                         SendToPlayer(&buf[next_out], oldi - next_out);
3588                         next_out = oldi;
3589                     }
3590                     Colorize(ColorNormal, FALSE);
3591                     curColor = ColorNormal;
3592                 }
3593                 if (started == STARTED_BOARD) {
3594                     started = STARTED_NONE;
3595                     parse[parse_pos] = NULLCHAR;
3596                     ParseBoard12(parse);
3597                     ics_user_moved = 0;
3598
3599                     /* Send premove here */
3600                     if (appData.premove) {
3601                       char str[MSG_SIZ];
3602                       if (currentMove == 0 &&
3603                           gameMode == IcsPlayingWhite &&
3604                           appData.premoveWhite) {
3605                         sprintf(str, "%s\n", appData.premoveWhiteText);
3606                         if (appData.debugMode)
3607                           fprintf(debugFP, "Sending premove:\n");
3608                         SendToICS(str);
3609                       } else if (currentMove == 1 &&
3610                                  gameMode == IcsPlayingBlack &&
3611                                  appData.premoveBlack) {
3612                         sprintf(str, "%s\n", appData.premoveBlackText);
3613                         if (appData.debugMode)
3614                           fprintf(debugFP, "Sending premove:\n");
3615                         SendToICS(str);
3616                       } else if (gotPremove) {
3617                         gotPremove = 0;
3618                         ClearPremoveHighlights();
3619                         if (appData.debugMode)
3620                           fprintf(debugFP, "Sending premove:\n");
3621                           UserMoveEvent(premoveFromX, premoveFromY, 
3622                                         premoveToX, premoveToY, 
3623                                         premovePromoChar);
3624                       }
3625                     }
3626
3627                     /* Usually suppress following prompt */
3628                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3629                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3630                         if (looking_at(buf, &i, "*% ")) {
3631                             savingComment = FALSE;
3632                             suppressKibitz = 0;
3633                         }
3634                     }
3635                     next_out = i;
3636                 } else if (started == STARTED_HOLDINGS) {
3637                     int gamenum;
3638                     char new_piece[MSG_SIZ];
3639                     started = STARTED_NONE;
3640                     parse[parse_pos] = NULLCHAR;
3641                     if (appData.debugMode)
3642                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3643                                                         parse, currentMove);
3644                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3645                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3646                         if (gameInfo.variant == VariantNormal) {
3647                           /* [HGM] We seem to switch variant during a game!
3648                            * Presumably no holdings were displayed, so we have
3649                            * to move the position two files to the right to
3650                            * create room for them!
3651                            */
3652                           VariantClass newVariant;
3653                           switch(gameInfo.boardWidth) { // base guess on board width
3654                                 case 9:  newVariant = VariantShogi; break;
3655                                 case 10: newVariant = VariantGreat; break;
3656                                 default: newVariant = VariantCrazyhouse; break;
3657                           }
3658                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3659                           /* Get a move list just to see the header, which
3660                              will tell us whether this is really bug or zh */
3661                           if (ics_getting_history == H_FALSE) {
3662                             ics_getting_history = H_REQUESTED;
3663                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3664                             SendToICS(str);
3665                           }
3666                         }
3667                         new_piece[0] = NULLCHAR;
3668                         sscanf(parse, "game %d white [%s black [%s <- %s",
3669                                &gamenum, white_holding, black_holding,
3670                                new_piece);
3671                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3672                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3673                         /* [HGM] copy holdings to board holdings area */
3674                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3675                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3676                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3677 #if ZIPPY
3678                         if (appData.zippyPlay && first.initDone) {
3679                             ZippyHoldings(white_holding, black_holding,
3680                                           new_piece);
3681                         }
3682 #endif /*ZIPPY*/
3683                         if (tinyLayout || smallLayout) {
3684                             char wh[16], bh[16];
3685                             PackHolding(wh, white_holding);
3686                             PackHolding(bh, black_holding);
3687                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3688                                     gameInfo.white, gameInfo.black);
3689                         } else {
3690                             sprintf(str, "%s [%s] vs. %s [%s]",
3691                                     gameInfo.white, white_holding,
3692                                     gameInfo.black, black_holding);
3693                         }
3694                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3695                         DrawPosition(FALSE, boards[currentMove]);
3696                         DisplayTitle(str);
3697                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3698                         sscanf(parse, "game %d white [%s black [%s <- %s",
3699                                &gamenum, white_holding, black_holding,
3700                                new_piece);
3701                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3702                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3703                         /* [HGM] copy holdings to partner-board holdings area */
3704                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3705                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3706                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3707                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3708                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3709                       }
3710                     }
3711                     /* Suppress following prompt */
3712                     if (looking_at(buf, &i, "*% ")) {
3713                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3714                         savingComment = FALSE;
3715                         suppressKibitz = 0;
3716                     }
3717                     next_out = i;
3718                 }
3719                 continue;
3720             }
3721
3722             i++;                /* skip unparsed character and loop back */
3723         }
3724         
3725         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3726 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3727 //          SendToPlayer(&buf[next_out], i - next_out);
3728             started != STARTED_HOLDINGS && leftover_start > next_out) {
3729             SendToPlayer(&buf[next_out], leftover_start - next_out);
3730             next_out = i;
3731         }
3732         
3733         leftover_len = buf_len - leftover_start;
3734         /* if buffer ends with something we couldn't parse,
3735            reparse it after appending the next read */
3736         
3737     } else if (count == 0) {
3738         RemoveInputSource(isr);
3739         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3740     } else {
3741         DisplayFatalError(_("Error reading from ICS"), error, 1);
3742     }
3743 }
3744
3745
3746 /* Board style 12 looks like this:
3747    
3748    <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
3749    
3750  * The "<12> " is stripped before it gets to this routine.  The two
3751  * trailing 0's (flip state and clock ticking) are later addition, and
3752  * some chess servers may not have them, or may have only the first.
3753  * Additional trailing fields may be added in the future.  
3754  */
3755
3756 #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"
3757
3758 #define RELATION_OBSERVING_PLAYED    0
3759 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3760 #define RELATION_PLAYING_MYMOVE      1
3761 #define RELATION_PLAYING_NOTMYMOVE  -1
3762 #define RELATION_EXAMINING           2
3763 #define RELATION_ISOLATED_BOARD     -3
3764 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3765
3766 void
3767 ParseBoard12(string)
3768      char *string;
3769
3770     GameMode newGameMode;
3771     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3772     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3773     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3774     char to_play, board_chars[200];
3775     char move_str[500], str[500], elapsed_time[500];
3776     char black[32], white[32];
3777     Board board;
3778     int prevMove = currentMove;
3779     int ticking = 2;
3780     ChessMove moveType;
3781     int fromX, fromY, toX, toY;
3782     char promoChar;
3783     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3784     char *bookHit = NULL; // [HGM] book
3785     Boolean weird = FALSE, reqFlag = FALSE;
3786
3787     fromX = fromY = toX = toY = -1;
3788     
3789     newGame = FALSE;
3790
3791     if (appData.debugMode)
3792       fprintf(debugFP, _("Parsing board: %s\n"), string);
3793
3794     move_str[0] = NULLCHAR;
3795     elapsed_time[0] = NULLCHAR;
3796     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3797         int  i = 0, j;
3798         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3799             if(string[i] == ' ') { ranks++; files = 0; }
3800             else files++;
3801             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3802             i++;
3803         }
3804         for(j = 0; j <i; j++) board_chars[j] = string[j];
3805         board_chars[i] = '\0';
3806         string += i + 1;
3807     }
3808     n = sscanf(string, PATTERN, &to_play, &double_push,
3809                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3810                &gamenum, white, black, &relation, &basetime, &increment,
3811                &white_stren, &black_stren, &white_time, &black_time,
3812                &moveNum, str, elapsed_time, move_str, &ics_flip,
3813                &ticking);
3814
3815     if (n < 21) {
3816         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3817         DisplayError(str, 0);
3818         return;
3819     }
3820
3821     /* Convert the move number to internal form */
3822     moveNum = (moveNum - 1) * 2;
3823     if (to_play == 'B') moveNum++;
3824     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3825       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3826                         0, 1);
3827       return;
3828     }
3829     
3830     switch (relation) {
3831       case RELATION_OBSERVING_PLAYED:
3832       case RELATION_OBSERVING_STATIC:
3833         if (gamenum == -1) {
3834             /* Old ICC buglet */
3835             relation = RELATION_OBSERVING_STATIC;
3836         }
3837         newGameMode = IcsObserving;
3838         break;
3839       case RELATION_PLAYING_MYMOVE:
3840       case RELATION_PLAYING_NOTMYMOVE:
3841         newGameMode =
3842           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3843             IcsPlayingWhite : IcsPlayingBlack;
3844         break;
3845       case RELATION_EXAMINING:
3846         newGameMode = IcsExamining;
3847         break;
3848       case RELATION_ISOLATED_BOARD:
3849       default:
3850         /* Just display this board.  If user was doing something else,
3851            we will forget about it until the next board comes. */ 
3852         newGameMode = IcsIdle;
3853         break;
3854       case RELATION_STARTING_POSITION:
3855         newGameMode = gameMode;
3856         break;
3857     }
3858     
3859     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3860          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3861       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3862       char *toSqr;
3863       for (k = 0; k < ranks; k++) {
3864         for (j = 0; j < files; j++)
3865           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3866         if(gameInfo.holdingsWidth > 1) {
3867              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3868              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3869         }
3870       }
3871       CopyBoard(partnerBoard, board);
3872       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3873         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3874         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3875       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3876       if(toSqr = strchr(str, '-')) {
3877         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3878         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3879       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3880       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3881       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3882       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3883       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3884       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3885                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3886       DisplayMessage(partnerStatus, "");
3887         partnerBoardValid = TRUE;
3888       return;
3889     }
3890
3891     /* Modify behavior for initial board display on move listing
3892        of wild games.
3893        */
3894     switch (ics_getting_history) {
3895       case H_FALSE:
3896       case H_REQUESTED:
3897         break;
3898       case H_GOT_REQ_HEADER:
3899       case H_GOT_UNREQ_HEADER:
3900         /* This is the initial position of the current game */
3901         gamenum = ics_gamenum;
3902         moveNum = 0;            /* old ICS bug workaround */
3903         if (to_play == 'B') {
3904           startedFromSetupPosition = TRUE;
3905           blackPlaysFirst = TRUE;
3906           moveNum = 1;
3907           if (forwardMostMove == 0) forwardMostMove = 1;
3908           if (backwardMostMove == 0) backwardMostMove = 1;
3909           if (currentMove == 0) currentMove = 1;
3910         }
3911         newGameMode = gameMode;
3912         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3913         break;
3914       case H_GOT_UNWANTED_HEADER:
3915         /* This is an initial board that we don't want */
3916         return;
3917       case H_GETTING_MOVES:
3918         /* Should not happen */
3919         DisplayError(_("Error gathering move list: extra board"), 0);
3920         ics_getting_history = H_FALSE;
3921         return;
3922     }
3923
3924    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3925                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3926      /* [HGM] We seem to have switched variant unexpectedly
3927       * Try to guess new variant from board size
3928       */
3929           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3930           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3931           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3932           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3933           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3934           if(!weird) newVariant = VariantNormal;
3935           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3936           /* Get a move list just to see the header, which
3937              will tell us whether this is really bug or zh */
3938           if (ics_getting_history == H_FALSE) {
3939             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3940             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3941             SendToICS(str);
3942           }
3943     }
3944     
3945     /* Take action if this is the first board of a new game, or of a
3946        different game than is currently being displayed.  */
3947     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3948         relation == RELATION_ISOLATED_BOARD) {
3949         
3950         /* Forget the old game and get the history (if any) of the new one */
3951         if (gameMode != BeginningOfGame) {
3952           Reset(TRUE, TRUE);
3953         }
3954         newGame = TRUE;
3955         if (appData.autoRaiseBoard) BoardToTop();
3956         prevMove = -3;
3957         if (gamenum == -1) {
3958             newGameMode = IcsIdle;
3959         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3960                    appData.getMoveList && !reqFlag) {
3961             /* Need to get game history */
3962             ics_getting_history = H_REQUESTED;
3963             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3964             SendToICS(str);
3965         }
3966         
3967         /* Initially flip the board to have black on the bottom if playing
3968            black or if the ICS flip flag is set, but let the user change
3969            it with the Flip View button. */
3970         flipView = appData.autoFlipView ? 
3971           (newGameMode == IcsPlayingBlack) || ics_flip :
3972           appData.flipView;
3973         
3974         /* Done with values from previous mode; copy in new ones */
3975         gameMode = newGameMode;
3976         ModeHighlight();
3977         ics_gamenum = gamenum;
3978         if (gamenum == gs_gamenum) {
3979             int klen = strlen(gs_kind);
3980             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3981             sprintf(str, "ICS %s", gs_kind);
3982             gameInfo.event = StrSave(str);
3983         } else {
3984             gameInfo.event = StrSave("ICS game");
3985         }
3986         gameInfo.site = StrSave(appData.icsHost);
3987         gameInfo.date = PGNDate();
3988         gameInfo.round = StrSave("-");
3989         gameInfo.white = StrSave(white);
3990         gameInfo.black = StrSave(black);
3991         timeControl = basetime * 60 * 1000;
3992         timeControl_2 = 0;
3993         timeIncrement = increment * 1000;
3994         movesPerSession = 0;
3995         gameInfo.timeControl = TimeControlTagValue();
3996         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3997   if (appData.debugMode) {
3998     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3999     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4000     setbuf(debugFP, NULL);
4001   }
4002
4003         gameInfo.outOfBook = NULL;
4004         
4005         /* Do we have the ratings? */
4006         if (strcmp(player1Name, white) == 0 &&
4007             strcmp(player2Name, black) == 0) {
4008             if (appData.debugMode)
4009               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4010                       player1Rating, player2Rating);
4011             gameInfo.whiteRating = player1Rating;
4012             gameInfo.blackRating = player2Rating;
4013         } else if (strcmp(player2Name, white) == 0 &&
4014                    strcmp(player1Name, black) == 0) {
4015             if (appData.debugMode)
4016               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4017                       player2Rating, player1Rating);
4018             gameInfo.whiteRating = player2Rating;
4019             gameInfo.blackRating = player1Rating;
4020         }
4021         player1Name[0] = player2Name[0] = NULLCHAR;
4022
4023         /* Silence shouts if requested */
4024         if (appData.quietPlay &&
4025             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4026             SendToICS(ics_prefix);
4027             SendToICS("set shout 0\n");
4028         }
4029     }
4030     
4031     /* Deal with midgame name changes */
4032     if (!newGame) {
4033         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4034             if (gameInfo.white) free(gameInfo.white);
4035             gameInfo.white = StrSave(white);
4036         }
4037         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4038             if (gameInfo.black) free(gameInfo.black);
4039             gameInfo.black = StrSave(black);
4040         }
4041     }
4042     
4043     /* Throw away game result if anything actually changes in examine mode */
4044     if (gameMode == IcsExamining && !newGame) {
4045         gameInfo.result = GameUnfinished;
4046         if (gameInfo.resultDetails != NULL) {
4047             free(gameInfo.resultDetails);
4048             gameInfo.resultDetails = NULL;
4049         }
4050     }
4051     
4052     /* In pausing && IcsExamining mode, we ignore boards coming
4053        in if they are in a different variation than we are. */
4054     if (pauseExamInvalid) return;
4055     if (pausing && gameMode == IcsExamining) {
4056         if (moveNum <= pauseExamForwardMostMove) {
4057             pauseExamInvalid = TRUE;
4058             forwardMostMove = pauseExamForwardMostMove;
4059             return;
4060         }
4061     }
4062     
4063   if (appData.debugMode) {
4064     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4065   }
4066     /* Parse the board */
4067     for (k = 0; k < ranks; k++) {
4068       for (j = 0; j < files; j++)
4069         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4070       if(gameInfo.holdingsWidth > 1) {
4071            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4072            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4073       }
4074     }
4075     CopyBoard(boards[moveNum], board);
4076     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4077     if (moveNum == 0) {
4078         startedFromSetupPosition =
4079           !CompareBoards(board, initialPosition);
4080         if(startedFromSetupPosition)
4081             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4082     }
4083
4084     /* [HGM] Set castling rights. Take the outermost Rooks,
4085        to make it also work for FRC opening positions. Note that board12
4086        is really defective for later FRC positions, as it has no way to
4087        indicate which Rook can castle if they are on the same side of King.
4088        For the initial position we grant rights to the outermost Rooks,
4089        and remember thos rights, and we then copy them on positions
4090        later in an FRC game. This means WB might not recognize castlings with
4091        Rooks that have moved back to their original position as illegal,
4092        but in ICS mode that is not its job anyway.
4093     */
4094     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4095     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4096
4097         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4098             if(board[0][i] == WhiteRook) j = i;
4099         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4100         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4101             if(board[0][i] == WhiteRook) j = i;
4102         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4103         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4104             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4105         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4106         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4107             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4108         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4109
4110         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4111         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4112             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4113         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4114             if(board[BOARD_HEIGHT-1][k] == bKing)
4115                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4116         if(gameInfo.variant == VariantTwoKings) {
4117             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4118             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4119             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4120         }
4121     } else { int r;
4122         r = boards[moveNum][CASTLING][0] = initialRights[0];
4123         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4124         r = boards[moveNum][CASTLING][1] = initialRights[1];
4125         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4126         r = boards[moveNum][CASTLING][3] = initialRights[3];
4127         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4128         r = boards[moveNum][CASTLING][4] = initialRights[4];
4129         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4130         /* wildcastle kludge: always assume King has rights */
4131         r = boards[moveNum][CASTLING][2] = initialRights[2];
4132         r = boards[moveNum][CASTLING][5] = initialRights[5];
4133     }
4134     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4135     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4136
4137     
4138     if (ics_getting_history == H_GOT_REQ_HEADER ||
4139         ics_getting_history == H_GOT_UNREQ_HEADER) {
4140         /* This was an initial position from a move list, not
4141            the current position */
4142         return;
4143     }
4144     
4145     /* Update currentMove and known move number limits */
4146     newMove = newGame || moveNum > forwardMostMove;
4147
4148     if (newGame) {
4149         forwardMostMove = backwardMostMove = currentMove = moveNum;
4150         if (gameMode == IcsExamining && moveNum == 0) {
4151           /* Workaround for ICS limitation: we are not told the wild
4152              type when starting to examine a game.  But if we ask for
4153              the move list, the move list header will tell us */
4154             ics_getting_history = H_REQUESTED;
4155             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4156             SendToICS(str);
4157         }
4158     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4159                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4160 #if ZIPPY
4161         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4162         /* [HGM] applied this also to an engine that is silently watching        */
4163         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4164             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4165             gameInfo.variant == currentlyInitializedVariant) {
4166           takeback = forwardMostMove - moveNum;
4167           for (i = 0; i < takeback; i++) {
4168             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4169             SendToProgram("undo\n", &first);
4170           }
4171         }
4172 #endif
4173
4174         forwardMostMove = moveNum;
4175         if (!pausing || currentMove > forwardMostMove)
4176           currentMove = forwardMostMove;
4177     } else {
4178         /* New part of history that is not contiguous with old part */ 
4179         if (pausing && gameMode == IcsExamining) {
4180             pauseExamInvalid = TRUE;
4181             forwardMostMove = pauseExamForwardMostMove;
4182             return;
4183         }
4184         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4185 #if ZIPPY
4186             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4187                 // [HGM] when we will receive the move list we now request, it will be
4188                 // fed to the engine from the first move on. So if the engine is not
4189                 // in the initial position now, bring it there.
4190                 InitChessProgram(&first, 0);
4191             }
4192 #endif
4193             ics_getting_history = H_REQUESTED;
4194             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4195             SendToICS(str);
4196         }
4197         forwardMostMove = backwardMostMove = currentMove = moveNum;
4198     }
4199     
4200     /* Update the clocks */
4201     if (strchr(elapsed_time, '.')) {
4202       /* Time is in ms */
4203       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4204       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4205     } else {
4206       /* Time is in seconds */
4207       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4208       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4209     }
4210       
4211
4212 #if ZIPPY
4213     if (appData.zippyPlay && newGame &&
4214         gameMode != IcsObserving && gameMode != IcsIdle &&
4215         gameMode != IcsExamining)
4216       ZippyFirstBoard(moveNum, basetime, increment);
4217 #endif
4218     
4219     /* Put the move on the move list, first converting
4220        to canonical algebraic form. */
4221     if (moveNum > 0) {
4222   if (appData.debugMode) {
4223     if (appData.debugMode) { int f = forwardMostMove;
4224         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4225                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4226                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4227     }
4228     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4229     fprintf(debugFP, "moveNum = %d\n", moveNum);
4230     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4231     setbuf(debugFP, NULL);
4232   }
4233         if (moveNum <= backwardMostMove) {
4234             /* We don't know what the board looked like before
4235                this move.  Punt. */
4236             strcpy(parseList[moveNum - 1], move_str);
4237             strcat(parseList[moveNum - 1], " ");
4238             strcat(parseList[moveNum - 1], elapsed_time);
4239             moveList[moveNum - 1][0] = NULLCHAR;
4240         } else if (strcmp(move_str, "none") == 0) {
4241             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4242             /* Again, we don't know what the board looked like;
4243                this is really the start of the game. */
4244             parseList[moveNum - 1][0] = NULLCHAR;
4245             moveList[moveNum - 1][0] = NULLCHAR;
4246             backwardMostMove = moveNum;
4247             startedFromSetupPosition = TRUE;
4248             fromX = fromY = toX = toY = -1;
4249         } else {
4250           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4251           //                 So we parse the long-algebraic move string in stead of the SAN move
4252           int valid; char buf[MSG_SIZ], *prom;
4253
4254           // str looks something like "Q/a1-a2"; kill the slash
4255           if(str[1] == '/') 
4256                 sprintf(buf, "%c%s", str[0], str+2);
4257           else  strcpy(buf, str); // might be castling
4258           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4259                 strcat(buf, prom); // long move lacks promo specification!
4260           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4261                 if(appData.debugMode) 
4262                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4263                 strcpy(move_str, buf);
4264           }
4265           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4266                                 &fromX, &fromY, &toX, &toY, &promoChar)
4267                || ParseOneMove(buf, moveNum - 1, &moveType,
4268                                 &fromX, &fromY, &toX, &toY, &promoChar);
4269           // end of long SAN patch
4270           if (valid) {
4271             (void) CoordsToAlgebraic(boards[moveNum - 1],
4272                                      PosFlags(moveNum - 1),
4273                                      fromY, fromX, toY, toX, promoChar,
4274                                      parseList[moveNum-1]);
4275             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4276               case MT_NONE:
4277               case MT_STALEMATE:
4278               default:
4279                 break;
4280               case MT_CHECK:
4281                 if(gameInfo.variant != VariantShogi)
4282                     strcat(parseList[moveNum - 1], "+");
4283                 break;
4284               case MT_CHECKMATE:
4285               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4286                 strcat(parseList[moveNum - 1], "#");
4287                 break;
4288             }
4289             strcat(parseList[moveNum - 1], " ");
4290             strcat(parseList[moveNum - 1], elapsed_time);
4291             /* currentMoveString is set as a side-effect of ParseOneMove */
4292             strcpy(moveList[moveNum - 1], currentMoveString);
4293             strcat(moveList[moveNum - 1], "\n");
4294           } else {
4295             /* Move from ICS was illegal!?  Punt. */
4296   if (appData.debugMode) {
4297     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4298     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4299   }
4300             strcpy(parseList[moveNum - 1], move_str);
4301             strcat(parseList[moveNum - 1], " ");
4302             strcat(parseList[moveNum - 1], elapsed_time);
4303             moveList[moveNum - 1][0] = NULLCHAR;
4304             fromX = fromY = toX = toY = -1;
4305           }
4306         }
4307   if (appData.debugMode) {
4308     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4309     setbuf(debugFP, NULL);
4310   }
4311
4312 #if ZIPPY
4313         /* Send move to chess program (BEFORE animating it). */
4314         if (appData.zippyPlay && !newGame && newMove && 
4315            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4316
4317             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4318                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4319                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4320                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4321                             move_str);
4322                     DisplayError(str, 0);
4323                 } else {
4324                     if (first.sendTime) {
4325                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4326                     }
4327                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4328                     if (firstMove && !bookHit) {
4329                         firstMove = FALSE;
4330                         if (first.useColors) {
4331                           SendToProgram(gameMode == IcsPlayingWhite ?
4332                                         "white\ngo\n" :
4333                                         "black\ngo\n", &first);
4334                         } else {
4335                           SendToProgram("go\n", &first);
4336                         }
4337                         first.maybeThinking = TRUE;
4338                     }
4339                 }
4340             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4341               if (moveList[moveNum - 1][0] == NULLCHAR) {
4342                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4343                 DisplayError(str, 0);
4344               } else {
4345                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4346                 SendMoveToProgram(moveNum - 1, &first);
4347               }
4348             }
4349         }
4350 #endif
4351     }
4352
4353     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4354         /* If move comes from a remote source, animate it.  If it
4355            isn't remote, it will have already been animated. */
4356         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4357             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4358         }
4359         if (!pausing && appData.highlightLastMove) {
4360             SetHighlights(fromX, fromY, toX, toY);
4361         }
4362     }
4363     
4364     /* Start the clocks */
4365     whiteFlag = blackFlag = FALSE;
4366     appData.clockMode = !(basetime == 0 && increment == 0);
4367     if (ticking == 0) {
4368       ics_clock_paused = TRUE;
4369       StopClocks();
4370     } else if (ticking == 1) {
4371       ics_clock_paused = FALSE;
4372     }
4373     if (gameMode == IcsIdle ||
4374         relation == RELATION_OBSERVING_STATIC ||
4375         relation == RELATION_EXAMINING ||
4376         ics_clock_paused)
4377       DisplayBothClocks();
4378     else
4379       StartClocks();
4380     
4381     /* Display opponents and material strengths */
4382     if (gameInfo.variant != VariantBughouse &&
4383         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4384         if (tinyLayout || smallLayout) {
4385             if(gameInfo.variant == VariantNormal)
4386                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4387                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4388                     basetime, increment);
4389             else
4390                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4391                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4392                     basetime, increment, (int) gameInfo.variant);
4393         } else {
4394             if(gameInfo.variant == VariantNormal)
4395                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4396                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4397                     basetime, increment);
4398             else
4399                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4400                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4401                     basetime, increment, VariantName(gameInfo.variant));
4402         }
4403         DisplayTitle(str);
4404   if (appData.debugMode) {
4405     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4406   }
4407     }
4408
4409
4410     /* Display the board */
4411     if (!pausing && !appData.noGUI) {
4412       
4413       if (appData.premove)
4414           if (!gotPremove || 
4415              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4416              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4417               ClearPremoveHighlights();
4418
4419       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4420         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4421       DrawPosition(j, boards[currentMove]);
4422
4423       DisplayMove(moveNum - 1);
4424       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4425             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4426               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4427         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4428       }
4429     }
4430
4431     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4432 #if ZIPPY
4433     if(bookHit) { // [HGM] book: simulate book reply
4434         static char bookMove[MSG_SIZ]; // a bit generous?
4435
4436         programStats.nodes = programStats.depth = programStats.time = 
4437         programStats.score = programStats.got_only_move = 0;
4438         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4439
4440         strcpy(bookMove, "move ");
4441         strcat(bookMove, bookHit);
4442         HandleMachineMove(bookMove, &first);
4443     }
4444 #endif
4445 }
4446
4447 void
4448 GetMoveListEvent()
4449 {
4450     char buf[MSG_SIZ];
4451     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4452         ics_getting_history = H_REQUESTED;
4453         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4454         SendToICS(buf);
4455     }
4456 }
4457
4458 void
4459 AnalysisPeriodicEvent(force)
4460      int force;
4461 {
4462     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4463          && !force) || !appData.periodicUpdates)
4464       return;
4465
4466     /* Send . command to Crafty to collect stats */
4467     SendToProgram(".\n", &first);
4468
4469     /* Don't send another until we get a response (this makes
4470        us stop sending to old Crafty's which don't understand
4471        the "." command (sending illegal cmds resets node count & time,
4472        which looks bad)) */
4473     programStats.ok_to_send = 0;
4474 }
4475
4476 void ics_update_width(new_width)
4477         int new_width;
4478 {
4479         ics_printf("set width %d\n", new_width);
4480 }
4481
4482 void
4483 SendMoveToProgram(moveNum, cps)
4484      int moveNum;
4485      ChessProgramState *cps;
4486 {
4487     char buf[MSG_SIZ];
4488
4489     if (cps->useUsermove) {
4490       SendToProgram("usermove ", cps);
4491     }
4492     if (cps->useSAN) {
4493       char *space;
4494       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4495         int len = space - parseList[moveNum];
4496         memcpy(buf, parseList[moveNum], len);
4497         buf[len++] = '\n';
4498         buf[len] = NULLCHAR;
4499       } else {
4500         sprintf(buf, "%s\n", parseList[moveNum]);
4501       }
4502       SendToProgram(buf, cps);
4503     } else {
4504       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4505         AlphaRank(moveList[moveNum], 4);
4506         SendToProgram(moveList[moveNum], cps);
4507         AlphaRank(moveList[moveNum], 4); // and back
4508       } else
4509       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4510        * the engine. It would be nice to have a better way to identify castle 
4511        * moves here. */
4512       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4513                                                                          && cps->useOOCastle) {
4514         int fromX = moveList[moveNum][0] - AAA; 
4515         int fromY = moveList[moveNum][1] - ONE;
4516         int toX = moveList[moveNum][2] - AAA; 
4517         int toY = moveList[moveNum][3] - ONE;
4518         if((boards[moveNum][fromY][fromX] == WhiteKing 
4519             && boards[moveNum][toY][toX] == WhiteRook)
4520            || (boards[moveNum][fromY][fromX] == BlackKing 
4521                && boards[moveNum][toY][toX] == BlackRook)) {
4522           if(toX > fromX) SendToProgram("O-O\n", cps);
4523           else SendToProgram("O-O-O\n", cps);
4524         }
4525         else SendToProgram(moveList[moveNum], cps);
4526       }
4527       else SendToProgram(moveList[moveNum], cps);
4528       /* End of additions by Tord */
4529     }
4530
4531     /* [HGM] setting up the opening has brought engine in force mode! */
4532     /*       Send 'go' if we are in a mode where machine should play. */
4533     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4534         (gameMode == TwoMachinesPlay   ||
4535 #ifdef ZIPPY
4536          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4537 #endif
4538          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4539         SendToProgram("go\n", cps);
4540   if (appData.debugMode) {
4541     fprintf(debugFP, "(extra)\n");
4542   }
4543     }
4544     setboardSpoiledMachineBlack = 0;
4545 }
4546
4547 void
4548 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4549      ChessMove moveType;
4550      int fromX, fromY, toX, toY;
4551 {
4552     char user_move[MSG_SIZ];
4553
4554     switch (moveType) {
4555       default:
4556         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4557                 (int)moveType, fromX, fromY, toX, toY);
4558         DisplayError(user_move + strlen("say "), 0);
4559         break;
4560       case WhiteKingSideCastle:
4561       case BlackKingSideCastle:
4562       case WhiteQueenSideCastleWild:
4563       case BlackQueenSideCastleWild:
4564       /* PUSH Fabien */
4565       case WhiteHSideCastleFR:
4566       case BlackHSideCastleFR:
4567       /* POP Fabien */
4568         sprintf(user_move, "o-o\n");
4569         break;
4570       case WhiteQueenSideCastle:
4571       case BlackQueenSideCastle:
4572       case WhiteKingSideCastleWild:
4573       case BlackKingSideCastleWild:
4574       /* PUSH Fabien */
4575       case WhiteASideCastleFR:
4576       case BlackASideCastleFR:
4577       /* POP Fabien */
4578         sprintf(user_move, "o-o-o\n");
4579         break;
4580       case WhitePromotionQueen:
4581       case BlackPromotionQueen:
4582       case WhitePromotionRook:
4583       case BlackPromotionRook:
4584       case WhitePromotionBishop:
4585       case BlackPromotionBishop:
4586       case WhitePromotionKnight:
4587       case BlackPromotionKnight:
4588       case WhitePromotionKing:
4589       case BlackPromotionKing:
4590       case WhitePromotionChancellor:
4591       case BlackPromotionChancellor:
4592       case WhitePromotionArchbishop:
4593       case BlackPromotionArchbishop:
4594         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4595             sprintf(user_move, "%c%c%c%c=%c\n",
4596                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4597                 PieceToChar(WhiteFerz));
4598         else if(gameInfo.variant == VariantGreat)
4599             sprintf(user_move, "%c%c%c%c=%c\n",
4600                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4601                 PieceToChar(WhiteMan));
4602         else
4603             sprintf(user_move, "%c%c%c%c=%c\n",
4604                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4605                 PieceToChar(PromoPiece(moveType)));
4606         break;
4607       case WhiteDrop:
4608       case BlackDrop:
4609         sprintf(user_move, "%c@%c%c\n",
4610                 ToUpper(PieceToChar((ChessSquare) fromX)),
4611                 AAA + toX, ONE + toY);
4612         break;
4613       case NormalMove:
4614       case WhiteCapturesEnPassant:
4615       case BlackCapturesEnPassant:
4616       case IllegalMove:  /* could be a variant we don't quite understand */
4617         sprintf(user_move, "%c%c%c%c\n",
4618                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4619         break;
4620     }
4621     SendToICS(user_move);
4622     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4623         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4624 }
4625
4626 void
4627 UploadGameEvent()
4628 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4629     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4630     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4631     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4632         DisplayError("You cannot do this while you are playing or observing", 0);
4633         return;
4634     }
4635     if(gameMode != IcsExamining) { // is this ever not the case?
4636         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4637
4638         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4639             sprintf(command, "match %s", ics_handle);
4640         } else { // on FICS we must first go to general examine mode
4641             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4642         }
4643         if(gameInfo.variant != VariantNormal) {
4644             // try figure out wild number, as xboard names are not always valid on ICS
4645             for(i=1; i<=36; i++) {
4646                 sprintf(buf, "wild/%d", i);
4647                 if(StringToVariant(buf) == gameInfo.variant) break;
4648             }
4649             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4650             else if(i == 22) sprintf(buf, "%s fr\n", command);
4651             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4652         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4653         SendToICS(ics_prefix);
4654         SendToICS(buf);
4655         if(startedFromSetupPosition || backwardMostMove != 0) {
4656           fen = PositionToFEN(backwardMostMove, NULL);
4657           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4658             sprintf(buf, "loadfen %s\n", fen);
4659             SendToICS(buf);
4660           } else { // FICS: everything has to set by separate bsetup commands
4661             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4662             sprintf(buf, "bsetup fen %s\n", fen);
4663             SendToICS(buf);
4664             if(!WhiteOnMove(backwardMostMove)) {
4665                 SendToICS("bsetup tomove black\n");
4666             }
4667             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4668             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4669             SendToICS(buf);
4670             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4671             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4672             SendToICS(buf);
4673             i = boards[backwardMostMove][EP_STATUS];
4674             if(i >= 0) { // set e.p.
4675                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4676                 SendToICS(buf);
4677             }
4678             bsetup++;
4679           }
4680         }
4681       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4682             SendToICS("bsetup done\n"); // switch to normal examining.
4683     }
4684     for(i = backwardMostMove; i<last; i++) {
4685         char buf[20];
4686         sprintf(buf, "%s\n", parseList[i]);
4687         SendToICS(buf);
4688     }
4689     SendToICS(ics_prefix);
4690     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4691 }
4692
4693 void
4694 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4695      int rf, ff, rt, ft;
4696      char promoChar;
4697      char move[7];
4698 {
4699     if (rf == DROP_RANK) {
4700         sprintf(move, "%c@%c%c\n",
4701                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4702     } else {
4703         if (promoChar == 'x' || promoChar == NULLCHAR) {
4704             sprintf(move, "%c%c%c%c\n",
4705                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4706         } else {
4707             sprintf(move, "%c%c%c%c%c\n",
4708                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4709         }
4710     }
4711 }
4712
4713 void
4714 ProcessICSInitScript(f)
4715      FILE *f;
4716 {
4717     char buf[MSG_SIZ];
4718
4719     while (fgets(buf, MSG_SIZ, f)) {
4720         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4721     }
4722
4723     fclose(f);
4724 }
4725
4726
4727 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4728 void
4729 AlphaRank(char *move, int n)
4730 {
4731 //    char *p = move, c; int x, y;
4732
4733     if (appData.debugMode) {
4734         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4735     }
4736
4737     if(move[1]=='*' && 
4738        move[2]>='0' && move[2]<='9' &&
4739        move[3]>='a' && move[3]<='x'    ) {
4740         move[1] = '@';
4741         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4742         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4743     } else
4744     if(move[0]>='0' && move[0]<='9' &&
4745        move[1]>='a' && move[1]<='x' &&
4746        move[2]>='0' && move[2]<='9' &&
4747        move[3]>='a' && move[3]<='x'    ) {
4748         /* input move, Shogi -> normal */
4749         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4750         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4751         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4752         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4753     } else
4754     if(move[1]=='@' &&
4755        move[3]>='0' && move[3]<='9' &&
4756        move[2]>='a' && move[2]<='x'    ) {
4757         move[1] = '*';
4758         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4759         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4760     } else
4761     if(
4762        move[0]>='a' && move[0]<='x' &&
4763        move[3]>='0' && move[3]<='9' &&
4764        move[2]>='a' && move[2]<='x'    ) {
4765          /* output move, normal -> Shogi */
4766         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4767         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4768         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4769         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4770         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4771     }
4772     if (appData.debugMode) {
4773         fprintf(debugFP, "   out = '%s'\n", move);
4774     }
4775 }
4776
4777 char yy_textstr[8000];
4778
4779 /* Parser for moves from gnuchess, ICS, or user typein box */
4780 Boolean
4781 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4782      char *move;
4783      int moveNum;
4784      ChessMove *moveType;
4785      int *fromX, *fromY, *toX, *toY;
4786      char *promoChar;
4787 {       
4788     if (appData.debugMode) {
4789         fprintf(debugFP, "move to parse: %s\n", move);
4790     }
4791     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4792
4793     switch (*moveType) {
4794       case WhitePromotionChancellor:
4795       case BlackPromotionChancellor:
4796       case WhitePromotionArchbishop:
4797       case BlackPromotionArchbishop:
4798       case WhitePromotionQueen:
4799       case BlackPromotionQueen:
4800       case WhitePromotionRook:
4801       case BlackPromotionRook:
4802       case WhitePromotionBishop:
4803       case BlackPromotionBishop:
4804       case WhitePromotionKnight:
4805       case BlackPromotionKnight:
4806       case WhitePromotionKing:
4807       case BlackPromotionKing:
4808       case NormalMove:
4809       case WhiteCapturesEnPassant:
4810       case BlackCapturesEnPassant:
4811       case WhiteKingSideCastle:
4812       case WhiteQueenSideCastle:
4813       case BlackKingSideCastle:
4814       case BlackQueenSideCastle:
4815       case WhiteKingSideCastleWild:
4816       case WhiteQueenSideCastleWild:
4817       case BlackKingSideCastleWild:
4818       case BlackQueenSideCastleWild:
4819       /* Code added by Tord: */
4820       case WhiteHSideCastleFR:
4821       case WhiteASideCastleFR:
4822       case BlackHSideCastleFR:
4823       case BlackASideCastleFR:
4824       /* End of code added by Tord */
4825       case IllegalMove:         /* bug or odd chess variant */
4826         *fromX = currentMoveString[0] - AAA;
4827         *fromY = currentMoveString[1] - ONE;
4828         *toX = currentMoveString[2] - AAA;
4829         *toY = currentMoveString[3] - ONE;
4830         *promoChar = currentMoveString[4];
4831         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4832             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4833     if (appData.debugMode) {
4834         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4835     }
4836             *fromX = *fromY = *toX = *toY = 0;
4837             return FALSE;
4838         }
4839         if (appData.testLegality) {
4840           return (*moveType != IllegalMove);
4841         } else {
4842           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4843                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4844         }
4845
4846       case WhiteDrop:
4847       case BlackDrop:
4848         *fromX = *moveType == WhiteDrop ?
4849           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4850           (int) CharToPiece(ToLower(currentMoveString[0]));
4851         *fromY = DROP_RANK;
4852         *toX = currentMoveString[2] - AAA;
4853         *toY = currentMoveString[3] - ONE;
4854         *promoChar = NULLCHAR;
4855         return TRUE;
4856
4857       case AmbiguousMove:
4858       case ImpossibleMove:
4859       case (ChessMove) 0:       /* end of file */
4860       case ElapsedTime:
4861       case Comment:
4862       case PGNTag:
4863       case NAG:
4864       case WhiteWins:
4865       case BlackWins:
4866       case GameIsDrawn:
4867       default:
4868     if (appData.debugMode) {
4869         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4870     }
4871         /* bug? */
4872         *fromX = *fromY = *toX = *toY = 0;
4873         *promoChar = NULLCHAR;
4874         return FALSE;
4875     }
4876 }
4877
4878
4879 void
4880 ParsePV(char *pv, Boolean storeComments)
4881 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4882   int fromX, fromY, toX, toY; char promoChar;
4883   ChessMove moveType;
4884   Boolean valid;
4885   int nr = 0;
4886
4887   endPV = forwardMostMove;
4888   do {
4889     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4890     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4891     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4892 if(appData.debugMode){
4893 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);
4894 }
4895     if(!valid && nr == 0 &&
4896        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4897         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4898         // Hande case where played move is different from leading PV move
4899         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4900         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4901         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4902         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4903           endPV += 2; // if position different, keep this
4904           moveList[endPV-1][0] = fromX + AAA;
4905           moveList[endPV-1][1] = fromY + ONE;
4906           moveList[endPV-1][2] = toX + AAA;
4907           moveList[endPV-1][3] = toY + ONE;
4908           parseList[endPV-1][0] = NULLCHAR;
4909           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4910         }
4911       }
4912     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4913     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4914     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4915     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4916         valid++; // allow comments in PV
4917         continue;
4918     }
4919     nr++;
4920     if(endPV+1 > framePtr) break; // no space, truncate
4921     if(!valid) break;
4922     endPV++;
4923     CopyBoard(boards[endPV], boards[endPV-1]);
4924     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4925     moveList[endPV-1][0] = fromX + AAA;
4926     moveList[endPV-1][1] = fromY + ONE;
4927     moveList[endPV-1][2] = toX + AAA;
4928     moveList[endPV-1][3] = toY + ONE;
4929     if(storeComments)
4930         CoordsToAlgebraic(boards[endPV - 1],
4931                              PosFlags(endPV - 1),
4932                              fromY, fromX, toY, toX, promoChar,
4933                              parseList[endPV - 1]);
4934     else
4935         parseList[endPV-1][0] = NULLCHAR;
4936   } while(valid);
4937   currentMove = endPV;
4938   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4939   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4940                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4941   DrawPosition(TRUE, boards[currentMove]);
4942 }
4943
4944 static int lastX, lastY;
4945
4946 Boolean
4947 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4948 {
4949         int startPV;
4950         char *p;
4951
4952         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4953         lastX = x; lastY = y;
4954         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4955         startPV = index;
4956         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4957         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4958         index = startPV;
4959         do{ while(buf[index] && buf[index] != '\n') index++;
4960         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4961         buf[index] = 0;
4962         ParsePV(buf+startPV, FALSE);
4963         *start = startPV; *end = index-1;
4964         return TRUE;
4965 }
4966
4967 Boolean
4968 LoadPV(int x, int y)
4969 { // called on right mouse click to load PV
4970   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4971   lastX = x; lastY = y;
4972   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4973   return TRUE;
4974 }
4975
4976 void
4977 UnLoadPV()
4978 {
4979   if(endPV < 0) return;
4980   endPV = -1;
4981   currentMove = forwardMostMove;
4982   ClearPremoveHighlights();
4983   DrawPosition(TRUE, boards[currentMove]);
4984 }
4985
4986 void
4987 MovePV(int x, int y, int h)
4988 { // step through PV based on mouse coordinates (called on mouse move)
4989   int margin = h>>3, step = 0;
4990
4991   if(endPV < 0) return;
4992   // we must somehow check if right button is still down (might be released off board!)
4993   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4994   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4995   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4996   if(!step) return;
4997   lastX = x; lastY = y;
4998   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4999   currentMove += step;
5000   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5001   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5002                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5003   DrawPosition(FALSE, boards[currentMove]);
5004 }
5005
5006
5007 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5008 // All positions will have equal probability, but the current method will not provide a unique
5009 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5010 #define DARK 1
5011 #define LITE 2
5012 #define ANY 3
5013
5014 int squaresLeft[4];
5015 int piecesLeft[(int)BlackPawn];
5016 int seed, nrOfShuffles;
5017
5018 void GetPositionNumber()
5019 {       // sets global variable seed
5020         int i;
5021
5022         seed = appData.defaultFrcPosition;
5023         if(seed < 0) { // randomize based on time for negative FRC position numbers
5024                 for(i=0; i<50; i++) seed += random();
5025                 seed = random() ^ random() >> 8 ^ random() << 8;
5026                 if(seed<0) seed = -seed;
5027         }
5028 }
5029
5030 int put(Board board, int pieceType, int rank, int n, int shade)
5031 // put the piece on the (n-1)-th empty squares of the given shade
5032 {
5033         int i;
5034
5035         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5036                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5037                         board[rank][i] = (ChessSquare) pieceType;
5038                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5039                         squaresLeft[ANY]--;
5040                         piecesLeft[pieceType]--; 
5041                         return i;
5042                 }
5043         }
5044         return -1;
5045 }
5046
5047
5048 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5049 // calculate where the next piece goes, (any empty square), and put it there
5050 {
5051         int i;
5052
5053         i = seed % squaresLeft[shade];
5054         nrOfShuffles *= squaresLeft[shade];
5055         seed /= squaresLeft[shade];
5056         put(board, pieceType, rank, i, shade);
5057 }
5058
5059 void AddTwoPieces(Board board, int pieceType, int rank)
5060 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5061 {
5062         int i, n=squaresLeft[ANY], j=n-1, k;
5063
5064         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5065         i = seed % k;  // pick one
5066         nrOfShuffles *= k;
5067         seed /= k;
5068         while(i >= j) i -= j--;
5069         j = n - 1 - j; i += j;
5070         put(board, pieceType, rank, j, ANY);
5071         put(board, pieceType, rank, i, ANY);
5072 }
5073
5074 void SetUpShuffle(Board board, int number)
5075 {
5076         int i, p, first=1;
5077
5078         GetPositionNumber(); nrOfShuffles = 1;
5079
5080         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5081         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5082         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5083
5084         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5085
5086         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5087             p = (int) board[0][i];
5088             if(p < (int) BlackPawn) piecesLeft[p] ++;
5089             board[0][i] = EmptySquare;
5090         }
5091
5092         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5093             // shuffles restricted to allow normal castling put KRR first
5094             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5095                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5096             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5097                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5098             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5099                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5100             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5101                 put(board, WhiteRook, 0, 0, ANY);
5102             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5103         }
5104
5105         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5106             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5107             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5108                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5109                 while(piecesLeft[p] >= 2) {
5110                     AddOnePiece(board, p, 0, LITE);
5111                     AddOnePiece(board, p, 0, DARK);
5112                 }
5113                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5114             }
5115
5116         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5117             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5118             // but we leave King and Rooks for last, to possibly obey FRC restriction
5119             if(p == (int)WhiteRook) continue;
5120             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5121             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5122         }
5123
5124         // now everything is placed, except perhaps King (Unicorn) and Rooks
5125
5126         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5127             // Last King gets castling rights
5128             while(piecesLeft[(int)WhiteUnicorn]) {
5129                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5130                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5131             }
5132
5133             while(piecesLeft[(int)WhiteKing]) {
5134                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5135                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5136             }
5137
5138
5139         } else {
5140             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5141             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5142         }
5143
5144         // Only Rooks can be left; simply place them all
5145         while(piecesLeft[(int)WhiteRook]) {
5146                 i = put(board, WhiteRook, 0, 0, ANY);
5147                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5148                         if(first) {
5149                                 first=0;
5150                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5151                         }
5152                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5153                 }
5154         }
5155         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5156             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5157         }
5158
5159         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5160 }
5161
5162 int SetCharTable( char *table, const char * map )
5163 /* [HGM] moved here from winboard.c because of its general usefulness */
5164 /*       Basically a safe strcpy that uses the last character as King */
5165 {
5166     int result = FALSE; int NrPieces;
5167
5168     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5169                     && NrPieces >= 12 && !(NrPieces&1)) {
5170         int i; /* [HGM] Accept even length from 12 to 34 */
5171
5172         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5173         for( i=0; i<NrPieces/2-1; i++ ) {
5174             table[i] = map[i];
5175             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5176         }
5177         table[(int) WhiteKing]  = map[NrPieces/2-1];
5178         table[(int) BlackKing]  = map[NrPieces-1];
5179
5180         result = TRUE;
5181     }
5182
5183     return result;
5184 }
5185
5186 void Prelude(Board board)
5187 {       // [HGM] superchess: random selection of exo-pieces
5188         int i, j, k; ChessSquare p; 
5189         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5190
5191         GetPositionNumber(); // use FRC position number
5192
5193         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5194             SetCharTable(pieceToChar, appData.pieceToCharTable);
5195             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5196                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5197         }
5198
5199         j = seed%4;                 seed /= 4; 
5200         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5201         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5202         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5203         j = seed%3 + (seed%3 >= j); seed /= 3; 
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; 
5208         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2; 
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%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5216         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5217         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5218         put(board, exoPieces[0],    0, 0, ANY);
5219         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5220 }
5221
5222 void
5223 InitPosition(redraw)
5224      int redraw;
5225 {
5226     ChessSquare (* pieces)[BOARD_FILES];
5227     int i, j, pawnRow, overrule,
5228     oldx = gameInfo.boardWidth,
5229     oldy = gameInfo.boardHeight,
5230     oldh = gameInfo.holdingsWidth,
5231     oldv = gameInfo.variant;
5232
5233     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5234
5235     /* [AS] Initialize pv info list [HGM] and game status */
5236     {
5237         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5238             pvInfoList[i].depth = 0;
5239             boards[i][EP_STATUS] = EP_NONE;
5240             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5241         }
5242
5243         initialRulePlies = 0; /* 50-move counter start */
5244
5245         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5246         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5247     }
5248
5249     
5250     /* [HGM] logic here is completely changed. In stead of full positions */
5251     /* the initialized data only consist of the two backranks. The switch */
5252     /* selects which one we will use, which is than copied to the Board   */
5253     /* initialPosition, which for the rest is initialized by Pawns and    */
5254     /* empty squares. This initial position is then copied to boards[0],  */
5255     /* possibly after shuffling, so that it remains available.            */
5256
5257     gameInfo.holdingsWidth = 0; /* default board sizes */
5258     gameInfo.boardWidth    = 8;
5259     gameInfo.boardHeight   = 8;
5260     gameInfo.holdingsSize  = 0;
5261     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5262     for(i=0; i<BOARD_FILES-2; i++)
5263       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5264     initialPosition[EP_STATUS] = EP_NONE;
5265     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5266     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5267          SetCharTable(pieceNickName, appData.pieceNickNames);
5268     else SetCharTable(pieceNickName, "............");
5269
5270     switch (gameInfo.variant) {
5271     case VariantFischeRandom:
5272       shuffleOpenings = TRUE;
5273     default:
5274       pieces = FIDEArray;
5275       break;
5276     case VariantShatranj:
5277       pieces = ShatranjArray;
5278       nrCastlingRights = 0;
5279       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5280       break;
5281     case VariantMakruk:
5282       pieces = makrukArray;
5283       nrCastlingRights = 0;
5284       startedFromSetupPosition = TRUE;
5285       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5286       break;
5287     case VariantTwoKings:
5288       pieces = twoKingsArray;
5289       break;
5290     case VariantCapaRandom:
5291       shuffleOpenings = TRUE;
5292     case VariantCapablanca:
5293       pieces = CapablancaArray;
5294       gameInfo.boardWidth = 10;
5295       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5296       break;
5297     case VariantGothic:
5298       pieces = GothicArray;
5299       gameInfo.boardWidth = 10;
5300       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5301       break;
5302     case VariantJanus:
5303       pieces = JanusArray;
5304       gameInfo.boardWidth = 10;
5305       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5306       nrCastlingRights = 6;
5307         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5308         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5309         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5310         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5311         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5312         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5313       break;
5314     case VariantFalcon:
5315       pieces = FalconArray;
5316       gameInfo.boardWidth = 10;
5317       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5318       break;
5319     case VariantXiangqi:
5320       pieces = XiangqiArray;
5321       gameInfo.boardWidth  = 9;
5322       gameInfo.boardHeight = 10;
5323       nrCastlingRights = 0;
5324       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5325       break;
5326     case VariantShogi:
5327       pieces = ShogiArray;
5328       gameInfo.boardWidth  = 9;
5329       gameInfo.boardHeight = 9;
5330       gameInfo.holdingsSize = 7;
5331       nrCastlingRights = 0;
5332       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5333       break;
5334     case VariantCourier:
5335       pieces = CourierArray;
5336       gameInfo.boardWidth  = 12;
5337       nrCastlingRights = 0;
5338       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5339       break;
5340     case VariantKnightmate:
5341       pieces = KnightmateArray;
5342       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5343       break;
5344     case VariantFairy:
5345       pieces = fairyArray;
5346       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5347       break;
5348     case VariantGreat:
5349       pieces = GreatArray;
5350       gameInfo.boardWidth = 10;
5351       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5352       gameInfo.holdingsSize = 8;
5353       break;
5354     case VariantSuper:
5355       pieces = FIDEArray;
5356       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5357       gameInfo.holdingsSize = 8;
5358       startedFromSetupPosition = TRUE;
5359       break;
5360     case VariantCrazyhouse:
5361     case VariantBughouse:
5362       pieces = FIDEArray;
5363       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5364       gameInfo.holdingsSize = 5;
5365       break;
5366     case VariantWildCastle:
5367       pieces = FIDEArray;
5368       /* !!?shuffle with kings guaranteed to be on d or e file */
5369       shuffleOpenings = 1;
5370       break;
5371     case VariantNoCastle:
5372       pieces = FIDEArray;
5373       nrCastlingRights = 0;
5374       /* !!?unconstrained back-rank shuffle */
5375       shuffleOpenings = 1;
5376       break;
5377     }
5378
5379     overrule = 0;
5380     if(appData.NrFiles >= 0) {
5381         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5382         gameInfo.boardWidth = appData.NrFiles;
5383     }
5384     if(appData.NrRanks >= 0) {
5385         gameInfo.boardHeight = appData.NrRanks;
5386     }
5387     if(appData.holdingsSize >= 0) {
5388         i = appData.holdingsSize;
5389         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5390         gameInfo.holdingsSize = i;
5391     }
5392     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5393     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5394         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5395
5396     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5397     if(pawnRow < 1) pawnRow = 1;
5398     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5399
5400     /* User pieceToChar list overrules defaults */
5401     if(appData.pieceToCharTable != NULL)
5402         SetCharTable(pieceToChar, appData.pieceToCharTable);
5403
5404     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5405
5406         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5407             s = (ChessSquare) 0; /* account holding counts in guard band */
5408         for( i=0; i<BOARD_HEIGHT; i++ )
5409             initialPosition[i][j] = s;
5410
5411         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5412         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5413         initialPosition[pawnRow][j] = WhitePawn;
5414         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5415         if(gameInfo.variant == VariantXiangqi) {
5416             if(j&1) {
5417                 initialPosition[pawnRow][j] = 
5418                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5419                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5420                    initialPosition[2][j] = WhiteCannon;
5421                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5422                 }
5423             }
5424         }
5425         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5426     }
5427     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5428
5429             j=BOARD_LEFT+1;
5430             initialPosition[1][j] = WhiteBishop;
5431             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5432             j=BOARD_RGHT-2;
5433             initialPosition[1][j] = WhiteRook;
5434             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5435     }
5436
5437     if( nrCastlingRights == -1) {
5438         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5439         /*       This sets default castling rights from none to normal corners   */
5440         /* Variants with other castling rights must set them themselves above    */
5441         nrCastlingRights = 6;
5442        
5443         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5444         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5445         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5446         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5447         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5448         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5449      }
5450
5451      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5452      if(gameInfo.variant == VariantGreat) { // promotion commoners
5453         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5454         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5455         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5456         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5457      }
5458   if (appData.debugMode) {
5459     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5460   }
5461     if(shuffleOpenings) {
5462         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5463         startedFromSetupPosition = TRUE;
5464     }
5465     if(startedFromPositionFile) {
5466       /* [HGM] loadPos: use PositionFile for every new game */
5467       CopyBoard(initialPosition, filePosition);
5468       for(i=0; i<nrCastlingRights; i++)
5469           initialRights[i] = filePosition[CASTLING][i];
5470       startedFromSetupPosition = TRUE;
5471     }
5472
5473     CopyBoard(boards[0], initialPosition);
5474
5475     if(oldx != gameInfo.boardWidth ||
5476        oldy != gameInfo.boardHeight ||
5477        oldh != gameInfo.holdingsWidth
5478 #ifdef GOTHIC
5479        || oldv == VariantGothic ||        // For licensing popups
5480        gameInfo.variant == VariantGothic
5481 #endif
5482 #ifdef FALCON
5483        || oldv == VariantFalcon ||
5484        gameInfo.variant == VariantFalcon
5485 #endif
5486                                          )
5487             InitDrawingSizes(-2 ,0);
5488
5489     if (redraw)
5490       DrawPosition(TRUE, boards[currentMove]);
5491 }
5492
5493 void
5494 SendBoard(cps, moveNum)
5495      ChessProgramState *cps;
5496      int moveNum;
5497 {
5498     char message[MSG_SIZ];
5499     
5500     if (cps->useSetboard) {
5501       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5502       sprintf(message, "setboard %s\n", fen);
5503       SendToProgram(message, cps);
5504       free(fen);
5505
5506     } else {
5507       ChessSquare *bp;
5508       int i, j;
5509       /* Kludge to set black to move, avoiding the troublesome and now
5510        * deprecated "black" command.
5511        */
5512       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5513
5514       SendToProgram("edit\n", cps);
5515       SendToProgram("#\n", cps);
5516       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5517         bp = &boards[moveNum][i][BOARD_LEFT];
5518         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5519           if ((int) *bp < (int) BlackPawn) {
5520             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5521                     AAA + j, ONE + i);
5522             if(message[0] == '+' || message[0] == '~') {
5523                 sprintf(message, "%c%c%c+\n",
5524                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5525                         AAA + j, ONE + i);
5526             }
5527             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5528                 message[1] = BOARD_RGHT   - 1 - j + '1';
5529                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5530             }
5531             SendToProgram(message, cps);
5532           }
5533         }
5534       }
5535     
5536       SendToProgram("c\n", cps);
5537       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5538         bp = &boards[moveNum][i][BOARD_LEFT];
5539         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5540           if (((int) *bp != (int) EmptySquare)
5541               && ((int) *bp >= (int) BlackPawn)) {
5542             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5543                     AAA + j, ONE + i);
5544             if(message[0] == '+' || message[0] == '~') {
5545                 sprintf(message, "%c%c%c+\n",
5546                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5547                         AAA + j, ONE + i);
5548             }
5549             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5550                 message[1] = BOARD_RGHT   - 1 - j + '1';
5551                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5552             }
5553             SendToProgram(message, cps);
5554           }
5555         }
5556       }
5557     
5558       SendToProgram(".\n", cps);
5559     }
5560     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5561 }
5562
5563 static int autoQueen; // [HGM] oneclick
5564
5565 int
5566 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5567 {
5568     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5569     /* [HGM] add Shogi promotions */
5570     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5571     ChessSquare piece;
5572     ChessMove moveType;
5573     Boolean premove;
5574
5575     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5576     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5577
5578     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5579       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5580         return FALSE;
5581
5582     piece = boards[currentMove][fromY][fromX];
5583     if(gameInfo.variant == VariantShogi) {
5584         promotionZoneSize = 3;
5585         highestPromotingPiece = (int)WhiteFerz;
5586     } else if(gameInfo.variant == VariantMakruk) {
5587         promotionZoneSize = 3;
5588     }
5589
5590     // next weed out all moves that do not touch the promotion zone at all
5591     if((int)piece >= BlackPawn) {
5592         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5593              return FALSE;
5594         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5595     } else {
5596         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5597            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5598     }
5599
5600     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5601
5602     // weed out mandatory Shogi promotions
5603     if(gameInfo.variant == VariantShogi) {
5604         if(piece >= BlackPawn) {
5605             if(toY == 0 && piece == BlackPawn ||
5606                toY == 0 && piece == BlackQueen ||
5607                toY <= 1 && piece == BlackKnight) {
5608                 *promoChoice = '+';
5609                 return FALSE;
5610             }
5611         } else {
5612             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5613                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5614                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5615                 *promoChoice = '+';
5616                 return FALSE;
5617             }
5618         }
5619     }
5620
5621     // weed out obviously illegal Pawn moves
5622     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5623         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5624         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5625         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5626         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5627         // note we are not allowed to test for valid (non-)capture, due to premove
5628     }
5629
5630     // we either have a choice what to promote to, or (in Shogi) whether to promote
5631     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5632         *promoChoice = PieceToChar(BlackFerz);  // no choice
5633         return FALSE;
5634     }
5635     if(autoQueen) { // predetermined
5636         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5637              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5638         else *promoChoice = PieceToChar(BlackQueen);
5639         return FALSE;
5640     }
5641
5642     // suppress promotion popup on illegal moves that are not premoves
5643     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5644               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5645     if(appData.testLegality && !premove) {
5646         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5647                         fromY, fromX, toY, toX, NULLCHAR);
5648         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5649            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5650             return FALSE;
5651     }
5652
5653     return TRUE;
5654 }
5655
5656 int
5657 InPalace(row, column)
5658      int row, column;
5659 {   /* [HGM] for Xiangqi */
5660     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5661          column < (BOARD_WIDTH + 4)/2 &&
5662          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5663     return FALSE;
5664 }
5665
5666 int
5667 PieceForSquare (x, y)
5668      int x;
5669      int y;
5670 {
5671   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5672      return -1;
5673   else
5674      return boards[currentMove][y][x];
5675 }
5676
5677 int
5678 OKToStartUserMove(x, y)
5679      int x, y;
5680 {
5681     ChessSquare from_piece;
5682     int white_piece;
5683
5684     if (matchMode) return FALSE;
5685     if (gameMode == EditPosition) return TRUE;
5686
5687     if (x >= 0 && y >= 0)
5688       from_piece = boards[currentMove][y][x];
5689     else
5690       from_piece = EmptySquare;
5691
5692     if (from_piece == EmptySquare) return FALSE;
5693
5694     white_piece = (int)from_piece >= (int)WhitePawn &&
5695       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5696
5697     switch (gameMode) {
5698       case PlayFromGameFile:
5699       case AnalyzeFile:
5700       case TwoMachinesPlay:
5701       case EndOfGame:
5702         return FALSE;
5703
5704       case IcsObserving:
5705       case IcsIdle:
5706         return FALSE;
5707
5708       case MachinePlaysWhite:
5709       case IcsPlayingBlack:
5710         if (appData.zippyPlay) return FALSE;
5711         if (white_piece) {
5712             DisplayMoveError(_("You are playing Black"));
5713             return FALSE;
5714         }
5715         break;
5716
5717       case MachinePlaysBlack:
5718       case IcsPlayingWhite:
5719         if (appData.zippyPlay) return FALSE;
5720         if (!white_piece) {
5721             DisplayMoveError(_("You are playing White"));
5722             return FALSE;
5723         }
5724         break;
5725
5726       case EditGame:
5727         if (!white_piece && WhiteOnMove(currentMove)) {
5728             DisplayMoveError(_("It is White's turn"));
5729             return FALSE;
5730         }           
5731         if (white_piece && !WhiteOnMove(currentMove)) {
5732             DisplayMoveError(_("It is Black's turn"));
5733             return FALSE;
5734         }           
5735         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5736             /* Editing correspondence game history */
5737             /* Could disallow this or prompt for confirmation */
5738             cmailOldMove = -1;
5739         }
5740         break;
5741
5742       case BeginningOfGame:
5743         if (appData.icsActive) return FALSE;
5744         if (!appData.noChessProgram) {
5745             if (!white_piece) {
5746                 DisplayMoveError(_("You are playing White"));
5747                 return FALSE;
5748             }
5749         }
5750         break;
5751         
5752       case Training:
5753         if (!white_piece && WhiteOnMove(currentMove)) {
5754             DisplayMoveError(_("It is White's turn"));
5755             return FALSE;
5756         }           
5757         if (white_piece && !WhiteOnMove(currentMove)) {
5758             DisplayMoveError(_("It is Black's turn"));
5759             return FALSE;
5760         }           
5761         break;
5762
5763       default:
5764       case IcsExamining:
5765         break;
5766     }
5767     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5768         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5769         && gameMode != AnalyzeFile && gameMode != Training) {
5770         DisplayMoveError(_("Displayed position is not current"));
5771         return FALSE;
5772     }
5773     return TRUE;
5774 }
5775
5776 Boolean
5777 OnlyMove(int *x, int *y, Boolean captures) {
5778     DisambiguateClosure cl;
5779     if (appData.zippyPlay) return FALSE;
5780     switch(gameMode) {
5781       case MachinePlaysBlack:
5782       case IcsPlayingWhite:
5783       case BeginningOfGame:
5784         if(!WhiteOnMove(currentMove)) return FALSE;
5785         break;
5786       case MachinePlaysWhite:
5787       case IcsPlayingBlack:
5788         if(WhiteOnMove(currentMove)) return FALSE;
5789         break;
5790       default:
5791         return FALSE;
5792     }
5793     cl.pieceIn = EmptySquare; 
5794     cl.rfIn = *y;
5795     cl.ffIn = *x;
5796     cl.rtIn = -1;
5797     cl.ftIn = -1;
5798     cl.promoCharIn = NULLCHAR;
5799     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5800     if( cl.kind == NormalMove ||
5801         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5802         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5803         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5804         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5805       fromX = cl.ff;
5806       fromY = cl.rf;
5807       *x = cl.ft;
5808       *y = cl.rt;
5809       return TRUE;
5810     }
5811     if(cl.kind != ImpossibleMove) return FALSE;
5812     cl.pieceIn = EmptySquare;
5813     cl.rfIn = -1;
5814     cl.ffIn = -1;
5815     cl.rtIn = *y;
5816     cl.ftIn = *x;
5817     cl.promoCharIn = NULLCHAR;
5818     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5819     if( cl.kind == NormalMove ||
5820         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5821         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5822         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5823         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5824       fromX = cl.ff;
5825       fromY = cl.rf;
5826       *x = cl.ft;
5827       *y = cl.rt;
5828       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5829       return TRUE;
5830     }
5831     return FALSE;
5832 }
5833
5834 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5835 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5836 int lastLoadGameUseList = FALSE;
5837 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5838 ChessMove lastLoadGameStart = (ChessMove) 0;
5839
5840 ChessMove
5841 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5842      int fromX, fromY, toX, toY;
5843      int promoChar;
5844      Boolean captureOwn;
5845 {
5846     ChessMove moveType;
5847     ChessSquare pdown, pup;
5848
5849     /* Check if the user is playing in turn.  This is complicated because we
5850        let the user "pick up" a piece before it is his turn.  So the piece he
5851        tried to pick up may have been captured by the time he puts it down!
5852        Therefore we use the color the user is supposed to be playing in this
5853        test, not the color of the piece that is currently on the starting
5854        square---except in EditGame mode, where the user is playing both
5855        sides; fortunately there the capture race can't happen.  (It can
5856        now happen in IcsExamining mode, but that's just too bad.  The user
5857        will get a somewhat confusing message in that case.)
5858        */
5859
5860     switch (gameMode) {
5861       case PlayFromGameFile:
5862       case AnalyzeFile:
5863       case TwoMachinesPlay:
5864       case EndOfGame:
5865       case IcsObserving:
5866       case IcsIdle:
5867         /* We switched into a game mode where moves are not accepted,
5868            perhaps while the mouse button was down. */
5869         return ImpossibleMove;
5870
5871       case MachinePlaysWhite:
5872         /* User is moving for Black */
5873         if (WhiteOnMove(currentMove)) {
5874             DisplayMoveError(_("It is White's turn"));
5875             return ImpossibleMove;
5876         }
5877         break;
5878
5879       case MachinePlaysBlack:
5880         /* User is moving for White */
5881         if (!WhiteOnMove(currentMove)) {
5882             DisplayMoveError(_("It is Black's turn"));
5883             return ImpossibleMove;
5884         }
5885         break;
5886
5887       case EditGame:
5888       case IcsExamining:
5889       case BeginningOfGame:
5890       case AnalyzeMode:
5891       case Training:
5892         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5893             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5894             /* User is moving for Black */
5895             if (WhiteOnMove(currentMove)) {
5896                 DisplayMoveError(_("It is White's turn"));
5897                 return ImpossibleMove;
5898             }
5899         } else {
5900             /* User is moving for White */
5901             if (!WhiteOnMove(currentMove)) {
5902                 DisplayMoveError(_("It is Black's turn"));
5903                 return ImpossibleMove;
5904             }
5905         }
5906         break;
5907
5908       case IcsPlayingBlack:
5909         /* User is moving for Black */
5910         if (WhiteOnMove(currentMove)) {
5911             if (!appData.premove) {
5912                 DisplayMoveError(_("It is White's turn"));
5913             } else if (toX >= 0 && toY >= 0) {
5914                 premoveToX = toX;
5915                 premoveToY = toY;
5916                 premoveFromX = fromX;
5917                 premoveFromY = fromY;
5918                 premovePromoChar = promoChar;
5919                 gotPremove = 1;
5920                 if (appData.debugMode) 
5921                     fprintf(debugFP, "Got premove: fromX %d,"
5922                             "fromY %d, toX %d, toY %d\n",
5923                             fromX, fromY, toX, toY);
5924             }
5925             return ImpossibleMove;
5926         }
5927         break;
5928
5929       case IcsPlayingWhite:
5930         /* User is moving for White */
5931         if (!WhiteOnMove(currentMove)) {
5932             if (!appData.premove) {
5933                 DisplayMoveError(_("It is Black's turn"));
5934             } else if (toX >= 0 && toY >= 0) {
5935                 premoveToX = toX;
5936                 premoveToY = toY;
5937                 premoveFromX = fromX;
5938                 premoveFromY = fromY;
5939                 premovePromoChar = promoChar;
5940                 gotPremove = 1;
5941                 if (appData.debugMode) 
5942                     fprintf(debugFP, "Got premove: fromX %d,"
5943                             "fromY %d, toX %d, toY %d\n",
5944                             fromX, fromY, toX, toY);
5945             }
5946             return ImpossibleMove;
5947         }
5948         break;
5949
5950       default:
5951         break;
5952
5953       case EditPosition:
5954         /* EditPosition, empty square, or different color piece;
5955            click-click move is possible */
5956         if (toX == -2 || toY == -2) {
5957             boards[0][fromY][fromX] = EmptySquare;
5958             return AmbiguousMove;
5959         } else if (toX >= 0 && toY >= 0) {
5960             boards[0][toY][toX] = boards[0][fromY][fromX];
5961             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5962                 if(boards[0][fromY][0] != EmptySquare) {
5963                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5964                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5965                 }
5966             } else
5967             if(fromX == BOARD_RGHT+1) {
5968                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5969                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5970                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5971                 }
5972             } else
5973             boards[0][fromY][fromX] = EmptySquare;
5974             return AmbiguousMove;
5975         }
5976         return ImpossibleMove;
5977     }
5978
5979     if(toX < 0 || toY < 0) return ImpossibleMove;
5980     pdown = boards[currentMove][fromY][fromX];
5981     pup = boards[currentMove][toY][toX];
5982
5983     /* [HGM] If move started in holdings, it means a drop */
5984     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5985          if( pup != EmptySquare ) return ImpossibleMove;
5986          if(appData.testLegality) {
5987              /* it would be more logical if LegalityTest() also figured out
5988               * which drops are legal. For now we forbid pawns on back rank.
5989               * Shogi is on its own here...
5990               */
5991              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5992                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5993                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5994          }
5995          return WhiteDrop; /* Not needed to specify white or black yet */
5996     }
5997
5998     /* [HGM] always test for legality, to get promotion info */
5999     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6000                                          fromY, fromX, toY, toX, promoChar);
6001     /* [HGM] but possibly ignore an IllegalMove result */
6002     if (appData.testLegality) {
6003         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6004             DisplayMoveError(_("Illegal move"));
6005             return ImpossibleMove;
6006         }
6007     }
6008
6009     return moveType;
6010     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
6011        function is made into one that returns an OK move type if FinishMove
6012        should be called. This to give the calling driver routine the
6013        opportunity to finish the userMove input with a promotion popup,
6014        without bothering the user with this for invalid or illegal moves */
6015
6016 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
6017 }
6018
6019 /* Common tail of UserMoveEvent and DropMenuEvent */
6020 int
6021 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6022      ChessMove moveType;
6023      int fromX, fromY, toX, toY;
6024      /*char*/int promoChar;
6025 {
6026     char *bookHit = 0;
6027
6028     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6029         // [HGM] superchess: suppress promotions to non-available piece
6030         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6031         if(WhiteOnMove(currentMove)) {
6032             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6033         } else {
6034             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6035         }
6036     }
6037
6038     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6039        move type in caller when we know the move is a legal promotion */
6040     if(moveType == NormalMove && promoChar)
6041         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6042
6043     /* [HGM] convert drag-and-drop piece drops to standard form */
6044     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6045          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6046            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6047                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6048            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6049            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6050            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6051            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6052          fromY = DROP_RANK;
6053     }
6054
6055     /* [HGM] <popupFix> The following if has been moved here from
6056        UserMoveEvent(). Because it seemed to belong here (why not allow
6057        piece drops in training games?), and because it can only be
6058        performed after it is known to what we promote. */
6059     if (gameMode == Training) {
6060       /* compare the move played on the board to the next move in the
6061        * game. If they match, display the move and the opponent's response. 
6062        * If they don't match, display an error message.
6063        */
6064       int saveAnimate;
6065       Board testBoard;
6066       CopyBoard(testBoard, boards[currentMove]);
6067       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6068
6069       if (CompareBoards(testBoard, boards[currentMove+1])) {
6070         ForwardInner(currentMove+1);
6071
6072         /* Autoplay the opponent's response.
6073          * if appData.animate was TRUE when Training mode was entered,
6074          * the response will be animated.
6075          */
6076         saveAnimate = appData.animate;
6077         appData.animate = animateTraining;
6078         ForwardInner(currentMove+1);
6079         appData.animate = saveAnimate;
6080
6081         /* check for the end of the game */
6082         if (currentMove >= forwardMostMove) {
6083           gameMode = PlayFromGameFile;
6084           ModeHighlight();
6085           SetTrainingModeOff();
6086           DisplayInformation(_("End of game"));
6087         }
6088       } else {
6089         DisplayError(_("Incorrect move"), 0);
6090       }
6091       return 1;
6092     }
6093
6094   /* Ok, now we know that the move is good, so we can kill
6095      the previous line in Analysis Mode */
6096   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6097                                 && currentMove < forwardMostMove) {
6098     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6099   }
6100
6101   /* If we need the chess program but it's dead, restart it */
6102   ResurrectChessProgram();
6103
6104   /* A user move restarts a paused game*/
6105   if (pausing)
6106     PauseEvent();
6107
6108   thinkOutput[0] = NULLCHAR;
6109
6110   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6111
6112   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6113
6114   if (gameMode == BeginningOfGame) {
6115     if (appData.noChessProgram) {
6116       gameMode = EditGame;
6117       SetGameInfo();
6118     } else {
6119       char buf[MSG_SIZ];
6120       gameMode = MachinePlaysBlack;
6121       StartClocks();
6122       SetGameInfo();
6123       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6124       DisplayTitle(buf);
6125       if (first.sendName) {
6126         sprintf(buf, "name %s\n", gameInfo.white);
6127         SendToProgram(buf, &first);
6128       }
6129       StartClocks();
6130     }
6131     ModeHighlight();
6132   }
6133
6134   /* Relay move to ICS or chess engine */
6135   if (appData.icsActive) {
6136     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6137         gameMode == IcsExamining) {
6138       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6139         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6140         SendToICS("draw ");
6141         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6142       }
6143       // also send plain move, in case ICS does not understand atomic claims
6144       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6145       ics_user_moved = 1;
6146     }
6147   } else {
6148     if (first.sendTime && (gameMode == BeginningOfGame ||
6149                            gameMode == MachinePlaysWhite ||
6150                            gameMode == MachinePlaysBlack)) {
6151       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6152     }
6153     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6154          // [HGM] book: if program might be playing, let it use book
6155         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6156         first.maybeThinking = TRUE;
6157     } else SendMoveToProgram(forwardMostMove-1, &first);
6158     if (currentMove == cmailOldMove + 1) {
6159       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6160     }
6161   }
6162
6163   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6164
6165   switch (gameMode) {
6166   case EditGame:
6167     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6168     case MT_NONE:
6169     case MT_CHECK:
6170       break;
6171     case MT_CHECKMATE:
6172     case MT_STAINMATE:
6173       if (WhiteOnMove(currentMove)) {
6174         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6175       } else {
6176         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6177       }
6178       break;
6179     case MT_STALEMATE:
6180       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6181       break;
6182     }
6183     break;
6184     
6185   case MachinePlaysBlack:
6186   case MachinePlaysWhite:
6187     /* disable certain menu options while machine is thinking */
6188     SetMachineThinkingEnables();
6189     break;
6190
6191   default:
6192     break;
6193   }
6194
6195   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6196         
6197   if(bookHit) { // [HGM] book: simulate book reply
6198         static char bookMove[MSG_SIZ]; // a bit generous?
6199
6200         programStats.nodes = programStats.depth = programStats.time = 
6201         programStats.score = programStats.got_only_move = 0;
6202         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6203
6204         strcpy(bookMove, "move ");
6205         strcat(bookMove, bookHit);
6206         HandleMachineMove(bookMove, &first);
6207   }
6208   return 1;
6209 }
6210
6211 void
6212 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6213      int fromX, fromY, toX, toY;
6214      int promoChar;
6215 {
6216     /* [HGM] This routine was added to allow calling of its two logical
6217        parts from other modules in the old way. Before, UserMoveEvent()
6218        automatically called FinishMove() if the move was OK, and returned
6219        otherwise. I separated the two, in order to make it possible to
6220        slip a promotion popup in between. But that it always needs two
6221        calls, to the first part, (now called UserMoveTest() ), and to
6222        FinishMove if the first part succeeded. Calls that do not need
6223        to do anything in between, can call this routine the old way. 
6224     */
6225     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6226 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6227     if(moveType == AmbiguousMove)
6228         DrawPosition(FALSE, boards[currentMove]);
6229     else if(moveType != ImpossibleMove && moveType != Comment)
6230         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6231 }
6232
6233 void
6234 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6235      Board board;
6236      int flags;
6237      ChessMove kind;
6238      int rf, ff, rt, ft;
6239      VOIDSTAR closure;
6240 {
6241     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6242     Markers *m = (Markers *) closure;
6243     if(rf == fromY && ff == fromX)
6244         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6245                          || kind == WhiteCapturesEnPassant
6246                          || kind == BlackCapturesEnPassant);
6247     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6248 }
6249
6250 void
6251 MarkTargetSquares(int clear)
6252 {
6253   int x, y;
6254   if(!appData.markers || !appData.highlightDragging || 
6255      !appData.testLegality || gameMode == EditPosition) return;
6256   if(clear) {
6257     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6258   } else {
6259     int capt = 0;
6260     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6261     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6262       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6263       if(capt)
6264       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6265     }
6266   }
6267   DrawPosition(TRUE, NULL);
6268 }
6269
6270 void LeftClick(ClickType clickType, int xPix, int yPix)
6271 {
6272     int x, y;
6273     Boolean saveAnimate;
6274     static int second = 0, promotionChoice = 0, dragging = 0;
6275     char promoChoice = NULLCHAR;
6276
6277     if(appData.seekGraph && appData.icsActive && loggedOn &&
6278         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6279         SeekGraphClick(clickType, xPix, yPix, 0);
6280         return;
6281     }
6282
6283     if (clickType == Press) ErrorPopDown();
6284     MarkTargetSquares(1);
6285
6286     x = EventToSquare(xPix, BOARD_WIDTH);
6287     y = EventToSquare(yPix, BOARD_HEIGHT);
6288     if (!flipView && y >= 0) {
6289         y = BOARD_HEIGHT - 1 - y;
6290     }
6291     if (flipView && x >= 0) {
6292         x = BOARD_WIDTH - 1 - x;
6293     }
6294
6295     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6296         if(clickType == Release) return; // ignore upclick of click-click destination
6297         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6298         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6299         if(gameInfo.holdingsWidth && 
6300                 (WhiteOnMove(currentMove) 
6301                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6302                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6303             // click in right holdings, for determining promotion piece
6304             ChessSquare p = boards[currentMove][y][x];
6305             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6306             if(p != EmptySquare) {
6307                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6308                 fromX = fromY = -1;
6309                 return;
6310             }
6311         }
6312         DrawPosition(FALSE, boards[currentMove]);
6313         return;
6314     }
6315
6316     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6317     if(clickType == Press
6318             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6319               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6320               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6321         return;
6322
6323     autoQueen = appData.alwaysPromoteToQueen;
6324
6325     if (fromX == -1) {
6326       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6327         if (clickType == Press) {
6328             /* First square */
6329             if (OKToStartUserMove(x, y)) {
6330                 fromX = x;
6331                 fromY = y;
6332                 second = 0;
6333                 MarkTargetSquares(0);
6334                 DragPieceBegin(xPix, yPix); dragging = 1;
6335                 if (appData.highlightDragging) {
6336                     SetHighlights(x, y, -1, -1);
6337                 }
6338             }
6339         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6340             DragPieceEnd(xPix, yPix); dragging = 0;
6341             DrawPosition(FALSE, NULL);
6342         }
6343         return;
6344       }
6345     }
6346
6347     /* fromX != -1 */
6348     if (clickType == Press && gameMode != EditPosition) {
6349         ChessSquare fromP;
6350         ChessSquare toP;
6351         int frc;
6352
6353         // ignore off-board to clicks
6354         if(y < 0 || x < 0) return;
6355
6356         /* Check if clicking again on the same color piece */
6357         fromP = boards[currentMove][fromY][fromX];
6358         toP = boards[currentMove][y][x];
6359         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6360         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6361              WhitePawn <= toP && toP <= WhiteKing &&
6362              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6363              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6364             (BlackPawn <= fromP && fromP <= BlackKing && 
6365              BlackPawn <= toP && toP <= BlackKing &&
6366              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6367              !(fromP == BlackKing && toP == BlackRook && frc))) {
6368             /* Clicked again on same color piece -- changed his mind */
6369             second = (x == fromX && y == fromY);
6370            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6371             if (appData.highlightDragging) {
6372                 SetHighlights(x, y, -1, -1);
6373             } else {
6374                 ClearHighlights();
6375             }
6376             if (OKToStartUserMove(x, y)) {
6377                 fromX = x;
6378                 fromY = y; dragging = 1;
6379                 MarkTargetSquares(0);
6380                 DragPieceBegin(xPix, yPix);
6381             }
6382             return;
6383            }
6384         }
6385         // ignore clicks on holdings
6386         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6387     }
6388
6389     if (clickType == Release && x == fromX && y == fromY) {
6390         DragPieceEnd(xPix, yPix); dragging = 0;
6391         if (appData.animateDragging) {
6392             /* Undo animation damage if any */
6393             DrawPosition(FALSE, NULL);
6394         }
6395         if (second) {
6396             /* Second up/down in same square; just abort move */
6397             second = 0;
6398             fromX = fromY = -1;
6399             ClearHighlights();
6400             gotPremove = 0;
6401             ClearPremoveHighlights();
6402         } else {
6403             /* First upclick in same square; start click-click mode */
6404             SetHighlights(x, y, -1, -1);
6405         }
6406         return;
6407     }
6408
6409     /* we now have a different from- and (possibly off-board) to-square */
6410     /* Completed move */
6411     toX = x;
6412     toY = y;
6413     saveAnimate = appData.animate;
6414     if (clickType == Press) {
6415         /* Finish clickclick move */
6416         if (appData.animate || appData.highlightLastMove) {
6417             SetHighlights(fromX, fromY, toX, toY);
6418         } else {
6419             ClearHighlights();
6420         }
6421     } else {
6422         /* Finish drag move */
6423         if (appData.highlightLastMove) {
6424             SetHighlights(fromX, fromY, toX, toY);
6425         } else {
6426             ClearHighlights();
6427         }
6428         DragPieceEnd(xPix, yPix); dragging = 0;
6429         /* Don't animate move and drag both */
6430         appData.animate = FALSE;
6431     }
6432
6433     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6434     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6435         ChessSquare piece = boards[currentMove][fromY][fromX];
6436         if(gameMode == EditPosition && piece != EmptySquare &&
6437            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6438             int n;
6439              
6440             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6441                 n = PieceToNumber(piece - (int)BlackPawn);
6442                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6443                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6444                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6445             } else
6446             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6447                 n = PieceToNumber(piece);
6448                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6449                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6450                 boards[currentMove][n][BOARD_WIDTH-2]++;
6451             }
6452             boards[currentMove][fromY][fromX] = EmptySquare;
6453         }
6454         ClearHighlights();
6455         fromX = fromY = -1;
6456         DrawPosition(TRUE, boards[currentMove]);
6457         return;
6458     }
6459
6460     // off-board moves should not be highlighted
6461     if(x < 0 || x < 0) ClearHighlights();
6462
6463     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6464         SetHighlights(fromX, fromY, toX, toY);
6465         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6466             // [HGM] super: promotion to captured piece selected from holdings
6467             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6468             promotionChoice = TRUE;
6469             // kludge follows to temporarily execute move on display, without promoting yet
6470             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6471             boards[currentMove][toY][toX] = p;
6472             DrawPosition(FALSE, boards[currentMove]);
6473             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6474             boards[currentMove][toY][toX] = q;
6475             DisplayMessage("Click in holdings to choose piece", "");
6476             return;
6477         }
6478         PromotionPopUp();
6479     } else {
6480         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6481         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6482         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6483         fromX = fromY = -1;
6484     }
6485     appData.animate = saveAnimate;
6486     if (appData.animate || appData.animateDragging) {
6487         /* Undo animation damage if needed */
6488         DrawPosition(FALSE, NULL);
6489     }
6490 }
6491
6492 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6493 {   // front-end-free part taken out of PieceMenuPopup
6494     int whichMenu; int xSqr, ySqr;
6495
6496     if(seekGraphUp) { // [HGM] seekgraph
6497         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6498         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6499         return -2;
6500     }
6501
6502     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6503          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6504         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6505         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6506         if(action == Press)   {
6507             originalFlip = flipView;
6508             flipView = !flipView; // temporarily flip board to see game from partners perspective
6509             DrawPosition(TRUE, partnerBoard);
6510             DisplayMessage(partnerStatus, "");
6511             partnerUp = TRUE;
6512         } else if(action == Release) {
6513             flipView = originalFlip;
6514             DrawPosition(TRUE, boards[currentMove]);
6515             partnerUp = FALSE;
6516         }
6517         return -2;
6518     }
6519
6520     xSqr = EventToSquare(x, BOARD_WIDTH);
6521     ySqr = EventToSquare(y, BOARD_HEIGHT);
6522     if (action == Release) UnLoadPV(); // [HGM] pv
6523     if (action != Press) return -2; // return code to be ignored
6524     switch (gameMode) {
6525       case IcsExamining:
6526         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6527       case EditPosition:
6528         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6529         if (xSqr < 0 || ySqr < 0) return -1;\r
6530         whichMenu = 0; // edit-position menu
6531         break;
6532       case IcsObserving:
6533         if(!appData.icsEngineAnalyze) return -1;
6534       case IcsPlayingWhite:
6535       case IcsPlayingBlack:
6536         if(!appData.zippyPlay) goto noZip;
6537       case AnalyzeMode:
6538       case AnalyzeFile:
6539       case MachinePlaysWhite:
6540       case MachinePlaysBlack:
6541       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6542         if (!appData.dropMenu) {
6543           LoadPV(x, y);
6544           return 2; // flag front-end to grab mouse events
6545         }
6546         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6547            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6548       case EditGame:
6549       noZip:
6550         if (xSqr < 0 || ySqr < 0) return -1;
6551         if (!appData.dropMenu || appData.testLegality &&
6552             gameInfo.variant != VariantBughouse &&
6553             gameInfo.variant != VariantCrazyhouse) return -1;
6554         whichMenu = 1; // drop menu
6555         break;
6556       default:
6557         return -1;
6558     }
6559
6560     if (((*fromX = xSqr) < 0) ||
6561         ((*fromY = ySqr) < 0)) {
6562         *fromX = *fromY = -1;
6563         return -1;
6564     }
6565     if (flipView)
6566       *fromX = BOARD_WIDTH - 1 - *fromX;
6567     else
6568       *fromY = BOARD_HEIGHT - 1 - *fromY;
6569
6570     return whichMenu;
6571 }
6572
6573 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6574 {
6575 //    char * hint = lastHint;
6576     FrontEndProgramStats stats;
6577
6578     stats.which = cps == &first ? 0 : 1;
6579     stats.depth = cpstats->depth;
6580     stats.nodes = cpstats->nodes;
6581     stats.score = cpstats->score;
6582     stats.time = cpstats->time;
6583     stats.pv = cpstats->movelist;
6584     stats.hint = lastHint;
6585     stats.an_move_index = 0;
6586     stats.an_move_count = 0;
6587
6588     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6589         stats.hint = cpstats->move_name;
6590         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6591         stats.an_move_count = cpstats->nr_moves;
6592     }
6593
6594     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6595
6596     SetProgramStats( &stats );
6597 }
6598
6599 void
6600 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6601 {       // count all piece types
6602         int p, f, r;
6603         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6604         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6605         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6606                 p = board[r][f];
6607                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6608                 if(p == BlackPawn && r == 0) (*bStale)++; else pCnt[p]++; // count last-Rank Pawns (XQ) separately
6609                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6610                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6611                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6612                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6613         }
6614 }
6615
6616 int
6617 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6618 {
6619         VariantClass v = gameInfo.variant;
6620
6621         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6622         if(v == VariantShatranj) return TRUE; // always winnable through baring
6623         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6624         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6625
6626         if(v == VariantXiangqi) {
6627                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6628
6629                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6630                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6631                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6632                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6633                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6634                 if(stale) // we have at least one last-rank P plus perhaps C
6635                     return majors // KPKX
6636                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6637                 else // KCA*E*
6638                     return pCnt[WhiteFerz+side] // KCAK
6639                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6640                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6641                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6642
6643         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6644                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6645                 
6646                 if(nMine == 1) return FALSE; // bare King
6647                 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
6648                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6649                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6650                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6651                 if(pCnt[WhiteKnight+side])
6652                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] + 
6653                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6654                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6655                 if(nBishops)
6656                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6657                 if(pCnt[WhiteAlfil+side])
6658                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6659                 if(pCnt[WhiteWazir+side])
6660                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6661         }
6662
6663         return TRUE;
6664 }
6665
6666 int
6667 Adjudicate(ChessProgramState *cps)
6668 {       // [HGM] some adjudications useful with buggy engines
6669         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6670         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6671         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6672         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6673         int k, count = 0; static int bare = 1;
6674         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6675         Boolean canAdjudicate = !appData.icsActive;
6676
6677         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6678         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6679             if( appData.testLegality )
6680             {   /* [HGM] Some more adjudications for obstinate engines */
6681                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6682                 static int moveCount = 6;
6683                 ChessMove result;
6684                 char *reason = NULL;
6685
6686                 /* Count what is on board. */
6687                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6688
6689                 /* Some material-based adjudications that have to be made before stalemate test */
6690                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6691                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6692                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6693                      if(canAdjudicate && appData.checkMates) {
6694                          if(engineOpponent)
6695                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6696                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6697                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6698                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6699                          return 1;
6700                      }
6701                 }
6702
6703                 /* Bare King in Shatranj (loses) or Losers (wins) */
6704                 if( nrW == 1 || nrB == 1) {
6705                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6706                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6707                      if(canAdjudicate && appData.checkMates) {
6708                          if(engineOpponent)
6709                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6710                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6711                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6712                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6713                          return 1;
6714                      }
6715                   } else
6716                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6717                   {    /* bare King */
6718                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6719                         if(canAdjudicate && appData.checkMates) {
6720                             /* but only adjudicate if adjudication enabled */
6721                             if(engineOpponent)
6722                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6723                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6724                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, 
6725                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6726                             return 1;
6727                         }
6728                   }
6729                 } else bare = 1;
6730
6731
6732             // don't wait for engine to announce game end if we can judge ourselves
6733             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6734               case MT_CHECK:
6735                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6736                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6737                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6738                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6739                             checkCnt++;
6740                         if(checkCnt >= 2) {
6741                             reason = "Xboard adjudication: 3rd check";
6742                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6743                             break;
6744                         }
6745                     }
6746                 }
6747               case MT_NONE:
6748               default:
6749                 break;
6750               case MT_STALEMATE:
6751               case MT_STAINMATE:
6752                 reason = "Xboard adjudication: Stalemate";
6753                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6754                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6755                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6756                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6757                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6758                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6759                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6760                                                                         EP_CHECKMATE : EP_WINS);
6761                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6762                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6763                 }
6764                 break;
6765               case MT_CHECKMATE:
6766                 reason = "Xboard adjudication: Checkmate";
6767                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6768                 break;
6769             }
6770
6771                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6772                     case EP_STALEMATE:
6773                         result = GameIsDrawn; break;
6774                     case EP_CHECKMATE:
6775                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6776                     case EP_WINS:
6777                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6778                     default:
6779                         result = (ChessMove) 0;
6780                 }
6781                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6782                     if(engineOpponent)
6783                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6784                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6785                     GameEnds( result, reason, GE_XBOARD );
6786                     return 1;
6787                 }
6788
6789                 /* Next absolutely insufficient mating material. */
6790                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6791                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6792                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6793
6794                      /* always flag draws, for judging claims */
6795                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6796
6797                      if(canAdjudicate && appData.materialDraws) {
6798                          /* but only adjudicate them if adjudication enabled */
6799                          if(engineOpponent) {
6800                            SendToProgram("force\n", engineOpponent); // suppress reply
6801                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6802                          }
6803                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6804                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6805                          return 1;
6806                      }
6807                 }
6808
6809                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6810                 if(nrW + nrB == 4 && 
6811                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6812                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6813                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6814                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6815                   ) ) {
6816                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6817                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6818                           if(engineOpponent) {
6819                             SendToProgram("force\n", engineOpponent); // suppress reply
6820                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6821                           }
6822                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6823                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6824                           return 1;
6825                      }
6826                 } else moveCount = 6;
6827             }
6828         }
6829           
6830         if (appData.debugMode) { int i;
6831             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6832                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6833                     appData.drawRepeats);
6834             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6835               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6836             
6837         }
6838
6839         // Repetition draws and 50-move rule can be applied independently of legality testing
6840
6841                 /* Check for rep-draws */
6842                 count = 0;
6843                 for(k = forwardMostMove-2;
6844                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6845                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6846                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6847                     k-=2)
6848                 {   int rights=0;
6849                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6850                         /* compare castling rights */
6851                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6852                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6853                                 rights++; /* King lost rights, while rook still had them */
6854                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6855                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6856                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6857                                    rights++; /* but at least one rook lost them */
6858                         }
6859                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6860                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6861                                 rights++; 
6862                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6863                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6864                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6865                                    rights++;
6866                         }
6867                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6868                             && appData.drawRepeats > 1) {
6869                              /* adjudicate after user-specified nr of repeats */
6870                              int result = GameIsDrawn;
6871                              char *details = "XBoard adjudication: repetition draw";
6872                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6873                                 // [HGM] xiangqi: check for forbidden perpetuals
6874                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6875                                 for(m=forwardMostMove; m>k; m-=2) {
6876                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6877                                         ourPerpetual = 0; // the current mover did not always check
6878                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6879                                         hisPerpetual = 0; // the opponent did not always check
6880                                 }
6881                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6882                                                                         ourPerpetual, hisPerpetual);
6883                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6884                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6885                                     details = "Xboard adjudication: perpetual checking";
6886                                 } else
6887                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6888                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6889                                 } else
6890                                 // Now check for perpetual chases
6891                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6892                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6893                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6894                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6895                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6896                                         details = "Xboard adjudication: perpetual chasing";
6897                                     } else
6898                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6899                                         break; // Abort repetition-checking loop.
6900                                 }
6901                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6902                              }
6903                              if(engineOpponent) {
6904                                SendToProgram("force\n", engineOpponent); // suppress reply
6905                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6906                              }
6907                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6908                              GameEnds( result, details, GE_XBOARD );
6909                              return 1;
6910                         }
6911                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6912                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6913                     }
6914                 }
6915
6916                 /* Now we test for 50-move draws. Determine ply count */
6917                 count = forwardMostMove;
6918                 /* look for last irreversble move */
6919                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6920                     count--;
6921                 /* if we hit starting position, add initial plies */
6922                 if( count == backwardMostMove )
6923                     count -= initialRulePlies;
6924                 count = forwardMostMove - count; 
6925                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6926                         // adjust reversible move counter for checks in Xiangqi
6927                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6928                         if(i < backwardMostMove) i = backwardMostMove;
6929                         while(i <= forwardMostMove) {
6930                                 lastCheck = inCheck; // check evasion does not count
6931                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6932                                 if(inCheck || lastCheck) count--; // check does not count
6933                                 i++;
6934                         }
6935                 }
6936                 if( count >= 100)
6937                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6938                          /* this is used to judge if draw claims are legal */
6939                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6940                          if(engineOpponent) {
6941                            SendToProgram("force\n", engineOpponent); // suppress reply
6942                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6943                          }
6944                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6945                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6946                          return 1;
6947                 }
6948
6949                 /* if draw offer is pending, treat it as a draw claim
6950                  * when draw condition present, to allow engines a way to
6951                  * claim draws before making their move to avoid a race
6952                  * condition occurring after their move
6953                  */
6954                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6955                          char *p = NULL;
6956                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6957                              p = "Draw claim: 50-move rule";
6958                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6959                              p = "Draw claim: 3-fold repetition";
6960                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6961                              p = "Draw claim: insufficient mating material";
6962                          if( p != NULL && canAdjudicate) {
6963                              if(engineOpponent) {
6964                                SendToProgram("force\n", engineOpponent); // suppress reply
6965                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6966                              }
6967                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6968                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6969                              return 1;
6970                          }
6971                 }
6972
6973                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6974                     if(engineOpponent) {
6975                       SendToProgram("force\n", engineOpponent); // suppress reply
6976                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6977                     }
6978                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6979                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6980                     return 1;
6981                 }
6982         return 0;
6983 }
6984
6985 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6986 {   // [HGM] book: this routine intercepts moves to simulate book replies
6987     char *bookHit = NULL;
6988
6989     //first determine if the incoming move brings opponent into his book
6990     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6991         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6992     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6993     if(bookHit != NULL && !cps->bookSuspend) {
6994         // make sure opponent is not going to reply after receiving move to book position
6995         SendToProgram("force\n", cps);
6996         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6997     }
6998     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6999     // now arrange restart after book miss
7000     if(bookHit) {
7001         // after a book hit we never send 'go', and the code after the call to this routine
7002         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7003         char buf[MSG_SIZ];
7004         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7005         SendToProgram(buf, cps);
7006         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7007     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7008         SendToProgram("go\n", cps);
7009         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7010     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7011         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7012             SendToProgram("go\n", cps); 
7013         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7014     }
7015     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7016 }
7017
7018 char *savedMessage;
7019 ChessProgramState *savedState;
7020 void DeferredBookMove(void)
7021 {
7022         if(savedState->lastPing != savedState->lastPong)
7023                     ScheduleDelayedEvent(DeferredBookMove, 10);
7024         else
7025         HandleMachineMove(savedMessage, savedState);
7026 }
7027
7028 void
7029 HandleMachineMove(message, cps)
7030      char *message;
7031      ChessProgramState *cps;
7032 {
7033     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7034     char realname[MSG_SIZ];
7035     int fromX, fromY, toX, toY;
7036     ChessMove moveType;
7037     char promoChar;
7038     char *p;
7039     int machineWhite;
7040     char *bookHit;
7041
7042     cps->userError = 0;
7043
7044 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7045     /*
7046      * Kludge to ignore BEL characters
7047      */
7048     while (*message == '\007') message++;
7049
7050     /*
7051      * [HGM] engine debug message: ignore lines starting with '#' character
7052      */
7053     if(cps->debug && *message == '#') return;
7054
7055     /*
7056      * Look for book output
7057      */
7058     if (cps == &first && bookRequested) {
7059         if (message[0] == '\t' || message[0] == ' ') {
7060             /* Part of the book output is here; append it */
7061             strcat(bookOutput, message);
7062             strcat(bookOutput, "  \n");
7063             return;
7064         } else if (bookOutput[0] != NULLCHAR) {
7065             /* All of book output has arrived; display it */
7066             char *p = bookOutput;
7067             while (*p != NULLCHAR) {
7068                 if (*p == '\t') *p = ' ';
7069                 p++;
7070             }
7071             DisplayInformation(bookOutput);
7072             bookRequested = FALSE;
7073             /* Fall through to parse the current output */
7074         }
7075     }
7076
7077     /*
7078      * Look for machine move.
7079      */
7080     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7081         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7082     {
7083         /* This method is only useful on engines that support ping */
7084         if (cps->lastPing != cps->lastPong) {
7085           if (gameMode == BeginningOfGame) {
7086             /* Extra move from before last new; ignore */
7087             if (appData.debugMode) {
7088                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7089             }
7090           } else {
7091             if (appData.debugMode) {
7092                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7093                         cps->which, gameMode);
7094             }
7095
7096             SendToProgram("undo\n", cps);
7097           }
7098           return;
7099         }
7100
7101         switch (gameMode) {
7102           case BeginningOfGame:
7103             /* Extra move from before last reset; ignore */
7104             if (appData.debugMode) {
7105                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7106             }
7107             return;
7108
7109           case EndOfGame:
7110           case IcsIdle:
7111           default:
7112             /* Extra move after we tried to stop.  The mode test is
7113                not a reliable way of detecting this problem, but it's
7114                the best we can do on engines that don't support ping.
7115             */
7116             if (appData.debugMode) {
7117                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7118                         cps->which, gameMode);
7119             }
7120             SendToProgram("undo\n", cps);
7121             return;
7122
7123           case MachinePlaysWhite:
7124           case IcsPlayingWhite:
7125             machineWhite = TRUE;
7126             break;
7127
7128           case MachinePlaysBlack:
7129           case IcsPlayingBlack:
7130             machineWhite = FALSE;
7131             break;
7132
7133           case TwoMachinesPlay:
7134             machineWhite = (cps->twoMachinesColor[0] == 'w');
7135             break;
7136         }
7137         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7138             if (appData.debugMode) {
7139                 fprintf(debugFP,
7140                         "Ignoring move out of turn by %s, gameMode %d"
7141                         ", forwardMost %d\n",
7142                         cps->which, gameMode, forwardMostMove);
7143             }
7144             return;
7145         }
7146
7147     if (appData.debugMode) { int f = forwardMostMove;
7148         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7149                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7150                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7151     }
7152         if(cps->alphaRank) AlphaRank(machineMove, 4);
7153         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7154                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7155             /* Machine move could not be parsed; ignore it. */
7156             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7157                     machineMove, cps->which);
7158             DisplayError(buf1, 0);
7159             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7160                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7161             if (gameMode == TwoMachinesPlay) {
7162               GameEnds(machineWhite ? BlackWins : WhiteWins,
7163                        buf1, GE_XBOARD);
7164             }
7165             return;
7166         }
7167
7168         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7169         /* So we have to redo legality test with true e.p. status here,  */
7170         /* to make sure an illegal e.p. capture does not slip through,   */
7171         /* to cause a forfeit on a justified illegal-move complaint      */
7172         /* of the opponent.                                              */
7173         if( gameMode==TwoMachinesPlay && appData.testLegality
7174             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7175                                                               ) {
7176            ChessMove moveType;
7177            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7178                              fromY, fromX, toY, toX, promoChar);
7179             if (appData.debugMode) {
7180                 int i;
7181                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7182                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7183                 fprintf(debugFP, "castling rights\n");
7184             }
7185             if(moveType == IllegalMove) {
7186                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7187                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7188                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7189                            buf1, GE_XBOARD);
7190                 return;
7191            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7192            /* [HGM] Kludge to handle engines that send FRC-style castling
7193               when they shouldn't (like TSCP-Gothic) */
7194            switch(moveType) {
7195              case WhiteASideCastleFR:
7196              case BlackASideCastleFR:
7197                toX+=2;
7198                currentMoveString[2]++;
7199                break;
7200              case WhiteHSideCastleFR:
7201              case BlackHSideCastleFR:
7202                toX--;
7203                currentMoveString[2]--;
7204                break;
7205              default: ; // nothing to do, but suppresses warning of pedantic compilers
7206            }
7207         }
7208         hintRequested = FALSE;
7209         lastHint[0] = NULLCHAR;
7210         bookRequested = FALSE;
7211         /* Program may be pondering now */
7212         cps->maybeThinking = TRUE;
7213         if (cps->sendTime == 2) cps->sendTime = 1;
7214         if (cps->offeredDraw) cps->offeredDraw--;
7215
7216         /* currentMoveString is set as a side-effect of ParseOneMove */
7217         strcpy(machineMove, currentMoveString);
7218         strcat(machineMove, "\n");
7219         strcpy(moveList[forwardMostMove], machineMove);
7220
7221         /* [AS] Save move info*/
7222         pvInfoList[ forwardMostMove ].score = programStats.score;
7223         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7224         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7225
7226         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7227
7228         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7229         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7230             int count = 0;
7231
7232             while( count < adjudicateLossPlies ) {
7233                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7234
7235                 if( count & 1 ) {
7236                     score = -score; /* Flip score for winning side */
7237                 }
7238
7239                 if( score > adjudicateLossThreshold ) {
7240                     break;
7241                 }
7242
7243                 count++;
7244             }
7245
7246             if( count >= adjudicateLossPlies ) {
7247                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7248
7249                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7250                     "Xboard adjudication", 
7251                     GE_XBOARD );
7252
7253                 return;
7254             }
7255         }
7256
7257         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7258
7259 #if ZIPPY
7260         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7261             first.initDone) {
7262           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7263                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7264                 SendToICS("draw ");
7265                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7266           }
7267           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7268           ics_user_moved = 1;
7269           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7270                 char buf[3*MSG_SIZ];
7271
7272                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7273                         programStats.score / 100.,
7274                         programStats.depth,
7275                         programStats.time / 100.,
7276                         (unsigned int)programStats.nodes,
7277                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7278                         programStats.movelist);
7279                 SendToICS(buf);
7280 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7281           }
7282         }
7283 #endif
7284
7285         /* [AS] Clear stats for next move */
7286         ClearProgramStats();
7287         thinkOutput[0] = NULLCHAR;
7288         hiddenThinkOutputState = 0;
7289
7290         bookHit = NULL;
7291         if (gameMode == TwoMachinesPlay) {
7292             /* [HGM] relaying draw offers moved to after reception of move */
7293             /* and interpreting offer as claim if it brings draw condition */
7294             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7295                 SendToProgram("draw\n", cps->other);
7296             }
7297             if (cps->other->sendTime) {
7298                 SendTimeRemaining(cps->other,
7299                                   cps->other->twoMachinesColor[0] == 'w');
7300             }
7301             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7302             if (firstMove && !bookHit) {
7303                 firstMove = FALSE;
7304                 if (cps->other->useColors) {
7305                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7306                 }
7307                 SendToProgram("go\n", cps->other);
7308             }
7309             cps->other->maybeThinking = TRUE;
7310         }
7311
7312         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7313         
7314         if (!pausing && appData.ringBellAfterMoves) {
7315             RingBell();
7316         }
7317
7318         /* 
7319          * Reenable menu items that were disabled while
7320          * machine was thinking
7321          */
7322         if (gameMode != TwoMachinesPlay)
7323             SetUserThinkingEnables();
7324
7325         // [HGM] book: after book hit opponent has received move and is now in force mode
7326         // force the book reply into it, and then fake that it outputted this move by jumping
7327         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7328         if(bookHit) {
7329                 static char bookMove[MSG_SIZ]; // a bit generous?
7330
7331                 strcpy(bookMove, "move ");
7332                 strcat(bookMove, bookHit);
7333                 message = bookMove;
7334                 cps = cps->other;
7335                 programStats.nodes = programStats.depth = programStats.time = 
7336                 programStats.score = programStats.got_only_move = 0;
7337                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7338
7339                 if(cps->lastPing != cps->lastPong) {
7340                     savedMessage = message; // args for deferred call
7341                     savedState = cps;
7342                     ScheduleDelayedEvent(DeferredBookMove, 10);
7343                     return;
7344                 }
7345                 goto FakeBookMove;
7346         }
7347
7348         return;
7349     }
7350
7351     /* Set special modes for chess engines.  Later something general
7352      *  could be added here; for now there is just one kludge feature,
7353      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7354      *  when "xboard" is given as an interactive command.
7355      */
7356     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7357         cps->useSigint = FALSE;
7358         cps->useSigterm = FALSE;
7359     }
7360     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7361       ParseFeatures(message+8, cps);
7362       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7363     }
7364
7365     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7366      * want this, I was asked to put it in, and obliged.
7367      */
7368     if (!strncmp(message, "setboard ", 9)) {
7369         Board initial_position;
7370
7371         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7372
7373         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7374             DisplayError(_("Bad FEN received from engine"), 0);
7375             return ;
7376         } else {
7377            Reset(TRUE, FALSE);
7378            CopyBoard(boards[0], initial_position);
7379            initialRulePlies = FENrulePlies;
7380            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7381            else gameMode = MachinePlaysBlack;                 
7382            DrawPosition(FALSE, boards[currentMove]);
7383         }
7384         return;
7385     }
7386
7387     /*
7388      * Look for communication commands
7389      */
7390     if (!strncmp(message, "telluser ", 9)) {
7391         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7392         DisplayNote(message + 9);
7393         return;
7394     }
7395     if (!strncmp(message, "tellusererror ", 14)) {
7396         cps->userError = 1;
7397         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7398         DisplayError(message + 14, 0);
7399         return;
7400     }
7401     if (!strncmp(message, "tellopponent ", 13)) {
7402       if (appData.icsActive) {
7403         if (loggedOn) {
7404           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7405           SendToICS(buf1);
7406         }
7407       } else {
7408         DisplayNote(message + 13);
7409       }
7410       return;
7411     }
7412     if (!strncmp(message, "tellothers ", 11)) {
7413       if (appData.icsActive) {
7414         if (loggedOn) {
7415           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7416           SendToICS(buf1);
7417         }
7418       }
7419       return;
7420     }
7421     if (!strncmp(message, "tellall ", 8)) {
7422       if (appData.icsActive) {
7423         if (loggedOn) {
7424           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7425           SendToICS(buf1);
7426         }
7427       } else {
7428         DisplayNote(message + 8);
7429       }
7430       return;
7431     }
7432     if (strncmp(message, "warning", 7) == 0) {
7433         /* Undocumented feature, use tellusererror in new code */
7434         DisplayError(message, 0);
7435         return;
7436     }
7437     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7438         strcpy(realname, cps->tidy);
7439         strcat(realname, " query");
7440         AskQuestion(realname, buf2, buf1, cps->pr);
7441         return;
7442     }
7443     /* Commands from the engine directly to ICS.  We don't allow these to be 
7444      *  sent until we are logged on. Crafty kibitzes have been known to 
7445      *  interfere with the login process.
7446      */
7447     if (loggedOn) {
7448         if (!strncmp(message, "tellics ", 8)) {
7449             SendToICS(message + 8);
7450             SendToICS("\n");
7451             return;
7452         }
7453         if (!strncmp(message, "tellicsnoalias ", 15)) {
7454             SendToICS(ics_prefix);
7455             SendToICS(message + 15);
7456             SendToICS("\n");
7457             return;
7458         }
7459         /* The following are for backward compatibility only */
7460         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7461             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7462             SendToICS(ics_prefix);
7463             SendToICS(message);
7464             SendToICS("\n");
7465             return;
7466         }
7467     }
7468     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7469         return;
7470     }
7471     /*
7472      * If the move is illegal, cancel it and redraw the board.
7473      * Also deal with other error cases.  Matching is rather loose
7474      * here to accommodate engines written before the spec.
7475      */
7476     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7477         strncmp(message, "Error", 5) == 0) {
7478         if (StrStr(message, "name") || 
7479             StrStr(message, "rating") || StrStr(message, "?") ||
7480             StrStr(message, "result") || StrStr(message, "board") ||
7481             StrStr(message, "bk") || StrStr(message, "computer") ||
7482             StrStr(message, "variant") || StrStr(message, "hint") ||
7483             StrStr(message, "random") || StrStr(message, "depth") ||
7484             StrStr(message, "accepted")) {
7485             return;
7486         }
7487         if (StrStr(message, "protover")) {
7488           /* Program is responding to input, so it's apparently done
7489              initializing, and this error message indicates it is
7490              protocol version 1.  So we don't need to wait any longer
7491              for it to initialize and send feature commands. */
7492           FeatureDone(cps, 1);
7493           cps->protocolVersion = 1;
7494           return;
7495         }
7496         cps->maybeThinking = FALSE;
7497
7498         if (StrStr(message, "draw")) {
7499             /* Program doesn't have "draw" command */
7500             cps->sendDrawOffers = 0;
7501             return;
7502         }
7503         if (cps->sendTime != 1 &&
7504             (StrStr(message, "time") || StrStr(message, "otim"))) {
7505           /* Program apparently doesn't have "time" or "otim" command */
7506           cps->sendTime = 0;
7507           return;
7508         }
7509         if (StrStr(message, "analyze")) {
7510             cps->analysisSupport = FALSE;
7511             cps->analyzing = FALSE;
7512             Reset(FALSE, TRUE);
7513             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7514             DisplayError(buf2, 0);
7515             return;
7516         }
7517         if (StrStr(message, "(no matching move)st")) {
7518           /* Special kludge for GNU Chess 4 only */
7519           cps->stKludge = TRUE;
7520           SendTimeControl(cps, movesPerSession, timeControl,
7521                           timeIncrement, appData.searchDepth,
7522                           searchTime);
7523           return;
7524         }
7525         if (StrStr(message, "(no matching move)sd")) {
7526           /* Special kludge for GNU Chess 4 only */
7527           cps->sdKludge = TRUE;
7528           SendTimeControl(cps, movesPerSession, timeControl,
7529                           timeIncrement, appData.searchDepth,
7530                           searchTime);
7531           return;
7532         }
7533         if (!StrStr(message, "llegal")) {
7534             return;
7535         }
7536         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7537             gameMode == IcsIdle) return;
7538         if (forwardMostMove <= backwardMostMove) return;
7539         if (pausing) PauseEvent();
7540       if(appData.forceIllegal) {
7541             // [HGM] illegal: machine refused move; force position after move into it
7542           SendToProgram("force\n", cps);
7543           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7544                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7545                 // when black is to move, while there might be nothing on a2 or black
7546                 // might already have the move. So send the board as if white has the move.
7547                 // But first we must change the stm of the engine, as it refused the last move
7548                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7549                 if(WhiteOnMove(forwardMostMove)) {
7550                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7551                     SendBoard(cps, forwardMostMove); // kludgeless board
7552                 } else {
7553                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7554                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7555                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7556                 }
7557           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7558             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7559                  gameMode == TwoMachinesPlay)
7560               SendToProgram("go\n", cps);
7561             return;
7562       } else
7563         if (gameMode == PlayFromGameFile) {
7564             /* Stop reading this game file */
7565             gameMode = EditGame;
7566             ModeHighlight();
7567         }
7568         currentMove = forwardMostMove-1;
7569         DisplayMove(currentMove-1); /* before DisplayMoveError */
7570         SwitchClocks(forwardMostMove-1); // [HGM] race
7571         DisplayBothClocks();
7572         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7573                 parseList[currentMove], cps->which);
7574         DisplayMoveError(buf1);
7575         DrawPosition(FALSE, boards[currentMove]);
7576
7577         /* [HGM] illegal-move claim should forfeit game when Xboard */
7578         /* only passes fully legal moves                            */
7579         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7580             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7581                                 "False illegal-move claim", GE_XBOARD );
7582         }
7583         return;
7584     }
7585     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7586         /* Program has a broken "time" command that
7587            outputs a string not ending in newline.
7588            Don't use it. */
7589         cps->sendTime = 0;
7590     }
7591     
7592     /*
7593      * If chess program startup fails, exit with an error message.
7594      * Attempts to recover here are futile.
7595      */
7596     if ((StrStr(message, "unknown host") != NULL)
7597         || (StrStr(message, "No remote directory") != NULL)
7598         || (StrStr(message, "not found") != NULL)
7599         || (StrStr(message, "No such file") != NULL)
7600         || (StrStr(message, "can't alloc") != NULL)
7601         || (StrStr(message, "Permission denied") != NULL)) {
7602
7603         cps->maybeThinking = FALSE;
7604         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7605                 cps->which, cps->program, cps->host, message);
7606         RemoveInputSource(cps->isr);
7607         DisplayFatalError(buf1, 0, 1);
7608         return;
7609     }
7610     
7611     /* 
7612      * Look for hint output
7613      */
7614     if (sscanf(message, "Hint: %s", buf1) == 1) {
7615         if (cps == &first && hintRequested) {
7616             hintRequested = FALSE;
7617             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7618                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7619                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7620                                     PosFlags(forwardMostMove),
7621                                     fromY, fromX, toY, toX, promoChar, buf1);
7622                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7623                 DisplayInformation(buf2);
7624             } else {
7625                 /* Hint move could not be parsed!? */
7626               snprintf(buf2, sizeof(buf2),
7627                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7628                         buf1, cps->which);
7629                 DisplayError(buf2, 0);
7630             }
7631         } else {
7632             strcpy(lastHint, buf1);
7633         }
7634         return;
7635     }
7636
7637     /*
7638      * Ignore other messages if game is not in progress
7639      */
7640     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7641         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7642
7643     /*
7644      * look for win, lose, draw, or draw offer
7645      */
7646     if (strncmp(message, "1-0", 3) == 0) {
7647         char *p, *q, *r = "";
7648         p = strchr(message, '{');
7649         if (p) {
7650             q = strchr(p, '}');
7651             if (q) {
7652                 *q = NULLCHAR;
7653                 r = p + 1;
7654             }
7655         }
7656         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7657         return;
7658     } else if (strncmp(message, "0-1", 3) == 0) {
7659         char *p, *q, *r = "";
7660         p = strchr(message, '{');
7661         if (p) {
7662             q = strchr(p, '}');
7663             if (q) {
7664                 *q = NULLCHAR;
7665                 r = p + 1;
7666             }
7667         }
7668         /* Kludge for Arasan 4.1 bug */
7669         if (strcmp(r, "Black resigns") == 0) {
7670             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7671             return;
7672         }
7673         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7674         return;
7675     } else if (strncmp(message, "1/2", 3) == 0) {
7676         char *p, *q, *r = "";
7677         p = strchr(message, '{');
7678         if (p) {
7679             q = strchr(p, '}');
7680             if (q) {
7681                 *q = NULLCHAR;
7682                 r = p + 1;
7683             }
7684         }
7685             
7686         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7687         return;
7688
7689     } else if (strncmp(message, "White resign", 12) == 0) {
7690         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7691         return;
7692     } else if (strncmp(message, "Black resign", 12) == 0) {
7693         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7694         return;
7695     } else if (strncmp(message, "White matches", 13) == 0 ||
7696                strncmp(message, "Black matches", 13) == 0   ) {
7697         /* [HGM] ignore GNUShogi noises */
7698         return;
7699     } else if (strncmp(message, "White", 5) == 0 &&
7700                message[5] != '(' &&
7701                StrStr(message, "Black") == NULL) {
7702         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7703         return;
7704     } else if (strncmp(message, "Black", 5) == 0 &&
7705                message[5] != '(') {
7706         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7707         return;
7708     } else if (strcmp(message, "resign") == 0 ||
7709                strcmp(message, "computer resigns") == 0) {
7710         switch (gameMode) {
7711           case MachinePlaysBlack:
7712           case IcsPlayingBlack:
7713             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7714             break;
7715           case MachinePlaysWhite:
7716           case IcsPlayingWhite:
7717             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7718             break;
7719           case TwoMachinesPlay:
7720             if (cps->twoMachinesColor[0] == 'w')
7721               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7722             else
7723               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7724             break;
7725           default:
7726             /* can't happen */
7727             break;
7728         }
7729         return;
7730     } else if (strncmp(message, "opponent mates", 14) == 0) {
7731         switch (gameMode) {
7732           case MachinePlaysBlack:
7733           case IcsPlayingBlack:
7734             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7735             break;
7736           case MachinePlaysWhite:
7737           case IcsPlayingWhite:
7738             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7739             break;
7740           case TwoMachinesPlay:
7741             if (cps->twoMachinesColor[0] == 'w')
7742               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7743             else
7744               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7745             break;
7746           default:
7747             /* can't happen */
7748             break;
7749         }
7750         return;
7751     } else if (strncmp(message, "computer mates", 14) == 0) {
7752         switch (gameMode) {
7753           case MachinePlaysBlack:
7754           case IcsPlayingBlack:
7755             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7756             break;
7757           case MachinePlaysWhite:
7758           case IcsPlayingWhite:
7759             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7760             break;
7761           case TwoMachinesPlay:
7762             if (cps->twoMachinesColor[0] == 'w')
7763               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7764             else
7765               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7766             break;
7767           default:
7768             /* can't happen */
7769             break;
7770         }
7771         return;
7772     } else if (strncmp(message, "checkmate", 9) == 0) {
7773         if (WhiteOnMove(forwardMostMove)) {
7774             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7775         } else {
7776             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7777         }
7778         return;
7779     } else if (strstr(message, "Draw") != NULL ||
7780                strstr(message, "game is a draw") != NULL) {
7781         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7782         return;
7783     } else if (strstr(message, "offer") != NULL &&
7784                strstr(message, "draw") != NULL) {
7785 #if ZIPPY
7786         if (appData.zippyPlay && first.initDone) {
7787             /* Relay offer to ICS */
7788             SendToICS(ics_prefix);
7789             SendToICS("draw\n");
7790         }
7791 #endif
7792         cps->offeredDraw = 2; /* valid until this engine moves twice */
7793         if (gameMode == TwoMachinesPlay) {
7794             if (cps->other->offeredDraw) {
7795                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7796             /* [HGM] in two-machine mode we delay relaying draw offer      */
7797             /* until after we also have move, to see if it is really claim */
7798             }
7799         } else if (gameMode == MachinePlaysWhite ||
7800                    gameMode == MachinePlaysBlack) {
7801           if (userOfferedDraw) {
7802             DisplayInformation(_("Machine accepts your draw offer"));
7803             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7804           } else {
7805             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7806           }
7807         }
7808     }
7809
7810     
7811     /*
7812      * Look for thinking output
7813      */
7814     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7815           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7816                                 ) {
7817         int plylev, mvleft, mvtot, curscore, time;
7818         char mvname[MOVE_LEN];
7819         u64 nodes; // [DM]
7820         char plyext;
7821         int ignore = FALSE;
7822         int prefixHint = FALSE;
7823         mvname[0] = NULLCHAR;
7824
7825         switch (gameMode) {
7826           case MachinePlaysBlack:
7827           case IcsPlayingBlack:
7828             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7829             break;
7830           case MachinePlaysWhite:
7831           case IcsPlayingWhite:
7832             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7833             break;
7834           case AnalyzeMode:
7835           case AnalyzeFile:
7836             break;
7837           case IcsObserving: /* [DM] icsEngineAnalyze */
7838             if (!appData.icsEngineAnalyze) ignore = TRUE;
7839             break;
7840           case TwoMachinesPlay:
7841             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7842                 ignore = TRUE;
7843             }
7844             break;
7845           default:
7846             ignore = TRUE;
7847             break;
7848         }
7849
7850         if (!ignore) {
7851             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7852             buf1[0] = NULLCHAR;
7853             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7854                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7855
7856                 if (plyext != ' ' && plyext != '\t') {
7857                     time *= 100;
7858                 }
7859
7860                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7861                 if( cps->scoreIsAbsolute && 
7862                     ( gameMode == MachinePlaysBlack ||
7863                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7864                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7865                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7866                      !WhiteOnMove(currentMove)
7867                     ) )
7868                 {
7869                     curscore = -curscore;
7870                 }
7871
7872
7873                 tempStats.depth = plylev;
7874                 tempStats.nodes = nodes;
7875                 tempStats.time = time;
7876                 tempStats.score = curscore;
7877                 tempStats.got_only_move = 0;
7878
7879                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7880                         int ticklen;
7881
7882                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7883                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7884                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7885                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7886                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7887                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7888                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7889                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7890                 }
7891
7892                 /* Buffer overflow protection */
7893                 if (buf1[0] != NULLCHAR) {
7894                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7895                         && appData.debugMode) {
7896                         fprintf(debugFP,
7897                                 "PV is too long; using the first %u bytes.\n",
7898                                 (unsigned) sizeof(tempStats.movelist) - 1);
7899                     }
7900
7901                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7902                 } else {
7903                     sprintf(tempStats.movelist, " no PV\n");
7904                 }
7905
7906                 if (tempStats.seen_stat) {
7907                     tempStats.ok_to_send = 1;
7908                 }
7909
7910                 if (strchr(tempStats.movelist, '(') != NULL) {
7911                     tempStats.line_is_book = 1;
7912                     tempStats.nr_moves = 0;
7913                     tempStats.moves_left = 0;
7914                 } else {
7915                     tempStats.line_is_book = 0;
7916                 }
7917
7918                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7919                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7920
7921                 SendProgramStatsToFrontend( cps, &tempStats );
7922
7923                 /* 
7924                     [AS] Protect the thinkOutput buffer from overflow... this
7925                     is only useful if buf1 hasn't overflowed first!
7926                 */
7927                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7928                         plylev, 
7929                         (gameMode == TwoMachinesPlay ?
7930                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7931                         ((double) curscore) / 100.0,
7932                         prefixHint ? lastHint : "",
7933                         prefixHint ? " " : "" );
7934
7935                 if( buf1[0] != NULLCHAR ) {
7936                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7937
7938                     if( strlen(buf1) > max_len ) {
7939                         if( appData.debugMode) {
7940                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7941                         }
7942                         buf1[max_len+1] = '\0';
7943                     }
7944
7945                     strcat( thinkOutput, buf1 );
7946                 }
7947
7948                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7949                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7950                     DisplayMove(currentMove - 1);
7951                 }
7952                 return;
7953
7954             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7955                 /* crafty (9.25+) says "(only move) <move>"
7956                  * if there is only 1 legal move
7957                  */
7958                 sscanf(p, "(only move) %s", buf1);
7959                 sprintf(thinkOutput, "%s (only move)", buf1);
7960                 sprintf(programStats.movelist, "%s (only move)", buf1);
7961                 programStats.depth = 1;
7962                 programStats.nr_moves = 1;
7963                 programStats.moves_left = 1;
7964                 programStats.nodes = 1;
7965                 programStats.time = 1;
7966                 programStats.got_only_move = 1;
7967
7968                 /* Not really, but we also use this member to
7969                    mean "line isn't going to change" (Crafty
7970                    isn't searching, so stats won't change) */
7971                 programStats.line_is_book = 1;
7972
7973                 SendProgramStatsToFrontend( cps, &programStats );
7974                 
7975                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7976                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7977                     DisplayMove(currentMove - 1);
7978                 }
7979                 return;
7980             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7981                               &time, &nodes, &plylev, &mvleft,
7982                               &mvtot, mvname) >= 5) {
7983                 /* The stat01: line is from Crafty (9.29+) in response
7984                    to the "." command */
7985                 programStats.seen_stat = 1;
7986                 cps->maybeThinking = TRUE;
7987
7988                 if (programStats.got_only_move || !appData.periodicUpdates)
7989                   return;
7990
7991                 programStats.depth = plylev;
7992                 programStats.time = time;
7993                 programStats.nodes = nodes;
7994                 programStats.moves_left = mvleft;
7995                 programStats.nr_moves = mvtot;
7996                 strcpy(programStats.move_name, mvname);
7997                 programStats.ok_to_send = 1;
7998                 programStats.movelist[0] = '\0';
7999
8000                 SendProgramStatsToFrontend( cps, &programStats );
8001
8002                 return;
8003
8004             } else if (strncmp(message,"++",2) == 0) {
8005                 /* Crafty 9.29+ outputs this */
8006                 programStats.got_fail = 2;
8007                 return;
8008
8009             } else if (strncmp(message,"--",2) == 0) {
8010                 /* Crafty 9.29+ outputs this */
8011                 programStats.got_fail = 1;
8012                 return;
8013
8014             } else if (thinkOutput[0] != NULLCHAR &&
8015                        strncmp(message, "    ", 4) == 0) {
8016                 unsigned message_len;
8017
8018                 p = message;
8019                 while (*p && *p == ' ') p++;
8020
8021                 message_len = strlen( p );
8022
8023                 /* [AS] Avoid buffer overflow */
8024                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8025                     strcat(thinkOutput, " ");
8026                     strcat(thinkOutput, p);
8027                 }
8028
8029                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8030                     strcat(programStats.movelist, " ");
8031                     strcat(programStats.movelist, p);
8032                 }
8033
8034                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8035                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8036                     DisplayMove(currentMove - 1);
8037                 }
8038                 return;
8039             }
8040         }
8041         else {
8042             buf1[0] = NULLCHAR;
8043
8044             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8045                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8046             {
8047                 ChessProgramStats cpstats;
8048
8049                 if (plyext != ' ' && plyext != '\t') {
8050                     time *= 100;
8051                 }
8052
8053                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8054                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8055                     curscore = -curscore;
8056                 }
8057
8058                 cpstats.depth = plylev;
8059                 cpstats.nodes = nodes;
8060                 cpstats.time = time;
8061                 cpstats.score = curscore;
8062                 cpstats.got_only_move = 0;
8063                 cpstats.movelist[0] = '\0';
8064
8065                 if (buf1[0] != NULLCHAR) {
8066                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8067                 }
8068
8069                 cpstats.ok_to_send = 0;
8070                 cpstats.line_is_book = 0;
8071                 cpstats.nr_moves = 0;
8072                 cpstats.moves_left = 0;
8073
8074                 SendProgramStatsToFrontend( cps, &cpstats );
8075             }
8076         }
8077     }
8078 }
8079
8080
8081 /* Parse a game score from the character string "game", and
8082    record it as the history of the current game.  The game
8083    score is NOT assumed to start from the standard position. 
8084    The display is not updated in any way.
8085    */
8086 void
8087 ParseGameHistory(game)
8088      char *game;
8089 {
8090     ChessMove moveType;
8091     int fromX, fromY, toX, toY, boardIndex;
8092     char promoChar;
8093     char *p, *q;
8094     char buf[MSG_SIZ];
8095
8096     if (appData.debugMode)
8097       fprintf(debugFP, "Parsing game history: %s\n", game);
8098
8099     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8100     gameInfo.site = StrSave(appData.icsHost);
8101     gameInfo.date = PGNDate();
8102     gameInfo.round = StrSave("-");
8103
8104     /* Parse out names of players */
8105     while (*game == ' ') game++;
8106     p = buf;
8107     while (*game != ' ') *p++ = *game++;
8108     *p = NULLCHAR;
8109     gameInfo.white = StrSave(buf);
8110     while (*game == ' ') game++;
8111     p = buf;
8112     while (*game != ' ' && *game != '\n') *p++ = *game++;
8113     *p = NULLCHAR;
8114     gameInfo.black = StrSave(buf);
8115
8116     /* Parse moves */
8117     boardIndex = blackPlaysFirst ? 1 : 0;
8118     yynewstr(game);
8119     for (;;) {
8120         yyboardindex = boardIndex;
8121         moveType = (ChessMove) yylex();
8122         switch (moveType) {
8123           case IllegalMove:             /* maybe suicide chess, etc. */
8124   if (appData.debugMode) {
8125     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8126     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8127     setbuf(debugFP, NULL);
8128   }
8129           case WhitePromotionChancellor:
8130           case BlackPromotionChancellor:
8131           case WhitePromotionArchbishop:
8132           case BlackPromotionArchbishop:
8133           case WhitePromotionQueen:
8134           case BlackPromotionQueen:
8135           case WhitePromotionRook:
8136           case BlackPromotionRook:
8137           case WhitePromotionBishop:
8138           case BlackPromotionBishop:
8139           case WhitePromotionKnight:
8140           case BlackPromotionKnight:
8141           case WhitePromotionKing:
8142           case BlackPromotionKing:
8143           case NormalMove:
8144           case WhiteCapturesEnPassant:
8145           case BlackCapturesEnPassant:
8146           case WhiteKingSideCastle:
8147           case WhiteQueenSideCastle:
8148           case BlackKingSideCastle:
8149           case BlackQueenSideCastle:
8150           case WhiteKingSideCastleWild:
8151           case WhiteQueenSideCastleWild:
8152           case BlackKingSideCastleWild:
8153           case BlackQueenSideCastleWild:
8154           /* PUSH Fabien */
8155           case WhiteHSideCastleFR:
8156           case WhiteASideCastleFR:
8157           case BlackHSideCastleFR:
8158           case BlackASideCastleFR:
8159           /* POP Fabien */
8160             fromX = currentMoveString[0] - AAA;
8161             fromY = currentMoveString[1] - ONE;
8162             toX = currentMoveString[2] - AAA;
8163             toY = currentMoveString[3] - ONE;
8164             promoChar = currentMoveString[4];
8165             break;
8166           case WhiteDrop:
8167           case BlackDrop:
8168             fromX = moveType == WhiteDrop ?
8169               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8170             (int) CharToPiece(ToLower(currentMoveString[0]));
8171             fromY = DROP_RANK;
8172             toX = currentMoveString[2] - AAA;
8173             toY = currentMoveString[3] - ONE;
8174             promoChar = NULLCHAR;
8175             break;
8176           case AmbiguousMove:
8177             /* bug? */
8178             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8179   if (appData.debugMode) {
8180     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8181     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8182     setbuf(debugFP, NULL);
8183   }
8184             DisplayError(buf, 0);
8185             return;
8186           case ImpossibleMove:
8187             /* bug? */
8188             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8189   if (appData.debugMode) {
8190     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8191     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8192     setbuf(debugFP, NULL);
8193   }
8194             DisplayError(buf, 0);
8195             return;
8196           case (ChessMove) 0:   /* end of file */
8197             if (boardIndex < backwardMostMove) {
8198                 /* Oops, gap.  How did that happen? */
8199                 DisplayError(_("Gap in move list"), 0);
8200                 return;
8201             }
8202             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8203             if (boardIndex > forwardMostMove) {
8204                 forwardMostMove = boardIndex;
8205             }
8206             return;
8207           case ElapsedTime:
8208             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8209                 strcat(parseList[boardIndex-1], " ");
8210                 strcat(parseList[boardIndex-1], yy_text);
8211             }
8212             continue;
8213           case Comment:
8214           case PGNTag:
8215           case NAG:
8216           default:
8217             /* ignore */
8218             continue;
8219           case WhiteWins:
8220           case BlackWins:
8221           case GameIsDrawn:
8222           case GameUnfinished:
8223             if (gameMode == IcsExamining) {
8224                 if (boardIndex < backwardMostMove) {
8225                     /* Oops, gap.  How did that happen? */
8226                     return;
8227                 }
8228                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8229                 return;
8230             }
8231             gameInfo.result = moveType;
8232             p = strchr(yy_text, '{');
8233             if (p == NULL) p = strchr(yy_text, '(');
8234             if (p == NULL) {
8235                 p = yy_text;
8236                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8237             } else {
8238                 q = strchr(p, *p == '{' ? '}' : ')');
8239                 if (q != NULL) *q = NULLCHAR;
8240                 p++;
8241             }
8242             gameInfo.resultDetails = StrSave(p);
8243             continue;
8244         }
8245         if (boardIndex >= forwardMostMove &&
8246             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8247             backwardMostMove = blackPlaysFirst ? 1 : 0;
8248             return;
8249         }
8250         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8251                                  fromY, fromX, toY, toX, promoChar,
8252                                  parseList[boardIndex]);
8253         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8254         /* currentMoveString is set as a side-effect of yylex */
8255         strcpy(moveList[boardIndex], currentMoveString);
8256         strcat(moveList[boardIndex], "\n");
8257         boardIndex++;
8258         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8259         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8260           case MT_NONE:
8261           case MT_STALEMATE:
8262           default:
8263             break;
8264           case MT_CHECK:
8265             if(gameInfo.variant != VariantShogi)
8266                 strcat(parseList[boardIndex - 1], "+");
8267             break;
8268           case MT_CHECKMATE:
8269           case MT_STAINMATE:
8270             strcat(parseList[boardIndex - 1], "#");
8271             break;
8272         }
8273     }
8274 }
8275
8276
8277 /* Apply a move to the given board  */
8278 void
8279 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8280      int fromX, fromY, toX, toY;
8281      int promoChar;
8282      Board board;
8283 {
8284   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8285   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8286
8287     /* [HGM] compute & store e.p. status and castling rights for new position */
8288     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8289     { int i;
8290
8291       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8292       oldEP = (signed char)board[EP_STATUS];
8293       board[EP_STATUS] = EP_NONE;
8294
8295       if( board[toY][toX] != EmptySquare ) 
8296            board[EP_STATUS] = EP_CAPTURE;  
8297
8298       if( board[fromY][fromX] == WhitePawn ) {
8299            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8300                board[EP_STATUS] = EP_PAWN_MOVE;
8301            if( toY-fromY==2) {
8302                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8303                         gameInfo.variant != VariantBerolina || toX < fromX)
8304                       board[EP_STATUS] = toX | berolina;
8305                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8306                         gameInfo.variant != VariantBerolina || toX > fromX) 
8307                       board[EP_STATUS] = toX;
8308            }
8309       } else 
8310       if( board[fromY][fromX] == BlackPawn ) {
8311            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8312                board[EP_STATUS] = EP_PAWN_MOVE; 
8313            if( toY-fromY== -2) {
8314                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8315                         gameInfo.variant != VariantBerolina || toX < fromX)
8316                       board[EP_STATUS] = toX | berolina;
8317                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8318                         gameInfo.variant != VariantBerolina || toX > fromX) 
8319                       board[EP_STATUS] = toX;
8320            }
8321        }
8322
8323        for(i=0; i<nrCastlingRights; i++) {
8324            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8325               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8326              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8327        }
8328
8329     }
8330
8331   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8332   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8333        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8334          
8335   if (fromX == toX && fromY == toY) return;
8336
8337   if (fromY == DROP_RANK) {
8338         /* must be first */
8339         piece = board[toY][toX] = (ChessSquare) fromX;
8340   } else {
8341      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8342      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8343      if(gameInfo.variant == VariantKnightmate)
8344          king += (int) WhiteUnicorn - (int) WhiteKing;
8345
8346     /* Code added by Tord: */
8347     /* FRC castling assumed when king captures friendly rook. */
8348     if (board[fromY][fromX] == WhiteKing &&
8349              board[toY][toX] == WhiteRook) {
8350       board[fromY][fromX] = EmptySquare;
8351       board[toY][toX] = EmptySquare;
8352       if(toX > fromX) {
8353         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8354       } else {
8355         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8356       }
8357     } else if (board[fromY][fromX] == BlackKing &&
8358                board[toY][toX] == BlackRook) {
8359       board[fromY][fromX] = EmptySquare;
8360       board[toY][toX] = EmptySquare;
8361       if(toX > fromX) {
8362         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8363       } else {
8364         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8365       }
8366     /* End of code added by Tord */
8367
8368     } else if (board[fromY][fromX] == king
8369         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8370         && toY == fromY && toX > fromX+1) {
8371         board[fromY][fromX] = EmptySquare;
8372         board[toY][toX] = king;
8373         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8374         board[fromY][BOARD_RGHT-1] = EmptySquare;
8375     } else if (board[fromY][fromX] == king
8376         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8377                && toY == fromY && toX < fromX-1) {
8378         board[fromY][fromX] = EmptySquare;
8379         board[toY][toX] = king;
8380         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8381         board[fromY][BOARD_LEFT] = EmptySquare;
8382     } else if (board[fromY][fromX] == WhitePawn
8383                && toY >= BOARD_HEIGHT-promoRank
8384                && gameInfo.variant != VariantXiangqi
8385                ) {
8386         /* white pawn promotion */
8387         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8388         if (board[toY][toX] == EmptySquare) {
8389             board[toY][toX] = WhiteQueen;
8390         }
8391         if(gameInfo.variant==VariantBughouse ||
8392            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8393             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8394         board[fromY][fromX] = EmptySquare;
8395     } else if ((fromY == BOARD_HEIGHT-4)
8396                && (toX != fromX)
8397                && gameInfo.variant != VariantXiangqi
8398                && gameInfo.variant != VariantBerolina
8399                && (board[fromY][fromX] == WhitePawn)
8400                && (board[toY][toX] == EmptySquare)) {
8401         board[fromY][fromX] = EmptySquare;
8402         board[toY][toX] = WhitePawn;
8403         captured = board[toY - 1][toX];
8404         board[toY - 1][toX] = EmptySquare;
8405     } else if ((fromY == BOARD_HEIGHT-4)
8406                && (toX == fromX)
8407                && gameInfo.variant == VariantBerolina
8408                && (board[fromY][fromX] == WhitePawn)
8409                && (board[toY][toX] == EmptySquare)) {
8410         board[fromY][fromX] = EmptySquare;
8411         board[toY][toX] = WhitePawn;
8412         if(oldEP & EP_BEROLIN_A) {
8413                 captured = board[fromY][fromX-1];
8414                 board[fromY][fromX-1] = EmptySquare;
8415         }else{  captured = board[fromY][fromX+1];
8416                 board[fromY][fromX+1] = EmptySquare;
8417         }
8418     } else if (board[fromY][fromX] == king
8419         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8420                && toY == fromY && toX > fromX+1) {
8421         board[fromY][fromX] = EmptySquare;
8422         board[toY][toX] = king;
8423         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8424         board[fromY][BOARD_RGHT-1] = EmptySquare;
8425     } else if (board[fromY][fromX] == king
8426         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8427                && toY == fromY && toX < fromX-1) {
8428         board[fromY][fromX] = EmptySquare;
8429         board[toY][toX] = king;
8430         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8431         board[fromY][BOARD_LEFT] = EmptySquare;
8432     } else if (fromY == 7 && fromX == 3
8433                && board[fromY][fromX] == BlackKing
8434                && toY == 7 && toX == 5) {
8435         board[fromY][fromX] = EmptySquare;
8436         board[toY][toX] = BlackKing;
8437         board[fromY][7] = EmptySquare;
8438         board[toY][4] = BlackRook;
8439     } else if (fromY == 7 && fromX == 3
8440                && board[fromY][fromX] == BlackKing
8441                && toY == 7 && toX == 1) {
8442         board[fromY][fromX] = EmptySquare;
8443         board[toY][toX] = BlackKing;
8444         board[fromY][0] = EmptySquare;
8445         board[toY][2] = BlackRook;
8446     } else if (board[fromY][fromX] == BlackPawn
8447                && toY < promoRank
8448                && gameInfo.variant != VariantXiangqi
8449                ) {
8450         /* black pawn promotion */
8451         board[toY][toX] = CharToPiece(ToLower(promoChar));
8452         if (board[toY][toX] == EmptySquare) {
8453             board[toY][toX] = BlackQueen;
8454         }
8455         if(gameInfo.variant==VariantBughouse ||
8456            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8457             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8458         board[fromY][fromX] = EmptySquare;
8459     } else if ((fromY == 3)
8460                && (toX != fromX)
8461                && gameInfo.variant != VariantXiangqi
8462                && gameInfo.variant != VariantBerolina
8463                && (board[fromY][fromX] == BlackPawn)
8464                && (board[toY][toX] == EmptySquare)) {
8465         board[fromY][fromX] = EmptySquare;
8466         board[toY][toX] = BlackPawn;
8467         captured = board[toY + 1][toX];
8468         board[toY + 1][toX] = EmptySquare;
8469     } else if ((fromY == 3)
8470                && (toX == fromX)
8471                && gameInfo.variant == VariantBerolina
8472                && (board[fromY][fromX] == BlackPawn)
8473                && (board[toY][toX] == EmptySquare)) {
8474         board[fromY][fromX] = EmptySquare;
8475         board[toY][toX] = BlackPawn;
8476         if(oldEP & EP_BEROLIN_A) {
8477                 captured = board[fromY][fromX-1];
8478                 board[fromY][fromX-1] = EmptySquare;
8479         }else{  captured = board[fromY][fromX+1];
8480                 board[fromY][fromX+1] = EmptySquare;
8481         }
8482     } else {
8483         board[toY][toX] = board[fromY][fromX];
8484         board[fromY][fromX] = EmptySquare;
8485     }
8486
8487     /* [HGM] now we promote for Shogi, if needed */
8488     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8489         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8490   }
8491
8492     if (gameInfo.holdingsWidth != 0) {
8493
8494       /* !!A lot more code needs to be written to support holdings  */
8495       /* [HGM] OK, so I have written it. Holdings are stored in the */
8496       /* penultimate board files, so they are automaticlly stored   */
8497       /* in the game history.                                       */
8498       if (fromY == DROP_RANK) {
8499         /* Delete from holdings, by decreasing count */
8500         /* and erasing image if necessary            */
8501         p = (int) fromX;
8502         if(p < (int) BlackPawn) { /* white drop */
8503              p -= (int)WhitePawn;
8504                  p = PieceToNumber((ChessSquare)p);
8505              if(p >= gameInfo.holdingsSize) p = 0;
8506              if(--board[p][BOARD_WIDTH-2] <= 0)
8507                   board[p][BOARD_WIDTH-1] = EmptySquare;
8508              if((int)board[p][BOARD_WIDTH-2] < 0)
8509                         board[p][BOARD_WIDTH-2] = 0;
8510         } else {                  /* black drop */
8511              p -= (int)BlackPawn;
8512                  p = PieceToNumber((ChessSquare)p);
8513              if(p >= gameInfo.holdingsSize) p = 0;
8514              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8515                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8516              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8517                         board[BOARD_HEIGHT-1-p][1] = 0;
8518         }
8519       }
8520       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8521           && gameInfo.variant != VariantBughouse        ) {
8522         /* [HGM] holdings: Add to holdings, if holdings exist */
8523         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8524                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8525                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8526         }
8527         p = (int) captured;
8528         if (p >= (int) BlackPawn) {
8529           p -= (int)BlackPawn;
8530           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8531                   /* in Shogi restore piece to its original  first */
8532                   captured = (ChessSquare) (DEMOTED captured);
8533                   p = DEMOTED p;
8534           }
8535           p = PieceToNumber((ChessSquare)p);
8536           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8537           board[p][BOARD_WIDTH-2]++;
8538           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8539         } else {
8540           p -= (int)WhitePawn;
8541           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8542                   captured = (ChessSquare) (DEMOTED captured);
8543                   p = DEMOTED p;
8544           }
8545           p = PieceToNumber((ChessSquare)p);
8546           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8547           board[BOARD_HEIGHT-1-p][1]++;
8548           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8549         }
8550       }
8551     } else if (gameInfo.variant == VariantAtomic) {
8552       if (captured != EmptySquare) {
8553         int y, x;
8554         for (y = toY-1; y <= toY+1; y++) {
8555           for (x = toX-1; x <= toX+1; x++) {
8556             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8557                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8558               board[y][x] = EmptySquare;
8559             }
8560           }
8561         }
8562         board[toY][toX] = EmptySquare;
8563       }
8564     }
8565     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8566         /* [HGM] Shogi promotions */
8567         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8568     }
8569
8570     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8571                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8572         // [HGM] superchess: take promotion piece out of holdings
8573         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8574         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8575             if(!--board[k][BOARD_WIDTH-2])
8576                 board[k][BOARD_WIDTH-1] = EmptySquare;
8577         } else {
8578             if(!--board[BOARD_HEIGHT-1-k][1])
8579                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8580         }
8581     }
8582
8583 }
8584
8585 /* Updates forwardMostMove */
8586 void
8587 MakeMove(fromX, fromY, toX, toY, promoChar)
8588      int fromX, fromY, toX, toY;
8589      int promoChar;
8590 {
8591 //    forwardMostMove++; // [HGM] bare: moved downstream
8592
8593     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8594         int timeLeft; static int lastLoadFlag=0; int king, piece;
8595         piece = boards[forwardMostMove][fromY][fromX];
8596         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8597         if(gameInfo.variant == VariantKnightmate)
8598             king += (int) WhiteUnicorn - (int) WhiteKing;
8599         if(forwardMostMove == 0) {
8600             if(blackPlaysFirst) 
8601                 fprintf(serverMoves, "%s;", second.tidy);
8602             fprintf(serverMoves, "%s;", first.tidy);
8603             if(!blackPlaysFirst) 
8604                 fprintf(serverMoves, "%s;", second.tidy);
8605         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8606         lastLoadFlag = loadFlag;
8607         // print base move
8608         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8609         // print castling suffix
8610         if( toY == fromY && piece == king ) {
8611             if(toX-fromX > 1)
8612                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8613             if(fromX-toX >1)
8614                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8615         }
8616         // e.p. suffix
8617         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8618              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8619              boards[forwardMostMove][toY][toX] == EmptySquare
8620              && fromX != toX && fromY != toY)
8621                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8622         // promotion suffix
8623         if(promoChar != NULLCHAR)
8624                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8625         if(!loadFlag) {
8626             fprintf(serverMoves, "/%d/%d",
8627                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8628             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8629             else                      timeLeft = blackTimeRemaining/1000;
8630             fprintf(serverMoves, "/%d", timeLeft);
8631         }
8632         fflush(serverMoves);
8633     }
8634
8635     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8636       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8637                         0, 1);
8638       return;
8639     }
8640     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8641     if (commentList[forwardMostMove+1] != NULL) {
8642         free(commentList[forwardMostMove+1]);
8643         commentList[forwardMostMove+1] = NULL;
8644     }
8645     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8646     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8647     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8648     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8649     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8650     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8651     gameInfo.result = GameUnfinished;
8652     if (gameInfo.resultDetails != NULL) {
8653         free(gameInfo.resultDetails);
8654         gameInfo.resultDetails = NULL;
8655     }
8656     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8657                               moveList[forwardMostMove - 1]);
8658     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8659                              PosFlags(forwardMostMove - 1),
8660                              fromY, fromX, toY, toX, promoChar,
8661                              parseList[forwardMostMove - 1]);
8662     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8663       case MT_NONE:
8664       case MT_STALEMATE:
8665       default:
8666         break;
8667       case MT_CHECK:
8668         if(gameInfo.variant != VariantShogi)
8669             strcat(parseList[forwardMostMove - 1], "+");
8670         break;
8671       case MT_CHECKMATE:
8672       case MT_STAINMATE:
8673         strcat(parseList[forwardMostMove - 1], "#");
8674         break;
8675     }
8676     if (appData.debugMode) {
8677         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8678     }
8679
8680 }
8681
8682 /* Updates currentMove if not pausing */
8683 void
8684 ShowMove(fromX, fromY, toX, toY)
8685 {
8686     int instant = (gameMode == PlayFromGameFile) ?
8687         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8688     if(appData.noGUI) return;
8689     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8690         if (!instant) {
8691             if (forwardMostMove == currentMove + 1) {
8692                 AnimateMove(boards[forwardMostMove - 1],
8693                             fromX, fromY, toX, toY);
8694             }
8695             if (appData.highlightLastMove) {
8696                 SetHighlights(fromX, fromY, toX, toY);
8697             }
8698         }
8699         currentMove = forwardMostMove;
8700     }
8701
8702     if (instant) return;
8703
8704     DisplayMove(currentMove - 1);
8705     DrawPosition(FALSE, boards[currentMove]);
8706     DisplayBothClocks();
8707     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8708 }
8709
8710 void SendEgtPath(ChessProgramState *cps)
8711 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8712         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8713
8714         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8715
8716         while(*p) {
8717             char c, *q = name+1, *r, *s;
8718
8719             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8720             while(*p && *p != ',') *q++ = *p++;
8721             *q++ = ':'; *q = 0;
8722             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8723                 strcmp(name, ",nalimov:") == 0 ) {
8724                 // take nalimov path from the menu-changeable option first, if it is defined
8725                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8726                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8727             } else
8728             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8729                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8730                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8731                 s = r = StrStr(s, ":") + 1; // beginning of path info
8732                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8733                 c = *r; *r = 0;             // temporarily null-terminate path info
8734                     *--q = 0;               // strip of trailig ':' from name
8735                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8736                 *r = c;
8737                 SendToProgram(buf,cps);     // send egtbpath command for this format
8738             }
8739             if(*p == ',') p++; // read away comma to position for next format name
8740         }
8741 }
8742
8743 void
8744 InitChessProgram(cps, setup)
8745      ChessProgramState *cps;
8746      int setup; /* [HGM] needed to setup FRC opening position */
8747 {
8748     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8749     if (appData.noChessProgram) return;
8750     hintRequested = FALSE;
8751     bookRequested = FALSE;
8752
8753     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8754     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8755     if(cps->memSize) { /* [HGM] memory */
8756         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8757         SendToProgram(buf, cps);
8758     }
8759     SendEgtPath(cps); /* [HGM] EGT */
8760     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8761         sprintf(buf, "cores %d\n", appData.smpCores);
8762         SendToProgram(buf, cps);
8763     }
8764
8765     SendToProgram(cps->initString, cps);
8766     if (gameInfo.variant != VariantNormal &&
8767         gameInfo.variant != VariantLoadable
8768         /* [HGM] also send variant if board size non-standard */
8769         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8770                                             ) {
8771       char *v = VariantName(gameInfo.variant);
8772       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8773         /* [HGM] in protocol 1 we have to assume all variants valid */
8774         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8775         DisplayFatalError(buf, 0, 1);
8776         return;
8777       }
8778
8779       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8780       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8781       if( gameInfo.variant == VariantXiangqi )
8782            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8783       if( gameInfo.variant == VariantShogi )
8784            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8785       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8786            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8787       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8788                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8789            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8790       if( gameInfo.variant == VariantCourier )
8791            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8792       if( gameInfo.variant == VariantSuper )
8793            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8794       if( gameInfo.variant == VariantGreat )
8795            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8796
8797       if(overruled) {
8798            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8799                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8800            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8801            if(StrStr(cps->variants, b) == NULL) { 
8802                // specific sized variant not known, check if general sizing allowed
8803                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8804                    if(StrStr(cps->variants, "boardsize") == NULL) {
8805                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8806                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8807                        DisplayFatalError(buf, 0, 1);
8808                        return;
8809                    }
8810                    /* [HGM] here we really should compare with the maximum supported board size */
8811                }
8812            }
8813       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8814       sprintf(buf, "variant %s\n", b);
8815       SendToProgram(buf, cps);
8816     }
8817     currentlyInitializedVariant = gameInfo.variant;
8818
8819     /* [HGM] send opening position in FRC to first engine */
8820     if(setup) {
8821           SendToProgram("force\n", cps);
8822           SendBoard(cps, 0);
8823           /* engine is now in force mode! Set flag to wake it up after first move. */
8824           setboardSpoiledMachineBlack = 1;
8825     }
8826
8827     if (cps->sendICS) {
8828       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8829       SendToProgram(buf, cps);
8830     }
8831     cps->maybeThinking = FALSE;
8832     cps->offeredDraw = 0;
8833     if (!appData.icsActive) {
8834         SendTimeControl(cps, movesPerSession, timeControl,
8835                         timeIncrement, appData.searchDepth,
8836                         searchTime);
8837     }
8838     if (appData.showThinking 
8839         // [HGM] thinking: four options require thinking output to be sent
8840         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8841                                 ) {
8842         SendToProgram("post\n", cps);
8843     }
8844     SendToProgram("hard\n", cps);
8845     if (!appData.ponderNextMove) {
8846         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8847            it without being sure what state we are in first.  "hard"
8848            is not a toggle, so that one is OK.
8849          */
8850         SendToProgram("easy\n", cps);
8851     }
8852     if (cps->usePing) {
8853       sprintf(buf, "ping %d\n", ++cps->lastPing);
8854       SendToProgram(buf, cps);
8855     }
8856     cps->initDone = TRUE;
8857 }   
8858
8859
8860 void
8861 StartChessProgram(cps)
8862      ChessProgramState *cps;
8863 {
8864     char buf[MSG_SIZ];
8865     int err;
8866
8867     if (appData.noChessProgram) return;
8868     cps->initDone = FALSE;
8869
8870     if (strcmp(cps->host, "localhost") == 0) {
8871         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8872     } else if (*appData.remoteShell == NULLCHAR) {
8873         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8874     } else {
8875         if (*appData.remoteUser == NULLCHAR) {
8876           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8877                     cps->program);
8878         } else {
8879           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8880                     cps->host, appData.remoteUser, cps->program);
8881         }
8882         err = StartChildProcess(buf, "", &cps->pr);
8883     }
8884     
8885     if (err != 0) {
8886         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8887         DisplayFatalError(buf, err, 1);
8888         cps->pr = NoProc;
8889         cps->isr = NULL;
8890         return;
8891     }
8892     
8893     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8894     if (cps->protocolVersion > 1) {
8895       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8896       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8897       cps->comboCnt = 0;  //                and values of combo boxes
8898       SendToProgram(buf, cps);
8899     } else {
8900       SendToProgram("xboard\n", cps);
8901     }
8902 }
8903
8904
8905 void
8906 TwoMachinesEventIfReady P((void))
8907 {
8908   if (first.lastPing != first.lastPong) {
8909     DisplayMessage("", _("Waiting for first chess program"));
8910     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8911     return;
8912   }
8913   if (second.lastPing != second.lastPong) {
8914     DisplayMessage("", _("Waiting for second chess program"));
8915     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8916     return;
8917   }
8918   ThawUI();
8919   TwoMachinesEvent();
8920 }
8921
8922 void
8923 NextMatchGame P((void))
8924 {
8925     int index; /* [HGM] autoinc: step load index during match */
8926     Reset(FALSE, TRUE);
8927     if (*appData.loadGameFile != NULLCHAR) {
8928         index = appData.loadGameIndex;
8929         if(index < 0) { // [HGM] autoinc
8930             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8931             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8932         } 
8933         LoadGameFromFile(appData.loadGameFile,
8934                          index,
8935                          appData.loadGameFile, FALSE);
8936     } else if (*appData.loadPositionFile != NULLCHAR) {
8937         index = appData.loadPositionIndex;
8938         if(index < 0) { // [HGM] autoinc
8939             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8940             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8941         } 
8942         LoadPositionFromFile(appData.loadPositionFile,
8943                              index,
8944                              appData.loadPositionFile);
8945     }
8946     TwoMachinesEventIfReady();
8947 }
8948
8949 void UserAdjudicationEvent( int result )
8950 {
8951     ChessMove gameResult = GameIsDrawn;
8952
8953     if( result > 0 ) {
8954         gameResult = WhiteWins;
8955     }
8956     else if( result < 0 ) {
8957         gameResult = BlackWins;
8958     }
8959
8960     if( gameMode == TwoMachinesPlay ) {
8961         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8962     }
8963 }
8964
8965
8966 // [HGM] save: calculate checksum of game to make games easily identifiable
8967 int StringCheckSum(char *s)
8968 {
8969         int i = 0;
8970         if(s==NULL) return 0;
8971         while(*s) i = i*259 + *s++;
8972         return i;
8973 }
8974
8975 int GameCheckSum()
8976 {
8977         int i, sum=0;
8978         for(i=backwardMostMove; i<forwardMostMove; i++) {
8979                 sum += pvInfoList[i].depth;
8980                 sum += StringCheckSum(parseList[i]);
8981                 sum += StringCheckSum(commentList[i]);
8982                 sum *= 261;
8983         }
8984         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8985         return sum + StringCheckSum(commentList[i]);
8986 } // end of save patch
8987
8988 void
8989 GameEnds(result, resultDetails, whosays)
8990      ChessMove result;
8991      char *resultDetails;
8992      int whosays;
8993 {
8994     GameMode nextGameMode;
8995     int isIcsGame;
8996     char buf[MSG_SIZ], popupRequested = 0;
8997
8998     if(endingGame) return; /* [HGM] crash: forbid recursion */
8999     endingGame = 1;
9000     if(twoBoards) { // [HGM] dual: switch back to one board
9001         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9002         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9003     }
9004     if (appData.debugMode) {
9005       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9006               result, resultDetails ? resultDetails : "(null)", whosays);
9007     }
9008
9009     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9010
9011     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9012         /* If we are playing on ICS, the server decides when the
9013            game is over, but the engine can offer to draw, claim 
9014            a draw, or resign. 
9015          */
9016 #if ZIPPY
9017         if (appData.zippyPlay && first.initDone) {
9018             if (result == GameIsDrawn) {
9019                 /* In case draw still needs to be claimed */
9020                 SendToICS(ics_prefix);
9021                 SendToICS("draw\n");
9022             } else if (StrCaseStr(resultDetails, "resign")) {
9023                 SendToICS(ics_prefix);
9024                 SendToICS("resign\n");
9025             }
9026         }
9027 #endif
9028         endingGame = 0; /* [HGM] crash */
9029         return;
9030     }
9031
9032     /* If we're loading the game from a file, stop */
9033     if (whosays == GE_FILE) {
9034       (void) StopLoadGameTimer();
9035       gameFileFP = NULL;
9036     }
9037
9038     /* Cancel draw offers */
9039     first.offeredDraw = second.offeredDraw = 0;
9040
9041     /* If this is an ICS game, only ICS can really say it's done;
9042        if not, anyone can. */
9043     isIcsGame = (gameMode == IcsPlayingWhite || 
9044                  gameMode == IcsPlayingBlack || 
9045                  gameMode == IcsObserving    || 
9046                  gameMode == IcsExamining);
9047
9048     if (!isIcsGame || whosays == GE_ICS) {
9049         /* OK -- not an ICS game, or ICS said it was done */
9050         StopClocks();
9051         if (!isIcsGame && !appData.noChessProgram) 
9052           SetUserThinkingEnables();
9053     
9054         /* [HGM] if a machine claims the game end we verify this claim */
9055         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9056             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9057                 char claimer;
9058                 ChessMove trueResult = (ChessMove) -1;
9059
9060                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9061                                             first.twoMachinesColor[0] :
9062                                             second.twoMachinesColor[0] ;
9063
9064                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9065                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9066                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9067                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9068                 } else
9069                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9070                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9071                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9072                 } else
9073                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9074                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9075                 }
9076
9077                 // now verify win claims, but not in drop games, as we don't understand those yet
9078                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9079                                                  || gameInfo.variant == VariantGreat) &&
9080                     (result == WhiteWins && claimer == 'w' ||
9081                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9082                       if (appData.debugMode) {
9083                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9084                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9085                       }
9086                       if(result != trueResult) {
9087                               sprintf(buf, "False win claim: '%s'", resultDetails);
9088                               result = claimer == 'w' ? BlackWins : WhiteWins;
9089                               resultDetails = buf;
9090                       }
9091                 } else
9092                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9093                     && (forwardMostMove <= backwardMostMove ||
9094                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9095                         (claimer=='b')==(forwardMostMove&1))
9096                                                                                   ) {
9097                       /* [HGM] verify: draws that were not flagged are false claims */
9098                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9099                       result = claimer == 'w' ? BlackWins : WhiteWins;
9100                       resultDetails = buf;
9101                 }
9102                 /* (Claiming a loss is accepted no questions asked!) */
9103             }
9104             /* [HGM] bare: don't allow bare King to win */
9105             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9106                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9107                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9108                && result != GameIsDrawn)
9109             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9110                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9111                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9112                         if(p >= 0 && p <= (int)WhiteKing) k++;
9113                 }
9114                 if (appData.debugMode) {
9115                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9116                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9117                 }
9118                 if(k <= 1) {
9119                         result = GameIsDrawn;
9120                         sprintf(buf, "%s but bare king", resultDetails);
9121                         resultDetails = buf;
9122                 }
9123             }
9124         }
9125
9126
9127         if(serverMoves != NULL && !loadFlag) { char c = '=';
9128             if(result==WhiteWins) c = '+';
9129             if(result==BlackWins) c = '-';
9130             if(resultDetails != NULL)
9131                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9132         }
9133         if (resultDetails != NULL) {
9134             gameInfo.result = result;
9135             gameInfo.resultDetails = StrSave(resultDetails);
9136
9137             /* display last move only if game was not loaded from file */
9138             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9139                 DisplayMove(currentMove - 1);
9140     
9141             if (forwardMostMove != 0) {
9142                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9143                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9144                                                                 ) {
9145                     if (*appData.saveGameFile != NULLCHAR) {
9146                         SaveGameToFile(appData.saveGameFile, TRUE);
9147                     } else if (appData.autoSaveGames) {
9148                         AutoSaveGame();
9149                     }
9150                     if (*appData.savePositionFile != NULLCHAR) {
9151                         SavePositionToFile(appData.savePositionFile);
9152                     }
9153                 }
9154             }
9155
9156             /* Tell program how game ended in case it is learning */
9157             /* [HGM] Moved this to after saving the PGN, just in case */
9158             /* engine died and we got here through time loss. In that */
9159             /* case we will get a fatal error writing the pipe, which */
9160             /* would otherwise lose us the PGN.                       */
9161             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9162             /* output during GameEnds should never be fatal anymore   */
9163             if (gameMode == MachinePlaysWhite ||
9164                 gameMode == MachinePlaysBlack ||
9165                 gameMode == TwoMachinesPlay ||
9166                 gameMode == IcsPlayingWhite ||
9167                 gameMode == IcsPlayingBlack ||
9168                 gameMode == BeginningOfGame) {
9169                 char buf[MSG_SIZ];
9170                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9171                         resultDetails);
9172                 if (first.pr != NoProc) {
9173                     SendToProgram(buf, &first);
9174                 }
9175                 if (second.pr != NoProc &&
9176                     gameMode == TwoMachinesPlay) {
9177                     SendToProgram(buf, &second);
9178                 }
9179             }
9180         }
9181
9182         if (appData.icsActive) {
9183             if (appData.quietPlay &&
9184                 (gameMode == IcsPlayingWhite ||
9185                  gameMode == IcsPlayingBlack)) {
9186                 SendToICS(ics_prefix);
9187                 SendToICS("set shout 1\n");
9188             }
9189             nextGameMode = IcsIdle;
9190             ics_user_moved = FALSE;
9191             /* clean up premove.  It's ugly when the game has ended and the
9192              * premove highlights are still on the board.
9193              */
9194             if (gotPremove) {
9195               gotPremove = FALSE;
9196               ClearPremoveHighlights();
9197               DrawPosition(FALSE, boards[currentMove]);
9198             }
9199             if (whosays == GE_ICS) {
9200                 switch (result) {
9201                 case WhiteWins:
9202                     if (gameMode == IcsPlayingWhite)
9203                         PlayIcsWinSound();
9204                     else if(gameMode == IcsPlayingBlack)
9205                         PlayIcsLossSound();
9206                     break;
9207                 case BlackWins:
9208                     if (gameMode == IcsPlayingBlack)
9209                         PlayIcsWinSound();
9210                     else if(gameMode == IcsPlayingWhite)
9211                         PlayIcsLossSound();
9212                     break;
9213                 case GameIsDrawn:
9214                     PlayIcsDrawSound();
9215                     break;
9216                 default:
9217                     PlayIcsUnfinishedSound();
9218                 }
9219             }
9220         } else if (gameMode == EditGame ||
9221                    gameMode == PlayFromGameFile || 
9222                    gameMode == AnalyzeMode || 
9223                    gameMode == AnalyzeFile) {
9224             nextGameMode = gameMode;
9225         } else {
9226             nextGameMode = EndOfGame;
9227         }
9228         pausing = FALSE;
9229         ModeHighlight();
9230     } else {
9231         nextGameMode = gameMode;
9232     }
9233
9234     if (appData.noChessProgram) {
9235         gameMode = nextGameMode;
9236         ModeHighlight();
9237         endingGame = 0; /* [HGM] crash */
9238         return;
9239     }
9240
9241     if (first.reuse) {
9242         /* Put first chess program into idle state */
9243         if (first.pr != NoProc &&
9244             (gameMode == MachinePlaysWhite ||
9245              gameMode == MachinePlaysBlack ||
9246              gameMode == TwoMachinesPlay ||
9247              gameMode == IcsPlayingWhite ||
9248              gameMode == IcsPlayingBlack ||
9249              gameMode == BeginningOfGame)) {
9250             SendToProgram("force\n", &first);
9251             if (first.usePing) {
9252               char buf[MSG_SIZ];
9253               sprintf(buf, "ping %d\n", ++first.lastPing);
9254               SendToProgram(buf, &first);
9255             }
9256         }
9257     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9258         /* Kill off first chess program */
9259         if (first.isr != NULL)
9260           RemoveInputSource(first.isr);
9261         first.isr = NULL;
9262     
9263         if (first.pr != NoProc) {
9264             ExitAnalyzeMode();
9265             DoSleep( appData.delayBeforeQuit );
9266             SendToProgram("quit\n", &first);
9267             DoSleep( appData.delayAfterQuit );
9268             DestroyChildProcess(first.pr, first.useSigterm);
9269         }
9270         first.pr = NoProc;
9271     }
9272     if (second.reuse) {
9273         /* Put second chess program into idle state */
9274         if (second.pr != NoProc &&
9275             gameMode == TwoMachinesPlay) {
9276             SendToProgram("force\n", &second);
9277             if (second.usePing) {
9278               char buf[MSG_SIZ];
9279               sprintf(buf, "ping %d\n", ++second.lastPing);
9280               SendToProgram(buf, &second);
9281             }
9282         }
9283     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9284         /* Kill off second chess program */
9285         if (second.isr != NULL)
9286           RemoveInputSource(second.isr);
9287         second.isr = NULL;
9288     
9289         if (second.pr != NoProc) {
9290             DoSleep( appData.delayBeforeQuit );
9291             SendToProgram("quit\n", &second);
9292             DoSleep( appData.delayAfterQuit );
9293             DestroyChildProcess(second.pr, second.useSigterm);
9294         }
9295         second.pr = NoProc;
9296     }
9297
9298     if (matchMode && gameMode == TwoMachinesPlay) {
9299         switch (result) {
9300         case WhiteWins:
9301           if (first.twoMachinesColor[0] == 'w') {
9302             first.matchWins++;
9303           } else {
9304             second.matchWins++;
9305           }
9306           break;
9307         case BlackWins:
9308           if (first.twoMachinesColor[0] == 'b') {
9309             first.matchWins++;
9310           } else {
9311             second.matchWins++;
9312           }
9313           break;
9314         default:
9315           break;
9316         }
9317         if (matchGame < appData.matchGames) {
9318             char *tmp;
9319             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9320                 tmp = first.twoMachinesColor;
9321                 first.twoMachinesColor = second.twoMachinesColor;
9322                 second.twoMachinesColor = tmp;
9323             }
9324             gameMode = nextGameMode;
9325             matchGame++;
9326             if(appData.matchPause>10000 || appData.matchPause<10)
9327                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9328             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9329             endingGame = 0; /* [HGM] crash */
9330             return;
9331         } else {
9332             char buf[MSG_SIZ];
9333             gameMode = nextGameMode;
9334             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9335                     first.tidy, second.tidy,
9336                     first.matchWins, second.matchWins,
9337                     appData.matchGames - (first.matchWins + second.matchWins));
9338             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9339         }
9340     }
9341     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9342         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9343       ExitAnalyzeMode();
9344     gameMode = nextGameMode;
9345     ModeHighlight();
9346     endingGame = 0;  /* [HGM] crash */
9347     if(popupRequested) DisplayFatalError(buf, 0, 0); // [HGM] crash: this call GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9348 }
9349
9350 /* Assumes program was just initialized (initString sent).
9351    Leaves program in force mode. */
9352 void
9353 FeedMovesToProgram(cps, upto) 
9354      ChessProgramState *cps;
9355      int upto;
9356 {
9357     int i;
9358     
9359     if (appData.debugMode)
9360       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9361               startedFromSetupPosition ? "position and " : "",
9362               backwardMostMove, upto, cps->which);
9363     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9364         // [HGM] variantswitch: make engine aware of new variant
9365         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9366                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9367         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9368         SendToProgram(buf, cps);
9369         currentlyInitializedVariant = gameInfo.variant;
9370     }
9371     SendToProgram("force\n", cps);
9372     if (startedFromSetupPosition) {
9373         SendBoard(cps, backwardMostMove);
9374     if (appData.debugMode) {
9375         fprintf(debugFP, "feedMoves\n");
9376     }
9377     }
9378     for (i = backwardMostMove; i < upto; i++) {
9379         SendMoveToProgram(i, cps);
9380     }
9381 }
9382
9383
9384 void
9385 ResurrectChessProgram()
9386 {
9387      /* The chess program may have exited.
9388         If so, restart it and feed it all the moves made so far. */
9389
9390     if (appData.noChessProgram || first.pr != NoProc) return;
9391     
9392     StartChessProgram(&first);
9393     InitChessProgram(&first, FALSE);
9394     FeedMovesToProgram(&first, currentMove);
9395
9396     if (!first.sendTime) {
9397         /* can't tell gnuchess what its clock should read,
9398            so we bow to its notion. */
9399         ResetClocks();
9400         timeRemaining[0][currentMove] = whiteTimeRemaining;
9401         timeRemaining[1][currentMove] = blackTimeRemaining;
9402     }
9403
9404     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9405                 appData.icsEngineAnalyze) && first.analysisSupport) {
9406       SendToProgram("analyze\n", &first);
9407       first.analyzing = TRUE;
9408     }
9409 }
9410
9411 /*
9412  * Button procedures
9413  */
9414 void
9415 Reset(redraw, init)
9416      int redraw, init;
9417 {
9418     int i;
9419
9420     if (appData.debugMode) {
9421         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9422                 redraw, init, gameMode);
9423     }
9424     CleanupTail(); // [HGM] vari: delete any stored variations
9425     pausing = pauseExamInvalid = FALSE;
9426     startedFromSetupPosition = blackPlaysFirst = FALSE;
9427     firstMove = TRUE;
9428     whiteFlag = blackFlag = FALSE;
9429     userOfferedDraw = FALSE;
9430     hintRequested = bookRequested = FALSE;
9431     first.maybeThinking = FALSE;
9432     second.maybeThinking = FALSE;
9433     first.bookSuspend = FALSE; // [HGM] book
9434     second.bookSuspend = FALSE;
9435     thinkOutput[0] = NULLCHAR;
9436     lastHint[0] = NULLCHAR;
9437     ClearGameInfo(&gameInfo);
9438     gameInfo.variant = StringToVariant(appData.variant);
9439     ics_user_moved = ics_clock_paused = FALSE;
9440     ics_getting_history = H_FALSE;
9441     ics_gamenum = -1;
9442     white_holding[0] = black_holding[0] = NULLCHAR;
9443     ClearProgramStats();
9444     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9445     
9446     ResetFrontEnd();
9447     ClearHighlights();
9448     flipView = appData.flipView;
9449     ClearPremoveHighlights();
9450     gotPremove = FALSE;
9451     alarmSounded = FALSE;
9452
9453     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9454     if(appData.serverMovesName != NULL) {
9455         /* [HGM] prepare to make moves file for broadcasting */
9456         clock_t t = clock();
9457         if(serverMoves != NULL) fclose(serverMoves);
9458         serverMoves = fopen(appData.serverMovesName, "r");
9459         if(serverMoves != NULL) {
9460             fclose(serverMoves);
9461             /* delay 15 sec before overwriting, so all clients can see end */
9462             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9463         }
9464         serverMoves = fopen(appData.serverMovesName, "w");
9465     }
9466
9467     ExitAnalyzeMode();
9468     gameMode = BeginningOfGame;
9469     ModeHighlight();
9470     if(appData.icsActive) gameInfo.variant = VariantNormal;
9471     currentMove = forwardMostMove = backwardMostMove = 0;
9472     InitPosition(redraw);
9473     for (i = 0; i < MAX_MOVES; i++) {
9474         if (commentList[i] != NULL) {
9475             free(commentList[i]);
9476             commentList[i] = NULL;
9477         }
9478     }
9479     ResetClocks();
9480     timeRemaining[0][0] = whiteTimeRemaining;
9481     timeRemaining[1][0] = blackTimeRemaining;
9482     if (first.pr == NULL) {
9483         StartChessProgram(&first);
9484     }
9485     if (init) {
9486             InitChessProgram(&first, startedFromSetupPosition);
9487     }
9488     DisplayTitle("");
9489     DisplayMessage("", "");
9490     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9491     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9492 }
9493
9494 void
9495 AutoPlayGameLoop()
9496 {
9497     for (;;) {
9498         if (!AutoPlayOneMove())
9499           return;
9500         if (matchMode || appData.timeDelay == 0)
9501           continue;
9502         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9503           return;
9504         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9505         break;
9506     }
9507 }
9508
9509
9510 int
9511 AutoPlayOneMove()
9512 {
9513     int fromX, fromY, toX, toY;
9514
9515     if (appData.debugMode) {
9516       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9517     }
9518
9519     if (gameMode != PlayFromGameFile)
9520       return FALSE;
9521
9522     if (currentMove >= forwardMostMove) {
9523       gameMode = EditGame;
9524       ModeHighlight();
9525
9526       /* [AS] Clear current move marker at the end of a game */
9527       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9528
9529       return FALSE;
9530     }
9531     
9532     toX = moveList[currentMove][2] - AAA;
9533     toY = moveList[currentMove][3] - ONE;
9534
9535     if (moveList[currentMove][1] == '@') {
9536         if (appData.highlightLastMove) {
9537             SetHighlights(-1, -1, toX, toY);
9538         }
9539     } else {
9540         fromX = moveList[currentMove][0] - AAA;
9541         fromY = moveList[currentMove][1] - ONE;
9542
9543         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9544
9545         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9546
9547         if (appData.highlightLastMove) {
9548             SetHighlights(fromX, fromY, toX, toY);
9549         }
9550     }
9551     DisplayMove(currentMove);
9552     SendMoveToProgram(currentMove++, &first);
9553     DisplayBothClocks();
9554     DrawPosition(FALSE, boards[currentMove]);
9555     // [HGM] PV info: always display, routine tests if empty
9556     DisplayComment(currentMove - 1, commentList[currentMove]);
9557     return TRUE;
9558 }
9559
9560
9561 int
9562 LoadGameOneMove(readAhead)
9563      ChessMove readAhead;
9564 {
9565     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9566     char promoChar = NULLCHAR;
9567     ChessMove moveType;
9568     char move[MSG_SIZ];
9569     char *p, *q;
9570     
9571     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9572         gameMode != AnalyzeMode && gameMode != Training) {
9573         gameFileFP = NULL;
9574         return FALSE;
9575     }
9576     
9577     yyboardindex = forwardMostMove;
9578     if (readAhead != (ChessMove)0) {
9579       moveType = readAhead;
9580     } else {
9581       if (gameFileFP == NULL)
9582           return FALSE;
9583       moveType = (ChessMove) yylex();
9584     }
9585     
9586     done = FALSE;
9587     switch (moveType) {
9588       case Comment:
9589         if (appData.debugMode) 
9590           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9591         p = yy_text;
9592
9593         /* append the comment but don't display it */
9594         AppendComment(currentMove, p, FALSE);
9595         return TRUE;
9596
9597       case WhiteCapturesEnPassant:
9598       case BlackCapturesEnPassant:
9599       case WhitePromotionChancellor:
9600       case BlackPromotionChancellor:
9601       case WhitePromotionArchbishop:
9602       case BlackPromotionArchbishop:
9603       case WhitePromotionCentaur:
9604       case BlackPromotionCentaur:
9605       case WhitePromotionQueen:
9606       case BlackPromotionQueen:
9607       case WhitePromotionRook:
9608       case BlackPromotionRook:
9609       case WhitePromotionBishop:
9610       case BlackPromotionBishop:
9611       case WhitePromotionKnight:
9612       case BlackPromotionKnight:
9613       case WhitePromotionKing:
9614       case BlackPromotionKing:
9615       case NormalMove:
9616       case WhiteKingSideCastle:
9617       case WhiteQueenSideCastle:
9618       case BlackKingSideCastle:
9619       case BlackQueenSideCastle:
9620       case WhiteKingSideCastleWild:
9621       case WhiteQueenSideCastleWild:
9622       case BlackKingSideCastleWild:
9623       case BlackQueenSideCastleWild:
9624       /* PUSH Fabien */
9625       case WhiteHSideCastleFR:
9626       case WhiteASideCastleFR:
9627       case BlackHSideCastleFR:
9628       case BlackASideCastleFR:
9629       /* POP Fabien */
9630         if (appData.debugMode)
9631           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9632         fromX = currentMoveString[0] - AAA;
9633         fromY = currentMoveString[1] - ONE;
9634         toX = currentMoveString[2] - AAA;
9635         toY = currentMoveString[3] - ONE;
9636         promoChar = currentMoveString[4];
9637         break;
9638
9639       case WhiteDrop:
9640       case BlackDrop:
9641         if (appData.debugMode)
9642           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9643         fromX = moveType == WhiteDrop ?
9644           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9645         (int) CharToPiece(ToLower(currentMoveString[0]));
9646         fromY = DROP_RANK;
9647         toX = currentMoveString[2] - AAA;
9648         toY = currentMoveString[3] - ONE;
9649         break;
9650
9651       case WhiteWins:
9652       case BlackWins:
9653       case GameIsDrawn:
9654       case GameUnfinished:
9655         if (appData.debugMode)
9656           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9657         p = strchr(yy_text, '{');
9658         if (p == NULL) p = strchr(yy_text, '(');
9659         if (p == NULL) {
9660             p = yy_text;
9661             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9662         } else {
9663             q = strchr(p, *p == '{' ? '}' : ')');
9664             if (q != NULL) *q = NULLCHAR;
9665             p++;
9666         }
9667         GameEnds(moveType, p, GE_FILE);
9668         done = TRUE;
9669         if (cmailMsgLoaded) {
9670             ClearHighlights();
9671             flipView = WhiteOnMove(currentMove);
9672             if (moveType == GameUnfinished) flipView = !flipView;
9673             if (appData.debugMode)
9674               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9675         }
9676         break;
9677
9678       case (ChessMove) 0:       /* end of file */
9679         if (appData.debugMode)
9680           fprintf(debugFP, "Parser hit end of file\n");
9681         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9682           case MT_NONE:
9683           case MT_CHECK:
9684             break;
9685           case MT_CHECKMATE:
9686           case MT_STAINMATE:
9687             if (WhiteOnMove(currentMove)) {
9688                 GameEnds(BlackWins, "Black mates", GE_FILE);
9689             } else {
9690                 GameEnds(WhiteWins, "White mates", GE_FILE);
9691             }
9692             break;
9693           case MT_STALEMATE:
9694             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9695             break;
9696         }
9697         done = TRUE;
9698         break;
9699
9700       case MoveNumberOne:
9701         if (lastLoadGameStart == GNUChessGame) {
9702             /* GNUChessGames have numbers, but they aren't move numbers */
9703             if (appData.debugMode)
9704               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9705                       yy_text, (int) moveType);
9706             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9707         }
9708         /* else fall thru */
9709
9710       case XBoardGame:
9711       case GNUChessGame:
9712       case PGNTag:
9713         /* Reached start of next game in file */
9714         if (appData.debugMode)
9715           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9716         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9717           case MT_NONE:
9718           case MT_CHECK:
9719             break;
9720           case MT_CHECKMATE:
9721           case MT_STAINMATE:
9722             if (WhiteOnMove(currentMove)) {
9723                 GameEnds(BlackWins, "Black mates", GE_FILE);
9724             } else {
9725                 GameEnds(WhiteWins, "White mates", GE_FILE);
9726             }
9727             break;
9728           case MT_STALEMATE:
9729             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9730             break;
9731         }
9732         done = TRUE;
9733         break;
9734
9735       case PositionDiagram:     /* should not happen; ignore */
9736       case ElapsedTime:         /* ignore */
9737       case NAG:                 /* ignore */
9738         if (appData.debugMode)
9739           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9740                   yy_text, (int) moveType);
9741         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9742
9743       case IllegalMove:
9744         if (appData.testLegality) {
9745             if (appData.debugMode)
9746               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9747             sprintf(move, _("Illegal move: %d.%s%s"),
9748                     (forwardMostMove / 2) + 1,
9749                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9750             DisplayError(move, 0);
9751             done = TRUE;
9752         } else {
9753             if (appData.debugMode)
9754               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9755                       yy_text, currentMoveString);
9756             fromX = currentMoveString[0] - AAA;
9757             fromY = currentMoveString[1] - ONE;
9758             toX = currentMoveString[2] - AAA;
9759             toY = currentMoveString[3] - ONE;
9760             promoChar = currentMoveString[4];
9761         }
9762         break;
9763
9764       case AmbiguousMove:
9765         if (appData.debugMode)
9766           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9767         sprintf(move, _("Ambiguous move: %d.%s%s"),
9768                 (forwardMostMove / 2) + 1,
9769                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9770         DisplayError(move, 0);
9771         done = TRUE;
9772         break;
9773
9774       default:
9775       case ImpossibleMove:
9776         if (appData.debugMode)
9777           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9778         sprintf(move, _("Illegal move: %d.%s%s"),
9779                 (forwardMostMove / 2) + 1,
9780                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9781         DisplayError(move, 0);
9782         done = TRUE;
9783         break;
9784     }
9785
9786     if (done) {
9787         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9788             DrawPosition(FALSE, boards[currentMove]);
9789             DisplayBothClocks();
9790             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9791               DisplayComment(currentMove - 1, commentList[currentMove]);
9792         }
9793         (void) StopLoadGameTimer();
9794         gameFileFP = NULL;
9795         cmailOldMove = forwardMostMove;
9796         return FALSE;
9797     } else {
9798         /* currentMoveString is set as a side-effect of yylex */
9799         strcat(currentMoveString, "\n");
9800         strcpy(moveList[forwardMostMove], currentMoveString);
9801         
9802         thinkOutput[0] = NULLCHAR;
9803         MakeMove(fromX, fromY, toX, toY, promoChar);
9804         currentMove = forwardMostMove;
9805         return TRUE;
9806     }
9807 }
9808
9809 /* Load the nth game from the given file */
9810 int
9811 LoadGameFromFile(filename, n, title, useList)
9812      char *filename;
9813      int n;
9814      char *title;
9815      /*Boolean*/ int useList;
9816 {
9817     FILE *f;
9818     char buf[MSG_SIZ];
9819
9820     if (strcmp(filename, "-") == 0) {
9821         f = stdin;
9822         title = "stdin";
9823     } else {
9824         f = fopen(filename, "rb");
9825         if (f == NULL) {
9826           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9827             DisplayError(buf, errno);
9828             return FALSE;
9829         }
9830     }
9831     if (fseek(f, 0, 0) == -1) {
9832         /* f is not seekable; probably a pipe */
9833         useList = FALSE;
9834     }
9835     if (useList && n == 0) {
9836         int error = GameListBuild(f);
9837         if (error) {
9838             DisplayError(_("Cannot build game list"), error);
9839         } else if (!ListEmpty(&gameList) &&
9840                    ((ListGame *) gameList.tailPred)->number > 1) {
9841             GameListPopUp(f, title);
9842             return TRUE;
9843         }
9844         GameListDestroy();
9845         n = 1;
9846     }
9847     if (n == 0) n = 1;
9848     return LoadGame(f, n, title, FALSE);
9849 }
9850
9851
9852 void
9853 MakeRegisteredMove()
9854 {
9855     int fromX, fromY, toX, toY;
9856     char promoChar;
9857     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9858         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9859           case CMAIL_MOVE:
9860           case CMAIL_DRAW:
9861             if (appData.debugMode)
9862               fprintf(debugFP, "Restoring %s for game %d\n",
9863                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9864     
9865             thinkOutput[0] = NULLCHAR;
9866             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9867             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9868             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9869             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9870             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9871             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9872             MakeMove(fromX, fromY, toX, toY, promoChar);
9873             ShowMove(fromX, fromY, toX, toY);
9874               
9875             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9876               case MT_NONE:
9877               case MT_CHECK:
9878                 break;
9879                 
9880               case MT_CHECKMATE:
9881               case MT_STAINMATE:
9882                 if (WhiteOnMove(currentMove)) {
9883                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9884                 } else {
9885                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9886                 }
9887                 break;
9888                 
9889               case MT_STALEMATE:
9890                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9891                 break;
9892             }
9893
9894             break;
9895             
9896           case CMAIL_RESIGN:
9897             if (WhiteOnMove(currentMove)) {
9898                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9899             } else {
9900                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9901             }
9902             break;
9903             
9904           case CMAIL_ACCEPT:
9905             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9906             break;
9907               
9908           default:
9909             break;
9910         }
9911     }
9912
9913     return;
9914 }
9915
9916 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9917 int
9918 CmailLoadGame(f, gameNumber, title, useList)
9919      FILE *f;
9920      int gameNumber;
9921      char *title;
9922      int useList;
9923 {
9924     int retVal;
9925
9926     if (gameNumber > nCmailGames) {
9927         DisplayError(_("No more games in this message"), 0);
9928         return FALSE;
9929     }
9930     if (f == lastLoadGameFP) {
9931         int offset = gameNumber - lastLoadGameNumber;
9932         if (offset == 0) {
9933             cmailMsg[0] = NULLCHAR;
9934             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9935                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9936                 nCmailMovesRegistered--;
9937             }
9938             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9939             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9940                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9941             }
9942         } else {
9943             if (! RegisterMove()) return FALSE;
9944         }
9945     }
9946
9947     retVal = LoadGame(f, gameNumber, title, useList);
9948
9949     /* Make move registered during previous look at this game, if any */
9950     MakeRegisteredMove();
9951
9952     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9953         commentList[currentMove]
9954           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9955         DisplayComment(currentMove - 1, commentList[currentMove]);
9956     }
9957
9958     return retVal;
9959 }
9960
9961 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9962 int
9963 ReloadGame(offset)
9964      int offset;
9965 {
9966     int gameNumber = lastLoadGameNumber + offset;
9967     if (lastLoadGameFP == NULL) {
9968         DisplayError(_("No game has been loaded yet"), 0);
9969         return FALSE;
9970     }
9971     if (gameNumber <= 0) {
9972         DisplayError(_("Can't back up any further"), 0);
9973         return FALSE;
9974     }
9975     if (cmailMsgLoaded) {
9976         return CmailLoadGame(lastLoadGameFP, gameNumber,
9977                              lastLoadGameTitle, lastLoadGameUseList);
9978     } else {
9979         return LoadGame(lastLoadGameFP, gameNumber,
9980                         lastLoadGameTitle, lastLoadGameUseList);
9981     }
9982 }
9983
9984
9985
9986 /* Load the nth game from open file f */
9987 int
9988 LoadGame(f, gameNumber, title, useList)
9989      FILE *f;
9990      int gameNumber;
9991      char *title;
9992      int useList;
9993 {
9994     ChessMove cm;
9995     char buf[MSG_SIZ];
9996     int gn = gameNumber;
9997     ListGame *lg = NULL;
9998     int numPGNTags = 0;
9999     int err;
10000     GameMode oldGameMode;
10001     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10002
10003     if (appData.debugMode) 
10004         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10005
10006     if (gameMode == Training )
10007         SetTrainingModeOff();
10008
10009     oldGameMode = gameMode;
10010     if (gameMode != BeginningOfGame) {
10011       Reset(FALSE, TRUE);
10012     }
10013
10014     gameFileFP = f;
10015     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10016         fclose(lastLoadGameFP);
10017     }
10018
10019     if (useList) {
10020         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10021         
10022         if (lg) {
10023             fseek(f, lg->offset, 0);
10024             GameListHighlight(gameNumber);
10025             gn = 1;
10026         }
10027         else {
10028             DisplayError(_("Game number out of range"), 0);
10029             return FALSE;
10030         }
10031     } else {
10032         GameListDestroy();
10033         if (fseek(f, 0, 0) == -1) {
10034             if (f == lastLoadGameFP ?
10035                 gameNumber == lastLoadGameNumber + 1 :
10036                 gameNumber == 1) {
10037                 gn = 1;
10038             } else {
10039                 DisplayError(_("Can't seek on game file"), 0);
10040                 return FALSE;
10041             }
10042         }
10043     }
10044     lastLoadGameFP = f;
10045     lastLoadGameNumber = gameNumber;
10046     strcpy(lastLoadGameTitle, title);
10047     lastLoadGameUseList = useList;
10048
10049     yynewfile(f);
10050
10051     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10052       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10053                 lg->gameInfo.black);
10054             DisplayTitle(buf);
10055     } else if (*title != NULLCHAR) {
10056         if (gameNumber > 1) {
10057             sprintf(buf, "%s %d", title, gameNumber);
10058             DisplayTitle(buf);
10059         } else {
10060             DisplayTitle(title);
10061         }
10062     }
10063
10064     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10065         gameMode = PlayFromGameFile;
10066         ModeHighlight();
10067     }
10068
10069     currentMove = forwardMostMove = backwardMostMove = 0;
10070     CopyBoard(boards[0], initialPosition);
10071     StopClocks();
10072
10073     /*
10074      * Skip the first gn-1 games in the file.
10075      * Also skip over anything that precedes an identifiable 
10076      * start of game marker, to avoid being confused by 
10077      * garbage at the start of the file.  Currently 
10078      * recognized start of game markers are the move number "1",
10079      * the pattern "gnuchess .* game", the pattern
10080      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10081      * A game that starts with one of the latter two patterns
10082      * will also have a move number 1, possibly
10083      * following a position diagram.
10084      * 5-4-02: Let's try being more lenient and allowing a game to
10085      * start with an unnumbered move.  Does that break anything?
10086      */
10087     cm = lastLoadGameStart = (ChessMove) 0;
10088     while (gn > 0) {
10089         yyboardindex = forwardMostMove;
10090         cm = (ChessMove) yylex();
10091         switch (cm) {
10092           case (ChessMove) 0:
10093             if (cmailMsgLoaded) {
10094                 nCmailGames = CMAIL_MAX_GAMES - gn;
10095             } else {
10096                 Reset(TRUE, TRUE);
10097                 DisplayError(_("Game not found in file"), 0);
10098             }
10099             return FALSE;
10100
10101           case GNUChessGame:
10102           case XBoardGame:
10103             gn--;
10104             lastLoadGameStart = cm;
10105             break;
10106             
10107           case MoveNumberOne:
10108             switch (lastLoadGameStart) {
10109               case GNUChessGame:
10110               case XBoardGame:
10111               case PGNTag:
10112                 break;
10113               case MoveNumberOne:
10114               case (ChessMove) 0:
10115                 gn--;           /* count this game */
10116                 lastLoadGameStart = cm;
10117                 break;
10118               default:
10119                 /* impossible */
10120                 break;
10121             }
10122             break;
10123
10124           case PGNTag:
10125             switch (lastLoadGameStart) {
10126               case GNUChessGame:
10127               case PGNTag:
10128               case MoveNumberOne:
10129               case (ChessMove) 0:
10130                 gn--;           /* count this game */
10131                 lastLoadGameStart = cm;
10132                 break;
10133               case XBoardGame:
10134                 lastLoadGameStart = cm; /* game counted already */
10135                 break;
10136               default:
10137                 /* impossible */
10138                 break;
10139             }
10140             if (gn > 0) {
10141                 do {
10142                     yyboardindex = forwardMostMove;
10143                     cm = (ChessMove) yylex();
10144                 } while (cm == PGNTag || cm == Comment);
10145             }
10146             break;
10147
10148           case WhiteWins:
10149           case BlackWins:
10150           case GameIsDrawn:
10151             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10152                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10153                     != CMAIL_OLD_RESULT) {
10154                     nCmailResults ++ ;
10155                     cmailResult[  CMAIL_MAX_GAMES
10156                                 - gn - 1] = CMAIL_OLD_RESULT;
10157                 }
10158             }
10159             break;
10160
10161           case NormalMove:
10162             /* Only a NormalMove can be at the start of a game
10163              * without a position diagram. */
10164             if (lastLoadGameStart == (ChessMove) 0) {
10165               gn--;
10166               lastLoadGameStart = MoveNumberOne;
10167             }
10168             break;
10169
10170           default:
10171             break;
10172         }
10173     }
10174     
10175     if (appData.debugMode)
10176       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10177
10178     if (cm == XBoardGame) {
10179         /* Skip any header junk before position diagram and/or move 1 */
10180         for (;;) {
10181             yyboardindex = forwardMostMove;
10182             cm = (ChessMove) yylex();
10183
10184             if (cm == (ChessMove) 0 ||
10185                 cm == GNUChessGame || cm == XBoardGame) {
10186                 /* Empty game; pretend end-of-file and handle later */
10187                 cm = (ChessMove) 0;
10188                 break;
10189             }
10190
10191             if (cm == MoveNumberOne || cm == PositionDiagram ||
10192                 cm == PGNTag || cm == Comment)
10193               break;
10194         }
10195     } else if (cm == GNUChessGame) {
10196         if (gameInfo.event != NULL) {
10197             free(gameInfo.event);
10198         }
10199         gameInfo.event = StrSave(yy_text);
10200     }   
10201
10202     startedFromSetupPosition = FALSE;
10203     while (cm == PGNTag) {
10204         if (appData.debugMode) 
10205           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10206         err = ParsePGNTag(yy_text, &gameInfo);
10207         if (!err) numPGNTags++;
10208
10209         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10210         if(gameInfo.variant != oldVariant) {
10211             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10212             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10213             InitPosition(TRUE);
10214             oldVariant = gameInfo.variant;
10215             if (appData.debugMode) 
10216               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10217         }
10218
10219
10220         if (gameInfo.fen != NULL) {
10221           Board initial_position;
10222           startedFromSetupPosition = TRUE;
10223           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10224             Reset(TRUE, TRUE);
10225             DisplayError(_("Bad FEN position in file"), 0);
10226             return FALSE;
10227           }
10228           CopyBoard(boards[0], initial_position);
10229           if (blackPlaysFirst) {
10230             currentMove = forwardMostMove = backwardMostMove = 1;
10231             CopyBoard(boards[1], initial_position);
10232             strcpy(moveList[0], "");
10233             strcpy(parseList[0], "");
10234             timeRemaining[0][1] = whiteTimeRemaining;
10235             timeRemaining[1][1] = blackTimeRemaining;
10236             if (commentList[0] != NULL) {
10237               commentList[1] = commentList[0];
10238               commentList[0] = NULL;
10239             }
10240           } else {
10241             currentMove = forwardMostMove = backwardMostMove = 0;
10242           }
10243           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10244           {   int i;
10245               initialRulePlies = FENrulePlies;
10246               for( i=0; i< nrCastlingRights; i++ )
10247                   initialRights[i] = initial_position[CASTLING][i];
10248           }
10249           yyboardindex = forwardMostMove;
10250           free(gameInfo.fen);
10251           gameInfo.fen = NULL;
10252         }
10253
10254         yyboardindex = forwardMostMove;
10255         cm = (ChessMove) yylex();
10256
10257         /* Handle comments interspersed among the tags */
10258         while (cm == Comment) {
10259             char *p;
10260             if (appData.debugMode) 
10261               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10262             p = yy_text;
10263             AppendComment(currentMove, p, FALSE);
10264             yyboardindex = forwardMostMove;
10265             cm = (ChessMove) yylex();
10266         }
10267     }
10268
10269     /* don't rely on existence of Event tag since if game was
10270      * pasted from clipboard the Event tag may not exist
10271      */
10272     if (numPGNTags > 0){
10273         char *tags;
10274         if (gameInfo.variant == VariantNormal) {
10275           VariantClass v = StringToVariant(gameInfo.event);
10276           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10277           if(v < VariantShogi) gameInfo.variant = v;
10278         }
10279         if (!matchMode) {
10280           if( appData.autoDisplayTags ) {
10281             tags = PGNTags(&gameInfo);
10282             TagsPopUp(tags, CmailMsg());
10283             free(tags);
10284           }
10285         }
10286     } else {
10287         /* Make something up, but don't display it now */
10288         SetGameInfo();
10289         TagsPopDown();
10290     }
10291
10292     if (cm == PositionDiagram) {
10293         int i, j;
10294         char *p;
10295         Board initial_position;
10296
10297         if (appData.debugMode)
10298           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10299
10300         if (!startedFromSetupPosition) {
10301             p = yy_text;
10302             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10303               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10304                 switch (*p) {
10305                   case '[':
10306                   case '-':
10307                   case ' ':
10308                   case '\t':
10309                   case '\n':
10310                   case '\r':
10311                     break;
10312                   default:
10313                     initial_position[i][j++] = CharToPiece(*p);
10314                     break;
10315                 }
10316             while (*p == ' ' || *p == '\t' ||
10317                    *p == '\n' || *p == '\r') p++;
10318         
10319             if (strncmp(p, "black", strlen("black"))==0)
10320               blackPlaysFirst = TRUE;
10321             else
10322               blackPlaysFirst = FALSE;
10323             startedFromSetupPosition = TRUE;
10324         
10325             CopyBoard(boards[0], initial_position);
10326             if (blackPlaysFirst) {
10327                 currentMove = forwardMostMove = backwardMostMove = 1;
10328                 CopyBoard(boards[1], initial_position);
10329                 strcpy(moveList[0], "");
10330                 strcpy(parseList[0], "");
10331                 timeRemaining[0][1] = whiteTimeRemaining;
10332                 timeRemaining[1][1] = blackTimeRemaining;
10333                 if (commentList[0] != NULL) {
10334                     commentList[1] = commentList[0];
10335                     commentList[0] = NULL;
10336                 }
10337             } else {
10338                 currentMove = forwardMostMove = backwardMostMove = 0;
10339             }
10340         }
10341         yyboardindex = forwardMostMove;
10342         cm = (ChessMove) yylex();
10343     }
10344
10345     if (first.pr == NoProc) {
10346         StartChessProgram(&first);
10347     }
10348     InitChessProgram(&first, FALSE);
10349     SendToProgram("force\n", &first);
10350     if (startedFromSetupPosition) {
10351         SendBoard(&first, forwardMostMove);
10352     if (appData.debugMode) {
10353         fprintf(debugFP, "Load Game\n");
10354     }
10355         DisplayBothClocks();
10356     }      
10357
10358     /* [HGM] server: flag to write setup moves in broadcast file as one */
10359     loadFlag = appData.suppressLoadMoves;
10360
10361     while (cm == Comment) {
10362         char *p;
10363         if (appData.debugMode) 
10364           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10365         p = yy_text;
10366         AppendComment(currentMove, p, FALSE);
10367         yyboardindex = forwardMostMove;
10368         cm = (ChessMove) yylex();
10369     }
10370
10371     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10372         cm == WhiteWins || cm == BlackWins ||
10373         cm == GameIsDrawn || cm == GameUnfinished) {
10374         DisplayMessage("", _("No moves in game"));
10375         if (cmailMsgLoaded) {
10376             if (appData.debugMode)
10377               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10378             ClearHighlights();
10379             flipView = FALSE;
10380         }
10381         DrawPosition(FALSE, boards[currentMove]);
10382         DisplayBothClocks();
10383         gameMode = EditGame;
10384         ModeHighlight();
10385         gameFileFP = NULL;
10386         cmailOldMove = 0;
10387         return TRUE;
10388     }
10389
10390     // [HGM] PV info: routine tests if comment empty
10391     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10392         DisplayComment(currentMove - 1, commentList[currentMove]);
10393     }
10394     if (!matchMode && appData.timeDelay != 0) 
10395       DrawPosition(FALSE, boards[currentMove]);
10396
10397     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10398       programStats.ok_to_send = 1;
10399     }
10400
10401     /* if the first token after the PGN tags is a move
10402      * and not move number 1, retrieve it from the parser 
10403      */
10404     if (cm != MoveNumberOne)
10405         LoadGameOneMove(cm);
10406
10407     /* load the remaining moves from the file */
10408     while (LoadGameOneMove((ChessMove)0)) {
10409       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10410       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10411     }
10412
10413     /* rewind to the start of the game */
10414     currentMove = backwardMostMove;
10415
10416     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10417
10418     if (oldGameMode == AnalyzeFile ||
10419         oldGameMode == AnalyzeMode) {
10420       AnalyzeFileEvent();
10421     }
10422
10423     if (matchMode || appData.timeDelay == 0) {
10424       ToEndEvent();
10425       gameMode = EditGame;
10426       ModeHighlight();
10427     } else if (appData.timeDelay > 0) {
10428       AutoPlayGameLoop();
10429     }
10430
10431     if (appData.debugMode) 
10432         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10433
10434     loadFlag = 0; /* [HGM] true game starts */
10435     return TRUE;
10436 }
10437
10438 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10439 int
10440 ReloadPosition(offset)
10441      int offset;
10442 {
10443     int positionNumber = lastLoadPositionNumber + offset;
10444     if (lastLoadPositionFP == NULL) {
10445         DisplayError(_("No position has been loaded yet"), 0);
10446         return FALSE;
10447     }
10448     if (positionNumber <= 0) {
10449         DisplayError(_("Can't back up any further"), 0);
10450         return FALSE;
10451     }
10452     return LoadPosition(lastLoadPositionFP, positionNumber,
10453                         lastLoadPositionTitle);
10454 }
10455
10456 /* Load the nth position from the given file */
10457 int
10458 LoadPositionFromFile(filename, n, title)
10459      char *filename;
10460      int n;
10461      char *title;
10462 {
10463     FILE *f;
10464     char buf[MSG_SIZ];
10465
10466     if (strcmp(filename, "-") == 0) {
10467         return LoadPosition(stdin, n, "stdin");
10468     } else {
10469         f = fopen(filename, "rb");
10470         if (f == NULL) {
10471             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10472             DisplayError(buf, errno);
10473             return FALSE;
10474         } else {
10475             return LoadPosition(f, n, title);
10476         }
10477     }
10478 }
10479
10480 /* Load the nth position from the given open file, and close it */
10481 int
10482 LoadPosition(f, positionNumber, title)
10483      FILE *f;
10484      int positionNumber;
10485      char *title;
10486 {
10487     char *p, line[MSG_SIZ];
10488     Board initial_position;
10489     int i, j, fenMode, pn;
10490     
10491     if (gameMode == Training )
10492         SetTrainingModeOff();
10493
10494     if (gameMode != BeginningOfGame) {
10495         Reset(FALSE, TRUE);
10496     }
10497     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10498         fclose(lastLoadPositionFP);
10499     }
10500     if (positionNumber == 0) positionNumber = 1;
10501     lastLoadPositionFP = f;
10502     lastLoadPositionNumber = positionNumber;
10503     strcpy(lastLoadPositionTitle, title);
10504     if (first.pr == NoProc) {
10505       StartChessProgram(&first);
10506       InitChessProgram(&first, FALSE);
10507     }    
10508     pn = positionNumber;
10509     if (positionNumber < 0) {
10510         /* Negative position number means to seek to that byte offset */
10511         if (fseek(f, -positionNumber, 0) == -1) {
10512             DisplayError(_("Can't seek on position file"), 0);
10513             return FALSE;
10514         };
10515         pn = 1;
10516     } else {
10517         if (fseek(f, 0, 0) == -1) {
10518             if (f == lastLoadPositionFP ?
10519                 positionNumber == lastLoadPositionNumber + 1 :
10520                 positionNumber == 1) {
10521                 pn = 1;
10522             } else {
10523                 DisplayError(_("Can't seek on position file"), 0);
10524                 return FALSE;
10525             }
10526         }
10527     }
10528     /* See if this file is FEN or old-style xboard */
10529     if (fgets(line, MSG_SIZ, f) == NULL) {
10530         DisplayError(_("Position not found in file"), 0);
10531         return FALSE;
10532     }
10533     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10534     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10535
10536     if (pn >= 2) {
10537         if (fenMode || line[0] == '#') pn--;
10538         while (pn > 0) {
10539             /* skip positions before number pn */
10540             if (fgets(line, MSG_SIZ, f) == NULL) {
10541                 Reset(TRUE, TRUE);
10542                 DisplayError(_("Position not found in file"), 0);
10543                 return FALSE;
10544             }
10545             if (fenMode || line[0] == '#') pn--;
10546         }
10547     }
10548
10549     if (fenMode) {
10550         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10551             DisplayError(_("Bad FEN position in file"), 0);
10552             return FALSE;
10553         }
10554     } else {
10555         (void) fgets(line, MSG_SIZ, f);
10556         (void) fgets(line, MSG_SIZ, f);
10557     
10558         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10559             (void) fgets(line, MSG_SIZ, f);
10560             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10561                 if (*p == ' ')
10562                   continue;
10563                 initial_position[i][j++] = CharToPiece(*p);
10564             }
10565         }
10566     
10567         blackPlaysFirst = FALSE;
10568         if (!feof(f)) {
10569             (void) fgets(line, MSG_SIZ, f);
10570             if (strncmp(line, "black", strlen("black"))==0)
10571               blackPlaysFirst = TRUE;
10572         }
10573     }
10574     startedFromSetupPosition = TRUE;
10575     
10576     SendToProgram("force\n", &first);
10577     CopyBoard(boards[0], initial_position);
10578     if (blackPlaysFirst) {
10579         currentMove = forwardMostMove = backwardMostMove = 1;
10580         strcpy(moveList[0], "");
10581         strcpy(parseList[0], "");
10582         CopyBoard(boards[1], initial_position);
10583         DisplayMessage("", _("Black to play"));
10584     } else {
10585         currentMove = forwardMostMove = backwardMostMove = 0;
10586         DisplayMessage("", _("White to play"));
10587     }
10588     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10589     SendBoard(&first, forwardMostMove);
10590     if (appData.debugMode) {
10591 int i, j;
10592   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10593   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10594         fprintf(debugFP, "Load Position\n");
10595     }
10596
10597     if (positionNumber > 1) {
10598         sprintf(line, "%s %d", title, positionNumber);
10599         DisplayTitle(line);
10600     } else {
10601         DisplayTitle(title);
10602     }
10603     gameMode = EditGame;
10604     ModeHighlight();
10605     ResetClocks();
10606     timeRemaining[0][1] = whiteTimeRemaining;
10607     timeRemaining[1][1] = blackTimeRemaining;
10608     DrawPosition(FALSE, boards[currentMove]);
10609    
10610     return TRUE;
10611 }
10612
10613
10614 void
10615 CopyPlayerNameIntoFileName(dest, src)
10616      char **dest, *src;
10617 {
10618     while (*src != NULLCHAR && *src != ',') {
10619         if (*src == ' ') {
10620             *(*dest)++ = '_';
10621             src++;
10622         } else {
10623             *(*dest)++ = *src++;
10624         }
10625     }
10626 }
10627
10628 char *DefaultFileName(ext)
10629      char *ext;
10630 {
10631     static char def[MSG_SIZ];
10632     char *p;
10633
10634     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10635         p = def;
10636         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10637         *p++ = '-';
10638         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10639         *p++ = '.';
10640         strcpy(p, ext);
10641     } else {
10642         def[0] = NULLCHAR;
10643     }
10644     return def;
10645 }
10646
10647 /* Save the current game to the given file */
10648 int
10649 SaveGameToFile(filename, append)
10650      char *filename;
10651      int append;
10652 {
10653     FILE *f;
10654     char buf[MSG_SIZ];
10655
10656     if (strcmp(filename, "-") == 0) {
10657         return SaveGame(stdout, 0, NULL);
10658     } else {
10659         f = fopen(filename, append ? "a" : "w");
10660         if (f == NULL) {
10661             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10662             DisplayError(buf, errno);
10663             return FALSE;
10664         } else {
10665             return SaveGame(f, 0, NULL);
10666         }
10667     }
10668 }
10669
10670 char *
10671 SavePart(str)
10672      char *str;
10673 {
10674     static char buf[MSG_SIZ];
10675     char *p;
10676     
10677     p = strchr(str, ' ');
10678     if (p == NULL) return str;
10679     strncpy(buf, str, p - str);
10680     buf[p - str] = NULLCHAR;
10681     return buf;
10682 }
10683
10684 #define PGN_MAX_LINE 75
10685
10686 #define PGN_SIDE_WHITE  0
10687 #define PGN_SIDE_BLACK  1
10688
10689 /* [AS] */
10690 static int FindFirstMoveOutOfBook( int side )
10691 {
10692     int result = -1;
10693
10694     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10695         int index = backwardMostMove;
10696         int has_book_hit = 0;
10697
10698         if( (index % 2) != side ) {
10699             index++;
10700         }
10701
10702         while( index < forwardMostMove ) {
10703             /* Check to see if engine is in book */
10704             int depth = pvInfoList[index].depth;
10705             int score = pvInfoList[index].score;
10706             int in_book = 0;
10707
10708             if( depth <= 2 ) {
10709                 in_book = 1;
10710             }
10711             else if( score == 0 && depth == 63 ) {
10712                 in_book = 1; /* Zappa */
10713             }
10714             else if( score == 2 && depth == 99 ) {
10715                 in_book = 1; /* Abrok */
10716             }
10717
10718             has_book_hit += in_book;
10719
10720             if( ! in_book ) {
10721                 result = index;
10722
10723                 break;
10724             }
10725
10726             index += 2;
10727         }
10728     }
10729
10730     return result;
10731 }
10732
10733 /* [AS] */
10734 void GetOutOfBookInfo( char * buf )
10735 {
10736     int oob[2];
10737     int i;
10738     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10739
10740     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10741     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10742
10743     *buf = '\0';
10744
10745     if( oob[0] >= 0 || oob[1] >= 0 ) {
10746         for( i=0; i<2; i++ ) {
10747             int idx = oob[i];
10748
10749             if( idx >= 0 ) {
10750                 if( i > 0 && oob[0] >= 0 ) {
10751                     strcat( buf, "   " );
10752                 }
10753
10754                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10755                 sprintf( buf+strlen(buf), "%s%.2f", 
10756                     pvInfoList[idx].score >= 0 ? "+" : "",
10757                     pvInfoList[idx].score / 100.0 );
10758             }
10759         }
10760     }
10761 }
10762
10763 /* Save game in PGN style and close the file */
10764 int
10765 SaveGamePGN(f)
10766      FILE *f;
10767 {
10768     int i, offset, linelen, newblock;
10769     time_t tm;
10770 //    char *movetext;
10771     char numtext[32];
10772     int movelen, numlen, blank;
10773     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10774
10775     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10776     
10777     tm = time((time_t *) NULL);
10778     
10779     PrintPGNTags(f, &gameInfo);
10780     
10781     if (backwardMostMove > 0 || startedFromSetupPosition) {
10782         char *fen = PositionToFEN(backwardMostMove, NULL);
10783         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10784         fprintf(f, "\n{--------------\n");
10785         PrintPosition(f, backwardMostMove);
10786         fprintf(f, "--------------}\n");
10787         free(fen);
10788     }
10789     else {
10790         /* [AS] Out of book annotation */
10791         if( appData.saveOutOfBookInfo ) {
10792             char buf[64];
10793
10794             GetOutOfBookInfo( buf );
10795
10796             if( buf[0] != '\0' ) {
10797                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10798             }
10799         }
10800
10801         fprintf(f, "\n");
10802     }
10803
10804     i = backwardMostMove;
10805     linelen = 0;
10806     newblock = TRUE;
10807
10808     while (i < forwardMostMove) {
10809         /* Print comments preceding this move */
10810         if (commentList[i] != NULL) {
10811             if (linelen > 0) fprintf(f, "\n");
10812             fprintf(f, "%s", commentList[i]);
10813             linelen = 0;
10814             newblock = TRUE;
10815         }
10816
10817         /* Format move number */
10818         if ((i % 2) == 0) {
10819             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10820         } else {
10821             if (newblock) {
10822                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10823             } else {
10824                 numtext[0] = NULLCHAR;
10825             }
10826         }
10827         numlen = strlen(numtext);
10828         newblock = FALSE;
10829
10830         /* Print move number */
10831         blank = linelen > 0 && numlen > 0;
10832         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10833             fprintf(f, "\n");
10834             linelen = 0;
10835             blank = 0;
10836         }
10837         if (blank) {
10838             fprintf(f, " ");
10839             linelen++;
10840         }
10841         fprintf(f, "%s", numtext);
10842         linelen += numlen;
10843
10844         /* Get move */
10845         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10846         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10847
10848         /* Print move */
10849         blank = linelen > 0 && movelen > 0;
10850         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10851             fprintf(f, "\n");
10852             linelen = 0;
10853             blank = 0;
10854         }
10855         if (blank) {
10856             fprintf(f, " ");
10857             linelen++;
10858         }
10859         fprintf(f, "%s", move_buffer);
10860         linelen += movelen;
10861
10862         /* [AS] Add PV info if present */
10863         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10864             /* [HGM] add time */
10865             char buf[MSG_SIZ]; int seconds;
10866
10867             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10868
10869             if( seconds <= 0) buf[0] = 0; else
10870             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10871                 seconds = (seconds + 4)/10; // round to full seconds
10872                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10873                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10874             }
10875
10876             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10877                 pvInfoList[i].score >= 0 ? "+" : "",
10878                 pvInfoList[i].score / 100.0,
10879                 pvInfoList[i].depth,
10880                 buf );
10881
10882             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10883
10884             /* Print score/depth */
10885             blank = linelen > 0 && movelen > 0;
10886             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10887                 fprintf(f, "\n");
10888                 linelen = 0;
10889                 blank = 0;
10890             }
10891             if (blank) {
10892                 fprintf(f, " ");
10893                 linelen++;
10894             }
10895             fprintf(f, "%s", move_buffer);
10896             linelen += movelen;
10897         }
10898
10899         i++;
10900     }
10901     
10902     /* Start a new line */
10903     if (linelen > 0) fprintf(f, "\n");
10904
10905     /* Print comments after last move */
10906     if (commentList[i] != NULL) {
10907         fprintf(f, "%s\n", commentList[i]);
10908     }
10909
10910     /* Print result */
10911     if (gameInfo.resultDetails != NULL &&
10912         gameInfo.resultDetails[0] != NULLCHAR) {
10913         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10914                 PGNResult(gameInfo.result));
10915     } else {
10916         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10917     }
10918
10919     fclose(f);
10920     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10921     return TRUE;
10922 }
10923
10924 /* Save game in old style and close the file */
10925 int
10926 SaveGameOldStyle(f)
10927      FILE *f;
10928 {
10929     int i, offset;
10930     time_t tm;
10931     
10932     tm = time((time_t *) NULL);
10933     
10934     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10935     PrintOpponents(f);
10936     
10937     if (backwardMostMove > 0 || startedFromSetupPosition) {
10938         fprintf(f, "\n[--------------\n");
10939         PrintPosition(f, backwardMostMove);
10940         fprintf(f, "--------------]\n");
10941     } else {
10942         fprintf(f, "\n");
10943     }
10944
10945     i = backwardMostMove;
10946     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10947
10948     while (i < forwardMostMove) {
10949         if (commentList[i] != NULL) {
10950             fprintf(f, "[%s]\n", commentList[i]);
10951         }
10952
10953         if ((i % 2) == 1) {
10954             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10955             i++;
10956         } else {
10957             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10958             i++;
10959             if (commentList[i] != NULL) {
10960                 fprintf(f, "\n");
10961                 continue;
10962             }
10963             if (i >= forwardMostMove) {
10964                 fprintf(f, "\n");
10965                 break;
10966             }
10967             fprintf(f, "%s\n", parseList[i]);
10968             i++;
10969         }
10970     }
10971     
10972     if (commentList[i] != NULL) {
10973         fprintf(f, "[%s]\n", commentList[i]);
10974     }
10975
10976     /* This isn't really the old style, but it's close enough */
10977     if (gameInfo.resultDetails != NULL &&
10978         gameInfo.resultDetails[0] != NULLCHAR) {
10979         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10980                 gameInfo.resultDetails);
10981     } else {
10982         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10983     }
10984
10985     fclose(f);
10986     return TRUE;
10987 }
10988
10989 /* Save the current game to open file f and close the file */
10990 int
10991 SaveGame(f, dummy, dummy2)
10992      FILE *f;
10993      int dummy;
10994      char *dummy2;
10995 {
10996     if (gameMode == EditPosition) EditPositionDone(TRUE);
10997     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10998     if (appData.oldSaveStyle)
10999       return SaveGameOldStyle(f);
11000     else
11001       return SaveGamePGN(f);
11002 }
11003
11004 /* Save the current position to the given file */
11005 int
11006 SavePositionToFile(filename)
11007      char *filename;
11008 {
11009     FILE *f;
11010     char buf[MSG_SIZ];
11011
11012     if (strcmp(filename, "-") == 0) {
11013         return SavePosition(stdout, 0, NULL);
11014     } else {
11015         f = fopen(filename, "a");
11016         if (f == NULL) {
11017             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11018             DisplayError(buf, errno);
11019             return FALSE;
11020         } else {
11021             SavePosition(f, 0, NULL);
11022             return TRUE;
11023         }
11024     }
11025 }
11026
11027 /* Save the current position to the given open file and close the file */
11028 int
11029 SavePosition(f, dummy, dummy2)
11030      FILE *f;
11031      int dummy;
11032      char *dummy2;
11033 {
11034     time_t tm;
11035     char *fen;
11036     
11037     if (gameMode == EditPosition) EditPositionDone(TRUE);
11038     if (appData.oldSaveStyle) {
11039         tm = time((time_t *) NULL);
11040     
11041         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11042         PrintOpponents(f);
11043         fprintf(f, "[--------------\n");
11044         PrintPosition(f, currentMove);
11045         fprintf(f, "--------------]\n");
11046     } else {
11047         fen = PositionToFEN(currentMove, NULL);
11048         fprintf(f, "%s\n", fen);
11049         free(fen);
11050     }
11051     fclose(f);
11052     return TRUE;
11053 }
11054
11055 void
11056 ReloadCmailMsgEvent(unregister)
11057      int unregister;
11058 {
11059 #if !WIN32
11060     static char *inFilename = NULL;
11061     static char *outFilename;
11062     int i;
11063     struct stat inbuf, outbuf;
11064     int status;
11065     
11066     /* Any registered moves are unregistered if unregister is set, */
11067     /* i.e. invoked by the signal handler */
11068     if (unregister) {
11069         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11070             cmailMoveRegistered[i] = FALSE;
11071             if (cmailCommentList[i] != NULL) {
11072                 free(cmailCommentList[i]);
11073                 cmailCommentList[i] = NULL;
11074             }
11075         }
11076         nCmailMovesRegistered = 0;
11077     }
11078
11079     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11080         cmailResult[i] = CMAIL_NOT_RESULT;
11081     }
11082     nCmailResults = 0;
11083
11084     if (inFilename == NULL) {
11085         /* Because the filenames are static they only get malloced once  */
11086         /* and they never get freed                                      */
11087         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11088         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11089
11090         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11091         sprintf(outFilename, "%s.out", appData.cmailGameName);
11092     }
11093     
11094     status = stat(outFilename, &outbuf);
11095     if (status < 0) {
11096         cmailMailedMove = FALSE;
11097     } else {
11098         status = stat(inFilename, &inbuf);
11099         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11100     }
11101     
11102     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11103        counts the games, notes how each one terminated, etc.
11104        
11105        It would be nice to remove this kludge and instead gather all
11106        the information while building the game list.  (And to keep it
11107        in the game list nodes instead of having a bunch of fixed-size
11108        parallel arrays.)  Note this will require getting each game's
11109        termination from the PGN tags, as the game list builder does
11110        not process the game moves.  --mann
11111        */
11112     cmailMsgLoaded = TRUE;
11113     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11114     
11115     /* Load first game in the file or popup game menu */
11116     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11117
11118 #endif /* !WIN32 */
11119     return;
11120 }
11121
11122 int
11123 RegisterMove()
11124 {
11125     FILE *f;
11126     char string[MSG_SIZ];
11127
11128     if (   cmailMailedMove
11129         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11130         return TRUE;            /* Allow free viewing  */
11131     }
11132
11133     /* Unregister move to ensure that we don't leave RegisterMove        */
11134     /* with the move registered when the conditions for registering no   */
11135     /* longer hold                                                       */
11136     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11137         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11138         nCmailMovesRegistered --;
11139
11140         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11141           {
11142               free(cmailCommentList[lastLoadGameNumber - 1]);
11143               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11144           }
11145     }
11146
11147     if (cmailOldMove == -1) {
11148         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11149         return FALSE;
11150     }
11151
11152     if (currentMove > cmailOldMove + 1) {
11153         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11154         return FALSE;
11155     }
11156
11157     if (currentMove < cmailOldMove) {
11158         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11159         return FALSE;
11160     }
11161
11162     if (forwardMostMove > currentMove) {
11163         /* Silently truncate extra moves */
11164         TruncateGame();
11165     }
11166
11167     if (   (currentMove == cmailOldMove + 1)
11168         || (   (currentMove == cmailOldMove)
11169             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11170                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11171         if (gameInfo.result != GameUnfinished) {
11172             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11173         }
11174
11175         if (commentList[currentMove] != NULL) {
11176             cmailCommentList[lastLoadGameNumber - 1]
11177               = StrSave(commentList[currentMove]);
11178         }
11179         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11180
11181         if (appData.debugMode)
11182           fprintf(debugFP, "Saving %s for game %d\n",
11183                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11184
11185         sprintf(string,
11186                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11187         
11188         f = fopen(string, "w");
11189         if (appData.oldSaveStyle) {
11190             SaveGameOldStyle(f); /* also closes the file */
11191             
11192             sprintf(string, "%s.pos.out", appData.cmailGameName);
11193             f = fopen(string, "w");
11194             SavePosition(f, 0, NULL); /* also closes the file */
11195         } else {
11196             fprintf(f, "{--------------\n");
11197             PrintPosition(f, currentMove);
11198             fprintf(f, "--------------}\n\n");
11199             
11200             SaveGame(f, 0, NULL); /* also closes the file*/
11201         }
11202         
11203         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11204         nCmailMovesRegistered ++;
11205     } else if (nCmailGames == 1) {
11206         DisplayError(_("You have not made a move yet"), 0);
11207         return FALSE;
11208     }
11209
11210     return TRUE;
11211 }
11212
11213 void
11214 MailMoveEvent()
11215 {
11216 #if !WIN32
11217     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11218     FILE *commandOutput;
11219     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11220     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11221     int nBuffers;
11222     int i;
11223     int archived;
11224     char *arcDir;
11225
11226     if (! cmailMsgLoaded) {
11227         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11228         return;
11229     }
11230
11231     if (nCmailGames == nCmailResults) {
11232         DisplayError(_("No unfinished games"), 0);
11233         return;
11234     }
11235
11236 #if CMAIL_PROHIBIT_REMAIL
11237     if (cmailMailedMove) {
11238         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);
11239         DisplayError(msg, 0);
11240         return;
11241     }
11242 #endif
11243
11244     if (! (cmailMailedMove || RegisterMove())) return;
11245     
11246     if (   cmailMailedMove
11247         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11248         sprintf(string, partCommandString,
11249                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11250         commandOutput = popen(string, "r");
11251
11252         if (commandOutput == NULL) {
11253             DisplayError(_("Failed to invoke cmail"), 0);
11254         } else {
11255             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11256                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11257             }
11258             if (nBuffers > 1) {
11259                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11260                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11261                 nBytes = MSG_SIZ - 1;
11262             } else {
11263                 (void) memcpy(msg, buffer, nBytes);
11264             }
11265             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11266
11267             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11268                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11269
11270                 archived = TRUE;
11271                 for (i = 0; i < nCmailGames; i ++) {
11272                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11273                         archived = FALSE;
11274                     }
11275                 }
11276                 if (   archived
11277                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11278                         != NULL)) {
11279                     sprintf(buffer, "%s/%s.%s.archive",
11280                             arcDir,
11281                             appData.cmailGameName,
11282                             gameInfo.date);
11283                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11284                     cmailMsgLoaded = FALSE;
11285                 }
11286             }
11287
11288             DisplayInformation(msg);
11289             pclose(commandOutput);
11290         }
11291     } else {
11292         if ((*cmailMsg) != '\0') {
11293             DisplayInformation(cmailMsg);
11294         }
11295     }
11296
11297     return;
11298 #endif /* !WIN32 */
11299 }
11300
11301 char *
11302 CmailMsg()
11303 {
11304 #if WIN32
11305     return NULL;
11306 #else
11307     int  prependComma = 0;
11308     char number[5];
11309     char string[MSG_SIZ];       /* Space for game-list */
11310     int  i;
11311     
11312     if (!cmailMsgLoaded) return "";
11313
11314     if (cmailMailedMove) {
11315         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11316     } else {
11317         /* Create a list of games left */
11318         sprintf(string, "[");
11319         for (i = 0; i < nCmailGames; i ++) {
11320             if (! (   cmailMoveRegistered[i]
11321                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11322                 if (prependComma) {
11323                     sprintf(number, ",%d", i + 1);
11324                 } else {
11325                     sprintf(number, "%d", i + 1);
11326                     prependComma = 1;
11327                 }
11328                 
11329                 strcat(string, number);
11330             }
11331         }
11332         strcat(string, "]");
11333
11334         if (nCmailMovesRegistered + nCmailResults == 0) {
11335             switch (nCmailGames) {
11336               case 1:
11337                 sprintf(cmailMsg,
11338                         _("Still need to make move for game\n"));
11339                 break;
11340                 
11341               case 2:
11342                 sprintf(cmailMsg,
11343                         _("Still need to make moves for both games\n"));
11344                 break;
11345                 
11346               default:
11347                 sprintf(cmailMsg,
11348                         _("Still need to make moves for all %d games\n"),
11349                         nCmailGames);
11350                 break;
11351             }
11352         } else {
11353             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11354               case 1:
11355                 sprintf(cmailMsg,
11356                         _("Still need to make a move for game %s\n"),
11357                         string);
11358                 break;
11359                 
11360               case 0:
11361                 if (nCmailResults == nCmailGames) {
11362                     sprintf(cmailMsg, _("No unfinished games\n"));
11363                 } else {
11364                     sprintf(cmailMsg, _("Ready to send mail\n"));
11365                 }
11366                 break;
11367                 
11368               default:
11369                 sprintf(cmailMsg,
11370                         _("Still need to make moves for games %s\n"),
11371                         string);
11372             }
11373         }
11374     }
11375     return cmailMsg;
11376 #endif /* WIN32 */
11377 }
11378
11379 void
11380 ResetGameEvent()
11381 {
11382     if (gameMode == Training)
11383       SetTrainingModeOff();
11384
11385     Reset(TRUE, TRUE);
11386     cmailMsgLoaded = FALSE;
11387     if (appData.icsActive) {
11388       SendToICS(ics_prefix);
11389       SendToICS("refresh\n");
11390     }
11391 }
11392
11393 void
11394 ExitEvent(status)
11395      int status;
11396 {
11397     exiting++;
11398     if (exiting > 2) {
11399       /* Give up on clean exit */
11400       exit(status);
11401     }
11402     if (exiting > 1) {
11403       /* Keep trying for clean exit */
11404       return;
11405     }
11406
11407     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11408
11409     if (telnetISR != NULL) {
11410       RemoveInputSource(telnetISR);
11411     }
11412     if (icsPR != NoProc) {
11413       DestroyChildProcess(icsPR, TRUE);
11414     }
11415
11416     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11417     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11418
11419     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11420     /* make sure this other one finishes before killing it!                  */
11421     if(endingGame) { int count = 0;
11422         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11423         while(endingGame && count++ < 10) DoSleep(1);
11424         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11425     }
11426
11427     /* Kill off chess programs */
11428     if (first.pr != NoProc) {
11429         ExitAnalyzeMode();
11430         
11431         DoSleep( appData.delayBeforeQuit );
11432         SendToProgram("quit\n", &first);
11433         DoSleep( appData.delayAfterQuit );
11434         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11435     }
11436     if (second.pr != NoProc) {
11437         DoSleep( appData.delayBeforeQuit );
11438         SendToProgram("quit\n", &second);
11439         DoSleep( appData.delayAfterQuit );
11440         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11441     }
11442     if (first.isr != NULL) {
11443         RemoveInputSource(first.isr);
11444     }
11445     if (second.isr != NULL) {
11446         RemoveInputSource(second.isr);
11447     }
11448
11449     ShutDownFrontEnd();
11450     exit(status);
11451 }
11452
11453 void
11454 PauseEvent()
11455 {
11456     if (appData.debugMode)
11457         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11458     if (pausing) {
11459         pausing = FALSE;
11460         ModeHighlight();
11461         if (gameMode == MachinePlaysWhite ||
11462             gameMode == MachinePlaysBlack) {
11463             StartClocks();
11464         } else {
11465             DisplayBothClocks();
11466         }
11467         if (gameMode == PlayFromGameFile) {
11468             if (appData.timeDelay >= 0) 
11469                 AutoPlayGameLoop();
11470         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11471             Reset(FALSE, TRUE);
11472             SendToICS(ics_prefix);
11473             SendToICS("refresh\n");
11474         } else if (currentMove < forwardMostMove) {
11475             ForwardInner(forwardMostMove);
11476         }
11477         pauseExamInvalid = FALSE;
11478     } else {
11479         switch (gameMode) {
11480           default:
11481             return;
11482           case IcsExamining:
11483             pauseExamForwardMostMove = forwardMostMove;
11484             pauseExamInvalid = FALSE;
11485             /* fall through */
11486           case IcsObserving:
11487           case IcsPlayingWhite:
11488           case IcsPlayingBlack:
11489             pausing = TRUE;
11490             ModeHighlight();
11491             return;
11492           case PlayFromGameFile:
11493             (void) StopLoadGameTimer();
11494             pausing = TRUE;
11495             ModeHighlight();
11496             break;
11497           case BeginningOfGame:
11498             if (appData.icsActive) return;
11499             /* else fall through */
11500           case MachinePlaysWhite:
11501           case MachinePlaysBlack:
11502           case TwoMachinesPlay:
11503             if (forwardMostMove == 0)
11504               return;           /* don't pause if no one has moved */
11505             if ((gameMode == MachinePlaysWhite &&
11506                  !WhiteOnMove(forwardMostMove)) ||
11507                 (gameMode == MachinePlaysBlack &&
11508                  WhiteOnMove(forwardMostMove))) {
11509                 StopClocks();
11510             }
11511             pausing = TRUE;
11512             ModeHighlight();
11513             break;
11514         }
11515     }
11516 }
11517
11518 void
11519 EditCommentEvent()
11520 {
11521     char title[MSG_SIZ];
11522
11523     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11524         strcpy(title, _("Edit comment"));
11525     } else {
11526         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11527                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11528                 parseList[currentMove - 1]);
11529     }
11530
11531     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11532 }
11533
11534
11535 void
11536 EditTagsEvent()
11537 {
11538     char *tags = PGNTags(&gameInfo);
11539     EditTagsPopUp(tags);
11540     free(tags);
11541 }
11542
11543 void
11544 AnalyzeModeEvent()
11545 {
11546     if (appData.noChessProgram || gameMode == AnalyzeMode)
11547       return;
11548
11549     if (gameMode != AnalyzeFile) {
11550         if (!appData.icsEngineAnalyze) {
11551                EditGameEvent();
11552                if (gameMode != EditGame) return;
11553         }
11554         ResurrectChessProgram();
11555         SendToProgram("analyze\n", &first);
11556         first.analyzing = TRUE;
11557         /*first.maybeThinking = TRUE;*/
11558         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11559         EngineOutputPopUp();
11560     }
11561     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11562     pausing = FALSE;
11563     ModeHighlight();
11564     SetGameInfo();
11565
11566     StartAnalysisClock();
11567     GetTimeMark(&lastNodeCountTime);
11568     lastNodeCount = 0;
11569 }
11570
11571 void
11572 AnalyzeFileEvent()
11573 {
11574     if (appData.noChessProgram || gameMode == AnalyzeFile)
11575       return;
11576
11577     if (gameMode != AnalyzeMode) {
11578         EditGameEvent();
11579         if (gameMode != EditGame) return;
11580         ResurrectChessProgram();
11581         SendToProgram("analyze\n", &first);
11582         first.analyzing = TRUE;
11583         /*first.maybeThinking = TRUE;*/
11584         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11585         EngineOutputPopUp();
11586     }
11587     gameMode = AnalyzeFile;
11588     pausing = FALSE;
11589     ModeHighlight();
11590     SetGameInfo();
11591
11592     StartAnalysisClock();
11593     GetTimeMark(&lastNodeCountTime);
11594     lastNodeCount = 0;
11595 }
11596
11597 void
11598 MachineWhiteEvent()
11599 {
11600     char buf[MSG_SIZ];
11601     char *bookHit = NULL;
11602
11603     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11604       return;
11605
11606
11607     if (gameMode == PlayFromGameFile || 
11608         gameMode == TwoMachinesPlay  || 
11609         gameMode == Training         || 
11610         gameMode == AnalyzeMode      || 
11611         gameMode == EndOfGame)
11612         EditGameEvent();
11613
11614     if (gameMode == EditPosition) 
11615         EditPositionDone(TRUE);
11616
11617     if (!WhiteOnMove(currentMove)) {
11618         DisplayError(_("It is not White's turn"), 0);
11619         return;
11620     }
11621   
11622     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11623       ExitAnalyzeMode();
11624
11625     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11626         gameMode == AnalyzeFile)
11627         TruncateGame();
11628
11629     ResurrectChessProgram();    /* in case it isn't running */
11630     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11631         gameMode = MachinePlaysWhite;
11632         ResetClocks();
11633     } else
11634     gameMode = MachinePlaysWhite;
11635     pausing = FALSE;
11636     ModeHighlight();
11637     SetGameInfo();
11638     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11639     DisplayTitle(buf);
11640     if (first.sendName) {
11641       sprintf(buf, "name %s\n", gameInfo.black);
11642       SendToProgram(buf, &first);
11643     }
11644     if (first.sendTime) {
11645       if (first.useColors) {
11646         SendToProgram("black\n", &first); /*gnu kludge*/
11647       }
11648       SendTimeRemaining(&first, TRUE);
11649     }
11650     if (first.useColors) {
11651       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11652     }
11653     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11654     SetMachineThinkingEnables();
11655     first.maybeThinking = TRUE;
11656     StartClocks();
11657     firstMove = FALSE;
11658
11659     if (appData.autoFlipView && !flipView) {
11660       flipView = !flipView;
11661       DrawPosition(FALSE, NULL);
11662       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11663     }
11664
11665     if(bookHit) { // [HGM] book: simulate book reply
11666         static char bookMove[MSG_SIZ]; // a bit generous?
11667
11668         programStats.nodes = programStats.depth = programStats.time = 
11669         programStats.score = programStats.got_only_move = 0;
11670         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11671
11672         strcpy(bookMove, "move ");
11673         strcat(bookMove, bookHit);
11674         HandleMachineMove(bookMove, &first);
11675     }
11676 }
11677
11678 void
11679 MachineBlackEvent()
11680 {
11681     char buf[MSG_SIZ];
11682    char *bookHit = NULL;
11683
11684     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11685         return;
11686
11687
11688     if (gameMode == PlayFromGameFile || 
11689         gameMode == TwoMachinesPlay  || 
11690         gameMode == Training         || 
11691         gameMode == AnalyzeMode      || 
11692         gameMode == EndOfGame)
11693         EditGameEvent();
11694
11695     if (gameMode == EditPosition) 
11696         EditPositionDone(TRUE);
11697
11698     if (WhiteOnMove(currentMove)) {
11699         DisplayError(_("It is not Black's turn"), 0);
11700         return;
11701     }
11702     
11703     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11704       ExitAnalyzeMode();
11705
11706     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11707         gameMode == AnalyzeFile)
11708         TruncateGame();
11709
11710     ResurrectChessProgram();    /* in case it isn't running */
11711     gameMode = MachinePlaysBlack;
11712     pausing = FALSE;
11713     ModeHighlight();
11714     SetGameInfo();
11715     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11716     DisplayTitle(buf);
11717     if (first.sendName) {
11718       sprintf(buf, "name %s\n", gameInfo.white);
11719       SendToProgram(buf, &first);
11720     }
11721     if (first.sendTime) {
11722       if (first.useColors) {
11723         SendToProgram("white\n", &first); /*gnu kludge*/
11724       }
11725       SendTimeRemaining(&first, FALSE);
11726     }
11727     if (first.useColors) {
11728       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11729     }
11730     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11731     SetMachineThinkingEnables();
11732     first.maybeThinking = TRUE;
11733     StartClocks();
11734
11735     if (appData.autoFlipView && flipView) {
11736       flipView = !flipView;
11737       DrawPosition(FALSE, NULL);
11738       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11739     }
11740     if(bookHit) { // [HGM] book: simulate book reply
11741         static char bookMove[MSG_SIZ]; // a bit generous?
11742
11743         programStats.nodes = programStats.depth = programStats.time = 
11744         programStats.score = programStats.got_only_move = 0;
11745         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11746
11747         strcpy(bookMove, "move ");
11748         strcat(bookMove, bookHit);
11749         HandleMachineMove(bookMove, &first);
11750     }
11751 }
11752
11753
11754 void
11755 DisplayTwoMachinesTitle()
11756 {
11757     char buf[MSG_SIZ];
11758     if (appData.matchGames > 0) {
11759         if (first.twoMachinesColor[0] == 'w') {
11760             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11761                     gameInfo.white, gameInfo.black,
11762                     first.matchWins, second.matchWins,
11763                     matchGame - 1 - (first.matchWins + second.matchWins));
11764         } else {
11765             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11766                     gameInfo.white, gameInfo.black,
11767                     second.matchWins, first.matchWins,
11768                     matchGame - 1 - (first.matchWins + second.matchWins));
11769         }
11770     } else {
11771         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11772     }
11773     DisplayTitle(buf);
11774 }
11775
11776 void
11777 TwoMachinesEvent P((void))
11778 {
11779     int i;
11780     char buf[MSG_SIZ];
11781     ChessProgramState *onmove;
11782     char *bookHit = NULL;
11783     
11784     if (appData.noChessProgram) return;
11785
11786     switch (gameMode) {
11787       case TwoMachinesPlay:
11788         return;
11789       case MachinePlaysWhite:
11790       case MachinePlaysBlack:
11791         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11792             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11793             return;
11794         }
11795         /* fall through */
11796       case BeginningOfGame:
11797       case PlayFromGameFile:
11798       case EndOfGame:
11799         EditGameEvent();
11800         if (gameMode != EditGame) return;
11801         break;
11802       case EditPosition:
11803         EditPositionDone(TRUE);
11804         break;
11805       case AnalyzeMode:
11806       case AnalyzeFile:
11807         ExitAnalyzeMode();
11808         break;
11809       case EditGame:
11810       default:
11811         break;
11812     }
11813
11814 //    forwardMostMove = currentMove;
11815     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11816     ResurrectChessProgram();    /* in case first program isn't running */
11817
11818     if (second.pr == NULL) {
11819         StartChessProgram(&second);
11820         if (second.protocolVersion == 1) {
11821           TwoMachinesEventIfReady();
11822         } else {
11823           /* kludge: allow timeout for initial "feature" command */
11824           FreezeUI();
11825           DisplayMessage("", _("Starting second chess program"));
11826           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11827         }
11828         return;
11829     }
11830     DisplayMessage("", "");
11831     InitChessProgram(&second, FALSE);
11832     SendToProgram("force\n", &second);
11833     if (startedFromSetupPosition) {
11834         SendBoard(&second, backwardMostMove);
11835     if (appData.debugMode) {
11836         fprintf(debugFP, "Two Machines\n");
11837     }
11838     }
11839     for (i = backwardMostMove; i < forwardMostMove; i++) {
11840         SendMoveToProgram(i, &second);
11841     }
11842
11843     gameMode = TwoMachinesPlay;
11844     pausing = FALSE;
11845     ModeHighlight();
11846     SetGameInfo();
11847     DisplayTwoMachinesTitle();
11848     firstMove = TRUE;
11849     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11850         onmove = &first;
11851     } else {
11852         onmove = &second;
11853     }
11854
11855     SendToProgram(first.computerString, &first);
11856     if (first.sendName) {
11857       sprintf(buf, "name %s\n", second.tidy);
11858       SendToProgram(buf, &first);
11859     }
11860     SendToProgram(second.computerString, &second);
11861     if (second.sendName) {
11862       sprintf(buf, "name %s\n", first.tidy);
11863       SendToProgram(buf, &second);
11864     }
11865
11866     ResetClocks();
11867     if (!first.sendTime || !second.sendTime) {
11868         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11869         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11870     }
11871     if (onmove->sendTime) {
11872       if (onmove->useColors) {
11873         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11874       }
11875       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11876     }
11877     if (onmove->useColors) {
11878       SendToProgram(onmove->twoMachinesColor, onmove);
11879     }
11880     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11881 //    SendToProgram("go\n", onmove);
11882     onmove->maybeThinking = TRUE;
11883     SetMachineThinkingEnables();
11884
11885     StartClocks();
11886
11887     if(bookHit) { // [HGM] book: simulate book reply
11888         static char bookMove[MSG_SIZ]; // a bit generous?
11889
11890         programStats.nodes = programStats.depth = programStats.time = 
11891         programStats.score = programStats.got_only_move = 0;
11892         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11893
11894         strcpy(bookMove, "move ");
11895         strcat(bookMove, bookHit);
11896         savedMessage = bookMove; // args for deferred call
11897         savedState = onmove;
11898         ScheduleDelayedEvent(DeferredBookMove, 1);
11899     }
11900 }
11901
11902 void
11903 TrainingEvent()
11904 {
11905     if (gameMode == Training) {
11906       SetTrainingModeOff();
11907       gameMode = PlayFromGameFile;
11908       DisplayMessage("", _("Training mode off"));
11909     } else {
11910       gameMode = Training;
11911       animateTraining = appData.animate;
11912
11913       /* make sure we are not already at the end of the game */
11914       if (currentMove < forwardMostMove) {
11915         SetTrainingModeOn();
11916         DisplayMessage("", _("Training mode on"));
11917       } else {
11918         gameMode = PlayFromGameFile;
11919         DisplayError(_("Already at end of game"), 0);
11920       }
11921     }
11922     ModeHighlight();
11923 }
11924
11925 void
11926 IcsClientEvent()
11927 {
11928     if (!appData.icsActive) return;
11929     switch (gameMode) {
11930       case IcsPlayingWhite:
11931       case IcsPlayingBlack:
11932       case IcsObserving:
11933       case IcsIdle:
11934       case BeginningOfGame:
11935       case IcsExamining:
11936         return;
11937
11938       case EditGame:
11939         break;
11940
11941       case EditPosition:
11942         EditPositionDone(TRUE);
11943         break;
11944
11945       case AnalyzeMode:
11946       case AnalyzeFile:
11947         ExitAnalyzeMode();
11948         break;
11949         
11950       default:
11951         EditGameEvent();
11952         break;
11953     }
11954
11955     gameMode = IcsIdle;
11956     ModeHighlight();
11957     return;
11958 }
11959
11960
11961 void
11962 EditGameEvent()
11963 {
11964     int i;
11965
11966     switch (gameMode) {
11967       case Training:
11968         SetTrainingModeOff();
11969         break;
11970       case MachinePlaysWhite:
11971       case MachinePlaysBlack:
11972       case BeginningOfGame:
11973         SendToProgram("force\n", &first);
11974         SetUserThinkingEnables();
11975         break;
11976       case PlayFromGameFile:
11977         (void) StopLoadGameTimer();
11978         if (gameFileFP != NULL) {
11979             gameFileFP = NULL;
11980         }
11981         break;
11982       case EditPosition:
11983         EditPositionDone(TRUE);
11984         break;
11985       case AnalyzeMode:
11986       case AnalyzeFile:
11987         ExitAnalyzeMode();
11988         SendToProgram("force\n", &first);
11989         break;
11990       case TwoMachinesPlay:
11991         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11992         ResurrectChessProgram();
11993         SetUserThinkingEnables();
11994         break;
11995       case EndOfGame:
11996         ResurrectChessProgram();
11997         break;
11998       case IcsPlayingBlack:
11999       case IcsPlayingWhite:
12000         DisplayError(_("Warning: You are still playing a game"), 0);
12001         break;
12002       case IcsObserving:
12003         DisplayError(_("Warning: You are still observing a game"), 0);
12004         break;
12005       case IcsExamining:
12006         DisplayError(_("Warning: You are still examining a game"), 0);
12007         break;
12008       case IcsIdle:
12009         break;
12010       case EditGame:
12011       default:
12012         return;
12013     }
12014     
12015     pausing = FALSE;
12016     StopClocks();
12017     first.offeredDraw = second.offeredDraw = 0;
12018
12019     if (gameMode == PlayFromGameFile) {
12020         whiteTimeRemaining = timeRemaining[0][currentMove];
12021         blackTimeRemaining = timeRemaining[1][currentMove];
12022         DisplayTitle("");
12023     }
12024
12025     if (gameMode == MachinePlaysWhite ||
12026         gameMode == MachinePlaysBlack ||
12027         gameMode == TwoMachinesPlay ||
12028         gameMode == EndOfGame) {
12029         i = forwardMostMove;
12030         while (i > currentMove) {
12031             SendToProgram("undo\n", &first);
12032             i--;
12033         }
12034         whiteTimeRemaining = timeRemaining[0][currentMove];
12035         blackTimeRemaining = timeRemaining[1][currentMove];
12036         DisplayBothClocks();
12037         if (whiteFlag || blackFlag) {
12038             whiteFlag = blackFlag = 0;
12039         }
12040         DisplayTitle("");
12041     }           
12042     
12043     gameMode = EditGame;
12044     ModeHighlight();
12045     SetGameInfo();
12046 }
12047
12048
12049 void
12050 EditPositionEvent()
12051 {
12052     if (gameMode == EditPosition) {
12053         EditGameEvent();
12054         return;
12055     }
12056     
12057     EditGameEvent();
12058     if (gameMode != EditGame) return;
12059     
12060     gameMode = EditPosition;
12061     ModeHighlight();
12062     SetGameInfo();
12063     if (currentMove > 0)
12064       CopyBoard(boards[0], boards[currentMove]);
12065     
12066     blackPlaysFirst = !WhiteOnMove(currentMove);
12067     ResetClocks();
12068     currentMove = forwardMostMove = backwardMostMove = 0;
12069     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12070     DisplayMove(-1);
12071 }
12072
12073 void
12074 ExitAnalyzeMode()
12075 {
12076     /* [DM] icsEngineAnalyze - possible call from other functions */
12077     if (appData.icsEngineAnalyze) {
12078         appData.icsEngineAnalyze = FALSE;
12079
12080         DisplayMessage("",_("Close ICS engine analyze..."));
12081     }
12082     if (first.analysisSupport && first.analyzing) {
12083       SendToProgram("exit\n", &first);
12084       first.analyzing = FALSE;
12085     }
12086     thinkOutput[0] = NULLCHAR;
12087 }
12088
12089 void
12090 EditPositionDone(Boolean fakeRights)
12091 {
12092     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12093
12094     startedFromSetupPosition = TRUE;
12095     InitChessProgram(&first, FALSE);
12096     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12097       boards[0][EP_STATUS] = EP_NONE;
12098       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12099     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12100         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12101         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12102       } else boards[0][CASTLING][2] = NoRights;
12103     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12104         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12105         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12106       } else boards[0][CASTLING][5] = NoRights;
12107     }
12108     SendToProgram("force\n", &first);
12109     if (blackPlaysFirst) {
12110         strcpy(moveList[0], "");
12111         strcpy(parseList[0], "");
12112         currentMove = forwardMostMove = backwardMostMove = 1;
12113         CopyBoard(boards[1], boards[0]);
12114     } else {
12115         currentMove = forwardMostMove = backwardMostMove = 0;
12116     }
12117     SendBoard(&first, forwardMostMove);
12118     if (appData.debugMode) {
12119         fprintf(debugFP, "EditPosDone\n");
12120     }
12121     DisplayTitle("");
12122     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12123     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12124     gameMode = EditGame;
12125     ModeHighlight();
12126     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12127     ClearHighlights(); /* [AS] */
12128 }
12129
12130 /* Pause for `ms' milliseconds */
12131 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12132 void
12133 TimeDelay(ms)
12134      long ms;
12135 {
12136     TimeMark m1, m2;
12137
12138     GetTimeMark(&m1);
12139     do {
12140         GetTimeMark(&m2);
12141     } while (SubtractTimeMarks(&m2, &m1) < ms);
12142 }
12143
12144 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12145 void
12146 SendMultiLineToICS(buf)
12147      char *buf;
12148 {
12149     char temp[MSG_SIZ+1], *p;
12150     int len;
12151
12152     len = strlen(buf);
12153     if (len > MSG_SIZ)
12154       len = MSG_SIZ;
12155   
12156     strncpy(temp, buf, len);
12157     temp[len] = 0;
12158
12159     p = temp;
12160     while (*p) {
12161         if (*p == '\n' || *p == '\r')
12162           *p = ' ';
12163         ++p;
12164     }
12165
12166     strcat(temp, "\n");
12167     SendToICS(temp);
12168     SendToPlayer(temp, strlen(temp));
12169 }
12170
12171 void
12172 SetWhiteToPlayEvent()
12173 {
12174     if (gameMode == EditPosition) {
12175         blackPlaysFirst = FALSE;
12176         DisplayBothClocks();    /* works because currentMove is 0 */
12177     } else if (gameMode == IcsExamining) {
12178         SendToICS(ics_prefix);
12179         SendToICS("tomove white\n");
12180     }
12181 }
12182
12183 void
12184 SetBlackToPlayEvent()
12185 {
12186     if (gameMode == EditPosition) {
12187         blackPlaysFirst = TRUE;
12188         currentMove = 1;        /* kludge */
12189         DisplayBothClocks();
12190         currentMove = 0;
12191     } else if (gameMode == IcsExamining) {
12192         SendToICS(ics_prefix);
12193         SendToICS("tomove black\n");
12194     }
12195 }
12196
12197 void
12198 EditPositionMenuEvent(selection, x, y)
12199      ChessSquare selection;
12200      int x, y;
12201 {
12202     char buf[MSG_SIZ];
12203     ChessSquare piece = boards[0][y][x];
12204
12205     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12206
12207     switch (selection) {
12208       case ClearBoard:
12209         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12210             SendToICS(ics_prefix);
12211             SendToICS("bsetup clear\n");
12212         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12213             SendToICS(ics_prefix);
12214             SendToICS("clearboard\n");
12215         } else {
12216             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12217                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12218                 for (y = 0; y < BOARD_HEIGHT; y++) {
12219                     if (gameMode == IcsExamining) {
12220                         if (boards[currentMove][y][x] != EmptySquare) {
12221                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12222                                     AAA + x, ONE + y);
12223                             SendToICS(buf);
12224                         }
12225                     } else {
12226                         boards[0][y][x] = p;
12227                     }
12228                 }
12229             }
12230         }
12231         if (gameMode == EditPosition) {
12232             DrawPosition(FALSE, boards[0]);
12233         }
12234         break;
12235
12236       case WhitePlay:
12237         SetWhiteToPlayEvent();
12238         break;
12239
12240       case BlackPlay:
12241         SetBlackToPlayEvent();
12242         break;
12243
12244       case EmptySquare:
12245         if (gameMode == IcsExamining) {
12246             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12247             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12248             SendToICS(buf);
12249         } else {
12250             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12251                 if(x == BOARD_LEFT-2) {
12252                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12253                     boards[0][y][1] = 0;
12254                 } else
12255                 if(x == BOARD_RGHT+1) {
12256                     if(y >= gameInfo.holdingsSize) break;
12257                     boards[0][y][BOARD_WIDTH-2] = 0;
12258                 } else break;
12259             }
12260             boards[0][y][x] = EmptySquare;
12261             DrawPosition(FALSE, boards[0]);
12262         }
12263         break;
12264
12265       case PromotePiece:
12266         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12267            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12268             selection = (ChessSquare) (PROMOTED piece);
12269         } else if(piece == EmptySquare) selection = WhiteSilver;
12270         else selection = (ChessSquare)((int)piece - 1);
12271         goto defaultlabel;
12272
12273       case DemotePiece:
12274         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12275            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12276             selection = (ChessSquare) (DEMOTED piece);
12277         } else if(piece == EmptySquare) selection = BlackSilver;
12278         else selection = (ChessSquare)((int)piece + 1);       
12279         goto defaultlabel;
12280
12281       case WhiteQueen:
12282       case BlackQueen:
12283         if(gameInfo.variant == VariantShatranj ||
12284            gameInfo.variant == VariantXiangqi  ||
12285            gameInfo.variant == VariantCourier  ||
12286            gameInfo.variant == VariantMakruk     )
12287             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12288         goto defaultlabel;
12289
12290       case WhiteKing:
12291       case BlackKing:
12292         if(gameInfo.variant == VariantXiangqi)
12293             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12294         if(gameInfo.variant == VariantKnightmate)
12295             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12296       default:
12297         defaultlabel:
12298         if (gameMode == IcsExamining) {
12299             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12300             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12301                     PieceToChar(selection), AAA + x, ONE + y);
12302             SendToICS(buf);
12303         } else {
12304             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12305                 int n;
12306                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12307                     n = PieceToNumber(selection - BlackPawn);
12308                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12309                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12310                     boards[0][BOARD_HEIGHT-1-n][1]++;
12311                 } else
12312                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12313                     n = PieceToNumber(selection);
12314                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12315                     boards[0][n][BOARD_WIDTH-1] = selection;
12316                     boards[0][n][BOARD_WIDTH-2]++;
12317                 }
12318             } else
12319             boards[0][y][x] = selection;
12320             DrawPosition(TRUE, boards[0]);
12321         }
12322         break;
12323     }
12324 }
12325
12326
12327 void
12328 DropMenuEvent(selection, x, y)
12329      ChessSquare selection;
12330      int x, y;
12331 {
12332     ChessMove moveType;
12333
12334     switch (gameMode) {
12335       case IcsPlayingWhite:
12336       case MachinePlaysBlack:
12337         if (!WhiteOnMove(currentMove)) {
12338             DisplayMoveError(_("It is Black's turn"));
12339             return;
12340         }
12341         moveType = WhiteDrop;
12342         break;
12343       case IcsPlayingBlack:
12344       case MachinePlaysWhite:
12345         if (WhiteOnMove(currentMove)) {
12346             DisplayMoveError(_("It is White's turn"));
12347             return;
12348         }
12349         moveType = BlackDrop;
12350         break;
12351       case EditGame:
12352         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12353         break;
12354       default:
12355         return;
12356     }
12357
12358     if (moveType == BlackDrop && selection < BlackPawn) {
12359       selection = (ChessSquare) ((int) selection
12360                                  + (int) BlackPawn - (int) WhitePawn);
12361     }
12362     if (boards[currentMove][y][x] != EmptySquare) {
12363         DisplayMoveError(_("That square is occupied"));
12364         return;
12365     }
12366
12367     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12368 }
12369
12370 void
12371 AcceptEvent()
12372 {
12373     /* Accept a pending offer of any kind from opponent */
12374     
12375     if (appData.icsActive) {
12376         SendToICS(ics_prefix);
12377         SendToICS("accept\n");
12378     } else if (cmailMsgLoaded) {
12379         if (currentMove == cmailOldMove &&
12380             commentList[cmailOldMove] != NULL &&
12381             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12382                    "Black offers a draw" : "White offers a draw")) {
12383             TruncateGame();
12384             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12385             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12386         } else {
12387             DisplayError(_("There is no pending offer on this move"), 0);
12388             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12389         }
12390     } else {
12391         /* Not used for offers from chess program */
12392     }
12393 }
12394
12395 void
12396 DeclineEvent()
12397 {
12398     /* Decline a pending offer of any kind from opponent */
12399     
12400     if (appData.icsActive) {
12401         SendToICS(ics_prefix);
12402         SendToICS("decline\n");
12403     } else if (cmailMsgLoaded) {
12404         if (currentMove == cmailOldMove &&
12405             commentList[cmailOldMove] != NULL &&
12406             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12407                    "Black offers a draw" : "White offers a draw")) {
12408 #ifdef NOTDEF
12409             AppendComment(cmailOldMove, "Draw declined", TRUE);
12410             DisplayComment(cmailOldMove - 1, "Draw declined");
12411 #endif /*NOTDEF*/
12412         } else {
12413             DisplayError(_("There is no pending offer on this move"), 0);
12414         }
12415     } else {
12416         /* Not used for offers from chess program */
12417     }
12418 }
12419
12420 void
12421 RematchEvent()
12422 {
12423     /* Issue ICS rematch command */
12424     if (appData.icsActive) {
12425         SendToICS(ics_prefix);
12426         SendToICS("rematch\n");
12427     }
12428 }
12429
12430 void
12431 CallFlagEvent()
12432 {
12433     /* Call your opponent's flag (claim a win on time) */
12434     if (appData.icsActive) {
12435         SendToICS(ics_prefix);
12436         SendToICS("flag\n");
12437     } else {
12438         switch (gameMode) {
12439           default:
12440             return;
12441           case MachinePlaysWhite:
12442             if (whiteFlag) {
12443                 if (blackFlag)
12444                   GameEnds(GameIsDrawn, "Both players ran out of time",
12445                            GE_PLAYER);
12446                 else
12447                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12448             } else {
12449                 DisplayError(_("Your opponent is not out of time"), 0);
12450             }
12451             break;
12452           case MachinePlaysBlack:
12453             if (blackFlag) {
12454                 if (whiteFlag)
12455                   GameEnds(GameIsDrawn, "Both players ran out of time",
12456                            GE_PLAYER);
12457                 else
12458                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12459             } else {
12460                 DisplayError(_("Your opponent is not out of time"), 0);
12461             }
12462             break;
12463         }
12464     }
12465 }
12466
12467 void
12468 DrawEvent()
12469 {
12470     /* Offer draw or accept pending draw offer from opponent */
12471     
12472     if (appData.icsActive) {
12473         /* Note: tournament rules require draw offers to be
12474            made after you make your move but before you punch
12475            your clock.  Currently ICS doesn't let you do that;
12476            instead, you immediately punch your clock after making
12477            a move, but you can offer a draw at any time. */
12478         
12479         SendToICS(ics_prefix);
12480         SendToICS("draw\n");
12481         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12482     } else if (cmailMsgLoaded) {
12483         if (currentMove == cmailOldMove &&
12484             commentList[cmailOldMove] != NULL &&
12485             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12486                    "Black offers a draw" : "White offers a draw")) {
12487             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12488             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12489         } else if (currentMove == cmailOldMove + 1) {
12490             char *offer = WhiteOnMove(cmailOldMove) ?
12491               "White offers a draw" : "Black offers a draw";
12492             AppendComment(currentMove, offer, TRUE);
12493             DisplayComment(currentMove - 1, offer);
12494             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12495         } else {
12496             DisplayError(_("You must make your move before offering a draw"), 0);
12497             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12498         }
12499     } else if (first.offeredDraw) {
12500         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12501     } else {
12502         if (first.sendDrawOffers) {
12503             SendToProgram("draw\n", &first);
12504             userOfferedDraw = TRUE;
12505         }
12506     }
12507 }
12508
12509 void
12510 AdjournEvent()
12511 {
12512     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12513     
12514     if (appData.icsActive) {
12515         SendToICS(ics_prefix);
12516         SendToICS("adjourn\n");
12517     } else {
12518         /* Currently GNU Chess doesn't offer or accept Adjourns */
12519     }
12520 }
12521
12522
12523 void
12524 AbortEvent()
12525 {
12526     /* Offer Abort or accept pending Abort offer from opponent */
12527     
12528     if (appData.icsActive) {
12529         SendToICS(ics_prefix);
12530         SendToICS("abort\n");
12531     } else {
12532         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12533     }
12534 }
12535
12536 void
12537 ResignEvent()
12538 {
12539     /* Resign.  You can do this even if it's not your turn. */
12540     
12541     if (appData.icsActive) {
12542         SendToICS(ics_prefix);
12543         SendToICS("resign\n");
12544     } else {
12545         switch (gameMode) {
12546           case MachinePlaysWhite:
12547             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12548             break;
12549           case MachinePlaysBlack:
12550             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12551             break;
12552           case EditGame:
12553             if (cmailMsgLoaded) {
12554                 TruncateGame();
12555                 if (WhiteOnMove(cmailOldMove)) {
12556                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12557                 } else {
12558                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12559                 }
12560                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12561             }
12562             break;
12563           default:
12564             break;
12565         }
12566     }
12567 }
12568
12569
12570 void
12571 StopObservingEvent()
12572 {
12573     /* Stop observing current games */
12574     SendToICS(ics_prefix);
12575     SendToICS("unobserve\n");
12576 }
12577
12578 void
12579 StopExaminingEvent()
12580 {
12581     /* Stop observing current game */
12582     SendToICS(ics_prefix);
12583     SendToICS("unexamine\n");
12584 }
12585
12586 void
12587 ForwardInner(target)
12588      int target;
12589 {
12590     int limit;
12591
12592     if (appData.debugMode)
12593         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12594                 target, currentMove, forwardMostMove);
12595
12596     if (gameMode == EditPosition)
12597       return;
12598
12599     if (gameMode == PlayFromGameFile && !pausing)
12600       PauseEvent();
12601     
12602     if (gameMode == IcsExamining && pausing)
12603       limit = pauseExamForwardMostMove;
12604     else
12605       limit = forwardMostMove;
12606     
12607     if (target > limit) target = limit;
12608
12609     if (target > 0 && moveList[target - 1][0]) {
12610         int fromX, fromY, toX, toY;
12611         toX = moveList[target - 1][2] - AAA;
12612         toY = moveList[target - 1][3] - ONE;
12613         if (moveList[target - 1][1] == '@') {
12614             if (appData.highlightLastMove) {
12615                 SetHighlights(-1, -1, toX, toY);
12616             }
12617         } else {
12618             fromX = moveList[target - 1][0] - AAA;
12619             fromY = moveList[target - 1][1] - ONE;
12620             if (target == currentMove + 1) {
12621                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12622             }
12623             if (appData.highlightLastMove) {
12624                 SetHighlights(fromX, fromY, toX, toY);
12625             }
12626         }
12627     }
12628     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12629         gameMode == Training || gameMode == PlayFromGameFile || 
12630         gameMode == AnalyzeFile) {
12631         while (currentMove < target) {
12632             SendMoveToProgram(currentMove++, &first);
12633         }
12634     } else {
12635         currentMove = target;
12636     }
12637     
12638     if (gameMode == EditGame || gameMode == EndOfGame) {
12639         whiteTimeRemaining = timeRemaining[0][currentMove];
12640         blackTimeRemaining = timeRemaining[1][currentMove];
12641     }
12642     DisplayBothClocks();
12643     DisplayMove(currentMove - 1);
12644     DrawPosition(FALSE, boards[currentMove]);
12645     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12646     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12647         DisplayComment(currentMove - 1, commentList[currentMove]);
12648     }
12649 }
12650
12651
12652 void
12653 ForwardEvent()
12654 {
12655     if (gameMode == IcsExamining && !pausing) {
12656         SendToICS(ics_prefix);
12657         SendToICS("forward\n");
12658     } else {
12659         ForwardInner(currentMove + 1);
12660     }
12661 }
12662
12663 void
12664 ToEndEvent()
12665 {
12666     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12667         /* to optimze, we temporarily turn off analysis mode while we feed
12668          * the remaining moves to the engine. Otherwise we get analysis output
12669          * after each move.
12670          */ 
12671         if (first.analysisSupport) {
12672           SendToProgram("exit\nforce\n", &first);
12673           first.analyzing = FALSE;
12674         }
12675     }
12676         
12677     if (gameMode == IcsExamining && !pausing) {
12678         SendToICS(ics_prefix);
12679         SendToICS("forward 999999\n");
12680     } else {
12681         ForwardInner(forwardMostMove);
12682     }
12683
12684     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12685         /* we have fed all the moves, so reactivate analysis mode */
12686         SendToProgram("analyze\n", &first);
12687         first.analyzing = TRUE;
12688         /*first.maybeThinking = TRUE;*/
12689         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12690     }
12691 }
12692
12693 void
12694 BackwardInner(target)
12695      int target;
12696 {
12697     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12698
12699     if (appData.debugMode)
12700         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12701                 target, currentMove, forwardMostMove);
12702
12703     if (gameMode == EditPosition) return;
12704     if (currentMove <= backwardMostMove) {
12705         ClearHighlights();
12706         DrawPosition(full_redraw, boards[currentMove]);
12707         return;
12708     }
12709     if (gameMode == PlayFromGameFile && !pausing)
12710       PauseEvent();
12711     
12712     if (moveList[target][0]) {
12713         int fromX, fromY, toX, toY;
12714         toX = moveList[target][2] - AAA;
12715         toY = moveList[target][3] - ONE;
12716         if (moveList[target][1] == '@') {
12717             if (appData.highlightLastMove) {
12718                 SetHighlights(-1, -1, toX, toY);
12719             }
12720         } else {
12721             fromX = moveList[target][0] - AAA;
12722             fromY = moveList[target][1] - ONE;
12723             if (target == currentMove - 1) {
12724                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12725             }
12726             if (appData.highlightLastMove) {
12727                 SetHighlights(fromX, fromY, toX, toY);
12728             }
12729         }
12730     }
12731     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12732         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12733         while (currentMove > target) {
12734             SendToProgram("undo\n", &first);
12735             currentMove--;
12736         }
12737     } else {
12738         currentMove = target;
12739     }
12740     
12741     if (gameMode == EditGame || gameMode == EndOfGame) {
12742         whiteTimeRemaining = timeRemaining[0][currentMove];
12743         blackTimeRemaining = timeRemaining[1][currentMove];
12744     }
12745     DisplayBothClocks();
12746     DisplayMove(currentMove - 1);
12747     DrawPosition(full_redraw, boards[currentMove]);
12748     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12749     // [HGM] PV info: routine tests if comment empty
12750     DisplayComment(currentMove - 1, commentList[currentMove]);
12751 }
12752
12753 void
12754 BackwardEvent()
12755 {
12756     if (gameMode == IcsExamining && !pausing) {
12757         SendToICS(ics_prefix);
12758         SendToICS("backward\n");
12759     } else {
12760         BackwardInner(currentMove - 1);
12761     }
12762 }
12763
12764 void
12765 ToStartEvent()
12766 {
12767     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12768         /* to optimize, we temporarily turn off analysis mode while we undo
12769          * all the moves. Otherwise we get analysis output after each undo.
12770          */ 
12771         if (first.analysisSupport) {
12772           SendToProgram("exit\nforce\n", &first);
12773           first.analyzing = FALSE;
12774         }
12775     }
12776
12777     if (gameMode == IcsExamining && !pausing) {
12778         SendToICS(ics_prefix);
12779         SendToICS("backward 999999\n");
12780     } else {
12781         BackwardInner(backwardMostMove);
12782     }
12783
12784     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12785         /* we have fed all the moves, so reactivate analysis mode */
12786         SendToProgram("analyze\n", &first);
12787         first.analyzing = TRUE;
12788         /*first.maybeThinking = TRUE;*/
12789         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12790     }
12791 }
12792
12793 void
12794 ToNrEvent(int to)
12795 {
12796   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12797   if (to >= forwardMostMove) to = forwardMostMove;
12798   if (to <= backwardMostMove) to = backwardMostMove;
12799   if (to < currentMove) {
12800     BackwardInner(to);
12801   } else {
12802     ForwardInner(to);
12803   }
12804 }
12805
12806 void
12807 RevertEvent(Boolean annotate)
12808 {
12809     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12810         return;
12811     }
12812     if (gameMode != IcsExamining) {
12813         DisplayError(_("You are not examining a game"), 0);
12814         return;
12815     }
12816     if (pausing) {
12817         DisplayError(_("You can't revert while pausing"), 0);
12818         return;
12819     }
12820     SendToICS(ics_prefix);
12821     SendToICS("revert\n");
12822 }
12823
12824 void
12825 RetractMoveEvent()
12826 {
12827     switch (gameMode) {
12828       case MachinePlaysWhite:
12829       case MachinePlaysBlack:
12830         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12831             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12832             return;
12833         }
12834         if (forwardMostMove < 2) return;
12835         currentMove = forwardMostMove = forwardMostMove - 2;
12836         whiteTimeRemaining = timeRemaining[0][currentMove];
12837         blackTimeRemaining = timeRemaining[1][currentMove];
12838         DisplayBothClocks();
12839         DisplayMove(currentMove - 1);
12840         ClearHighlights();/*!! could figure this out*/
12841         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12842         SendToProgram("remove\n", &first);
12843         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12844         break;
12845
12846       case BeginningOfGame:
12847       default:
12848         break;
12849
12850       case IcsPlayingWhite:
12851       case IcsPlayingBlack:
12852         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12853             SendToICS(ics_prefix);
12854             SendToICS("takeback 2\n");
12855         } else {
12856             SendToICS(ics_prefix);
12857             SendToICS("takeback 1\n");
12858         }
12859         break;
12860     }
12861 }
12862
12863 void
12864 MoveNowEvent()
12865 {
12866     ChessProgramState *cps;
12867
12868     switch (gameMode) {
12869       case MachinePlaysWhite:
12870         if (!WhiteOnMove(forwardMostMove)) {
12871             DisplayError(_("It is your turn"), 0);
12872             return;
12873         }
12874         cps = &first;
12875         break;
12876       case MachinePlaysBlack:
12877         if (WhiteOnMove(forwardMostMove)) {
12878             DisplayError(_("It is your turn"), 0);
12879             return;
12880         }
12881         cps = &first;
12882         break;
12883       case TwoMachinesPlay:
12884         if (WhiteOnMove(forwardMostMove) ==
12885             (first.twoMachinesColor[0] == 'w')) {
12886             cps = &first;
12887         } else {
12888             cps = &second;
12889         }
12890         break;
12891       case BeginningOfGame:
12892       default:
12893         return;
12894     }
12895     SendToProgram("?\n", cps);
12896 }
12897
12898 void
12899 TruncateGameEvent()
12900 {
12901     EditGameEvent();
12902     if (gameMode != EditGame) return;
12903     TruncateGame();
12904 }
12905
12906 void
12907 TruncateGame()
12908 {
12909     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12910     if (forwardMostMove > currentMove) {
12911         if (gameInfo.resultDetails != NULL) {
12912             free(gameInfo.resultDetails);
12913             gameInfo.resultDetails = NULL;
12914             gameInfo.result = GameUnfinished;
12915         }
12916         forwardMostMove = currentMove;
12917         HistorySet(parseList, backwardMostMove, forwardMostMove,
12918                    currentMove-1);
12919     }
12920 }
12921
12922 void
12923 HintEvent()
12924 {
12925     if (appData.noChessProgram) return;
12926     switch (gameMode) {
12927       case MachinePlaysWhite:
12928         if (WhiteOnMove(forwardMostMove)) {
12929             DisplayError(_("Wait until your turn"), 0);
12930             return;
12931         }
12932         break;
12933       case BeginningOfGame:
12934       case MachinePlaysBlack:
12935         if (!WhiteOnMove(forwardMostMove)) {
12936             DisplayError(_("Wait until your turn"), 0);
12937             return;
12938         }
12939         break;
12940       default:
12941         DisplayError(_("No hint available"), 0);
12942         return;
12943     }
12944     SendToProgram("hint\n", &first);
12945     hintRequested = TRUE;
12946 }
12947
12948 void
12949 BookEvent()
12950 {
12951     if (appData.noChessProgram) return;
12952     switch (gameMode) {
12953       case MachinePlaysWhite:
12954         if (WhiteOnMove(forwardMostMove)) {
12955             DisplayError(_("Wait until your turn"), 0);
12956             return;
12957         }
12958         break;
12959       case BeginningOfGame:
12960       case MachinePlaysBlack:
12961         if (!WhiteOnMove(forwardMostMove)) {
12962             DisplayError(_("Wait until your turn"), 0);
12963             return;
12964         }
12965         break;
12966       case EditPosition:
12967         EditPositionDone(TRUE);
12968         break;
12969       case TwoMachinesPlay:
12970         return;
12971       default:
12972         break;
12973     }
12974     SendToProgram("bk\n", &first);
12975     bookOutput[0] = NULLCHAR;
12976     bookRequested = TRUE;
12977 }
12978
12979 void
12980 AboutGameEvent()
12981 {
12982     char *tags = PGNTags(&gameInfo);
12983     TagsPopUp(tags, CmailMsg());
12984     free(tags);
12985 }
12986
12987 /* end button procedures */
12988
12989 void
12990 PrintPosition(fp, move)
12991      FILE *fp;
12992      int move;
12993 {
12994     int i, j;
12995     
12996     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12997         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12998             char c = PieceToChar(boards[move][i][j]);
12999             fputc(c == 'x' ? '.' : c, fp);
13000             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13001         }
13002     }
13003     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13004       fprintf(fp, "white to play\n");
13005     else
13006       fprintf(fp, "black to play\n");
13007 }
13008
13009 void
13010 PrintOpponents(fp)
13011      FILE *fp;
13012 {
13013     if (gameInfo.white != NULL) {
13014         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13015     } else {
13016         fprintf(fp, "\n");
13017     }
13018 }
13019
13020 /* Find last component of program's own name, using some heuristics */
13021 void
13022 TidyProgramName(prog, host, buf)
13023      char *prog, *host, buf[MSG_SIZ];
13024 {
13025     char *p, *q;
13026     int local = (strcmp(host, "localhost") == 0);
13027     while (!local && (p = strchr(prog, ';')) != NULL) {
13028         p++;
13029         while (*p == ' ') p++;
13030         prog = p;
13031     }
13032     if (*prog == '"' || *prog == '\'') {
13033         q = strchr(prog + 1, *prog);
13034     } else {
13035         q = strchr(prog, ' ');
13036     }
13037     if (q == NULL) q = prog + strlen(prog);
13038     p = q;
13039     while (p >= prog && *p != '/' && *p != '\\') p--;
13040     p++;
13041     if(p == prog && *p == '"') p++;
13042     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13043     memcpy(buf, p, q - p);
13044     buf[q - p] = NULLCHAR;
13045     if (!local) {
13046         strcat(buf, "@");
13047         strcat(buf, host);
13048     }
13049 }
13050
13051 char *
13052 TimeControlTagValue()
13053 {
13054     char buf[MSG_SIZ];
13055     if (!appData.clockMode) {
13056         strcpy(buf, "-");
13057     } else if (movesPerSession > 0) {
13058         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13059     } else if (timeIncrement == 0) {
13060         sprintf(buf, "%ld", timeControl/1000);
13061     } else {
13062         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13063     }
13064     return StrSave(buf);
13065 }
13066
13067 void
13068 SetGameInfo()
13069 {
13070     /* This routine is used only for certain modes */
13071     VariantClass v = gameInfo.variant;
13072     ChessMove r = GameUnfinished;
13073     char *p = NULL;
13074
13075     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13076         r = gameInfo.result; 
13077         p = gameInfo.resultDetails; 
13078         gameInfo.resultDetails = NULL;
13079     }
13080     ClearGameInfo(&gameInfo);
13081     gameInfo.variant = v;
13082
13083     switch (gameMode) {
13084       case MachinePlaysWhite:
13085         gameInfo.event = StrSave( appData.pgnEventHeader );
13086         gameInfo.site = StrSave(HostName());
13087         gameInfo.date = PGNDate();
13088         gameInfo.round = StrSave("-");
13089         gameInfo.white = StrSave(first.tidy);
13090         gameInfo.black = StrSave(UserName());
13091         gameInfo.timeControl = TimeControlTagValue();
13092         break;
13093
13094       case MachinePlaysBlack:
13095         gameInfo.event = StrSave( appData.pgnEventHeader );
13096         gameInfo.site = StrSave(HostName());
13097         gameInfo.date = PGNDate();
13098         gameInfo.round = StrSave("-");
13099         gameInfo.white = StrSave(UserName());
13100         gameInfo.black = StrSave(first.tidy);
13101         gameInfo.timeControl = TimeControlTagValue();
13102         break;
13103
13104       case TwoMachinesPlay:
13105         gameInfo.event = StrSave( appData.pgnEventHeader );
13106         gameInfo.site = StrSave(HostName());
13107         gameInfo.date = PGNDate();
13108         if (matchGame > 0) {
13109             char buf[MSG_SIZ];
13110             sprintf(buf, "%d", matchGame);
13111             gameInfo.round = StrSave(buf);
13112         } else {
13113             gameInfo.round = StrSave("-");
13114         }
13115         if (first.twoMachinesColor[0] == 'w') {
13116             gameInfo.white = StrSave(first.tidy);
13117             gameInfo.black = StrSave(second.tidy);
13118         } else {
13119             gameInfo.white = StrSave(second.tidy);
13120             gameInfo.black = StrSave(first.tidy);
13121         }
13122         gameInfo.timeControl = TimeControlTagValue();
13123         break;
13124
13125       case EditGame:
13126         gameInfo.event = StrSave("Edited game");
13127         gameInfo.site = StrSave(HostName());
13128         gameInfo.date = PGNDate();
13129         gameInfo.round = StrSave("-");
13130         gameInfo.white = StrSave("-");
13131         gameInfo.black = StrSave("-");
13132         gameInfo.result = r;
13133         gameInfo.resultDetails = p;
13134         break;
13135
13136       case EditPosition:
13137         gameInfo.event = StrSave("Edited position");
13138         gameInfo.site = StrSave(HostName());
13139         gameInfo.date = PGNDate();
13140         gameInfo.round = StrSave("-");
13141         gameInfo.white = StrSave("-");
13142         gameInfo.black = StrSave("-");
13143         break;
13144
13145       case IcsPlayingWhite:
13146       case IcsPlayingBlack:
13147       case IcsObserving:
13148       case IcsExamining:
13149         break;
13150
13151       case PlayFromGameFile:
13152         gameInfo.event = StrSave("Game from non-PGN file");
13153         gameInfo.site = StrSave(HostName());
13154         gameInfo.date = PGNDate();
13155         gameInfo.round = StrSave("-");
13156         gameInfo.white = StrSave("?");
13157         gameInfo.black = StrSave("?");
13158         break;
13159
13160       default:
13161         break;
13162     }
13163 }
13164
13165 void
13166 ReplaceComment(index, text)
13167      int index;
13168      char *text;
13169 {
13170     int len;
13171
13172     while (*text == '\n') text++;
13173     len = strlen(text);
13174     while (len > 0 && text[len - 1] == '\n') len--;
13175
13176     if (commentList[index] != NULL)
13177       free(commentList[index]);
13178
13179     if (len == 0) {
13180         commentList[index] = NULL;
13181         return;
13182     }
13183   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13184       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13185       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13186     commentList[index] = (char *) malloc(len + 2);
13187     strncpy(commentList[index], text, len);
13188     commentList[index][len] = '\n';
13189     commentList[index][len + 1] = NULLCHAR;
13190   } else { 
13191     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13192     char *p;
13193     commentList[index] = (char *) malloc(len + 6);
13194     strcpy(commentList[index], "{\n");
13195     strncpy(commentList[index]+2, text, len);
13196     commentList[index][len+2] = NULLCHAR;
13197     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13198     strcat(commentList[index], "\n}\n");
13199   }
13200 }
13201
13202 void
13203 CrushCRs(text)
13204      char *text;
13205 {
13206   char *p = text;
13207   char *q = text;
13208   char ch;
13209
13210   do {
13211     ch = *p++;
13212     if (ch == '\r') continue;
13213     *q++ = ch;
13214   } while (ch != '\0');
13215 }
13216
13217 void
13218 AppendComment(index, text, addBraces)
13219      int index;
13220      char *text;
13221      Boolean addBraces; // [HGM] braces: tells if we should add {}
13222 {
13223     int oldlen, len;
13224     char *old;
13225
13226 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13227     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13228
13229     CrushCRs(text);
13230     while (*text == '\n') text++;
13231     len = strlen(text);
13232     while (len > 0 && text[len - 1] == '\n') len--;
13233
13234     if (len == 0) return;
13235
13236     if (commentList[index] != NULL) {
13237         old = commentList[index];
13238         oldlen = strlen(old);
13239         while(commentList[index][oldlen-1] ==  '\n')
13240           commentList[index][--oldlen] = NULLCHAR;
13241         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13242         strcpy(commentList[index], old);
13243         free(old);
13244         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13245         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13246           if(addBraces) addBraces = FALSE; else { text++; len--; }
13247           while (*text == '\n') { text++; len--; }
13248           commentList[index][--oldlen] = NULLCHAR;
13249       }
13250         if(addBraces) strcat(commentList[index], "\n{\n");
13251         else          strcat(commentList[index], "\n");
13252         strcat(commentList[index], text);
13253         if(addBraces) strcat(commentList[index], "\n}\n");
13254         else          strcat(commentList[index], "\n");
13255     } else {
13256         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13257         if(addBraces)
13258              strcpy(commentList[index], "{\n");
13259         else commentList[index][0] = NULLCHAR;
13260         strcat(commentList[index], text);
13261         strcat(commentList[index], "\n");
13262         if(addBraces) strcat(commentList[index], "}\n");
13263     }
13264 }
13265
13266 static char * FindStr( char * text, char * sub_text )
13267 {
13268     char * result = strstr( text, sub_text );
13269
13270     if( result != NULL ) {
13271         result += strlen( sub_text );
13272     }
13273
13274     return result;
13275 }
13276
13277 /* [AS] Try to extract PV info from PGN comment */
13278 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13279 char *GetInfoFromComment( int index, char * text )
13280 {
13281     char * sep = text;
13282
13283     if( text != NULL && index > 0 ) {
13284         int score = 0;
13285         int depth = 0;
13286         int time = -1, sec = 0, deci;
13287         char * s_eval = FindStr( text, "[%eval " );
13288         char * s_emt = FindStr( text, "[%emt " );
13289
13290         if( s_eval != NULL || s_emt != NULL ) {
13291             /* New style */
13292             char delim;
13293
13294             if( s_eval != NULL ) {
13295                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13296                     return text;
13297                 }
13298
13299                 if( delim != ']' ) {
13300                     return text;
13301                 }
13302             }
13303
13304             if( s_emt != NULL ) {
13305             }
13306                 return text;
13307         }
13308         else {
13309             /* We expect something like: [+|-]nnn.nn/dd */
13310             int score_lo = 0;
13311
13312             if(*text != '{') return text; // [HGM] braces: must be normal comment
13313
13314             sep = strchr( text, '/' );
13315             if( sep == NULL || sep < (text+4) ) {
13316                 return text;
13317             }
13318
13319             time = -1; sec = -1; deci = -1;
13320             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13321                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13322                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13323                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13324                 return text;
13325             }
13326
13327             if( score_lo < 0 || score_lo >= 100 ) {
13328                 return text;
13329             }
13330
13331             if(sec >= 0) time = 600*time + 10*sec; else
13332             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13333
13334             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13335
13336             /* [HGM] PV time: now locate end of PV info */
13337             while( *++sep >= '0' && *sep <= '9'); // strip depth
13338             if(time >= 0)
13339             while( *++sep >= '0' && *sep <= '9'); // strip time
13340             if(sec >= 0)
13341             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13342             if(deci >= 0)
13343             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13344             while(*sep == ' ') sep++;
13345         }
13346
13347         if( depth <= 0 ) {
13348             return text;
13349         }
13350
13351         if( time < 0 ) {
13352             time = -1;
13353         }
13354
13355         pvInfoList[index-1].depth = depth;
13356         pvInfoList[index-1].score = score;
13357         pvInfoList[index-1].time  = 10*time; // centi-sec
13358         if(*sep == '}') *sep = 0; else *--sep = '{';
13359     }
13360     return sep;
13361 }
13362
13363 void
13364 SendToProgram(message, cps)
13365      char *message;
13366      ChessProgramState *cps;
13367 {
13368     int count, outCount, error;
13369     char buf[MSG_SIZ];
13370
13371     if (cps->pr == NULL) return;
13372     Attention(cps);
13373     
13374     if (appData.debugMode) {
13375         TimeMark now;
13376         GetTimeMark(&now);
13377         fprintf(debugFP, "%ld >%-6s: %s", 
13378                 SubtractTimeMarks(&now, &programStartTime),
13379                 cps->which, message);
13380     }
13381     
13382     count = strlen(message);
13383     outCount = OutputToProcess(cps->pr, message, count, &error);
13384     if (outCount < count && !exiting 
13385                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13386         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13387         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13388             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13389                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13390                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13391             } else {
13392                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13393             }
13394             gameInfo.resultDetails = StrSave(buf);
13395         }
13396         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13397     }
13398 }
13399
13400 void
13401 ReceiveFromProgram(isr, closure, message, count, error)
13402      InputSourceRef isr;
13403      VOIDSTAR closure;
13404      char *message;
13405      int count;
13406      int error;
13407 {
13408     char *end_str;
13409     char buf[MSG_SIZ];
13410     ChessProgramState *cps = (ChessProgramState *)closure;
13411
13412     if (isr != cps->isr) return; /* Killed intentionally */
13413     if (count <= 0) {
13414         if (count == 0) {
13415             sprintf(buf,
13416                     _("Error: %s chess program (%s) exited unexpectedly"),
13417                     cps->which, cps->program);
13418         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13419                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13420                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13421                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13422                 } else {
13423                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13424                 }
13425                 gameInfo.resultDetails = StrSave(buf);
13426             }
13427             RemoveInputSource(cps->isr);
13428             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13429         } else {
13430             sprintf(buf,
13431                     _("Error reading from %s chess program (%s)"),
13432                     cps->which, cps->program);
13433             RemoveInputSource(cps->isr);
13434
13435             /* [AS] Program is misbehaving badly... kill it */
13436             if( count == -2 ) {
13437                 DestroyChildProcess( cps->pr, 9 );
13438                 cps->pr = NoProc;
13439             }
13440
13441             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13442         }
13443         return;
13444     }
13445     
13446     if ((end_str = strchr(message, '\r')) != NULL)
13447       *end_str = NULLCHAR;
13448     if ((end_str = strchr(message, '\n')) != NULL)
13449       *end_str = NULLCHAR;
13450     
13451     if (appData.debugMode) {
13452         TimeMark now; int print = 1;
13453         char *quote = ""; char c; int i;
13454
13455         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13456                 char start = message[0];
13457                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13458                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13459                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13460                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13461                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13462                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13463                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13464                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13465                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13466                     print = (appData.engineComments >= 2);
13467                 }
13468                 message[0] = start; // restore original message
13469         }
13470         if(print) {
13471                 GetTimeMark(&now);
13472                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13473                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13474                         quote,
13475                         message);
13476         }
13477     }
13478
13479     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13480     if (appData.icsEngineAnalyze) {
13481         if (strstr(message, "whisper") != NULL ||
13482              strstr(message, "kibitz") != NULL || 
13483             strstr(message, "tellics") != NULL) return;
13484     }
13485
13486     HandleMachineMove(message, cps);
13487 }
13488
13489
13490 void
13491 SendTimeControl(cps, mps, tc, inc, sd, st)
13492      ChessProgramState *cps;
13493      int mps, inc, sd, st;
13494      long tc;
13495 {
13496     char buf[MSG_SIZ];
13497     int seconds;
13498
13499     if( timeControl_2 > 0 ) {
13500         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13501             tc = timeControl_2;
13502         }
13503     }
13504     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13505     inc /= cps->timeOdds;
13506     st  /= cps->timeOdds;
13507
13508     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13509
13510     if (st > 0) {
13511       /* Set exact time per move, normally using st command */
13512       if (cps->stKludge) {
13513         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13514         seconds = st % 60;
13515         if (seconds == 0) {
13516           sprintf(buf, "level 1 %d\n", st/60);
13517         } else {
13518           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13519         }
13520       } else {
13521         sprintf(buf, "st %d\n", st);
13522       }
13523     } else {
13524       /* Set conventional or incremental time control, using level command */
13525       if (seconds == 0) {
13526         /* Note old gnuchess bug -- minutes:seconds used to not work.
13527            Fixed in later versions, but still avoid :seconds
13528            when seconds is 0. */
13529         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13530       } else {
13531         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13532                 seconds, inc/1000);
13533       }
13534     }
13535     SendToProgram(buf, cps);
13536
13537     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13538     /* Orthogonally, limit search to given depth */
13539     if (sd > 0) {
13540       if (cps->sdKludge) {
13541         sprintf(buf, "depth\n%d\n", sd);
13542       } else {
13543         sprintf(buf, "sd %d\n", sd);
13544       }
13545       SendToProgram(buf, cps);
13546     }
13547
13548     if(cps->nps > 0) { /* [HGM] nps */
13549         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13550         else {
13551                 sprintf(buf, "nps %d\n", cps->nps);
13552               SendToProgram(buf, cps);
13553         }
13554     }
13555 }
13556
13557 ChessProgramState *WhitePlayer()
13558 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13559 {
13560     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13561        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13562         return &second;
13563     return &first;
13564 }
13565
13566 void
13567 SendTimeRemaining(cps, machineWhite)
13568      ChessProgramState *cps;
13569      int /*boolean*/ machineWhite;
13570 {
13571     char message[MSG_SIZ];
13572     long time, otime;
13573
13574     /* Note: this routine must be called when the clocks are stopped
13575        or when they have *just* been set or switched; otherwise
13576        it will be off by the time since the current tick started.
13577     */
13578     if (machineWhite) {
13579         time = whiteTimeRemaining / 10;
13580         otime = blackTimeRemaining / 10;
13581     } else {
13582         time = blackTimeRemaining / 10;
13583         otime = whiteTimeRemaining / 10;
13584     }
13585     /* [HGM] translate opponent's time by time-odds factor */
13586     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13587     if (appData.debugMode) {
13588         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13589     }
13590
13591     if (time <= 0) time = 1;
13592     if (otime <= 0) otime = 1;
13593     
13594     sprintf(message, "time %ld\n", time);
13595     SendToProgram(message, cps);
13596
13597     sprintf(message, "otim %ld\n", otime);
13598     SendToProgram(message, cps);
13599 }
13600
13601 int
13602 BoolFeature(p, name, loc, cps)
13603      char **p;
13604      char *name;
13605      int *loc;
13606      ChessProgramState *cps;
13607 {
13608   char buf[MSG_SIZ];
13609   int len = strlen(name);
13610   int val;
13611   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13612     (*p) += len + 1;
13613     sscanf(*p, "%d", &val);
13614     *loc = (val != 0);
13615     while (**p && **p != ' ') (*p)++;
13616     sprintf(buf, "accepted %s\n", name);
13617     SendToProgram(buf, cps);
13618     return TRUE;
13619   }
13620   return FALSE;
13621 }
13622
13623 int
13624 IntFeature(p, name, loc, cps)
13625      char **p;
13626      char *name;
13627      int *loc;
13628      ChessProgramState *cps;
13629 {
13630   char buf[MSG_SIZ];
13631   int len = strlen(name);
13632   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13633     (*p) += len + 1;
13634     sscanf(*p, "%d", loc);
13635     while (**p && **p != ' ') (*p)++;
13636     sprintf(buf, "accepted %s\n", name);
13637     SendToProgram(buf, cps);
13638     return TRUE;
13639   }
13640   return FALSE;
13641 }
13642
13643 int
13644 StringFeature(p, name, loc, cps)
13645      char **p;
13646      char *name;
13647      char loc[];
13648      ChessProgramState *cps;
13649 {
13650   char buf[MSG_SIZ];
13651   int len = strlen(name);
13652   if (strncmp((*p), name, len) == 0
13653       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13654     (*p) += len + 2;
13655     sscanf(*p, "%[^\"]", loc);
13656     while (**p && **p != '\"') (*p)++;
13657     if (**p == '\"') (*p)++;
13658     sprintf(buf, "accepted %s\n", name);
13659     SendToProgram(buf, cps);
13660     return TRUE;
13661   }
13662   return FALSE;
13663 }
13664
13665 int 
13666 ParseOption(Option *opt, ChessProgramState *cps)
13667 // [HGM] options: process the string that defines an engine option, and determine
13668 // name, type, default value, and allowed value range
13669 {
13670         char *p, *q, buf[MSG_SIZ];
13671         int n, min = (-1)<<31, max = 1<<31, def;
13672
13673         if(p = strstr(opt->name, " -spin ")) {
13674             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13675             if(max < min) max = min; // enforce consistency
13676             if(def < min) def = min;
13677             if(def > max) def = max;
13678             opt->value = def;
13679             opt->min = min;
13680             opt->max = max;
13681             opt->type = Spin;
13682         } else if((p = strstr(opt->name, " -slider "))) {
13683             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13684             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13685             if(max < min) max = min; // enforce consistency
13686             if(def < min) def = min;
13687             if(def > max) def = max;
13688             opt->value = def;
13689             opt->min = min;
13690             opt->max = max;
13691             opt->type = Spin; // Slider;
13692         } else if((p = strstr(opt->name, " -string "))) {
13693             opt->textValue = p+9;
13694             opt->type = TextBox;
13695         } else if((p = strstr(opt->name, " -file "))) {
13696             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13697             opt->textValue = p+7;
13698             opt->type = TextBox; // FileName;
13699         } else if((p = strstr(opt->name, " -path "))) {
13700             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13701             opt->textValue = p+7;
13702             opt->type = TextBox; // PathName;
13703         } else if(p = strstr(opt->name, " -check ")) {
13704             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13705             opt->value = (def != 0);
13706             opt->type = CheckBox;
13707         } else if(p = strstr(opt->name, " -combo ")) {
13708             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13709             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13710             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13711             opt->value = n = 0;
13712             while(q = StrStr(q, " /// ")) {
13713                 n++; *q = 0;    // count choices, and null-terminate each of them
13714                 q += 5;
13715                 if(*q == '*') { // remember default, which is marked with * prefix
13716                     q++;
13717                     opt->value = n;
13718                 }
13719                 cps->comboList[cps->comboCnt++] = q;
13720             }
13721             cps->comboList[cps->comboCnt++] = NULL;
13722             opt->max = n + 1;
13723             opt->type = ComboBox;
13724         } else if(p = strstr(opt->name, " -button")) {
13725             opt->type = Button;
13726         } else if(p = strstr(opt->name, " -save")) {
13727             opt->type = SaveButton;
13728         } else return FALSE;
13729         *p = 0; // terminate option name
13730         // now look if the command-line options define a setting for this engine option.
13731         if(cps->optionSettings && cps->optionSettings[0])
13732             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13733         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13734                 sprintf(buf, "option %s", p);
13735                 if(p = strstr(buf, ",")) *p = 0;
13736                 strcat(buf, "\n");
13737                 SendToProgram(buf, cps);
13738         }
13739         return TRUE;
13740 }
13741
13742 void
13743 FeatureDone(cps, val)
13744      ChessProgramState* cps;
13745      int val;
13746 {
13747   DelayedEventCallback cb = GetDelayedEvent();
13748   if ((cb == InitBackEnd3 && cps == &first) ||
13749       (cb == TwoMachinesEventIfReady && cps == &second)) {
13750     CancelDelayedEvent();
13751     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13752   }
13753   cps->initDone = val;
13754 }
13755
13756 /* Parse feature command from engine */
13757 void
13758 ParseFeatures(args, cps)
13759      char* args;
13760      ChessProgramState *cps;  
13761 {
13762   char *p = args;
13763   char *q;
13764   int val;
13765   char buf[MSG_SIZ];
13766
13767   for (;;) {
13768     while (*p == ' ') p++;
13769     if (*p == NULLCHAR) return;
13770
13771     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13772     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13773     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13774     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13775     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13776     if (BoolFeature(&p, "reuse", &val, cps)) {
13777       /* Engine can disable reuse, but can't enable it if user said no */
13778       if (!val) cps->reuse = FALSE;
13779       continue;
13780     }
13781     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13782     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13783       if (gameMode == TwoMachinesPlay) {
13784         DisplayTwoMachinesTitle();
13785       } else {
13786         DisplayTitle("");
13787       }
13788       continue;
13789     }
13790     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13791     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13792     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13793     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13794     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13795     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13796     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13797     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13798     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13799     if (IntFeature(&p, "done", &val, cps)) {
13800       FeatureDone(cps, val);
13801       continue;
13802     }
13803     /* Added by Tord: */
13804     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13805     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13806     /* End of additions by Tord */
13807
13808     /* [HGM] added features: */
13809     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13810     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13811     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13812     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13813     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13814     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13815     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13816         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13817             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13818             SendToProgram(buf, cps);
13819             continue;
13820         }
13821         if(cps->nrOptions >= MAX_OPTIONS) {
13822             cps->nrOptions--;
13823             sprintf(buf, "%s engine has too many options\n", cps->which);
13824             DisplayError(buf, 0);
13825         }
13826         continue;
13827     }
13828     /* End of additions by HGM */
13829
13830     /* unknown feature: complain and skip */
13831     q = p;
13832     while (*q && *q != '=') q++;
13833     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13834     SendToProgram(buf, cps);
13835     p = q;
13836     if (*p == '=') {
13837       p++;
13838       if (*p == '\"') {
13839         p++;
13840         while (*p && *p != '\"') p++;
13841         if (*p == '\"') p++;
13842       } else {
13843         while (*p && *p != ' ') p++;
13844       }
13845     }
13846   }
13847
13848 }
13849
13850 void
13851 PeriodicUpdatesEvent(newState)
13852      int newState;
13853 {
13854     if (newState == appData.periodicUpdates)
13855       return;
13856
13857     appData.periodicUpdates=newState;
13858
13859     /* Display type changes, so update it now */
13860 //    DisplayAnalysis();
13861
13862     /* Get the ball rolling again... */
13863     if (newState) {
13864         AnalysisPeriodicEvent(1);
13865         StartAnalysisClock();
13866     }
13867 }
13868
13869 void
13870 PonderNextMoveEvent(newState)
13871      int newState;
13872 {
13873     if (newState == appData.ponderNextMove) return;
13874     if (gameMode == EditPosition) EditPositionDone(TRUE);
13875     if (newState) {
13876         SendToProgram("hard\n", &first);
13877         if (gameMode == TwoMachinesPlay) {
13878             SendToProgram("hard\n", &second);
13879         }
13880     } else {
13881         SendToProgram("easy\n", &first);
13882         thinkOutput[0] = NULLCHAR;
13883         if (gameMode == TwoMachinesPlay) {
13884             SendToProgram("easy\n", &second);
13885         }
13886     }
13887     appData.ponderNextMove = newState;
13888 }
13889
13890 void
13891 NewSettingEvent(option, feature, command, value)
13892      char *command;
13893      int option, value, *feature;
13894 {
13895     char buf[MSG_SIZ];
13896
13897     if (gameMode == EditPosition) EditPositionDone(TRUE);
13898     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13899     if(feature == NULL || *feature) SendToProgram(buf, &first);
13900     if (gameMode == TwoMachinesPlay) {
13901         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13902     }
13903 }
13904
13905 void
13906 ShowThinkingEvent()
13907 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13908 {
13909     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13910     int newState = appData.showThinking
13911         // [HGM] thinking: other features now need thinking output as well
13912         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13913     
13914     if (oldState == newState) return;
13915     oldState = newState;
13916     if (gameMode == EditPosition) EditPositionDone(TRUE);
13917     if (oldState) {
13918         SendToProgram("post\n", &first);
13919         if (gameMode == TwoMachinesPlay) {
13920             SendToProgram("post\n", &second);
13921         }
13922     } else {
13923         SendToProgram("nopost\n", &first);
13924         thinkOutput[0] = NULLCHAR;
13925         if (gameMode == TwoMachinesPlay) {
13926             SendToProgram("nopost\n", &second);
13927         }
13928     }
13929 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13930 }
13931
13932 void
13933 AskQuestionEvent(title, question, replyPrefix, which)
13934      char *title; char *question; char *replyPrefix; char *which;
13935 {
13936   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13937   if (pr == NoProc) return;
13938   AskQuestion(title, question, replyPrefix, pr);
13939 }
13940
13941 void
13942 DisplayMove(moveNumber)
13943      int moveNumber;
13944 {
13945     char message[MSG_SIZ];
13946     char res[MSG_SIZ];
13947     char cpThinkOutput[MSG_SIZ];
13948
13949     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13950     
13951     if (moveNumber == forwardMostMove - 1 || 
13952         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13953
13954         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13955
13956         if (strchr(cpThinkOutput, '\n')) {
13957             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13958         }
13959     } else {
13960         *cpThinkOutput = NULLCHAR;
13961     }
13962
13963     /* [AS] Hide thinking from human user */
13964     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13965         *cpThinkOutput = NULLCHAR;
13966         if( thinkOutput[0] != NULLCHAR ) {
13967             int i;
13968
13969             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13970                 cpThinkOutput[i] = '.';
13971             }
13972             cpThinkOutput[i] = NULLCHAR;
13973             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13974         }
13975     }
13976
13977     if (moveNumber == forwardMostMove - 1 &&
13978         gameInfo.resultDetails != NULL) {
13979         if (gameInfo.resultDetails[0] == NULLCHAR) {
13980             sprintf(res, " %s", PGNResult(gameInfo.result));
13981         } else {
13982             sprintf(res, " {%s} %s",
13983                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13984         }
13985     } else {
13986         res[0] = NULLCHAR;
13987     }
13988
13989     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13990         DisplayMessage(res, cpThinkOutput);
13991     } else {
13992         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13993                 WhiteOnMove(moveNumber) ? " " : ".. ",
13994                 parseList[moveNumber], res);
13995         DisplayMessage(message, cpThinkOutput);
13996     }
13997 }
13998
13999 void
14000 DisplayComment(moveNumber, text)
14001      int moveNumber;
14002      char *text;
14003 {
14004     char title[MSG_SIZ];
14005     char buf[8000]; // comment can be long!
14006     int score, depth;
14007     
14008     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14009       strcpy(title, "Comment");
14010     } else {
14011       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14012               WhiteOnMove(moveNumber) ? " " : ".. ",
14013               parseList[moveNumber]);
14014     }
14015     // [HGM] PV info: display PV info together with (or as) comment
14016     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14017       if(text == NULL) text = "";                                           
14018       score = pvInfoList[moveNumber].score;
14019       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14020               depth, (pvInfoList[moveNumber].time+50)/100, text);
14021       text = buf;
14022     }
14023     if (text != NULL && (appData.autoDisplayComment || commentUp))
14024         CommentPopUp(title, text);
14025 }
14026
14027 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14028  * might be busy thinking or pondering.  It can be omitted if your
14029  * gnuchess is configured to stop thinking immediately on any user
14030  * input.  However, that gnuchess feature depends on the FIONREAD
14031  * ioctl, which does not work properly on some flavors of Unix.
14032  */
14033 void
14034 Attention(cps)
14035      ChessProgramState *cps;
14036 {
14037 #if ATTENTION
14038     if (!cps->useSigint) return;
14039     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14040     switch (gameMode) {
14041       case MachinePlaysWhite:
14042       case MachinePlaysBlack:
14043       case TwoMachinesPlay:
14044       case IcsPlayingWhite:
14045       case IcsPlayingBlack:
14046       case AnalyzeMode:
14047       case AnalyzeFile:
14048         /* Skip if we know it isn't thinking */
14049         if (!cps->maybeThinking) return;
14050         if (appData.debugMode)
14051           fprintf(debugFP, "Interrupting %s\n", cps->which);
14052         InterruptChildProcess(cps->pr);
14053         cps->maybeThinking = FALSE;
14054         break;
14055       default:
14056         break;
14057     }
14058 #endif /*ATTENTION*/
14059 }
14060
14061 int
14062 CheckFlags()
14063 {
14064     if (whiteTimeRemaining <= 0) {
14065         if (!whiteFlag) {
14066             whiteFlag = TRUE;
14067             if (appData.icsActive) {
14068                 if (appData.autoCallFlag &&
14069                     gameMode == IcsPlayingBlack && !blackFlag) {
14070                   SendToICS(ics_prefix);
14071                   SendToICS("flag\n");
14072                 }
14073             } else {
14074                 if (blackFlag) {
14075                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14076                 } else {
14077                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14078                     if (appData.autoCallFlag) {
14079                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14080                         return TRUE;
14081                     }
14082                 }
14083             }
14084         }
14085     }
14086     if (blackTimeRemaining <= 0) {
14087         if (!blackFlag) {
14088             blackFlag = TRUE;
14089             if (appData.icsActive) {
14090                 if (appData.autoCallFlag &&
14091                     gameMode == IcsPlayingWhite && !whiteFlag) {
14092                   SendToICS(ics_prefix);
14093                   SendToICS("flag\n");
14094                 }
14095             } else {
14096                 if (whiteFlag) {
14097                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14098                 } else {
14099                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14100                     if (appData.autoCallFlag) {
14101                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14102                         return TRUE;
14103                     }
14104                 }
14105             }
14106         }
14107     }
14108     return FALSE;
14109 }
14110
14111 void
14112 CheckTimeControl()
14113 {
14114     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14115         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14116
14117     /*
14118      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14119      */
14120     if ( !WhiteOnMove(forwardMostMove) )
14121         /* White made time control */
14122         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14123         /* [HGM] time odds: correct new time quota for time odds! */
14124                                             / WhitePlayer()->timeOdds;
14125       else
14126         /* Black made time control */
14127         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14128                                             / WhitePlayer()->other->timeOdds;
14129 }
14130
14131 void
14132 DisplayBothClocks()
14133 {
14134     int wom = gameMode == EditPosition ?
14135       !blackPlaysFirst : WhiteOnMove(currentMove);
14136     DisplayWhiteClock(whiteTimeRemaining, wom);
14137     DisplayBlackClock(blackTimeRemaining, !wom);
14138 }
14139
14140
14141 /* Timekeeping seems to be a portability nightmare.  I think everyone
14142    has ftime(), but I'm really not sure, so I'm including some ifdefs
14143    to use other calls if you don't.  Clocks will be less accurate if
14144    you have neither ftime nor gettimeofday.
14145 */
14146
14147 /* VS 2008 requires the #include outside of the function */
14148 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14149 #include <sys/timeb.h>
14150 #endif
14151
14152 /* Get the current time as a TimeMark */
14153 void
14154 GetTimeMark(tm)
14155      TimeMark *tm;
14156 {
14157 #if HAVE_GETTIMEOFDAY
14158
14159     struct timeval timeVal;
14160     struct timezone timeZone;
14161
14162     gettimeofday(&timeVal, &timeZone);
14163     tm->sec = (long) timeVal.tv_sec; 
14164     tm->ms = (int) (timeVal.tv_usec / 1000L);
14165
14166 #else /*!HAVE_GETTIMEOFDAY*/
14167 #if HAVE_FTIME
14168
14169 // include <sys/timeb.h> / moved to just above start of function
14170     struct timeb timeB;
14171
14172     ftime(&timeB);
14173     tm->sec = (long) timeB.time;
14174     tm->ms = (int) timeB.millitm;
14175
14176 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14177     tm->sec = (long) time(NULL);
14178     tm->ms = 0;
14179 #endif
14180 #endif
14181 }
14182
14183 /* Return the difference in milliseconds between two
14184    time marks.  We assume the difference will fit in a long!
14185 */
14186 long
14187 SubtractTimeMarks(tm2, tm1)
14188      TimeMark *tm2, *tm1;
14189 {
14190     return 1000L*(tm2->sec - tm1->sec) +
14191            (long) (tm2->ms - tm1->ms);
14192 }
14193
14194
14195 /*
14196  * Code to manage the game clocks.
14197  *
14198  * In tournament play, black starts the clock and then white makes a move.
14199  * We give the human user a slight advantage if he is playing white---the
14200  * clocks don't run until he makes his first move, so it takes zero time.
14201  * Also, we don't account for network lag, so we could get out of sync
14202  * with GNU Chess's clock -- but then, referees are always right.  
14203  */
14204
14205 static TimeMark tickStartTM;
14206 static long intendedTickLength;
14207
14208 long
14209 NextTickLength(timeRemaining)
14210      long timeRemaining;
14211 {
14212     long nominalTickLength, nextTickLength;
14213
14214     if (timeRemaining > 0L && timeRemaining <= 10000L)
14215       nominalTickLength = 100L;
14216     else
14217       nominalTickLength = 1000L;
14218     nextTickLength = timeRemaining % nominalTickLength;
14219     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14220
14221     return nextTickLength;
14222 }
14223
14224 /* Adjust clock one minute up or down */
14225 void
14226 AdjustClock(Boolean which, int dir)
14227 {
14228     if(which) blackTimeRemaining += 60000*dir;
14229     else      whiteTimeRemaining += 60000*dir;
14230     DisplayBothClocks();
14231 }
14232
14233 /* Stop clocks and reset to a fresh time control */
14234 void
14235 ResetClocks() 
14236 {
14237     (void) StopClockTimer();
14238     if (appData.icsActive) {
14239         whiteTimeRemaining = blackTimeRemaining = 0;
14240     } else if (searchTime) {
14241         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14242         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14243     } else { /* [HGM] correct new time quote for time odds */
14244         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14245         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14246     }
14247     if (whiteFlag || blackFlag) {
14248         DisplayTitle("");
14249         whiteFlag = blackFlag = FALSE;
14250     }
14251     DisplayBothClocks();
14252 }
14253
14254 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14255
14256 /* Decrement running clock by amount of time that has passed */
14257 void
14258 DecrementClocks()
14259 {
14260     long timeRemaining;
14261     long lastTickLength, fudge;
14262     TimeMark now;
14263
14264     if (!appData.clockMode) return;
14265     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14266         
14267     GetTimeMark(&now);
14268
14269     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14270
14271     /* Fudge if we woke up a little too soon */
14272     fudge = intendedTickLength - lastTickLength;
14273     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14274
14275     if (WhiteOnMove(forwardMostMove)) {
14276         if(whiteNPS >= 0) lastTickLength = 0;
14277         timeRemaining = whiteTimeRemaining -= lastTickLength;
14278         DisplayWhiteClock(whiteTimeRemaining - fudge,
14279                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14280     } else {
14281         if(blackNPS >= 0) lastTickLength = 0;
14282         timeRemaining = blackTimeRemaining -= lastTickLength;
14283         DisplayBlackClock(blackTimeRemaining - fudge,
14284                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14285     }
14286
14287     if (CheckFlags()) return;
14288         
14289     tickStartTM = now;
14290     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14291     StartClockTimer(intendedTickLength);
14292
14293     /* if the time remaining has fallen below the alarm threshold, sound the
14294      * alarm. if the alarm has sounded and (due to a takeback or time control
14295      * with increment) the time remaining has increased to a level above the
14296      * threshold, reset the alarm so it can sound again. 
14297      */
14298     
14299     if (appData.icsActive && appData.icsAlarm) {
14300
14301         /* make sure we are dealing with the user's clock */
14302         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14303                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14304            )) return;
14305
14306         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14307             alarmSounded = FALSE;
14308         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14309             PlayAlarmSound();
14310             alarmSounded = TRUE;
14311         }
14312     }
14313 }
14314
14315
14316 /* A player has just moved, so stop the previously running
14317    clock and (if in clock mode) start the other one.
14318    We redisplay both clocks in case we're in ICS mode, because
14319    ICS gives us an update to both clocks after every move.
14320    Note that this routine is called *after* forwardMostMove
14321    is updated, so the last fractional tick must be subtracted
14322    from the color that is *not* on move now.
14323 */
14324 void
14325 SwitchClocks(int newMoveNr)
14326 {
14327     long lastTickLength;
14328     TimeMark now;
14329     int flagged = FALSE;
14330
14331     GetTimeMark(&now);
14332
14333     if (StopClockTimer() && appData.clockMode) {
14334         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14335         if (!WhiteOnMove(forwardMostMove)) {
14336             if(blackNPS >= 0) lastTickLength = 0;
14337             blackTimeRemaining -= lastTickLength;
14338            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14339 //         if(pvInfoList[forwardMostMove-1].time == -1)
14340                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14341                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14342         } else {
14343            if(whiteNPS >= 0) lastTickLength = 0;
14344            whiteTimeRemaining -= lastTickLength;
14345            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14346 //         if(pvInfoList[forwardMostMove-1].time == -1)
14347                  pvInfoList[forwardMostMove-1].time = 
14348                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14349         }
14350         flagged = CheckFlags();
14351     }
14352     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14353     CheckTimeControl();
14354
14355     if (flagged || !appData.clockMode) return;
14356
14357     switch (gameMode) {
14358       case MachinePlaysBlack:
14359       case MachinePlaysWhite:
14360       case BeginningOfGame:
14361         if (pausing) return;
14362         break;
14363
14364       case EditGame:
14365       case PlayFromGameFile:
14366       case IcsExamining:
14367         return;
14368
14369       default:
14370         break;
14371     }
14372
14373     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14374         if(WhiteOnMove(forwardMostMove))
14375              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14376         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14377     }
14378
14379     tickStartTM = now;
14380     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14381       whiteTimeRemaining : blackTimeRemaining);
14382     StartClockTimer(intendedTickLength);
14383 }
14384         
14385
14386 /* Stop both clocks */
14387 void
14388 StopClocks()
14389 {       
14390     long lastTickLength;
14391     TimeMark now;
14392
14393     if (!StopClockTimer()) return;
14394     if (!appData.clockMode) return;
14395
14396     GetTimeMark(&now);
14397
14398     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14399     if (WhiteOnMove(forwardMostMove)) {
14400         if(whiteNPS >= 0) lastTickLength = 0;
14401         whiteTimeRemaining -= lastTickLength;
14402         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14403     } else {
14404         if(blackNPS >= 0) lastTickLength = 0;
14405         blackTimeRemaining -= lastTickLength;
14406         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14407     }
14408     CheckFlags();
14409 }
14410         
14411 /* Start clock of player on move.  Time may have been reset, so
14412    if clock is already running, stop and restart it. */
14413 void
14414 StartClocks()
14415 {
14416     (void) StopClockTimer(); /* in case it was running already */
14417     DisplayBothClocks();
14418     if (CheckFlags()) return;
14419
14420     if (!appData.clockMode) return;
14421     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14422
14423     GetTimeMark(&tickStartTM);
14424     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14425       whiteTimeRemaining : blackTimeRemaining);
14426
14427    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14428     whiteNPS = blackNPS = -1; 
14429     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14430        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14431         whiteNPS = first.nps;
14432     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14433        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14434         blackNPS = first.nps;
14435     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14436         whiteNPS = second.nps;
14437     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14438         blackNPS = second.nps;
14439     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14440
14441     StartClockTimer(intendedTickLength);
14442 }
14443
14444 char *
14445 TimeString(ms)
14446      long ms;
14447 {
14448     long second, minute, hour, day;
14449     char *sign = "";
14450     static char buf[32];
14451     
14452     if (ms > 0 && ms <= 9900) {
14453       /* convert milliseconds to tenths, rounding up */
14454       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14455
14456       sprintf(buf, " %03.1f ", tenths/10.0);
14457       return buf;
14458     }
14459
14460     /* convert milliseconds to seconds, rounding up */
14461     /* use floating point to avoid strangeness of integer division
14462        with negative dividends on many machines */
14463     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14464
14465     if (second < 0) {
14466         sign = "-";
14467         second = -second;
14468     }
14469     
14470     day = second / (60 * 60 * 24);
14471     second = second % (60 * 60 * 24);
14472     hour = second / (60 * 60);
14473     second = second % (60 * 60);
14474     minute = second / 60;
14475     second = second % 60;
14476     
14477     if (day > 0)
14478       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14479               sign, day, hour, minute, second);
14480     else if (hour > 0)
14481       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14482     else
14483       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14484     
14485     return buf;
14486 }
14487
14488
14489 /*
14490  * This is necessary because some C libraries aren't ANSI C compliant yet.
14491  */
14492 char *
14493 StrStr(string, match)
14494      char *string, *match;
14495 {
14496     int i, length;
14497     
14498     length = strlen(match);
14499     
14500     for (i = strlen(string) - length; i >= 0; i--, string++)
14501       if (!strncmp(match, string, length))
14502         return string;
14503     
14504     return NULL;
14505 }
14506
14507 char *
14508 StrCaseStr(string, match)
14509      char *string, *match;
14510 {
14511     int i, j, length;
14512     
14513     length = strlen(match);
14514     
14515     for (i = strlen(string) - length; i >= 0; i--, string++) {
14516         for (j = 0; j < length; j++) {
14517             if (ToLower(match[j]) != ToLower(string[j]))
14518               break;
14519         }
14520         if (j == length) return string;
14521     }
14522
14523     return NULL;
14524 }
14525
14526 #ifndef _amigados
14527 int
14528 StrCaseCmp(s1, s2)
14529      char *s1, *s2;
14530 {
14531     char c1, c2;
14532     
14533     for (;;) {
14534         c1 = ToLower(*s1++);
14535         c2 = ToLower(*s2++);
14536         if (c1 > c2) return 1;
14537         if (c1 < c2) return -1;
14538         if (c1 == NULLCHAR) return 0;
14539     }
14540 }
14541
14542
14543 int
14544 ToLower(c)
14545      int c;
14546 {
14547     return isupper(c) ? tolower(c) : c;
14548 }
14549
14550
14551 int
14552 ToUpper(c)
14553      int c;
14554 {
14555     return islower(c) ? toupper(c) : c;
14556 }
14557 #endif /* !_amigados    */
14558
14559 char *
14560 StrSave(s)
14561      char *s;
14562 {
14563     char *ret;
14564
14565     if ((ret = (char *) malloc(strlen(s) + 1))) {
14566         strcpy(ret, s);
14567     }
14568     return ret;
14569 }
14570
14571 char *
14572 StrSavePtr(s, savePtr)
14573      char *s, **savePtr;
14574 {
14575     if (*savePtr) {
14576         free(*savePtr);
14577     }
14578     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14579         strcpy(*savePtr, s);
14580     }
14581     return(*savePtr);
14582 }
14583
14584 char *
14585 PGNDate()
14586 {
14587     time_t clock;
14588     struct tm *tm;
14589     char buf[MSG_SIZ];
14590
14591     clock = time((time_t *)NULL);
14592     tm = localtime(&clock);
14593     sprintf(buf, "%04d.%02d.%02d",
14594             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14595     return StrSave(buf);
14596 }
14597
14598
14599 char *
14600 PositionToFEN(move, overrideCastling)
14601      int move;
14602      char *overrideCastling;
14603 {
14604     int i, j, fromX, fromY, toX, toY;
14605     int whiteToPlay;
14606     char buf[128];
14607     char *p, *q;
14608     int emptycount;
14609     ChessSquare piece;
14610
14611     whiteToPlay = (gameMode == EditPosition) ?
14612       !blackPlaysFirst : (move % 2 == 0);
14613     p = buf;
14614
14615     /* Piece placement data */
14616     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14617         emptycount = 0;
14618         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14619             if (boards[move][i][j] == EmptySquare) {
14620                 emptycount++;
14621             } else { ChessSquare piece = boards[move][i][j];
14622                 if (emptycount > 0) {
14623                     if(emptycount<10) /* [HGM] can be >= 10 */
14624                         *p++ = '0' + emptycount;
14625                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14626                     emptycount = 0;
14627                 }
14628                 if(PieceToChar(piece) == '+') {
14629                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14630                     *p++ = '+';
14631                     piece = (ChessSquare)(DEMOTED piece);
14632                 } 
14633                 *p++ = PieceToChar(piece);
14634                 if(p[-1] == '~') {
14635                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14636                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14637                     *p++ = '~';
14638                 }
14639             }
14640         }
14641         if (emptycount > 0) {
14642             if(emptycount<10) /* [HGM] can be >= 10 */
14643                 *p++ = '0' + emptycount;
14644             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14645             emptycount = 0;
14646         }
14647         *p++ = '/';
14648     }
14649     *(p - 1) = ' ';
14650
14651     /* [HGM] print Crazyhouse or Shogi holdings */
14652     if( gameInfo.holdingsWidth ) {
14653         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14654         q = p;
14655         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14656             piece = boards[move][i][BOARD_WIDTH-1];
14657             if( piece != EmptySquare )
14658               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14659                   *p++ = PieceToChar(piece);
14660         }
14661         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14662             piece = boards[move][BOARD_HEIGHT-i-1][0];
14663             if( piece != EmptySquare )
14664               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14665                   *p++ = PieceToChar(piece);
14666         }
14667
14668         if( q == p ) *p++ = '-';
14669         *p++ = ']';
14670         *p++ = ' ';
14671     }
14672
14673     /* Active color */
14674     *p++ = whiteToPlay ? 'w' : 'b';
14675     *p++ = ' ';
14676
14677   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14678     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14679   } else {
14680   if(nrCastlingRights) {
14681      q = p;
14682      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14683        /* [HGM] write directly from rights */
14684            if(boards[move][CASTLING][2] != NoRights &&
14685               boards[move][CASTLING][0] != NoRights   )
14686                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14687            if(boards[move][CASTLING][2] != NoRights &&
14688               boards[move][CASTLING][1] != NoRights   )
14689                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14690            if(boards[move][CASTLING][5] != NoRights &&
14691               boards[move][CASTLING][3] != NoRights   )
14692                 *p++ = boards[move][CASTLING][3] + AAA;
14693            if(boards[move][CASTLING][5] != NoRights &&
14694               boards[move][CASTLING][4] != NoRights   )
14695                 *p++ = boards[move][CASTLING][4] + AAA;
14696      } else {
14697
14698         /* [HGM] write true castling rights */
14699         if( nrCastlingRights == 6 ) {
14700             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14701                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14702             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14703                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14704             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14705                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14706             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14707                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14708         }
14709      }
14710      if (q == p) *p++ = '-'; /* No castling rights */
14711      *p++ = ' ';
14712   }
14713
14714   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14715      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14716     /* En passant target square */
14717     if (move > backwardMostMove) {
14718         fromX = moveList[move - 1][0] - AAA;
14719         fromY = moveList[move - 1][1] - ONE;
14720         toX = moveList[move - 1][2] - AAA;
14721         toY = moveList[move - 1][3] - ONE;
14722         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14723             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14724             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14725             fromX == toX) {
14726             /* 2-square pawn move just happened */
14727             *p++ = toX + AAA;
14728             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14729         } else {
14730             *p++ = '-';
14731         }
14732     } else if(move == backwardMostMove) {
14733         // [HGM] perhaps we should always do it like this, and forget the above?
14734         if((signed char)boards[move][EP_STATUS] >= 0) {
14735             *p++ = boards[move][EP_STATUS] + AAA;
14736             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14737         } else {
14738             *p++ = '-';
14739         }
14740     } else {
14741         *p++ = '-';
14742     }
14743     *p++ = ' ';
14744   }
14745   }
14746
14747     /* [HGM] find reversible plies */
14748     {   int i = 0, j=move;
14749
14750         if (appData.debugMode) { int k;
14751             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14752             for(k=backwardMostMove; k<=forwardMostMove; k++)
14753                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14754
14755         }
14756
14757         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14758         if( j == backwardMostMove ) i += initialRulePlies;
14759         sprintf(p, "%d ", i);
14760         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14761     }
14762     /* Fullmove number */
14763     sprintf(p, "%d", (move / 2) + 1);
14764     
14765     return StrSave(buf);
14766 }
14767
14768 Boolean
14769 ParseFEN(board, blackPlaysFirst, fen)
14770     Board board;
14771      int *blackPlaysFirst;
14772      char *fen;
14773 {
14774     int i, j;
14775     char *p;
14776     int emptycount;
14777     ChessSquare piece;
14778
14779     p = fen;
14780
14781     /* [HGM] by default clear Crazyhouse holdings, if present */
14782     if(gameInfo.holdingsWidth) {
14783        for(i=0; i<BOARD_HEIGHT; i++) {
14784            board[i][0]             = EmptySquare; /* black holdings */
14785            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14786            board[i][1]             = (ChessSquare) 0; /* black counts */
14787            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14788        }
14789     }
14790
14791     /* Piece placement data */
14792     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14793         j = 0;
14794         for (;;) {
14795             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14796                 if (*p == '/') p++;
14797                 emptycount = gameInfo.boardWidth - j;
14798                 while (emptycount--)
14799                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14800                 break;
14801 #if(BOARD_FILES >= 10)
14802             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14803                 p++; emptycount=10;
14804                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14805                 while (emptycount--)
14806                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14807 #endif
14808             } else if (isdigit(*p)) {
14809                 emptycount = *p++ - '0';
14810                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14811                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14812                 while (emptycount--)
14813                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14814             } else if (*p == '+' || isalpha(*p)) {
14815                 if (j >= gameInfo.boardWidth) return FALSE;
14816                 if(*p=='+') {
14817                     piece = CharToPiece(*++p);
14818                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14819                     piece = (ChessSquare) (PROMOTED piece ); p++;
14820                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14821                 } else piece = CharToPiece(*p++);
14822
14823                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14824                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14825                     piece = (ChessSquare) (PROMOTED piece);
14826                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14827                     p++;
14828                 }
14829                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14830             } else {
14831                 return FALSE;
14832             }
14833         }
14834     }
14835     while (*p == '/' || *p == ' ') p++;
14836
14837     /* [HGM] look for Crazyhouse holdings here */
14838     while(*p==' ') p++;
14839     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14840         if(*p == '[') p++;
14841         if(*p == '-' ) *p++; /* empty holdings */ else {
14842             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14843             /* if we would allow FEN reading to set board size, we would   */
14844             /* have to add holdings and shift the board read so far here   */
14845             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14846                 *p++;
14847                 if((int) piece >= (int) BlackPawn ) {
14848                     i = (int)piece - (int)BlackPawn;
14849                     i = PieceToNumber((ChessSquare)i);
14850                     if( i >= gameInfo.holdingsSize ) return FALSE;
14851                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14852                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14853                 } else {
14854                     i = (int)piece - (int)WhitePawn;
14855                     i = PieceToNumber((ChessSquare)i);
14856                     if( i >= gameInfo.holdingsSize ) return FALSE;
14857                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14858                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14859                 }
14860             }
14861         }
14862         if(*p == ']') *p++;
14863     }
14864
14865     while(*p == ' ') p++;
14866
14867     /* Active color */
14868     switch (*p++) {
14869       case 'w':
14870         *blackPlaysFirst = FALSE;
14871         break;
14872       case 'b': 
14873         *blackPlaysFirst = TRUE;
14874         break;
14875       default:
14876         return FALSE;
14877     }
14878
14879     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14880     /* return the extra info in global variiables             */
14881
14882     /* set defaults in case FEN is incomplete */
14883     board[EP_STATUS] = EP_UNKNOWN;
14884     for(i=0; i<nrCastlingRights; i++ ) {
14885         board[CASTLING][i] =
14886             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14887     }   /* assume possible unless obviously impossible */
14888     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14889     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14890     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14891                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14892     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14893     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14894     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14895                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14896     FENrulePlies = 0;
14897
14898     while(*p==' ') p++;
14899     if(nrCastlingRights) {
14900       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14901           /* castling indicator present, so default becomes no castlings */
14902           for(i=0; i<nrCastlingRights; i++ ) {
14903                  board[CASTLING][i] = NoRights;
14904           }
14905       }
14906       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14907              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14908              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14909              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14910         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14911
14912         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14913             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14914             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14915         }
14916         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14917             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14918         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14919                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14920         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14921                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14922         switch(c) {
14923           case'K':
14924               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14925               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14926               board[CASTLING][2] = whiteKingFile;
14927               break;
14928           case'Q':
14929               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14930               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14931               board[CASTLING][2] = whiteKingFile;
14932               break;
14933           case'k':
14934               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14935               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14936               board[CASTLING][5] = blackKingFile;
14937               break;
14938           case'q':
14939               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14940               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14941               board[CASTLING][5] = blackKingFile;
14942           case '-':
14943               break;
14944           default: /* FRC castlings */
14945               if(c >= 'a') { /* black rights */
14946                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14947                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14948                   if(i == BOARD_RGHT) break;
14949                   board[CASTLING][5] = i;
14950                   c -= AAA;
14951                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14952                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14953                   if(c > i)
14954                       board[CASTLING][3] = c;
14955                   else
14956                       board[CASTLING][4] = c;
14957               } else { /* white rights */
14958                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14959                     if(board[0][i] == WhiteKing) break;
14960                   if(i == BOARD_RGHT) break;
14961                   board[CASTLING][2] = i;
14962                   c -= AAA - 'a' + 'A';
14963                   if(board[0][c] >= WhiteKing) break;
14964                   if(c > i)
14965                       board[CASTLING][0] = c;
14966                   else
14967                       board[CASTLING][1] = c;
14968               }
14969         }
14970       }
14971       for(i=0; i<nrCastlingRights; i++)
14972         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14973     if (appData.debugMode) {
14974         fprintf(debugFP, "FEN castling rights:");
14975         for(i=0; i<nrCastlingRights; i++)
14976         fprintf(debugFP, " %d", board[CASTLING][i]);
14977         fprintf(debugFP, "\n");
14978     }
14979
14980       while(*p==' ') p++;
14981     }
14982
14983     /* read e.p. field in games that know e.p. capture */
14984     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14985        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14986       if(*p=='-') {
14987         p++; board[EP_STATUS] = EP_NONE;
14988       } else {
14989          char c = *p++ - AAA;
14990
14991          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14992          if(*p >= '0' && *p <='9') *p++;
14993          board[EP_STATUS] = c;
14994       }
14995     }
14996
14997
14998     if(sscanf(p, "%d", &i) == 1) {
14999         FENrulePlies = i; /* 50-move ply counter */
15000         /* (The move number is still ignored)    */
15001     }
15002
15003     return TRUE;
15004 }
15005       
15006 void
15007 EditPositionPasteFEN(char *fen)
15008 {
15009   if (fen != NULL) {
15010     Board initial_position;
15011
15012     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15013       DisplayError(_("Bad FEN position in clipboard"), 0);
15014       return ;
15015     } else {
15016       int savedBlackPlaysFirst = blackPlaysFirst;
15017       EditPositionEvent();
15018       blackPlaysFirst = savedBlackPlaysFirst;
15019       CopyBoard(boards[0], initial_position);
15020       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15021       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15022       DisplayBothClocks();
15023       DrawPosition(FALSE, boards[currentMove]);
15024     }
15025   }
15026 }
15027
15028 static char cseq[12] = "\\   ";
15029
15030 Boolean set_cont_sequence(char *new_seq)
15031 {
15032     int len;
15033     Boolean ret;
15034
15035     // handle bad attempts to set the sequence
15036         if (!new_seq)
15037                 return 0; // acceptable error - no debug
15038
15039     len = strlen(new_seq);
15040     ret = (len > 0) && (len < sizeof(cseq));
15041     if (ret)
15042         strcpy(cseq, new_seq);
15043     else if (appData.debugMode)
15044         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15045     return ret;
15046 }
15047
15048 /*
15049     reformat a source message so words don't cross the width boundary.  internal
15050     newlines are not removed.  returns the wrapped size (no null character unless
15051     included in source message).  If dest is NULL, only calculate the size required
15052     for the dest buffer.  lp argument indicats line position upon entry, and it's
15053     passed back upon exit.
15054 */
15055 int wrap(char *dest, char *src, int count, int width, int *lp)
15056 {
15057     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15058
15059     cseq_len = strlen(cseq);
15060     old_line = line = *lp;
15061     ansi = len = clen = 0;
15062
15063     for (i=0; i < count; i++)
15064     {
15065         if (src[i] == '\033')
15066             ansi = 1;
15067
15068         // if we hit the width, back up
15069         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15070         {
15071             // store i & len in case the word is too long
15072             old_i = i, old_len = len;
15073
15074             // find the end of the last word
15075             while (i && src[i] != ' ' && src[i] != '\n')
15076             {
15077                 i--;
15078                 len--;
15079             }
15080
15081             // word too long?  restore i & len before splitting it
15082             if ((old_i-i+clen) >= width)
15083             {
15084                 i = old_i;
15085                 len = old_len;
15086             }
15087
15088             // extra space?
15089             if (i && src[i-1] == ' ')
15090                 len--;
15091
15092             if (src[i] != ' ' && src[i] != '\n')
15093             {
15094                 i--;
15095                 if (len)
15096                     len--;
15097             }
15098
15099             // now append the newline and continuation sequence
15100             if (dest)
15101                 dest[len] = '\n';
15102             len++;
15103             if (dest)
15104                 strncpy(dest+len, cseq, cseq_len);
15105             len += cseq_len;
15106             line = cseq_len;
15107             clen = cseq_len;
15108             continue;
15109         }
15110
15111         if (dest)
15112             dest[len] = src[i];
15113         len++;
15114         if (!ansi)
15115             line++;
15116         if (src[i] == '\n')
15117             line = 0;
15118         if (src[i] == 'm')
15119             ansi = 0;
15120     }
15121     if (dest && appData.debugMode)
15122     {
15123         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15124             count, width, line, len, *lp);
15125         show_bytes(debugFP, src, count);
15126         fprintf(debugFP, "\ndest: ");
15127         show_bytes(debugFP, dest, len);
15128         fprintf(debugFP, "\n");
15129     }
15130     *lp = dest ? line : old_line;
15131
15132     return len;
15133 }
15134
15135 // [HGM] vari: routines for shelving variations
15136
15137 void 
15138 PushTail(int firstMove, int lastMove)
15139 {
15140         int i, j, nrMoves = lastMove - firstMove;
15141
15142         if(appData.icsActive) { // only in local mode
15143                 forwardMostMove = currentMove; // mimic old ICS behavior
15144                 return;
15145         }
15146         if(storedGames >= MAX_VARIATIONS-1) return;
15147
15148         // push current tail of game on stack
15149         savedResult[storedGames] = gameInfo.result;
15150         savedDetails[storedGames] = gameInfo.resultDetails;
15151         gameInfo.resultDetails = NULL;
15152         savedFirst[storedGames] = firstMove;
15153         savedLast [storedGames] = lastMove;
15154         savedFramePtr[storedGames] = framePtr;
15155         framePtr -= nrMoves; // reserve space for the boards
15156         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15157             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15158             for(j=0; j<MOVE_LEN; j++)
15159                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15160             for(j=0; j<2*MOVE_LEN; j++)
15161                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15162             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15163             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15164             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15165             pvInfoList[firstMove+i-1].depth = 0;
15166             commentList[framePtr+i] = commentList[firstMove+i];
15167             commentList[firstMove+i] = NULL;
15168         }
15169
15170         storedGames++;
15171         forwardMostMove = firstMove; // truncate game so we can start variation
15172         if(storedGames == 1) GreyRevert(FALSE);
15173 }
15174
15175 Boolean
15176 PopTail(Boolean annotate)
15177 {
15178         int i, j, nrMoves;
15179         char buf[8000], moveBuf[20];
15180
15181         if(appData.icsActive) return FALSE; // only in local mode
15182         if(!storedGames) return FALSE; // sanity
15183         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15184
15185         storedGames--;
15186         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15187         nrMoves = savedLast[storedGames] - currentMove;
15188         if(annotate) {
15189                 int cnt = 10;
15190                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15191                 else strcpy(buf, "(");
15192                 for(i=currentMove; i<forwardMostMove; i++) {
15193                         if(WhiteOnMove(i))
15194                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15195                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15196                         strcat(buf, moveBuf);
15197                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15198                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15199                 }
15200                 strcat(buf, ")");
15201         }
15202         for(i=1; i<=nrMoves; i++) { // copy last variation back
15203             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15204             for(j=0; j<MOVE_LEN; j++)
15205                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15206             for(j=0; j<2*MOVE_LEN; j++)
15207                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15208             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15209             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15210             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15211             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15212             commentList[currentMove+i] = commentList[framePtr+i];
15213             commentList[framePtr+i] = NULL;
15214         }
15215         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15216         framePtr = savedFramePtr[storedGames];
15217         gameInfo.result = savedResult[storedGames];
15218         if(gameInfo.resultDetails != NULL) {
15219             free(gameInfo.resultDetails);
15220       }
15221         gameInfo.resultDetails = savedDetails[storedGames];
15222         forwardMostMove = currentMove + nrMoves;
15223         if(storedGames == 0) GreyRevert(TRUE);
15224         return TRUE;
15225 }
15226
15227 void 
15228 CleanupTail()
15229 {       // remove all shelved variations
15230         int i;
15231         for(i=0; i<storedGames; i++) {
15232             if(savedDetails[i])
15233                 free(savedDetails[i]);
15234             savedDetails[i] = NULL;
15235         }
15236         for(i=framePtr; i<MAX_MOVES; i++) {
15237                 if(commentList[i]) free(commentList[i]);
15238                 commentList[i] = NULL;
15239         }
15240         framePtr = MAX_MOVES-1;
15241         storedGames = 0;
15242 }
15243
15244 void
15245 LoadVariation(int index, char *text)
15246 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15247         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15248         int level = 0, move;
15249
15250         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15251         // first find outermost bracketing variation
15252         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15253             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15254                 if(*p == '{') wait = '}'; else
15255                 if(*p == '[') wait = ']'; else
15256                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15257                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15258             }
15259             if(*p == wait) wait = NULLCHAR; // closing ]} found
15260             p++;
15261         }
15262         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15263         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15264         end[1] = NULLCHAR; // clip off comment beyond variation
15265         ToNrEvent(currentMove-1);
15266         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15267         // kludge: use ParsePV() to append variation to game
15268         move = currentMove;
15269         ParsePV(start, TRUE);
15270         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15271         ClearPremoveHighlights();
15272         CommentPopDown();
15273         ToNrEvent(currentMove+1);
15274 }
15275