Do not exit after match when match started from menu
[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     int i;
2210     if(!seekGraphUp) return FALSE;
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                if (loggedOn == TRUE)
2842                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2843                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2844 #endif
2845             } // [DM] 'else { ' deleted
2846                 if (
2847                     /* Regular tells and says */
2848                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2849                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2850                     looking_at(buf, &i, "* says: ") ||
2851                     /* Don't color "message" or "messages" output */
2852                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2853                     looking_at(buf, &i, "*. * at *:*: ") ||
2854                     looking_at(buf, &i, "--* (*:*): ") ||
2855                     /* Message notifications (same color as tells) */
2856                     looking_at(buf, &i, "* has left a message ") ||
2857                     looking_at(buf, &i, "* just sent you a message:\n") ||
2858                     /* Whispers and kibitzes */
2859                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2860                     looking_at(buf, &i, "* kibitzes: ") ||
2861                     /* Channel tells */
2862                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2863
2864                   if (tkind == 1 && strchr(star_match[0], ':')) {
2865                       /* Avoid "tells you:" spoofs in channels */
2866                      tkind = 3;
2867                   }
2868                   if (star_match[0][0] == NULLCHAR ||
2869                       strchr(star_match[0], ' ') ||
2870                       (tkind == 3 && strchr(star_match[1], ' '))) {
2871                     /* Reject bogus matches */
2872                     i = oldi;
2873                   } else {
2874                     if (appData.colorize) {
2875                       if (oldi > next_out) {
2876                         SendToPlayer(&buf[next_out], oldi - next_out);
2877                         next_out = oldi;
2878                       }
2879                       switch (tkind) {
2880                       case 1:
2881                         Colorize(ColorTell, FALSE);
2882                         curColor = ColorTell;
2883                         break;
2884                       case 2:
2885                         Colorize(ColorKibitz, FALSE);
2886                         curColor = ColorKibitz;
2887                         break;
2888                       case 3:
2889                         p = strrchr(star_match[1], '(');
2890                         if (p == NULL) {
2891                           p = star_match[1];
2892                         } else {
2893                           p++;
2894                         }
2895                         if (atoi(p) == 1) {
2896                           Colorize(ColorChannel1, FALSE);
2897                           curColor = ColorChannel1;
2898                         } else {
2899                           Colorize(ColorChannel, FALSE);
2900                           curColor = ColorChannel;
2901                         }
2902                         break;
2903                       case 5:
2904                         curColor = ColorNormal;
2905                         break;
2906                       }
2907                     }
2908                     if (started == STARTED_NONE && appData.autoComment &&
2909                         (gameMode == IcsObserving ||
2910                          gameMode == IcsPlayingWhite ||
2911                          gameMode == IcsPlayingBlack)) {
2912                       parse_pos = i - oldi;
2913                       memcpy(parse, &buf[oldi], parse_pos);
2914                       parse[parse_pos] = NULLCHAR;
2915                       started = STARTED_COMMENT;
2916                       savingComment = TRUE;
2917                     } else {
2918                       started = STARTED_CHATTER;
2919                       savingComment = FALSE;
2920                     }
2921                     loggedOn = TRUE;
2922                     continue;
2923                   }
2924                 }
2925
2926                 if (looking_at(buf, &i, "* s-shouts: ") ||
2927                     looking_at(buf, &i, "* c-shouts: ")) {
2928                     if (appData.colorize) {
2929                         if (oldi > next_out) {
2930                             SendToPlayer(&buf[next_out], oldi - next_out);
2931                             next_out = oldi;
2932                         }
2933                         Colorize(ColorSShout, FALSE);
2934                         curColor = ColorSShout;
2935                     }
2936                     loggedOn = TRUE;
2937                     started = STARTED_CHATTER;
2938                     continue;
2939                 }
2940
2941                 if (looking_at(buf, &i, "--->")) {
2942                     loggedOn = TRUE;
2943                     continue;
2944                 }
2945
2946                 if (looking_at(buf, &i, "* shouts: ") ||
2947                     looking_at(buf, &i, "--> ")) {
2948                     if (appData.colorize) {
2949                         if (oldi > next_out) {
2950                             SendToPlayer(&buf[next_out], oldi - next_out);
2951                             next_out = oldi;
2952                         }
2953                         Colorize(ColorShout, FALSE);
2954                         curColor = ColorShout;
2955                     }
2956                     loggedOn = TRUE;
2957                     started = STARTED_CHATTER;
2958                     continue;
2959                 }
2960
2961                 if (looking_at( buf, &i, "Challenge:")) {
2962                     if (appData.colorize) {
2963                         if (oldi > next_out) {
2964                             SendToPlayer(&buf[next_out], oldi - next_out);
2965                             next_out = oldi;
2966                         }
2967                         Colorize(ColorChallenge, FALSE);
2968                         curColor = ColorChallenge;
2969                     }
2970                     loggedOn = TRUE;
2971                     continue;
2972                 }
2973
2974                 if (looking_at(buf, &i, "* offers you") ||
2975                     looking_at(buf, &i, "* offers to be") ||
2976                     looking_at(buf, &i, "* would like to") ||
2977                     looking_at(buf, &i, "* requests to") ||
2978                     looking_at(buf, &i, "Your opponent offers") ||
2979                     looking_at(buf, &i, "Your opponent requests")) {
2980
2981                     if (appData.colorize) {
2982                         if (oldi > next_out) {
2983                             SendToPlayer(&buf[next_out], oldi - next_out);
2984                             next_out = oldi;
2985                         }
2986                         Colorize(ColorRequest, FALSE);
2987                         curColor = ColorRequest;
2988                     }
2989                     continue;
2990                 }
2991
2992                 if (looking_at(buf, &i, "* (*) seeking")) {
2993                     if (appData.colorize) {
2994                         if (oldi > next_out) {
2995                             SendToPlayer(&buf[next_out], oldi - next_out);
2996                             next_out = oldi;
2997                         }
2998                         Colorize(ColorSeek, FALSE);
2999                         curColor = ColorSeek;
3000                     }
3001                     continue;
3002             }
3003
3004             if (looking_at(buf, &i, "\\   ")) {
3005                 if (prevColor != ColorNormal) {
3006                     if (oldi > next_out) {
3007                         SendToPlayer(&buf[next_out], oldi - next_out);
3008                         next_out = oldi;
3009                     }
3010                     Colorize(prevColor, TRUE);
3011                     curColor = prevColor;
3012                 }
3013                 if (savingComment) {
3014                     parse_pos = i - oldi;
3015                     memcpy(parse, &buf[oldi], parse_pos);
3016                     parse[parse_pos] = NULLCHAR;
3017                     started = STARTED_COMMENT;
3018                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3019                         chattingPartner = savingComment - 3; // kludge to remember the box
3020                 } else {
3021                     started = STARTED_CHATTER;
3022                 }
3023                 continue;
3024             }
3025
3026             if (looking_at(buf, &i, "Black Strength :") ||
3027                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3028                 looking_at(buf, &i, "<10>") ||
3029                 looking_at(buf, &i, "#@#")) {
3030                 /* Wrong board style */
3031                 loggedOn = TRUE;
3032                 SendToICS(ics_prefix);
3033                 SendToICS("set style 12\n");
3034                 SendToICS(ics_prefix);
3035                 SendToICS("refresh\n");
3036                 continue;
3037             }
3038             
3039             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3040                 ICSInitScript();
3041                 have_sent_ICS_logon = 1;
3042                 continue;
3043             }
3044               
3045             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3046                 (looking_at(buf, &i, "\n<12> ") ||
3047                  looking_at(buf, &i, "<12> "))) {
3048                 loggedOn = TRUE;
3049                 if (oldi > next_out) {
3050                     SendToPlayer(&buf[next_out], oldi - next_out);
3051                 }
3052                 next_out = i;
3053                 started = STARTED_BOARD;
3054                 parse_pos = 0;
3055                 continue;
3056             }
3057
3058             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3059                 looking_at(buf, &i, "<b1> ")) {
3060                 if (oldi > next_out) {
3061                     SendToPlayer(&buf[next_out], oldi - next_out);
3062                 }
3063                 next_out = i;
3064                 started = STARTED_HOLDINGS;
3065                 parse_pos = 0;
3066                 continue;
3067             }
3068
3069             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3070                 loggedOn = TRUE;
3071                 /* Header for a move list -- first line */
3072
3073                 switch (ics_getting_history) {
3074                   case H_FALSE:
3075                     switch (gameMode) {
3076                       case IcsIdle:
3077                       case BeginningOfGame:
3078                         /* User typed "moves" or "oldmoves" while we
3079                            were idle.  Pretend we asked for these
3080                            moves and soak them up so user can step
3081                            through them and/or save them.
3082                            */
3083                         Reset(FALSE, TRUE);
3084                         gameMode = IcsObserving;
3085                         ModeHighlight();
3086                         ics_gamenum = -1;
3087                         ics_getting_history = H_GOT_UNREQ_HEADER;
3088                         break;
3089                       case EditGame: /*?*/
3090                       case EditPosition: /*?*/
3091                         /* Should above feature work in these modes too? */
3092                         /* For now it doesn't */
3093                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3094                         break;
3095                       default:
3096                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3097                         break;
3098                     }
3099                     break;
3100                   case H_REQUESTED:
3101                     /* Is this the right one? */
3102                     if (gameInfo.white && gameInfo.black &&
3103                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3104                         strcmp(gameInfo.black, star_match[2]) == 0) {
3105                         /* All is well */
3106                         ics_getting_history = H_GOT_REQ_HEADER;
3107                     }
3108                     break;
3109                   case H_GOT_REQ_HEADER:
3110                   case H_GOT_UNREQ_HEADER:
3111                   case H_GOT_UNWANTED_HEADER:
3112                   case H_GETTING_MOVES:
3113                     /* Should not happen */
3114                     DisplayError(_("Error gathering move list: two headers"), 0);
3115                     ics_getting_history = H_FALSE;
3116                     break;
3117                 }
3118
3119                 /* Save player ratings into gameInfo if needed */
3120                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3121                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3122                     (gameInfo.whiteRating == -1 ||
3123                      gameInfo.blackRating == -1)) {
3124
3125                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3126                     gameInfo.blackRating = string_to_rating(star_match[3]);
3127                     if (appData.debugMode)
3128                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3129                               gameInfo.whiteRating, gameInfo.blackRating);
3130                 }
3131                 continue;
3132             }
3133
3134             if (looking_at(buf, &i,
3135               "* * match, initial time: * minute*, increment: * second")) {
3136                 /* Header for a move list -- second line */
3137                 /* Initial board will follow if this is a wild game */
3138                 if (gameInfo.event != NULL) free(gameInfo.event);
3139                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3140                 gameInfo.event = StrSave(str);
3141                 /* [HGM] we switched variant. Translate boards if needed. */
3142                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3143                 continue;
3144             }
3145
3146             if (looking_at(buf, &i, "Move  ")) {
3147                 /* Beginning of a move list */
3148                 switch (ics_getting_history) {
3149                   case H_FALSE:
3150                     /* Normally should not happen */
3151                     /* Maybe user hit reset while we were parsing */
3152                     break;
3153                   case H_REQUESTED:
3154                     /* Happens if we are ignoring a move list that is not
3155                      * the one we just requested.  Common if the user
3156                      * tries to observe two games without turning off
3157                      * getMoveList */
3158                     break;
3159                   case H_GETTING_MOVES:
3160                     /* Should not happen */
3161                     DisplayError(_("Error gathering move list: nested"), 0);
3162                     ics_getting_history = H_FALSE;
3163                     break;
3164                   case H_GOT_REQ_HEADER:
3165                     ics_getting_history = H_GETTING_MOVES;
3166                     started = STARTED_MOVES;
3167                     parse_pos = 0;
3168                     if (oldi > next_out) {
3169                         SendToPlayer(&buf[next_out], oldi - next_out);
3170                     }
3171                     break;
3172                   case H_GOT_UNREQ_HEADER:
3173                     ics_getting_history = H_GETTING_MOVES;
3174                     started = STARTED_MOVES_NOHIDE;
3175                     parse_pos = 0;
3176                     break;
3177                   case H_GOT_UNWANTED_HEADER:
3178                     ics_getting_history = H_FALSE;
3179                     break;
3180                 }
3181                 continue;
3182             }                           
3183             
3184             if (looking_at(buf, &i, "% ") ||
3185                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3186                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3187                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3188                     soughtPending = FALSE;
3189                     seekGraphUp = TRUE;
3190                     DrawSeekGraph();
3191                 }
3192                 if(suppressKibitz) next_out = i;
3193                 savingComment = FALSE;
3194                 suppressKibitz = 0;
3195                 switch (started) {
3196                   case STARTED_MOVES:
3197                   case STARTED_MOVES_NOHIDE:
3198                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3199                     parse[parse_pos + i - oldi] = NULLCHAR;
3200                     ParseGameHistory(parse);
3201 #if ZIPPY
3202                     if (appData.zippyPlay && first.initDone) {
3203                         FeedMovesToProgram(&first, forwardMostMove);
3204                         if (gameMode == IcsPlayingWhite) {
3205                             if (WhiteOnMove(forwardMostMove)) {
3206                                 if (first.sendTime) {
3207                                   if (first.useColors) {
3208                                     SendToProgram("black\n", &first); 
3209                                   }
3210                                   SendTimeRemaining(&first, TRUE);
3211                                 }
3212                                 if (first.useColors) {
3213                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3214                                 }
3215                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3216                                 first.maybeThinking = TRUE;
3217                             } else {
3218                                 if (first.usePlayother) {
3219                                   if (first.sendTime) {
3220                                     SendTimeRemaining(&first, TRUE);
3221                                   }
3222                                   SendToProgram("playother\n", &first);
3223                                   firstMove = FALSE;
3224                                 } else {
3225                                   firstMove = TRUE;
3226                                 }
3227                             }
3228                         } else if (gameMode == IcsPlayingBlack) {
3229                             if (!WhiteOnMove(forwardMostMove)) {
3230                                 if (first.sendTime) {
3231                                   if (first.useColors) {
3232                                     SendToProgram("white\n", &first);
3233                                   }
3234                                   SendTimeRemaining(&first, FALSE);
3235                                 }
3236                                 if (first.useColors) {
3237                                   SendToProgram("black\n", &first);
3238                                 }
3239                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3240                                 first.maybeThinking = TRUE;
3241                             } else {
3242                                 if (first.usePlayother) {
3243                                   if (first.sendTime) {
3244                                     SendTimeRemaining(&first, FALSE);
3245                                   }
3246                                   SendToProgram("playother\n", &first);
3247                                   firstMove = FALSE;
3248                                 } else {
3249                                   firstMove = TRUE;
3250                                 }
3251                             }
3252                         }                       
3253                     }
3254 #endif
3255                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3256                         /* Moves came from oldmoves or moves command
3257                            while we weren't doing anything else.
3258                            */
3259                         currentMove = forwardMostMove;
3260                         ClearHighlights();/*!!could figure this out*/
3261                         flipView = appData.flipView;
3262                         DrawPosition(TRUE, boards[currentMove]);
3263                         DisplayBothClocks();
3264                         sprintf(str, "%s vs. %s",
3265                                 gameInfo.white, gameInfo.black);
3266                         DisplayTitle(str);
3267                         gameMode = IcsIdle;
3268                     } else {
3269                         /* Moves were history of an active game */
3270                         if (gameInfo.resultDetails != NULL) {
3271                             free(gameInfo.resultDetails);
3272                             gameInfo.resultDetails = NULL;
3273                         }
3274                     }
3275                     HistorySet(parseList, backwardMostMove,
3276                                forwardMostMove, currentMove-1);
3277                     DisplayMove(currentMove - 1);
3278                     if (started == STARTED_MOVES) next_out = i;
3279                     started = STARTED_NONE;
3280                     ics_getting_history = H_FALSE;
3281                     break;
3282
3283                   case STARTED_OBSERVE:
3284                     started = STARTED_NONE;
3285                     SendToICS(ics_prefix);
3286                     SendToICS("refresh\n");
3287                     break;
3288
3289                   default:
3290                     break;
3291                 }
3292                 if(bookHit) { // [HGM] book: simulate book reply
3293                     static char bookMove[MSG_SIZ]; // a bit generous?
3294
3295                     programStats.nodes = programStats.depth = programStats.time = 
3296                     programStats.score = programStats.got_only_move = 0;
3297                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3298
3299                     strcpy(bookMove, "move ");
3300                     strcat(bookMove, bookHit);
3301                     HandleMachineMove(bookMove, &first);
3302                 }
3303                 continue;
3304             }
3305             
3306             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3307                  started == STARTED_HOLDINGS ||
3308                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3309                 /* Accumulate characters in move list or board */
3310                 parse[parse_pos++] = buf[i];
3311             }
3312             
3313             /* Start of game messages.  Mostly we detect start of game
3314                when the first board image arrives.  On some versions
3315                of the ICS, though, we need to do a "refresh" after starting
3316                to observe in order to get the current board right away. */
3317             if (looking_at(buf, &i, "Adding game * to observation list")) {
3318                 started = STARTED_OBSERVE;
3319                 continue;
3320             }
3321
3322             /* Handle auto-observe */
3323             if (appData.autoObserve &&
3324                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3325                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3326                 char *player;
3327                 /* Choose the player that was highlighted, if any. */
3328                 if (star_match[0][0] == '\033' ||
3329                     star_match[1][0] != '\033') {
3330                     player = star_match[0];
3331                 } else {
3332                     player = star_match[2];
3333                 }
3334                 sprintf(str, "%sobserve %s\n",
3335                         ics_prefix, StripHighlightAndTitle(player));
3336                 SendToICS(str);
3337
3338                 /* Save ratings from notify string */
3339                 strcpy(player1Name, star_match[0]);
3340                 player1Rating = string_to_rating(star_match[1]);
3341                 strcpy(player2Name, star_match[2]);
3342                 player2Rating = string_to_rating(star_match[3]);
3343
3344                 if (appData.debugMode)
3345                   fprintf(debugFP, 
3346                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3347                           player1Name, player1Rating,
3348                           player2Name, player2Rating);
3349
3350                 continue;
3351             }
3352
3353             /* Deal with automatic examine mode after a game,
3354                and with IcsObserving -> IcsExamining transition */
3355             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3356                 looking_at(buf, &i, "has made you an examiner of game *")) {
3357
3358                 int gamenum = atoi(star_match[0]);
3359                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3360                     gamenum == ics_gamenum) {
3361                     /* We were already playing or observing this game;
3362                        no need to refetch history */
3363                     gameMode = IcsExamining;
3364                     if (pausing) {
3365                         pauseExamForwardMostMove = forwardMostMove;
3366                     } else if (currentMove < forwardMostMove) {
3367                         ForwardInner(forwardMostMove);
3368                     }
3369                 } else {
3370                     /* I don't think this case really can happen */
3371                     SendToICS(ics_prefix);
3372                     SendToICS("refresh\n");
3373                 }
3374                 continue;
3375             }    
3376             
3377             /* Error messages */
3378 //          if (ics_user_moved) {
3379             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3380                 if (looking_at(buf, &i, "Illegal move") ||
3381                     looking_at(buf, &i, "Not a legal move") ||
3382                     looking_at(buf, &i, "Your king is in check") ||
3383                     looking_at(buf, &i, "It isn't your turn") ||
3384                     looking_at(buf, &i, "It is not your move")) {
3385                     /* Illegal move */
3386                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3387                         currentMove = forwardMostMove-1;
3388                         DisplayMove(currentMove - 1); /* before DMError */
3389                         DrawPosition(FALSE, boards[currentMove]);
3390                         SwitchClocks(forwardMostMove-1); // [HGM] race
3391                         DisplayBothClocks();
3392                     }
3393                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3394                     ics_user_moved = 0;
3395                     continue;
3396                 }
3397             }
3398
3399             if (looking_at(buf, &i, "still have time") ||
3400                 looking_at(buf, &i, "not out of time") ||
3401                 looking_at(buf, &i, "either player is out of time") ||
3402                 looking_at(buf, &i, "has timeseal; checking")) {
3403                 /* We must have called his flag a little too soon */
3404                 whiteFlag = blackFlag = FALSE;
3405                 continue;
3406             }
3407
3408             if (looking_at(buf, &i, "added * seconds to") ||
3409                 looking_at(buf, &i, "seconds were added to")) {
3410                 /* Update the clocks */
3411                 SendToICS(ics_prefix);
3412                 SendToICS("refresh\n");
3413                 continue;
3414             }
3415
3416             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3417                 ics_clock_paused = TRUE;
3418                 StopClocks();
3419                 continue;
3420             }
3421
3422             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3423                 ics_clock_paused = FALSE;
3424                 StartClocks();
3425                 continue;
3426             }
3427
3428             /* Grab player ratings from the Creating: message.
3429                Note we have to check for the special case when
3430                the ICS inserts things like [white] or [black]. */
3431             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3432                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3433                 /* star_matches:
3434                    0    player 1 name (not necessarily white)
3435                    1    player 1 rating
3436                    2    empty, white, or black (IGNORED)
3437                    3    player 2 name (not necessarily black)
3438                    4    player 2 rating
3439                    
3440                    The names/ratings are sorted out when the game
3441                    actually starts (below).
3442                 */
3443                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3444                 player1Rating = string_to_rating(star_match[1]);
3445                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3446                 player2Rating = string_to_rating(star_match[4]);
3447
3448                 if (appData.debugMode)
3449                   fprintf(debugFP, 
3450                           "Ratings from 'Creating:' %s %d, %s %d\n",
3451                           player1Name, player1Rating,
3452                           player2Name, player2Rating);
3453
3454                 continue;
3455             }
3456             
3457             /* Improved generic start/end-of-game messages */
3458             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3459                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3460                 /* If tkind == 0: */
3461                 /* star_match[0] is the game number */
3462                 /*           [1] is the white player's name */
3463                 /*           [2] is the black player's name */
3464                 /* For end-of-game: */
3465                 /*           [3] is the reason for the game end */
3466                 /*           [4] is a PGN end game-token, preceded by " " */
3467                 /* For start-of-game: */
3468                 /*           [3] begins with "Creating" or "Continuing" */
3469                 /*           [4] is " *" or empty (don't care). */
3470                 int gamenum = atoi(star_match[0]);
3471                 char *whitename, *blackname, *why, *endtoken;
3472                 ChessMove endtype = (ChessMove) 0;
3473
3474                 if (tkind == 0) {
3475                   whitename = star_match[1];
3476                   blackname = star_match[2];
3477                   why = star_match[3];
3478                   endtoken = star_match[4];
3479                 } else {
3480                   whitename = star_match[1];
3481                   blackname = star_match[3];
3482                   why = star_match[5];
3483                   endtoken = star_match[6];
3484                 }
3485
3486                 /* Game start messages */
3487                 if (strncmp(why, "Creating ", 9) == 0 ||
3488                     strncmp(why, "Continuing ", 11) == 0) {
3489                     gs_gamenum = gamenum;
3490                     strcpy(gs_kind, strchr(why, ' ') + 1);
3491                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3492 #if ZIPPY
3493                     if (appData.zippyPlay) {
3494                         ZippyGameStart(whitename, blackname);
3495                     }
3496 #endif /*ZIPPY*/
3497                     partnerBoardValid = FALSE; // [HGM] bughouse
3498                     continue;
3499                 }
3500
3501                 /* Game end messages */
3502                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3503                     ics_gamenum != gamenum) {
3504                     continue;
3505                 }
3506                 while (endtoken[0] == ' ') endtoken++;
3507                 switch (endtoken[0]) {
3508                   case '*':
3509                   default:
3510                     endtype = GameUnfinished;
3511                     break;
3512                   case '0':
3513                     endtype = BlackWins;
3514                     break;
3515                   case '1':
3516                     if (endtoken[1] == '/')
3517                       endtype = GameIsDrawn;
3518                     else
3519                       endtype = WhiteWins;
3520                     break;
3521                 }
3522                 GameEnds(endtype, why, GE_ICS);
3523 #if ZIPPY
3524                 if (appData.zippyPlay && first.initDone) {
3525                     ZippyGameEnd(endtype, why);
3526                     if (first.pr == NULL) {
3527                       /* Start the next process early so that we'll
3528                          be ready for the next challenge */
3529                       StartChessProgram(&first);
3530                     }
3531                     /* Send "new" early, in case this command takes
3532                        a long time to finish, so that we'll be ready
3533                        for the next challenge. */
3534                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3535                     Reset(TRUE, TRUE);
3536                 }
3537 #endif /*ZIPPY*/
3538                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3539                 continue;
3540             }
3541
3542             if (looking_at(buf, &i, "Removing game * from observation") ||
3543                 looking_at(buf, &i, "no longer observing game *") ||
3544                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3545                 if (gameMode == IcsObserving &&
3546                     atoi(star_match[0]) == ics_gamenum)
3547                   {
3548                       /* icsEngineAnalyze */
3549                       if (appData.icsEngineAnalyze) {
3550                             ExitAnalyzeMode();
3551                             ModeHighlight();
3552                       }
3553                       StopClocks();
3554                       gameMode = IcsIdle;
3555                       ics_gamenum = -1;
3556                       ics_user_moved = FALSE;
3557                   }
3558                 continue;
3559             }
3560
3561             if (looking_at(buf, &i, "no longer examining game *")) {
3562                 if (gameMode == IcsExamining &&
3563                     atoi(star_match[0]) == ics_gamenum)
3564                   {
3565                       gameMode = IcsIdle;
3566                       ics_gamenum = -1;
3567                       ics_user_moved = FALSE;
3568                   }
3569                 continue;
3570             }
3571
3572             /* Advance leftover_start past any newlines we find,
3573                so only partial lines can get reparsed */
3574             if (looking_at(buf, &i, "\n")) {
3575                 prevColor = curColor;
3576                 if (curColor != ColorNormal) {
3577                     if (oldi > next_out) {
3578                         SendToPlayer(&buf[next_out], oldi - next_out);
3579                         next_out = oldi;
3580                     }
3581                     Colorize(ColorNormal, FALSE);
3582                     curColor = ColorNormal;
3583                 }
3584                 if (started == STARTED_BOARD) {
3585                     started = STARTED_NONE;
3586                     parse[parse_pos] = NULLCHAR;
3587                     ParseBoard12(parse);
3588                     ics_user_moved = 0;
3589
3590                     /* Send premove here */
3591                     if (appData.premove) {
3592                       char str[MSG_SIZ];
3593                       if (currentMove == 0 &&
3594                           gameMode == IcsPlayingWhite &&
3595                           appData.premoveWhite) {
3596                         sprintf(str, "%s\n", appData.premoveWhiteText);
3597                         if (appData.debugMode)
3598                           fprintf(debugFP, "Sending premove:\n");
3599                         SendToICS(str);
3600                       } else if (currentMove == 1 &&
3601                                  gameMode == IcsPlayingBlack &&
3602                                  appData.premoveBlack) {
3603                         sprintf(str, "%s\n", appData.premoveBlackText);
3604                         if (appData.debugMode)
3605                           fprintf(debugFP, "Sending premove:\n");
3606                         SendToICS(str);
3607                       } else if (gotPremove) {
3608                         gotPremove = 0;
3609                         ClearPremoveHighlights();
3610                         if (appData.debugMode)
3611                           fprintf(debugFP, "Sending premove:\n");
3612                           UserMoveEvent(premoveFromX, premoveFromY, 
3613                                         premoveToX, premoveToY, 
3614                                         premovePromoChar);
3615                       }
3616                     }
3617
3618                     /* Usually suppress following prompt */
3619                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3620                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3621                         if (looking_at(buf, &i, "*% ")) {
3622                             savingComment = FALSE;
3623                             suppressKibitz = 0;
3624                         }
3625                     }
3626                     next_out = i;
3627                 } else if (started == STARTED_HOLDINGS) {
3628                     int gamenum;
3629                     char new_piece[MSG_SIZ];
3630                     started = STARTED_NONE;
3631                     parse[parse_pos] = NULLCHAR;
3632                     if (appData.debugMode)
3633                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3634                                                         parse, currentMove);
3635                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3636                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3637                         if (gameInfo.variant == VariantNormal) {
3638                           /* [HGM] We seem to switch variant during a game!
3639                            * Presumably no holdings were displayed, so we have
3640                            * to move the position two files to the right to
3641                            * create room for them!
3642                            */
3643                           VariantClass newVariant;
3644                           switch(gameInfo.boardWidth) { // base guess on board width
3645                                 case 9:  newVariant = VariantShogi; break;
3646                                 case 10: newVariant = VariantGreat; break;
3647                                 default: newVariant = VariantCrazyhouse; break;
3648                           }
3649                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3650                           /* Get a move list just to see the header, which
3651                              will tell us whether this is really bug or zh */
3652                           if (ics_getting_history == H_FALSE) {
3653                             ics_getting_history = H_REQUESTED;
3654                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3655                             SendToICS(str);
3656                           }
3657                         }
3658                         new_piece[0] = NULLCHAR;
3659                         sscanf(parse, "game %d white [%s black [%s <- %s",
3660                                &gamenum, white_holding, black_holding,
3661                                new_piece);
3662                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3663                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3664                         /* [HGM] copy holdings to board holdings area */
3665                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3666                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3667                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3668 #if ZIPPY
3669                         if (appData.zippyPlay && first.initDone) {
3670                             ZippyHoldings(white_holding, black_holding,
3671                                           new_piece);
3672                         }
3673 #endif /*ZIPPY*/
3674                         if (tinyLayout || smallLayout) {
3675                             char wh[16], bh[16];
3676                             PackHolding(wh, white_holding);
3677                             PackHolding(bh, black_holding);
3678                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3679                                     gameInfo.white, gameInfo.black);
3680                         } else {
3681                             sprintf(str, "%s [%s] vs. %s [%s]",
3682                                     gameInfo.white, white_holding,
3683                                     gameInfo.black, black_holding);
3684                         }
3685                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3686                         DrawPosition(FALSE, boards[currentMove]);
3687                         DisplayTitle(str);
3688                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3689                         sscanf(parse, "game %d white [%s black [%s <- %s",
3690                                &gamenum, white_holding, black_holding,
3691                                new_piece);
3692                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3693                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3694                         /* [HGM] copy holdings to partner-board holdings area */
3695                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3696                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3697                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3698                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3699                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3700                       }
3701                     }
3702                     /* Suppress following prompt */
3703                     if (looking_at(buf, &i, "*% ")) {
3704                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3705                         savingComment = FALSE;
3706                         suppressKibitz = 0;
3707                     }
3708                     next_out = i;
3709                 }
3710                 continue;
3711             }
3712
3713             i++;                /* skip unparsed character and loop back */
3714         }
3715         
3716         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3717 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3718 //          SendToPlayer(&buf[next_out], i - next_out);
3719             started != STARTED_HOLDINGS && leftover_start > next_out) {
3720             SendToPlayer(&buf[next_out], leftover_start - next_out);
3721             next_out = i;
3722         }
3723         
3724         leftover_len = buf_len - leftover_start;
3725         /* if buffer ends with something we couldn't parse,
3726            reparse it after appending the next read */
3727         
3728     } else if (count == 0) {
3729         RemoveInputSource(isr);
3730         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3731     } else {
3732         DisplayFatalError(_("Error reading from ICS"), error, 1);
3733     }
3734 }
3735
3736
3737 /* Board style 12 looks like this:
3738    
3739    <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
3740    
3741  * The "<12> " is stripped before it gets to this routine.  The two
3742  * trailing 0's (flip state and clock ticking) are later addition, and
3743  * some chess servers may not have them, or may have only the first.
3744  * Additional trailing fields may be added in the future.  
3745  */
3746
3747 #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"
3748
3749 #define RELATION_OBSERVING_PLAYED    0
3750 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3751 #define RELATION_PLAYING_MYMOVE      1
3752 #define RELATION_PLAYING_NOTMYMOVE  -1
3753 #define RELATION_EXAMINING           2
3754 #define RELATION_ISOLATED_BOARD     -3
3755 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3756
3757 void
3758 ParseBoard12(string)
3759      char *string;
3760
3761     GameMode newGameMode;
3762     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3763     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3764     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3765     char to_play, board_chars[200];
3766     char move_str[500], str[500], elapsed_time[500];
3767     char black[32], white[32];
3768     Board board;
3769     int prevMove = currentMove;
3770     int ticking = 2;
3771     ChessMove moveType;
3772     int fromX, fromY, toX, toY;
3773     char promoChar;
3774     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3775     char *bookHit = NULL; // [HGM] book
3776     Boolean weird = FALSE, reqFlag = FALSE;
3777
3778     fromX = fromY = toX = toY = -1;
3779     
3780     newGame = FALSE;
3781
3782     if (appData.debugMode)
3783       fprintf(debugFP, _("Parsing board: %s\n"), string);
3784
3785     move_str[0] = NULLCHAR;
3786     elapsed_time[0] = NULLCHAR;
3787     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3788         int  i = 0, j;
3789         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3790             if(string[i] == ' ') { ranks++; files = 0; }
3791             else files++;
3792             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3793             i++;
3794         }
3795         for(j = 0; j <i; j++) board_chars[j] = string[j];
3796         board_chars[i] = '\0';
3797         string += i + 1;
3798     }
3799     n = sscanf(string, PATTERN, &to_play, &double_push,
3800                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3801                &gamenum, white, black, &relation, &basetime, &increment,
3802                &white_stren, &black_stren, &white_time, &black_time,
3803                &moveNum, str, elapsed_time, move_str, &ics_flip,
3804                &ticking);
3805
3806     if (n < 21) {
3807         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3808         DisplayError(str, 0);
3809         return;
3810     }
3811
3812     /* Convert the move number to internal form */
3813     moveNum = (moveNum - 1) * 2;
3814     if (to_play == 'B') moveNum++;
3815     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3816       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3817                         0, 1);
3818       return;
3819     }
3820     
3821     switch (relation) {
3822       case RELATION_OBSERVING_PLAYED:
3823       case RELATION_OBSERVING_STATIC:
3824         if (gamenum == -1) {
3825             /* Old ICC buglet */
3826             relation = RELATION_OBSERVING_STATIC;
3827         }
3828         newGameMode = IcsObserving;
3829         break;
3830       case RELATION_PLAYING_MYMOVE:
3831       case RELATION_PLAYING_NOTMYMOVE:
3832         newGameMode =
3833           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3834             IcsPlayingWhite : IcsPlayingBlack;
3835         break;
3836       case RELATION_EXAMINING:
3837         newGameMode = IcsExamining;
3838         break;
3839       case RELATION_ISOLATED_BOARD:
3840       default:
3841         /* Just display this board.  If user was doing something else,
3842            we will forget about it until the next board comes. */ 
3843         newGameMode = IcsIdle;
3844         break;
3845       case RELATION_STARTING_POSITION:
3846         newGameMode = gameMode;
3847         break;
3848     }
3849     
3850     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3851          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3852       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3853       char *toSqr;
3854       for (k = 0; k < ranks; k++) {
3855         for (j = 0; j < files; j++)
3856           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3857         if(gameInfo.holdingsWidth > 1) {
3858              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3859              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3860         }
3861       }
3862       CopyBoard(partnerBoard, board);
3863       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3864         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3865         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3866       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3867       if(toSqr = strchr(str, '-')) {
3868         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3869         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3870       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3871       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3872       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3873       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3874       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3875       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3876                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3877       DisplayMessage(partnerStatus, "");
3878         partnerBoardValid = TRUE;
3879       return;
3880     }
3881
3882     /* Modify behavior for initial board display on move listing
3883        of wild games.
3884        */
3885     switch (ics_getting_history) {
3886       case H_FALSE:
3887       case H_REQUESTED:
3888         break;
3889       case H_GOT_REQ_HEADER:
3890       case H_GOT_UNREQ_HEADER:
3891         /* This is the initial position of the current game */
3892         gamenum = ics_gamenum;
3893         moveNum = 0;            /* old ICS bug workaround */
3894         if (to_play == 'B') {
3895           startedFromSetupPosition = TRUE;
3896           blackPlaysFirst = TRUE;
3897           moveNum = 1;
3898           if (forwardMostMove == 0) forwardMostMove = 1;
3899           if (backwardMostMove == 0) backwardMostMove = 1;
3900           if (currentMove == 0) currentMove = 1;
3901         }
3902         newGameMode = gameMode;
3903         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3904         break;
3905       case H_GOT_UNWANTED_HEADER:
3906         /* This is an initial board that we don't want */
3907         return;
3908       case H_GETTING_MOVES:
3909         /* Should not happen */
3910         DisplayError(_("Error gathering move list: extra board"), 0);
3911         ics_getting_history = H_FALSE;
3912         return;
3913     }
3914
3915    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3916                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3917      /* [HGM] We seem to have switched variant unexpectedly
3918       * Try to guess new variant from board size
3919       */
3920           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3921           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3922           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3923           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3924           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3925           if(!weird) newVariant = VariantNormal;
3926           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3927           /* Get a move list just to see the header, which
3928              will tell us whether this is really bug or zh */
3929           if (ics_getting_history == H_FALSE) {
3930             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3931             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3932             SendToICS(str);
3933           }
3934     }
3935     
3936     /* Take action if this is the first board of a new game, or of a
3937        different game than is currently being displayed.  */
3938     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3939         relation == RELATION_ISOLATED_BOARD) {
3940         
3941         /* Forget the old game and get the history (if any) of the new one */
3942         if (gameMode != BeginningOfGame) {
3943           Reset(TRUE, TRUE);
3944         }
3945         newGame = TRUE;
3946         if (appData.autoRaiseBoard) BoardToTop();
3947         prevMove = -3;
3948         if (gamenum == -1) {
3949             newGameMode = IcsIdle;
3950         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3951                    appData.getMoveList && !reqFlag) {
3952             /* Need to get game history */
3953             ics_getting_history = H_REQUESTED;
3954             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3955             SendToICS(str);
3956         }
3957         
3958         /* Initially flip the board to have black on the bottom if playing
3959            black or if the ICS flip flag is set, but let the user change
3960            it with the Flip View button. */
3961         flipView = appData.autoFlipView ? 
3962           (newGameMode == IcsPlayingBlack) || ics_flip :
3963           appData.flipView;
3964         
3965         /* Done with values from previous mode; copy in new ones */
3966         gameMode = newGameMode;
3967         ModeHighlight();
3968         ics_gamenum = gamenum;
3969         if (gamenum == gs_gamenum) {
3970             int klen = strlen(gs_kind);
3971             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3972             sprintf(str, "ICS %s", gs_kind);
3973             gameInfo.event = StrSave(str);
3974         } else {
3975             gameInfo.event = StrSave("ICS game");
3976         }
3977         gameInfo.site = StrSave(appData.icsHost);
3978         gameInfo.date = PGNDate();
3979         gameInfo.round = StrSave("-");
3980         gameInfo.white = StrSave(white);
3981         gameInfo.black = StrSave(black);
3982         timeControl = basetime * 60 * 1000;
3983         timeControl_2 = 0;
3984         timeIncrement = increment * 1000;
3985         movesPerSession = 0;
3986         gameInfo.timeControl = TimeControlTagValue();
3987         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3988   if (appData.debugMode) {
3989     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3990     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3991     setbuf(debugFP, NULL);
3992   }
3993
3994         gameInfo.outOfBook = NULL;
3995         
3996         /* Do we have the ratings? */
3997         if (strcmp(player1Name, white) == 0 &&
3998             strcmp(player2Name, black) == 0) {
3999             if (appData.debugMode)
4000               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4001                       player1Rating, player2Rating);
4002             gameInfo.whiteRating = player1Rating;
4003             gameInfo.blackRating = player2Rating;
4004         } else if (strcmp(player2Name, white) == 0 &&
4005                    strcmp(player1Name, black) == 0) {
4006             if (appData.debugMode)
4007               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4008                       player2Rating, player1Rating);
4009             gameInfo.whiteRating = player2Rating;
4010             gameInfo.blackRating = player1Rating;
4011         }
4012         player1Name[0] = player2Name[0] = NULLCHAR;
4013
4014         /* Silence shouts if requested */
4015         if (appData.quietPlay &&
4016             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4017             SendToICS(ics_prefix);
4018             SendToICS("set shout 0\n");
4019         }
4020     }
4021     
4022     /* Deal with midgame name changes */
4023     if (!newGame) {
4024         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4025             if (gameInfo.white) free(gameInfo.white);
4026             gameInfo.white = StrSave(white);
4027         }
4028         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4029             if (gameInfo.black) free(gameInfo.black);
4030             gameInfo.black = StrSave(black);
4031         }
4032     }
4033     
4034     /* Throw away game result if anything actually changes in examine mode */
4035     if (gameMode == IcsExamining && !newGame) {
4036         gameInfo.result = GameUnfinished;
4037         if (gameInfo.resultDetails != NULL) {
4038             free(gameInfo.resultDetails);
4039             gameInfo.resultDetails = NULL;
4040         }
4041     }
4042     
4043     /* In pausing && IcsExamining mode, we ignore boards coming
4044        in if they are in a different variation than we are. */
4045     if (pauseExamInvalid) return;
4046     if (pausing && gameMode == IcsExamining) {
4047         if (moveNum <= pauseExamForwardMostMove) {
4048             pauseExamInvalid = TRUE;
4049             forwardMostMove = pauseExamForwardMostMove;
4050             return;
4051         }
4052     }
4053     
4054   if (appData.debugMode) {
4055     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4056   }
4057     /* Parse the board */
4058     for (k = 0; k < ranks; k++) {
4059       for (j = 0; j < files; j++)
4060         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4061       if(gameInfo.holdingsWidth > 1) {
4062            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4063            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4064       }
4065     }
4066     CopyBoard(boards[moveNum], board);
4067     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4068     if (moveNum == 0) {
4069         startedFromSetupPosition =
4070           !CompareBoards(board, initialPosition);
4071         if(startedFromSetupPosition)
4072             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4073     }
4074
4075     /* [HGM] Set castling rights. Take the outermost Rooks,
4076        to make it also work for FRC opening positions. Note that board12
4077        is really defective for later FRC positions, as it has no way to
4078        indicate which Rook can castle if they are on the same side of King.
4079        For the initial position we grant rights to the outermost Rooks,
4080        and remember thos rights, and we then copy them on positions
4081        later in an FRC game. This means WB might not recognize castlings with
4082        Rooks that have moved back to their original position as illegal,
4083        but in ICS mode that is not its job anyway.
4084     */
4085     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4086     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4087
4088         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4089             if(board[0][i] == WhiteRook) j = i;
4090         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4091         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4092             if(board[0][i] == WhiteRook) j = i;
4093         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4094         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4095             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4096         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4097         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4098             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4099         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4100
4101         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4102         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4103             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4104         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4105             if(board[BOARD_HEIGHT-1][k] == bKing)
4106                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4107         if(gameInfo.variant == VariantTwoKings) {
4108             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4109             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4110             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4111         }
4112     } else { int r;
4113         r = boards[moveNum][CASTLING][0] = initialRights[0];
4114         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4115         r = boards[moveNum][CASTLING][1] = initialRights[1];
4116         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4117         r = boards[moveNum][CASTLING][3] = initialRights[3];
4118         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4119         r = boards[moveNum][CASTLING][4] = initialRights[4];
4120         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4121         /* wildcastle kludge: always assume King has rights */
4122         r = boards[moveNum][CASTLING][2] = initialRights[2];
4123         r = boards[moveNum][CASTLING][5] = initialRights[5];
4124     }
4125     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4126     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4127
4128     
4129     if (ics_getting_history == H_GOT_REQ_HEADER ||
4130         ics_getting_history == H_GOT_UNREQ_HEADER) {
4131         /* This was an initial position from a move list, not
4132            the current position */
4133         return;
4134     }
4135     
4136     /* Update currentMove and known move number limits */
4137     newMove = newGame || moveNum > forwardMostMove;
4138
4139     if (newGame) {
4140         forwardMostMove = backwardMostMove = currentMove = moveNum;
4141         if (gameMode == IcsExamining && moveNum == 0) {
4142           /* Workaround for ICS limitation: we are not told the wild
4143              type when starting to examine a game.  But if we ask for
4144              the move list, the move list header will tell us */
4145             ics_getting_history = H_REQUESTED;
4146             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4147             SendToICS(str);
4148         }
4149     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4150                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4151 #if ZIPPY
4152         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4153         /* [HGM] applied this also to an engine that is silently watching        */
4154         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4155             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4156             gameInfo.variant == currentlyInitializedVariant) {
4157           takeback = forwardMostMove - moveNum;
4158           for (i = 0; i < takeback; i++) {
4159             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4160             SendToProgram("undo\n", &first);
4161           }
4162         }
4163 #endif
4164
4165         forwardMostMove = moveNum;
4166         if (!pausing || currentMove > forwardMostMove)
4167           currentMove = forwardMostMove;
4168     } else {
4169         /* New part of history that is not contiguous with old part */ 
4170         if (pausing && gameMode == IcsExamining) {
4171             pauseExamInvalid = TRUE;
4172             forwardMostMove = pauseExamForwardMostMove;
4173             return;
4174         }
4175         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4176 #if ZIPPY
4177             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4178                 // [HGM] when we will receive the move list we now request, it will be
4179                 // fed to the engine from the first move on. So if the engine is not
4180                 // in the initial position now, bring it there.
4181                 InitChessProgram(&first, 0);
4182             }
4183 #endif
4184             ics_getting_history = H_REQUESTED;
4185             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4186             SendToICS(str);
4187         }
4188         forwardMostMove = backwardMostMove = currentMove = moveNum;
4189     }
4190     
4191     /* Update the clocks */
4192     if (strchr(elapsed_time, '.')) {
4193       /* Time is in ms */
4194       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4195       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4196     } else {
4197       /* Time is in seconds */
4198       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4199       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4200     }
4201       
4202
4203 #if ZIPPY
4204     if (appData.zippyPlay && newGame &&
4205         gameMode != IcsObserving && gameMode != IcsIdle &&
4206         gameMode != IcsExamining)
4207       ZippyFirstBoard(moveNum, basetime, increment);
4208 #endif
4209     
4210     /* Put the move on the move list, first converting
4211        to canonical algebraic form. */
4212     if (moveNum > 0) {
4213   if (appData.debugMode) {
4214     if (appData.debugMode) { int f = forwardMostMove;
4215         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4216                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4217                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4218     }
4219     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4220     fprintf(debugFP, "moveNum = %d\n", moveNum);
4221     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4222     setbuf(debugFP, NULL);
4223   }
4224         if (moveNum <= backwardMostMove) {
4225             /* We don't know what the board looked like before
4226                this move.  Punt. */
4227             strcpy(parseList[moveNum - 1], move_str);
4228             strcat(parseList[moveNum - 1], " ");
4229             strcat(parseList[moveNum - 1], elapsed_time);
4230             moveList[moveNum - 1][0] = NULLCHAR;
4231         } else if (strcmp(move_str, "none") == 0) {
4232             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4233             /* Again, we don't know what the board looked like;
4234                this is really the start of the game. */
4235             parseList[moveNum - 1][0] = NULLCHAR;
4236             moveList[moveNum - 1][0] = NULLCHAR;
4237             backwardMostMove = moveNum;
4238             startedFromSetupPosition = TRUE;
4239             fromX = fromY = toX = toY = -1;
4240         } else {
4241           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4242           //                 So we parse the long-algebraic move string in stead of the SAN move
4243           int valid; char buf[MSG_SIZ], *prom;
4244
4245           // str looks something like "Q/a1-a2"; kill the slash
4246           if(str[1] == '/') 
4247                 sprintf(buf, "%c%s", str[0], str+2);
4248           else  strcpy(buf, str); // might be castling
4249           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4250                 strcat(buf, prom); // long move lacks promo specification!
4251           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4252                 if(appData.debugMode) 
4253                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4254                 strcpy(move_str, buf);
4255           }
4256           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4257                                 &fromX, &fromY, &toX, &toY, &promoChar)
4258                || ParseOneMove(buf, moveNum - 1, &moveType,
4259                                 &fromX, &fromY, &toX, &toY, &promoChar);
4260           // end of long SAN patch
4261           if (valid) {
4262             (void) CoordsToAlgebraic(boards[moveNum - 1],
4263                                      PosFlags(moveNum - 1),
4264                                      fromY, fromX, toY, toX, promoChar,
4265                                      parseList[moveNum-1]);
4266             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4267               case MT_NONE:
4268               case MT_STALEMATE:
4269               default:
4270                 break;
4271               case MT_CHECK:
4272                 if(gameInfo.variant != VariantShogi)
4273                     strcat(parseList[moveNum - 1], "+");
4274                 break;
4275               case MT_CHECKMATE:
4276               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4277                 strcat(parseList[moveNum - 1], "#");
4278                 break;
4279             }
4280             strcat(parseList[moveNum - 1], " ");
4281             strcat(parseList[moveNum - 1], elapsed_time);
4282             /* currentMoveString is set as a side-effect of ParseOneMove */
4283             strcpy(moveList[moveNum - 1], currentMoveString);
4284             strcat(moveList[moveNum - 1], "\n");
4285           } else {
4286             /* Move from ICS was illegal!?  Punt. */
4287   if (appData.debugMode) {
4288     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4289     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4290   }
4291             strcpy(parseList[moveNum - 1], move_str);
4292             strcat(parseList[moveNum - 1], " ");
4293             strcat(parseList[moveNum - 1], elapsed_time);
4294             moveList[moveNum - 1][0] = NULLCHAR;
4295             fromX = fromY = toX = toY = -1;
4296           }
4297         }
4298   if (appData.debugMode) {
4299     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4300     setbuf(debugFP, NULL);
4301   }
4302
4303 #if ZIPPY
4304         /* Send move to chess program (BEFORE animating it). */
4305         if (appData.zippyPlay && !newGame && newMove && 
4306            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4307
4308             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4309                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4310                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4311                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4312                             move_str);
4313                     DisplayError(str, 0);
4314                 } else {
4315                     if (first.sendTime) {
4316                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4317                     }
4318                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4319                     if (firstMove && !bookHit) {
4320                         firstMove = FALSE;
4321                         if (first.useColors) {
4322                           SendToProgram(gameMode == IcsPlayingWhite ?
4323                                         "white\ngo\n" :
4324                                         "black\ngo\n", &first);
4325                         } else {
4326                           SendToProgram("go\n", &first);
4327                         }
4328                         first.maybeThinking = TRUE;
4329                     }
4330                 }
4331             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4332               if (moveList[moveNum - 1][0] == NULLCHAR) {
4333                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4334                 DisplayError(str, 0);
4335               } else {
4336                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4337                 SendMoveToProgram(moveNum - 1, &first);
4338               }
4339             }
4340         }
4341 #endif
4342     }
4343
4344     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4345         /* If move comes from a remote source, animate it.  If it
4346            isn't remote, it will have already been animated. */
4347         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4348             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4349         }
4350         if (!pausing && appData.highlightLastMove) {
4351             SetHighlights(fromX, fromY, toX, toY);
4352         }
4353     }
4354     
4355     /* Start the clocks */
4356     whiteFlag = blackFlag = FALSE;
4357     appData.clockMode = !(basetime == 0 && increment == 0);
4358     if (ticking == 0) {
4359       ics_clock_paused = TRUE;
4360       StopClocks();
4361     } else if (ticking == 1) {
4362       ics_clock_paused = FALSE;
4363     }
4364     if (gameMode == IcsIdle ||
4365         relation == RELATION_OBSERVING_STATIC ||
4366         relation == RELATION_EXAMINING ||
4367         ics_clock_paused)
4368       DisplayBothClocks();
4369     else
4370       StartClocks();
4371     
4372     /* Display opponents and material strengths */
4373     if (gameInfo.variant != VariantBughouse &&
4374         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4375         if (tinyLayout || smallLayout) {
4376             if(gameInfo.variant == VariantNormal)
4377                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4378                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4379                     basetime, increment);
4380             else
4381                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4382                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4383                     basetime, increment, (int) gameInfo.variant);
4384         } else {
4385             if(gameInfo.variant == VariantNormal)
4386                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4387                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4388                     basetime, increment);
4389             else
4390                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4391                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4392                     basetime, increment, VariantName(gameInfo.variant));
4393         }
4394         DisplayTitle(str);
4395   if (appData.debugMode) {
4396     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4397   }
4398     }
4399
4400
4401     /* Display the board */
4402     if (!pausing && !appData.noGUI) {
4403       
4404       if (appData.premove)
4405           if (!gotPremove || 
4406              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4407              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4408               ClearPremoveHighlights();
4409
4410       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4411         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4412       DrawPosition(j, boards[currentMove]);
4413
4414       DisplayMove(moveNum - 1);
4415       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4416             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4417               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4418         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4419       }
4420     }
4421
4422     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4423 #if ZIPPY
4424     if(bookHit) { // [HGM] book: simulate book reply
4425         static char bookMove[MSG_SIZ]; // a bit generous?
4426
4427         programStats.nodes = programStats.depth = programStats.time = 
4428         programStats.score = programStats.got_only_move = 0;
4429         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4430
4431         strcpy(bookMove, "move ");
4432         strcat(bookMove, bookHit);
4433         HandleMachineMove(bookMove, &first);
4434     }
4435 #endif
4436 }
4437
4438 void
4439 GetMoveListEvent()
4440 {
4441     char buf[MSG_SIZ];
4442     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4443         ics_getting_history = H_REQUESTED;
4444         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4445         SendToICS(buf);
4446     }
4447 }
4448
4449 void
4450 AnalysisPeriodicEvent(force)
4451      int force;
4452 {
4453     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4454          && !force) || !appData.periodicUpdates)
4455       return;
4456
4457     /* Send . command to Crafty to collect stats */
4458     SendToProgram(".\n", &first);
4459
4460     /* Don't send another until we get a response (this makes
4461        us stop sending to old Crafty's which don't understand
4462        the "." command (sending illegal cmds resets node count & time,
4463        which looks bad)) */
4464     programStats.ok_to_send = 0;
4465 }
4466
4467 void ics_update_width(new_width)
4468         int new_width;
4469 {
4470         ics_printf("set width %d\n", new_width);
4471 }
4472
4473 void
4474 SendMoveToProgram(moveNum, cps)
4475      int moveNum;
4476      ChessProgramState *cps;
4477 {
4478     char buf[MSG_SIZ];
4479
4480     if (cps->useUsermove) {
4481       SendToProgram("usermove ", cps);
4482     }
4483     if (cps->useSAN) {
4484       char *space;
4485       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4486         int len = space - parseList[moveNum];
4487         memcpy(buf, parseList[moveNum], len);
4488         buf[len++] = '\n';
4489         buf[len] = NULLCHAR;
4490       } else {
4491         sprintf(buf, "%s\n", parseList[moveNum]);
4492       }
4493       SendToProgram(buf, cps);
4494     } else {
4495       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4496         AlphaRank(moveList[moveNum], 4);
4497         SendToProgram(moveList[moveNum], cps);
4498         AlphaRank(moveList[moveNum], 4); // and back
4499       } else
4500       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4501        * the engine. It would be nice to have a better way to identify castle 
4502        * moves here. */
4503       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4504                                                                          && cps->useOOCastle) {
4505         int fromX = moveList[moveNum][0] - AAA; 
4506         int fromY = moveList[moveNum][1] - ONE;
4507         int toX = moveList[moveNum][2] - AAA; 
4508         int toY = moveList[moveNum][3] - ONE;
4509         if((boards[moveNum][fromY][fromX] == WhiteKing 
4510             && boards[moveNum][toY][toX] == WhiteRook)
4511            || (boards[moveNum][fromY][fromX] == BlackKing 
4512                && boards[moveNum][toY][toX] == BlackRook)) {
4513           if(toX > fromX) SendToProgram("O-O\n", cps);
4514           else SendToProgram("O-O-O\n", cps);
4515         }
4516         else SendToProgram(moveList[moveNum], cps);
4517       }
4518       else SendToProgram(moveList[moveNum], cps);
4519       /* End of additions by Tord */
4520     }
4521
4522     /* [HGM] setting up the opening has brought engine in force mode! */
4523     /*       Send 'go' if we are in a mode where machine should play. */
4524     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4525         (gameMode == TwoMachinesPlay   ||
4526 #if ZIPPY
4527          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4528 #endif
4529          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4530         SendToProgram("go\n", cps);
4531   if (appData.debugMode) {
4532     fprintf(debugFP, "(extra)\n");
4533   }
4534     }
4535     setboardSpoiledMachineBlack = 0;
4536 }
4537
4538 void
4539 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4540      ChessMove moveType;
4541      int fromX, fromY, toX, toY;
4542 {
4543     char user_move[MSG_SIZ];
4544
4545     switch (moveType) {
4546       default:
4547         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4548                 (int)moveType, fromX, fromY, toX, toY);
4549         DisplayError(user_move + strlen("say "), 0);
4550         break;
4551       case WhiteKingSideCastle:
4552       case BlackKingSideCastle:
4553       case WhiteQueenSideCastleWild:
4554       case BlackQueenSideCastleWild:
4555       /* PUSH Fabien */
4556       case WhiteHSideCastleFR:
4557       case BlackHSideCastleFR:
4558       /* POP Fabien */
4559         sprintf(user_move, "o-o\n");
4560         break;
4561       case WhiteQueenSideCastle:
4562       case BlackQueenSideCastle:
4563       case WhiteKingSideCastleWild:
4564       case BlackKingSideCastleWild:
4565       /* PUSH Fabien */
4566       case WhiteASideCastleFR:
4567       case BlackASideCastleFR:
4568       /* POP Fabien */
4569         sprintf(user_move, "o-o-o\n");
4570         break;
4571       case WhitePromotionQueen:
4572       case BlackPromotionQueen:
4573       case WhitePromotionRook:
4574       case BlackPromotionRook:
4575       case WhitePromotionBishop:
4576       case BlackPromotionBishop:
4577       case WhitePromotionKnight:
4578       case BlackPromotionKnight:
4579       case WhitePromotionKing:
4580       case BlackPromotionKing:
4581       case WhitePromotionChancellor:
4582       case BlackPromotionChancellor:
4583       case WhitePromotionArchbishop:
4584       case BlackPromotionArchbishop:
4585         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4586             sprintf(user_move, "%c%c%c%c=%c\n",
4587                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4588                 PieceToChar(WhiteFerz));
4589         else if(gameInfo.variant == VariantGreat)
4590             sprintf(user_move, "%c%c%c%c=%c\n",
4591                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4592                 PieceToChar(WhiteMan));
4593         else
4594             sprintf(user_move, "%c%c%c%c=%c\n",
4595                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4596                 PieceToChar(PromoPiece(moveType)));
4597         break;
4598       case WhiteDrop:
4599       case BlackDrop:
4600         sprintf(user_move, "%c@%c%c\n",
4601                 ToUpper(PieceToChar((ChessSquare) fromX)),
4602                 AAA + toX, ONE + toY);
4603         break;
4604       case NormalMove:
4605       case WhiteCapturesEnPassant:
4606       case BlackCapturesEnPassant:
4607       case IllegalMove:  /* could be a variant we don't quite understand */
4608         sprintf(user_move, "%c%c%c%c\n",
4609                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4610         break;
4611     }
4612     SendToICS(user_move);
4613     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4614         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4615 }
4616
4617 void
4618 UploadGameEvent()
4619 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4620     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4621     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4622     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4623         DisplayError("You cannot do this while you are playing or observing", 0);
4624         return;
4625     }
4626     if(gameMode != IcsExamining) { // is this ever not the case?
4627         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4628
4629         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4630             sprintf(command, "match %s", ics_handle);
4631         } else { // on FICS we must first go to general examine mode
4632             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4633         }
4634         if(gameInfo.variant != VariantNormal) {
4635             // try figure out wild number, as xboard names are not always valid on ICS
4636             for(i=1; i<=36; i++) {
4637                 sprintf(buf, "wild/%d", i);
4638                 if(StringToVariant(buf) == gameInfo.variant) break;
4639             }
4640             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4641             else if(i == 22) sprintf(buf, "%s fr\n", command);
4642             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4643         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4644         SendToICS(ics_prefix);
4645         SendToICS(buf);
4646         if(startedFromSetupPosition || backwardMostMove != 0) {
4647           fen = PositionToFEN(backwardMostMove, NULL);
4648           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4649             sprintf(buf, "loadfen %s\n", fen);
4650             SendToICS(buf);
4651           } else { // FICS: everything has to set by separate bsetup commands
4652             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4653             sprintf(buf, "bsetup fen %s\n", fen);
4654             SendToICS(buf);
4655             if(!WhiteOnMove(backwardMostMove)) {
4656                 SendToICS("bsetup tomove black\n");
4657             }
4658             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4659             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4660             SendToICS(buf);
4661             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4662             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4663             SendToICS(buf);
4664             i = boards[backwardMostMove][EP_STATUS];
4665             if(i >= 0) { // set e.p.
4666                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4667                 SendToICS(buf);
4668             }
4669             bsetup++;
4670           }
4671         }
4672       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4673             SendToICS("bsetup done\n"); // switch to normal examining.
4674     }
4675     for(i = backwardMostMove; i<last; i++) {
4676         char buf[20];
4677         sprintf(buf, "%s\n", parseList[i]);
4678         SendToICS(buf);
4679     }
4680     SendToICS(ics_prefix);
4681     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4682 }
4683
4684 void
4685 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4686      int rf, ff, rt, ft;
4687      char promoChar;
4688      char move[7];
4689 {
4690     if (rf == DROP_RANK) {
4691         sprintf(move, "%c@%c%c\n",
4692                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4693     } else {
4694         if (promoChar == 'x' || promoChar == NULLCHAR) {
4695             sprintf(move, "%c%c%c%c\n",
4696                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4697         } else {
4698             sprintf(move, "%c%c%c%c%c\n",
4699                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4700         }
4701     }
4702 }
4703
4704 void
4705 ProcessICSInitScript(f)
4706      FILE *f;
4707 {
4708     char buf[MSG_SIZ];
4709
4710     while (fgets(buf, MSG_SIZ, f)) {
4711         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4712     }
4713
4714     fclose(f);
4715 }
4716
4717
4718 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4719 void
4720 AlphaRank(char *move, int n)
4721 {
4722 //    char *p = move, c; int x, y;
4723
4724     if (appData.debugMode) {
4725         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4726     }
4727
4728     if(move[1]=='*' && 
4729        move[2]>='0' && move[2]<='9' &&
4730        move[3]>='a' && move[3]<='x'    ) {
4731         move[1] = '@';
4732         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4733         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4734     } else
4735     if(move[0]>='0' && move[0]<='9' &&
4736        move[1]>='a' && move[1]<='x' &&
4737        move[2]>='0' && move[2]<='9' &&
4738        move[3]>='a' && move[3]<='x'    ) {
4739         /* input move, Shogi -> normal */
4740         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4741         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4742         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4743         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4744     } else
4745     if(move[1]=='@' &&
4746        move[3]>='0' && move[3]<='9' &&
4747        move[2]>='a' && move[2]<='x'    ) {
4748         move[1] = '*';
4749         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4750         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4751     } else
4752     if(
4753        move[0]>='a' && move[0]<='x' &&
4754        move[3]>='0' && move[3]<='9' &&
4755        move[2]>='a' && move[2]<='x'    ) {
4756          /* output move, normal -> Shogi */
4757         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4758         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4759         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4760         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4761         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4762     }
4763     if (appData.debugMode) {
4764         fprintf(debugFP, "   out = '%s'\n", move);
4765     }
4766 }
4767
4768 char yy_textstr[8000];
4769
4770 /* Parser for moves from gnuchess, ICS, or user typein box */
4771 Boolean
4772 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4773      char *move;
4774      int moveNum;
4775      ChessMove *moveType;
4776      int *fromX, *fromY, *toX, *toY;
4777      char *promoChar;
4778 {       
4779     if (appData.debugMode) {
4780         fprintf(debugFP, "move to parse: %s\n", move);
4781     }
4782     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4783
4784     switch (*moveType) {
4785       case WhitePromotionChancellor:
4786       case BlackPromotionChancellor:
4787       case WhitePromotionArchbishop:
4788       case BlackPromotionArchbishop:
4789       case WhitePromotionQueen:
4790       case BlackPromotionQueen:
4791       case WhitePromotionRook:
4792       case BlackPromotionRook:
4793       case WhitePromotionBishop:
4794       case BlackPromotionBishop:
4795       case WhitePromotionKnight:
4796       case BlackPromotionKnight:
4797       case WhitePromotionKing:
4798       case BlackPromotionKing:
4799       case NormalMove:
4800       case WhiteCapturesEnPassant:
4801       case BlackCapturesEnPassant:
4802       case WhiteKingSideCastle:
4803       case WhiteQueenSideCastle:
4804       case BlackKingSideCastle:
4805       case BlackQueenSideCastle:
4806       case WhiteKingSideCastleWild:
4807       case WhiteQueenSideCastleWild:
4808       case BlackKingSideCastleWild:
4809       case BlackQueenSideCastleWild:
4810       /* Code added by Tord: */
4811       case WhiteHSideCastleFR:
4812       case WhiteASideCastleFR:
4813       case BlackHSideCastleFR:
4814       case BlackASideCastleFR:
4815       /* End of code added by Tord */
4816       case IllegalMove:         /* bug or odd chess variant */
4817         *fromX = currentMoveString[0] - AAA;
4818         *fromY = currentMoveString[1] - ONE;
4819         *toX = currentMoveString[2] - AAA;
4820         *toY = currentMoveString[3] - ONE;
4821         *promoChar = currentMoveString[4];
4822         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4823             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4824     if (appData.debugMode) {
4825         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4826     }
4827             *fromX = *fromY = *toX = *toY = 0;
4828             return FALSE;
4829         }
4830         if (appData.testLegality) {
4831           return (*moveType != IllegalMove);
4832         } else {
4833           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4834                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4835         }
4836
4837       case WhiteDrop:
4838       case BlackDrop:
4839         *fromX = *moveType == WhiteDrop ?
4840           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4841           (int) CharToPiece(ToLower(currentMoveString[0]));
4842         *fromY = DROP_RANK;
4843         *toX = currentMoveString[2] - AAA;
4844         *toY = currentMoveString[3] - ONE;
4845         *promoChar = NULLCHAR;
4846         return TRUE;
4847
4848       case AmbiguousMove:
4849       case ImpossibleMove:
4850       case (ChessMove) 0:       /* end of file */
4851       case ElapsedTime:
4852       case Comment:
4853       case PGNTag:
4854       case NAG:
4855       case WhiteWins:
4856       case BlackWins:
4857       case GameIsDrawn:
4858       default:
4859     if (appData.debugMode) {
4860         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4861     }
4862         /* bug? */
4863         *fromX = *fromY = *toX = *toY = 0;
4864         *promoChar = NULLCHAR;
4865         return FALSE;
4866     }
4867 }
4868
4869
4870 void
4871 ParsePV(char *pv, Boolean storeComments)
4872 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4873   int fromX, fromY, toX, toY; char promoChar;
4874   ChessMove moveType;
4875   Boolean valid;
4876   int nr = 0;
4877
4878   endPV = forwardMostMove;
4879   do {
4880     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4881     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4882     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4883 if(appData.debugMode){
4884 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);
4885 }
4886     if(!valid && nr == 0 &&
4887        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4888         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4889         // Hande case where played move is different from leading PV move
4890         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4891         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4892         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4893         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4894           endPV += 2; // if position different, keep this
4895           moveList[endPV-1][0] = fromX + AAA;
4896           moveList[endPV-1][1] = fromY + ONE;
4897           moveList[endPV-1][2] = toX + AAA;
4898           moveList[endPV-1][3] = toY + ONE;
4899           parseList[endPV-1][0] = NULLCHAR;
4900           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4901         }
4902       }
4903     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4904     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4905     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4906     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4907         valid++; // allow comments in PV
4908         continue;
4909     }
4910     nr++;
4911     if(endPV+1 > framePtr) break; // no space, truncate
4912     if(!valid) break;
4913     endPV++;
4914     CopyBoard(boards[endPV], boards[endPV-1]);
4915     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4916     moveList[endPV-1][0] = fromX + AAA;
4917     moveList[endPV-1][1] = fromY + ONE;
4918     moveList[endPV-1][2] = toX + AAA;
4919     moveList[endPV-1][3] = toY + ONE;
4920     if(storeComments)
4921         CoordsToAlgebraic(boards[endPV - 1],
4922                              PosFlags(endPV - 1),
4923                              fromY, fromX, toY, toX, promoChar,
4924                              parseList[endPV - 1]);
4925     else
4926         parseList[endPV-1][0] = NULLCHAR;
4927   } while(valid);
4928   currentMove = endPV;
4929   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4930   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4931                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4932   DrawPosition(TRUE, boards[currentMove]);
4933 }
4934
4935 static int lastX, lastY;
4936
4937 Boolean
4938 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4939 {
4940         int startPV;
4941         char *p;
4942
4943         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4944         lastX = x; lastY = y;
4945         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4946         startPV = index;
4947         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4948         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4949         index = startPV;
4950         do{ while(buf[index] && buf[index] != '\n') index++;
4951         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4952         buf[index] = 0;
4953         ParsePV(buf+startPV, FALSE);
4954         *start = startPV; *end = index-1;
4955         return TRUE;
4956 }
4957
4958 Boolean
4959 LoadPV(int x, int y)
4960 { // called on right mouse click to load PV
4961   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4962   lastX = x; lastY = y;
4963   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4964   return TRUE;
4965 }
4966
4967 void
4968 UnLoadPV()
4969 {
4970   if(endPV < 0) return;
4971   endPV = -1;
4972   currentMove = forwardMostMove;
4973   ClearPremoveHighlights();
4974   DrawPosition(TRUE, boards[currentMove]);
4975 }
4976
4977 void
4978 MovePV(int x, int y, int h)
4979 { // step through PV based on mouse coordinates (called on mouse move)
4980   int margin = h>>3, step = 0;
4981
4982   if(endPV < 0) return;
4983   // we must somehow check if right button is still down (might be released off board!)
4984   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4985   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4986   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4987   if(!step) return;
4988   lastX = x; lastY = y;
4989   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4990   currentMove += step;
4991   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4992   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4993                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4994   DrawPosition(FALSE, boards[currentMove]);
4995 }
4996
4997
4998 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4999 // All positions will have equal probability, but the current method will not provide a unique
5000 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5001 #define DARK 1
5002 #define LITE 2
5003 #define ANY 3
5004
5005 int squaresLeft[4];
5006 int piecesLeft[(int)BlackPawn];
5007 int seed, nrOfShuffles;
5008
5009 void GetPositionNumber()
5010 {       // sets global variable seed
5011         int i;
5012
5013         seed = appData.defaultFrcPosition;
5014         if(seed < 0) { // randomize based on time for negative FRC position numbers
5015                 for(i=0; i<50; i++) seed += random();
5016                 seed = random() ^ random() >> 8 ^ random() << 8;
5017                 if(seed<0) seed = -seed;
5018         }
5019 }
5020
5021 int put(Board board, int pieceType, int rank, int n, int shade)
5022 // put the piece on the (n-1)-th empty squares of the given shade
5023 {
5024         int i;
5025
5026         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5027                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5028                         board[rank][i] = (ChessSquare) pieceType;
5029                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5030                         squaresLeft[ANY]--;
5031                         piecesLeft[pieceType]--; 
5032                         return i;
5033                 }
5034         }
5035         return -1;
5036 }
5037
5038
5039 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5040 // calculate where the next piece goes, (any empty square), and put it there
5041 {
5042         int i;
5043
5044         i = seed % squaresLeft[shade];
5045         nrOfShuffles *= squaresLeft[shade];
5046         seed /= squaresLeft[shade];
5047         put(board, pieceType, rank, i, shade);
5048 }
5049
5050 void AddTwoPieces(Board board, int pieceType, int rank)
5051 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5052 {
5053         int i, n=squaresLeft[ANY], j=n-1, k;
5054
5055         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5056         i = seed % k;  // pick one
5057         nrOfShuffles *= k;
5058         seed /= k;
5059         while(i >= j) i -= j--;
5060         j = n - 1 - j; i += j;
5061         put(board, pieceType, rank, j, ANY);
5062         put(board, pieceType, rank, i, ANY);
5063 }
5064
5065 void SetUpShuffle(Board board, int number)
5066 {
5067         int i, p, first=1;
5068
5069         GetPositionNumber(); nrOfShuffles = 1;
5070
5071         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5072         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5073         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5074
5075         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5076
5077         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5078             p = (int) board[0][i];
5079             if(p < (int) BlackPawn) piecesLeft[p] ++;
5080             board[0][i] = EmptySquare;
5081         }
5082
5083         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5084             // shuffles restricted to allow normal castling put KRR first
5085             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5086                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5087             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5088                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5089             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5090                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5091             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5092                 put(board, WhiteRook, 0, 0, ANY);
5093             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5094         }
5095
5096         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5097             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5098             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5099                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5100                 while(piecesLeft[p] >= 2) {
5101                     AddOnePiece(board, p, 0, LITE);
5102                     AddOnePiece(board, p, 0, DARK);
5103                 }
5104                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5105             }
5106
5107         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5108             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5109             // but we leave King and Rooks for last, to possibly obey FRC restriction
5110             if(p == (int)WhiteRook) continue;
5111             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5112             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5113         }
5114
5115         // now everything is placed, except perhaps King (Unicorn) and Rooks
5116
5117         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5118             // Last King gets castling rights
5119             while(piecesLeft[(int)WhiteUnicorn]) {
5120                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5121                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5122             }
5123
5124             while(piecesLeft[(int)WhiteKing]) {
5125                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5126                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5127             }
5128
5129
5130         } else {
5131             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5132             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5133         }
5134
5135         // Only Rooks can be left; simply place them all
5136         while(piecesLeft[(int)WhiteRook]) {
5137                 i = put(board, WhiteRook, 0, 0, ANY);
5138                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5139                         if(first) {
5140                                 first=0;
5141                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5142                         }
5143                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5144                 }
5145         }
5146         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5147             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5148         }
5149
5150         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5151 }
5152
5153 int SetCharTable( char *table, const char * map )
5154 /* [HGM] moved here from winboard.c because of its general usefulness */
5155 /*       Basically a safe strcpy that uses the last character as King */
5156 {
5157     int result = FALSE; int NrPieces;
5158
5159     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5160                     && NrPieces >= 12 && !(NrPieces&1)) {
5161         int i; /* [HGM] Accept even length from 12 to 34 */
5162
5163         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5164         for( i=0; i<NrPieces/2-1; i++ ) {
5165             table[i] = map[i];
5166             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5167         }
5168         table[(int) WhiteKing]  = map[NrPieces/2-1];
5169         table[(int) BlackKing]  = map[NrPieces-1];
5170
5171         result = TRUE;
5172     }
5173
5174     return result;
5175 }
5176
5177 void Prelude(Board board)
5178 {       // [HGM] superchess: random selection of exo-pieces
5179         int i, j, k; ChessSquare p; 
5180         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5181
5182         GetPositionNumber(); // use FRC position number
5183
5184         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5185             SetCharTable(pieceToChar, appData.pieceToCharTable);
5186             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5187                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5188         }
5189
5190         j = seed%4;                 seed /= 4; 
5191         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5192         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5193         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5194         j = seed%3 + (seed%3 >= j); seed /= 3; 
5195         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5196         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5197         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5198         j = seed%3;                 seed /= 3; 
5199         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5200         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5201         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5202         j = seed%2 + (seed%2 >= j); seed /= 2; 
5203         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5204         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5205         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5206         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5207         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5208         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5209         put(board, exoPieces[0],    0, 0, ANY);
5210         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5211 }
5212
5213 void
5214 InitPosition(redraw)
5215      int redraw;
5216 {
5217     ChessSquare (* pieces)[BOARD_FILES];
5218     int i, j, pawnRow, overrule,
5219     oldx = gameInfo.boardWidth,
5220     oldy = gameInfo.boardHeight,
5221     oldh = gameInfo.holdingsWidth,
5222     oldv = gameInfo.variant;
5223
5224     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5225
5226     /* [AS] Initialize pv info list [HGM] and game status */
5227     {
5228         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5229             pvInfoList[i].depth = 0;
5230             boards[i][EP_STATUS] = EP_NONE;
5231             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5232         }
5233
5234         initialRulePlies = 0; /* 50-move counter start */
5235
5236         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5237         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5238     }
5239
5240     
5241     /* [HGM] logic here is completely changed. In stead of full positions */
5242     /* the initialized data only consist of the two backranks. The switch */
5243     /* selects which one we will use, which is than copied to the Board   */
5244     /* initialPosition, which for the rest is initialized by Pawns and    */
5245     /* empty squares. This initial position is then copied to boards[0],  */
5246     /* possibly after shuffling, so that it remains available.            */
5247
5248     gameInfo.holdingsWidth = 0; /* default board sizes */
5249     gameInfo.boardWidth    = 8;
5250     gameInfo.boardHeight   = 8;
5251     gameInfo.holdingsSize  = 0;
5252     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5253     for(i=0; i<BOARD_FILES-2; i++)
5254       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5255     initialPosition[EP_STATUS] = EP_NONE;
5256     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5257     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5258          SetCharTable(pieceNickName, appData.pieceNickNames);
5259     else SetCharTable(pieceNickName, "............");
5260
5261     switch (gameInfo.variant) {
5262     case VariantFischeRandom:
5263       shuffleOpenings = TRUE;
5264     default:
5265       pieces = FIDEArray;
5266       break;
5267     case VariantShatranj:
5268       pieces = ShatranjArray;
5269       nrCastlingRights = 0;
5270       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5271       break;
5272     case VariantMakruk:
5273       pieces = makrukArray;
5274       nrCastlingRights = 0;
5275       startedFromSetupPosition = TRUE;
5276       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5277       break;
5278     case VariantTwoKings:
5279       pieces = twoKingsArray;
5280       break;
5281     case VariantCapaRandom:
5282       shuffleOpenings = TRUE;
5283     case VariantCapablanca:
5284       pieces = CapablancaArray;
5285       gameInfo.boardWidth = 10;
5286       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5287       break;
5288     case VariantGothic:
5289       pieces = GothicArray;
5290       gameInfo.boardWidth = 10;
5291       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5292       break;
5293     case VariantJanus:
5294       pieces = JanusArray;
5295       gameInfo.boardWidth = 10;
5296       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5297       nrCastlingRights = 6;
5298         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5299         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5300         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5301         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5302         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5303         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5304       break;
5305     case VariantFalcon:
5306       pieces = FalconArray;
5307       gameInfo.boardWidth = 10;
5308       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5309       break;
5310     case VariantXiangqi:
5311       pieces = XiangqiArray;
5312       gameInfo.boardWidth  = 9;
5313       gameInfo.boardHeight = 10;
5314       nrCastlingRights = 0;
5315       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5316       break;
5317     case VariantShogi:
5318       pieces = ShogiArray;
5319       gameInfo.boardWidth  = 9;
5320       gameInfo.boardHeight = 9;
5321       gameInfo.holdingsSize = 7;
5322       nrCastlingRights = 0;
5323       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5324       break;
5325     case VariantCourier:
5326       pieces = CourierArray;
5327       gameInfo.boardWidth  = 12;
5328       nrCastlingRights = 0;
5329       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5330       break;
5331     case VariantKnightmate:
5332       pieces = KnightmateArray;
5333       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5334       break;
5335     case VariantFairy:
5336       pieces = fairyArray;
5337       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5338       break;
5339     case VariantGreat:
5340       pieces = GreatArray;
5341       gameInfo.boardWidth = 10;
5342       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5343       gameInfo.holdingsSize = 8;
5344       break;
5345     case VariantSuper:
5346       pieces = FIDEArray;
5347       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5348       gameInfo.holdingsSize = 8;
5349       startedFromSetupPosition = TRUE;
5350       break;
5351     case VariantCrazyhouse:
5352     case VariantBughouse:
5353       pieces = FIDEArray;
5354       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5355       gameInfo.holdingsSize = 5;
5356       break;
5357     case VariantWildCastle:
5358       pieces = FIDEArray;
5359       /* !!?shuffle with kings guaranteed to be on d or e file */
5360       shuffleOpenings = 1;
5361       break;
5362     case VariantNoCastle:
5363       pieces = FIDEArray;
5364       nrCastlingRights = 0;
5365       /* !!?unconstrained back-rank shuffle */
5366       shuffleOpenings = 1;
5367       break;
5368     }
5369
5370     overrule = 0;
5371     if(appData.NrFiles >= 0) {
5372         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5373         gameInfo.boardWidth = appData.NrFiles;
5374     }
5375     if(appData.NrRanks >= 0) {
5376         gameInfo.boardHeight = appData.NrRanks;
5377     }
5378     if(appData.holdingsSize >= 0) {
5379         i = appData.holdingsSize;
5380         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5381         gameInfo.holdingsSize = i;
5382     }
5383     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5384     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5385         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5386
5387     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5388     if(pawnRow < 1) pawnRow = 1;
5389     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5390
5391     /* User pieceToChar list overrules defaults */
5392     if(appData.pieceToCharTable != NULL)
5393         SetCharTable(pieceToChar, appData.pieceToCharTable);
5394
5395     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5396
5397         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5398             s = (ChessSquare) 0; /* account holding counts in guard band */
5399         for( i=0; i<BOARD_HEIGHT; i++ )
5400             initialPosition[i][j] = s;
5401
5402         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5403         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5404         initialPosition[pawnRow][j] = WhitePawn;
5405         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5406         if(gameInfo.variant == VariantXiangqi) {
5407             if(j&1) {
5408                 initialPosition[pawnRow][j] = 
5409                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5410                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5411                    initialPosition[2][j] = WhiteCannon;
5412                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5413                 }
5414             }
5415         }
5416         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5417     }
5418     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5419
5420             j=BOARD_LEFT+1;
5421             initialPosition[1][j] = WhiteBishop;
5422             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5423             j=BOARD_RGHT-2;
5424             initialPosition[1][j] = WhiteRook;
5425             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5426     }
5427
5428     if( nrCastlingRights == -1) {
5429         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5430         /*       This sets default castling rights from none to normal corners   */
5431         /* Variants with other castling rights must set them themselves above    */
5432         nrCastlingRights = 6;
5433        
5434         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5435         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5436         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5437         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5438         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5439         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5440      }
5441
5442      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5443      if(gameInfo.variant == VariantGreat) { // promotion commoners
5444         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5445         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5446         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5447         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5448      }
5449   if (appData.debugMode) {
5450     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5451   }
5452     if(shuffleOpenings) {
5453         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5454         startedFromSetupPosition = TRUE;
5455     }
5456     if(startedFromPositionFile) {
5457       /* [HGM] loadPos: use PositionFile for every new game */
5458       CopyBoard(initialPosition, filePosition);
5459       for(i=0; i<nrCastlingRights; i++)
5460           initialRights[i] = filePosition[CASTLING][i];
5461       startedFromSetupPosition = TRUE;
5462     }
5463
5464     CopyBoard(boards[0], initialPosition);
5465
5466     if(oldx != gameInfo.boardWidth ||
5467        oldy != gameInfo.boardHeight ||
5468        oldh != gameInfo.holdingsWidth
5469 #ifdef GOTHIC
5470        || oldv == VariantGothic ||        // For licensing popups
5471        gameInfo.variant == VariantGothic
5472 #endif
5473 #ifdef FALCON
5474        || oldv == VariantFalcon ||
5475        gameInfo.variant == VariantFalcon
5476 #endif
5477                                          )
5478             InitDrawingSizes(-2 ,0);
5479
5480     if (redraw)
5481       DrawPosition(TRUE, boards[currentMove]);
5482 }
5483
5484 void
5485 SendBoard(cps, moveNum)
5486      ChessProgramState *cps;
5487      int moveNum;
5488 {
5489     char message[MSG_SIZ];
5490     
5491     if (cps->useSetboard) {
5492       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5493       sprintf(message, "setboard %s\n", fen);
5494       SendToProgram(message, cps);
5495       free(fen);
5496
5497     } else {
5498       ChessSquare *bp;
5499       int i, j;
5500       /* Kludge to set black to move, avoiding the troublesome and now
5501        * deprecated "black" command.
5502        */
5503       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5504
5505       SendToProgram("edit\n", cps);
5506       SendToProgram("#\n", cps);
5507       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5508         bp = &boards[moveNum][i][BOARD_LEFT];
5509         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5510           if ((int) *bp < (int) BlackPawn) {
5511             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5512                     AAA + j, ONE + i);
5513             if(message[0] == '+' || message[0] == '~') {
5514                 sprintf(message, "%c%c%c+\n",
5515                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5516                         AAA + j, ONE + i);
5517             }
5518             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5519                 message[1] = BOARD_RGHT   - 1 - j + '1';
5520                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5521             }
5522             SendToProgram(message, cps);
5523           }
5524         }
5525       }
5526     
5527       SendToProgram("c\n", cps);
5528       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5529         bp = &boards[moveNum][i][BOARD_LEFT];
5530         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5531           if (((int) *bp != (int) EmptySquare)
5532               && ((int) *bp >= (int) BlackPawn)) {
5533             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5534                     AAA + j, ONE + i);
5535             if(message[0] == '+' || message[0] == '~') {
5536                 sprintf(message, "%c%c%c+\n",
5537                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5538                         AAA + j, ONE + i);
5539             }
5540             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5541                 message[1] = BOARD_RGHT   - 1 - j + '1';
5542                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5543             }
5544             SendToProgram(message, cps);
5545           }
5546         }
5547       }
5548     
5549       SendToProgram(".\n", cps);
5550     }
5551     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5552 }
5553
5554 static int autoQueen; // [HGM] oneclick
5555
5556 int
5557 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5558 {
5559     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5560     /* [HGM] add Shogi promotions */
5561     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5562     ChessSquare piece;
5563     ChessMove moveType;
5564     Boolean premove;
5565
5566     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5567     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5568
5569     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5570       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5571         return FALSE;
5572
5573     piece = boards[currentMove][fromY][fromX];
5574     if(gameInfo.variant == VariantShogi) {
5575         promotionZoneSize = 3;
5576         highestPromotingPiece = (int)WhiteFerz;
5577     } else if(gameInfo.variant == VariantMakruk) {
5578         promotionZoneSize = 3;
5579     }
5580
5581     // next weed out all moves that do not touch the promotion zone at all
5582     if((int)piece >= BlackPawn) {
5583         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5584              return FALSE;
5585         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5586     } else {
5587         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5588            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5589     }
5590
5591     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5592
5593     // weed out mandatory Shogi promotions
5594     if(gameInfo.variant == VariantShogi) {
5595         if(piece >= BlackPawn) {
5596             if(toY == 0 && piece == BlackPawn ||
5597                toY == 0 && piece == BlackQueen ||
5598                toY <= 1 && piece == BlackKnight) {
5599                 *promoChoice = '+';
5600                 return FALSE;
5601             }
5602         } else {
5603             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5604                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5605                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5606                 *promoChoice = '+';
5607                 return FALSE;
5608             }
5609         }
5610     }
5611
5612     // weed out obviously illegal Pawn moves
5613     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5614         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5615         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5616         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5617         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5618         // note we are not allowed to test for valid (non-)capture, due to premove
5619     }
5620
5621     // we either have a choice what to promote to, or (in Shogi) whether to promote
5622     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5623         *promoChoice = PieceToChar(BlackFerz);  // no choice
5624         return FALSE;
5625     }
5626     if(autoQueen) { // predetermined
5627         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5628              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5629         else *promoChoice = PieceToChar(BlackQueen);
5630         return FALSE;
5631     }
5632
5633     // suppress promotion popup on illegal moves that are not premoves
5634     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5635               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5636     if(appData.testLegality && !premove) {
5637         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5638                         fromY, fromX, toY, toX, NULLCHAR);
5639         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5640            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5641             return FALSE;
5642     }
5643
5644     return TRUE;
5645 }
5646
5647 int
5648 InPalace(row, column)
5649      int row, column;
5650 {   /* [HGM] for Xiangqi */
5651     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5652          column < (BOARD_WIDTH + 4)/2 &&
5653          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5654     return FALSE;
5655 }
5656
5657 int
5658 PieceForSquare (x, y)
5659      int x;
5660      int y;
5661 {
5662   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5663      return -1;
5664   else
5665      return boards[currentMove][y][x];
5666 }
5667
5668 int
5669 OKToStartUserMove(x, y)
5670      int x, y;
5671 {
5672     ChessSquare from_piece;
5673     int white_piece;
5674
5675     if (matchMode) return FALSE;
5676     if (gameMode == EditPosition) return TRUE;
5677
5678     if (x >= 0 && y >= 0)
5679       from_piece = boards[currentMove][y][x];
5680     else
5681       from_piece = EmptySquare;
5682
5683     if (from_piece == EmptySquare) return FALSE;
5684
5685     white_piece = (int)from_piece >= (int)WhitePawn &&
5686       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5687
5688     switch (gameMode) {
5689       case PlayFromGameFile:
5690       case AnalyzeFile:
5691       case TwoMachinesPlay:
5692       case EndOfGame:
5693         return FALSE;
5694
5695       case IcsObserving:
5696       case IcsIdle:
5697         return FALSE;
5698
5699       case MachinePlaysWhite:
5700       case IcsPlayingBlack:
5701         if (appData.zippyPlay) return FALSE;
5702         if (white_piece) {
5703             DisplayMoveError(_("You are playing Black"));
5704             return FALSE;
5705         }
5706         break;
5707
5708       case MachinePlaysBlack:
5709       case IcsPlayingWhite:
5710         if (appData.zippyPlay) return FALSE;
5711         if (!white_piece) {
5712             DisplayMoveError(_("You are playing White"));
5713             return FALSE;
5714         }
5715         break;
5716
5717       case EditGame:
5718         if (!white_piece && WhiteOnMove(currentMove)) {
5719             DisplayMoveError(_("It is White's turn"));
5720             return FALSE;
5721         }           
5722         if (white_piece && !WhiteOnMove(currentMove)) {
5723             DisplayMoveError(_("It is Black's turn"));
5724             return FALSE;
5725         }           
5726         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5727             /* Editing correspondence game history */
5728             /* Could disallow this or prompt for confirmation */
5729             cmailOldMove = -1;
5730         }
5731         break;
5732
5733       case BeginningOfGame:
5734         if (appData.icsActive) return FALSE;
5735         if (!appData.noChessProgram) {
5736             if (!white_piece) {
5737                 DisplayMoveError(_("You are playing White"));
5738                 return FALSE;
5739             }
5740         }
5741         break;
5742         
5743       case Training:
5744         if (!white_piece && WhiteOnMove(currentMove)) {
5745             DisplayMoveError(_("It is White's turn"));
5746             return FALSE;
5747         }           
5748         if (white_piece && !WhiteOnMove(currentMove)) {
5749             DisplayMoveError(_("It is Black's turn"));
5750             return FALSE;
5751         }           
5752         break;
5753
5754       default:
5755       case IcsExamining:
5756         break;
5757     }
5758     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5759         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5760         && gameMode != AnalyzeFile && gameMode != Training) {
5761         DisplayMoveError(_("Displayed position is not current"));
5762         return FALSE;
5763     }
5764     return TRUE;
5765 }
5766
5767 Boolean
5768 OnlyMove(int *x, int *y, Boolean captures) {
5769     DisambiguateClosure cl;
5770     if (appData.zippyPlay) return FALSE;
5771     switch(gameMode) {
5772       case MachinePlaysBlack:
5773       case IcsPlayingWhite:
5774       case BeginningOfGame:
5775         if(!WhiteOnMove(currentMove)) return FALSE;
5776         break;
5777       case MachinePlaysWhite:
5778       case IcsPlayingBlack:
5779         if(WhiteOnMove(currentMove)) return FALSE;
5780         break;
5781       default:
5782         return FALSE;
5783     }
5784     cl.pieceIn = EmptySquare; 
5785     cl.rfIn = *y;
5786     cl.ffIn = *x;
5787     cl.rtIn = -1;
5788     cl.ftIn = -1;
5789     cl.promoCharIn = NULLCHAR;
5790     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5791     if( cl.kind == NormalMove ||
5792         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5793         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5794         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5795         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5796       fromX = cl.ff;
5797       fromY = cl.rf;
5798       *x = cl.ft;
5799       *y = cl.rt;
5800       return TRUE;
5801     }
5802     if(cl.kind != ImpossibleMove) return FALSE;
5803     cl.pieceIn = EmptySquare;
5804     cl.rfIn = -1;
5805     cl.ffIn = -1;
5806     cl.rtIn = *y;
5807     cl.ftIn = *x;
5808     cl.promoCharIn = NULLCHAR;
5809     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5810     if( cl.kind == NormalMove ||
5811         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5812         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5813         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5814         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5815       fromX = cl.ff;
5816       fromY = cl.rf;
5817       *x = cl.ft;
5818       *y = cl.rt;
5819       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5820       return TRUE;
5821     }
5822     return FALSE;
5823 }
5824
5825 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5826 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5827 int lastLoadGameUseList = FALSE;
5828 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5829 ChessMove lastLoadGameStart = (ChessMove) 0;
5830
5831 ChessMove
5832 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5833      int fromX, fromY, toX, toY;
5834      int promoChar;
5835      Boolean captureOwn;
5836 {
5837     ChessMove moveType;
5838     ChessSquare pdown, pup;
5839
5840     /* Check if the user is playing in turn.  This is complicated because we
5841        let the user "pick up" a piece before it is his turn.  So the piece he
5842        tried to pick up may have been captured by the time he puts it down!
5843        Therefore we use the color the user is supposed to be playing in this
5844        test, not the color of the piece that is currently on the starting
5845        square---except in EditGame mode, where the user is playing both
5846        sides; fortunately there the capture race can't happen.  (It can
5847        now happen in IcsExamining mode, but that's just too bad.  The user
5848        will get a somewhat confusing message in that case.)
5849        */
5850
5851     switch (gameMode) {
5852       case PlayFromGameFile:
5853       case AnalyzeFile:
5854       case TwoMachinesPlay:
5855       case EndOfGame:
5856       case IcsObserving:
5857       case IcsIdle:
5858         /* We switched into a game mode where moves are not accepted,
5859            perhaps while the mouse button was down. */
5860         return ImpossibleMove;
5861
5862       case MachinePlaysWhite:
5863         /* User is moving for Black */
5864         if (WhiteOnMove(currentMove)) {
5865             DisplayMoveError(_("It is White's turn"));
5866             return ImpossibleMove;
5867         }
5868         break;
5869
5870       case MachinePlaysBlack:
5871         /* User is moving for White */
5872         if (!WhiteOnMove(currentMove)) {
5873             DisplayMoveError(_("It is Black's turn"));
5874             return ImpossibleMove;
5875         }
5876         break;
5877
5878       case EditGame:
5879       case IcsExamining:
5880       case BeginningOfGame:
5881       case AnalyzeMode:
5882       case Training:
5883         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5884             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5885             /* User is moving for Black */
5886             if (WhiteOnMove(currentMove)) {
5887                 DisplayMoveError(_("It is White's turn"));
5888                 return ImpossibleMove;
5889             }
5890         } else {
5891             /* User is moving for White */
5892             if (!WhiteOnMove(currentMove)) {
5893                 DisplayMoveError(_("It is Black's turn"));
5894                 return ImpossibleMove;
5895             }
5896         }
5897         break;
5898
5899       case IcsPlayingBlack:
5900         /* User is moving for Black */
5901         if (WhiteOnMove(currentMove)) {
5902             if (!appData.premove) {
5903                 DisplayMoveError(_("It is White's turn"));
5904             } else if (toX >= 0 && toY >= 0) {
5905                 premoveToX = toX;
5906                 premoveToY = toY;
5907                 premoveFromX = fromX;
5908                 premoveFromY = fromY;
5909                 premovePromoChar = promoChar;
5910                 gotPremove = 1;
5911                 if (appData.debugMode) 
5912                     fprintf(debugFP, "Got premove: fromX %d,"
5913                             "fromY %d, toX %d, toY %d\n",
5914                             fromX, fromY, toX, toY);
5915             }
5916             return ImpossibleMove;
5917         }
5918         break;
5919
5920       case IcsPlayingWhite:
5921         /* User is moving for White */
5922         if (!WhiteOnMove(currentMove)) {
5923             if (!appData.premove) {
5924                 DisplayMoveError(_("It is Black's turn"));
5925             } else if (toX >= 0 && toY >= 0) {
5926                 premoveToX = toX;
5927                 premoveToY = toY;
5928                 premoveFromX = fromX;
5929                 premoveFromY = fromY;
5930                 premovePromoChar = promoChar;
5931                 gotPremove = 1;
5932                 if (appData.debugMode) 
5933                     fprintf(debugFP, "Got premove: fromX %d,"
5934                             "fromY %d, toX %d, toY %d\n",
5935                             fromX, fromY, toX, toY);
5936             }
5937             return ImpossibleMove;
5938         }
5939         break;
5940
5941       default:
5942         break;
5943
5944       case EditPosition:
5945         /* EditPosition, empty square, or different color piece;
5946            click-click move is possible */
5947         if (toX == -2 || toY == -2) {
5948             boards[0][fromY][fromX] = EmptySquare;
5949             return AmbiguousMove;
5950         } else if (toX >= 0 && toY >= 0) {
5951             boards[0][toY][toX] = boards[0][fromY][fromX];
5952             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5953                 if(boards[0][fromY][0] != EmptySquare) {
5954                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5955                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5956                 }
5957             } else
5958             if(fromX == BOARD_RGHT+1) {
5959                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5960                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5961                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5962                 }
5963             } else
5964             boards[0][fromY][fromX] = EmptySquare;
5965             return AmbiguousMove;
5966         }
5967         return ImpossibleMove;
5968     }
5969
5970     if(toX < 0 || toY < 0) return ImpossibleMove;
5971     pdown = boards[currentMove][fromY][fromX];
5972     pup = boards[currentMove][toY][toX];
5973
5974     /* [HGM] If move started in holdings, it means a drop */
5975     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5976          if( pup != EmptySquare ) return ImpossibleMove;
5977          if(appData.testLegality) {
5978              /* it would be more logical if LegalityTest() also figured out
5979               * which drops are legal. For now we forbid pawns on back rank.
5980               * Shogi is on its own here...
5981               */
5982              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5983                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5984                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5985          }
5986          return WhiteDrop; /* Not needed to specify white or black yet */
5987     }
5988
5989     /* [HGM] always test for legality, to get promotion info */
5990     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5991                                          fromY, fromX, toY, toX, promoChar);
5992     /* [HGM] but possibly ignore an IllegalMove result */
5993     if (appData.testLegality) {
5994         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5995             DisplayMoveError(_("Illegal move"));
5996             return ImpossibleMove;
5997         }
5998     }
5999
6000     return moveType;
6001     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
6002        function is made into one that returns an OK move type if FinishMove
6003        should be called. This to give the calling driver routine the
6004        opportunity to finish the userMove input with a promotion popup,
6005        without bothering the user with this for invalid or illegal moves */
6006
6007 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
6008 }
6009
6010 /* Common tail of UserMoveEvent and DropMenuEvent */
6011 int
6012 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6013      ChessMove moveType;
6014      int fromX, fromY, toX, toY;
6015      /*char*/int promoChar;
6016 {
6017     char *bookHit = 0;
6018
6019     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6020         // [HGM] superchess: suppress promotions to non-available piece
6021         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6022         if(WhiteOnMove(currentMove)) {
6023             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6024         } else {
6025             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6026         }
6027     }
6028
6029     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6030        move type in caller when we know the move is a legal promotion */
6031     if(moveType == NormalMove && promoChar)
6032         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6033
6034     /* [HGM] convert drag-and-drop piece drops to standard form */
6035     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6036          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6037            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6038                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6039            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6040            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6041            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6042            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6043          fromY = DROP_RANK;
6044     }
6045
6046     /* [HGM] <popupFix> The following if has been moved here from
6047        UserMoveEvent(). Because it seemed to belong here (why not allow
6048        piece drops in training games?), and because it can only be
6049        performed after it is known to what we promote. */
6050     if (gameMode == Training) {
6051       /* compare the move played on the board to the next move in the
6052        * game. If they match, display the move and the opponent's response. 
6053        * If they don't match, display an error message.
6054        */
6055       int saveAnimate;
6056       Board testBoard;
6057       CopyBoard(testBoard, boards[currentMove]);
6058       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6059
6060       if (CompareBoards(testBoard, boards[currentMove+1])) {
6061         ForwardInner(currentMove+1);
6062
6063         /* Autoplay the opponent's response.
6064          * if appData.animate was TRUE when Training mode was entered,
6065          * the response will be animated.
6066          */
6067         saveAnimate = appData.animate;
6068         appData.animate = animateTraining;
6069         ForwardInner(currentMove+1);
6070         appData.animate = saveAnimate;
6071
6072         /* check for the end of the game */
6073         if (currentMove >= forwardMostMove) {
6074           gameMode = PlayFromGameFile;
6075           ModeHighlight();
6076           SetTrainingModeOff();
6077           DisplayInformation(_("End of game"));
6078         }
6079       } else {
6080         DisplayError(_("Incorrect move"), 0);
6081       }
6082       return 1;
6083     }
6084
6085   /* Ok, now we know that the move is good, so we can kill
6086      the previous line in Analysis Mode */
6087   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6088                                 && currentMove < forwardMostMove) {
6089     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6090   }
6091
6092   /* If we need the chess program but it's dead, restart it */
6093   ResurrectChessProgram();
6094
6095   /* A user move restarts a paused game*/
6096   if (pausing)
6097     PauseEvent();
6098
6099   thinkOutput[0] = NULLCHAR;
6100
6101   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6102
6103   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6104
6105   if (gameMode == BeginningOfGame) {
6106     if (appData.noChessProgram) {
6107       gameMode = EditGame;
6108       SetGameInfo();
6109     } else {
6110       char buf[MSG_SIZ];
6111       gameMode = MachinePlaysBlack;
6112       StartClocks();
6113       SetGameInfo();
6114       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6115       DisplayTitle(buf);
6116       if (first.sendName) {
6117         sprintf(buf, "name %s\n", gameInfo.white);
6118         SendToProgram(buf, &first);
6119       }
6120       StartClocks();
6121     }
6122     ModeHighlight();
6123   }
6124
6125   /* Relay move to ICS or chess engine */
6126   if (appData.icsActive) {
6127     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6128         gameMode == IcsExamining) {
6129       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6130         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6131         SendToICS("draw ");
6132         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6133       }
6134       // also send plain move, in case ICS does not understand atomic claims
6135       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6136       ics_user_moved = 1;
6137     }
6138   } else {
6139     if (first.sendTime && (gameMode == BeginningOfGame ||
6140                            gameMode == MachinePlaysWhite ||
6141                            gameMode == MachinePlaysBlack)) {
6142       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6143     }
6144     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6145          // [HGM] book: if program might be playing, let it use book
6146         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6147         first.maybeThinking = TRUE;
6148     } else SendMoveToProgram(forwardMostMove-1, &first);
6149     if (currentMove == cmailOldMove + 1) {
6150       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6151     }
6152   }
6153
6154   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6155
6156   switch (gameMode) {
6157   case EditGame:
6158     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6159     case MT_NONE:
6160     case MT_CHECK:
6161       break;
6162     case MT_CHECKMATE:
6163     case MT_STAINMATE:
6164       if (WhiteOnMove(currentMove)) {
6165         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6166       } else {
6167         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6168       }
6169       break;
6170     case MT_STALEMATE:
6171       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6172       break;
6173     }
6174     break;
6175     
6176   case MachinePlaysBlack:
6177   case MachinePlaysWhite:
6178     /* disable certain menu options while machine is thinking */
6179     SetMachineThinkingEnables();
6180     break;
6181
6182   default:
6183     break;
6184   }
6185
6186   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6187         
6188   if(bookHit) { // [HGM] book: simulate book reply
6189         static char bookMove[MSG_SIZ]; // a bit generous?
6190
6191         programStats.nodes = programStats.depth = programStats.time = 
6192         programStats.score = programStats.got_only_move = 0;
6193         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6194
6195         strcpy(bookMove, "move ");
6196         strcat(bookMove, bookHit);
6197         HandleMachineMove(bookMove, &first);
6198   }
6199   return 1;
6200 }
6201
6202 void
6203 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6204      int fromX, fromY, toX, toY;
6205      int promoChar;
6206 {
6207     /* [HGM] This routine was added to allow calling of its two logical
6208        parts from other modules in the old way. Before, UserMoveEvent()
6209        automatically called FinishMove() if the move was OK, and returned
6210        otherwise. I separated the two, in order to make it possible to
6211        slip a promotion popup in between. But that it always needs two
6212        calls, to the first part, (now called UserMoveTest() ), and to
6213        FinishMove if the first part succeeded. Calls that do not need
6214        to do anything in between, can call this routine the old way. 
6215     */
6216     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6217 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6218     if(moveType == AmbiguousMove)
6219         DrawPosition(FALSE, boards[currentMove]);
6220     else if(moveType != ImpossibleMove && moveType != Comment)
6221         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6222 }
6223
6224 void
6225 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6226      Board board;
6227      int flags;
6228      ChessMove kind;
6229      int rf, ff, rt, ft;
6230      VOIDSTAR closure;
6231 {
6232     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6233     Markers *m = (Markers *) closure;
6234     if(rf == fromY && ff == fromX)
6235         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6236                          || kind == WhiteCapturesEnPassant
6237                          || kind == BlackCapturesEnPassant);
6238     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6239 }
6240
6241 void
6242 MarkTargetSquares(int clear)
6243 {
6244   int x, y;
6245   if(!appData.markers || !appData.highlightDragging || 
6246      !appData.testLegality || gameMode == EditPosition) return;
6247   if(clear) {
6248     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6249   } else {
6250     int capt = 0;
6251     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6252     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6253       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6254       if(capt)
6255       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6256     }
6257   }
6258   DrawPosition(TRUE, NULL);
6259 }
6260
6261 void LeftClick(ClickType clickType, int xPix, int yPix)
6262 {
6263     int x, y;
6264     Boolean saveAnimate;
6265     static int second = 0, promotionChoice = 0, dragging = 0;
6266     char promoChoice = NULLCHAR;
6267
6268     if(appData.seekGraph && appData.icsActive && loggedOn &&
6269         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6270         SeekGraphClick(clickType, xPix, yPix, 0);
6271         return;
6272     }
6273
6274     if (clickType == Press) ErrorPopDown();
6275     MarkTargetSquares(1);
6276
6277     x = EventToSquare(xPix, BOARD_WIDTH);
6278     y = EventToSquare(yPix, BOARD_HEIGHT);
6279     if (!flipView && y >= 0) {
6280         y = BOARD_HEIGHT - 1 - y;
6281     }
6282     if (flipView && x >= 0) {
6283         x = BOARD_WIDTH - 1 - x;
6284     }
6285
6286     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6287         if(clickType == Release) return; // ignore upclick of click-click destination
6288         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6289         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6290         if(gameInfo.holdingsWidth && 
6291                 (WhiteOnMove(currentMove) 
6292                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6293                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6294             // click in right holdings, for determining promotion piece
6295             ChessSquare p = boards[currentMove][y][x];
6296             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6297             if(p != EmptySquare) {
6298                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6299                 fromX = fromY = -1;
6300                 return;
6301             }
6302         }
6303         DrawPosition(FALSE, boards[currentMove]);
6304         return;
6305     }
6306
6307     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6308     if(clickType == Press
6309             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6310               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6311               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6312         return;
6313
6314     autoQueen = appData.alwaysPromoteToQueen;
6315
6316     if (fromX == -1) {
6317       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6318         if (clickType == Press) {
6319             /* First square */
6320             if (OKToStartUserMove(x, y)) {
6321                 fromX = x;
6322                 fromY = y;
6323                 second = 0;
6324                 MarkTargetSquares(0);
6325                 DragPieceBegin(xPix, yPix); dragging = 1;
6326                 if (appData.highlightDragging) {
6327                     SetHighlights(x, y, -1, -1);
6328                 }
6329             }
6330         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6331             DragPieceEnd(xPix, yPix); dragging = 0;
6332             DrawPosition(FALSE, NULL);
6333         }
6334         return;
6335       }
6336     }
6337
6338     /* fromX != -1 */
6339     if (clickType == Press && gameMode != EditPosition) {
6340         ChessSquare fromP;
6341         ChessSquare toP;
6342         int frc;
6343
6344         // ignore off-board to clicks
6345         if(y < 0 || x < 0) return;
6346
6347         /* Check if clicking again on the same color piece */
6348         fromP = boards[currentMove][fromY][fromX];
6349         toP = boards[currentMove][y][x];
6350         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6351         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6352              WhitePawn <= toP && toP <= WhiteKing &&
6353              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6354              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6355             (BlackPawn <= fromP && fromP <= BlackKing && 
6356              BlackPawn <= toP && toP <= BlackKing &&
6357              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6358              !(fromP == BlackKing && toP == BlackRook && frc))) {
6359             /* Clicked again on same color piece -- changed his mind */
6360             second = (x == fromX && y == fromY);
6361            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6362             if (appData.highlightDragging) {
6363                 SetHighlights(x, y, -1, -1);
6364             } else {
6365                 ClearHighlights();
6366             }
6367             if (OKToStartUserMove(x, y)) {
6368                 fromX = x;
6369                 fromY = y; dragging = 1;
6370                 MarkTargetSquares(0);
6371                 DragPieceBegin(xPix, yPix);
6372             }
6373             return;
6374            }
6375         }
6376         // ignore clicks on holdings
6377         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6378     }
6379
6380     if (clickType == Release && x == fromX && y == fromY) {
6381         DragPieceEnd(xPix, yPix); dragging = 0;
6382         if (appData.animateDragging) {
6383             /* Undo animation damage if any */
6384             DrawPosition(FALSE, NULL);
6385         }
6386         if (second) {
6387             /* Second up/down in same square; just abort move */
6388             second = 0;
6389             fromX = fromY = -1;
6390             ClearHighlights();
6391             gotPremove = 0;
6392             ClearPremoveHighlights();
6393         } else {
6394             /* First upclick in same square; start click-click mode */
6395             SetHighlights(x, y, -1, -1);
6396         }
6397         return;
6398     }
6399
6400     /* we now have a different from- and (possibly off-board) to-square */
6401     /* Completed move */
6402     toX = x;
6403     toY = y;
6404     saveAnimate = appData.animate;
6405     if (clickType == Press) {
6406         /* Finish clickclick move */
6407         if (appData.animate || appData.highlightLastMove) {
6408             SetHighlights(fromX, fromY, toX, toY);
6409         } else {
6410             ClearHighlights();
6411         }
6412     } else {
6413         /* Finish drag move */
6414         if (appData.highlightLastMove) {
6415             SetHighlights(fromX, fromY, toX, toY);
6416         } else {
6417             ClearHighlights();
6418         }
6419         DragPieceEnd(xPix, yPix); dragging = 0;
6420         /* Don't animate move and drag both */
6421         appData.animate = FALSE;
6422     }
6423
6424     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6425     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6426         ChessSquare piece = boards[currentMove][fromY][fromX];
6427         if(gameMode == EditPosition && piece != EmptySquare &&
6428            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6429             int n;
6430              
6431             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6432                 n = PieceToNumber(piece - (int)BlackPawn);
6433                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6434                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6435                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6436             } else
6437             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6438                 n = PieceToNumber(piece);
6439                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6440                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6441                 boards[currentMove][n][BOARD_WIDTH-2]++;
6442             }
6443             boards[currentMove][fromY][fromX] = EmptySquare;
6444         }
6445         ClearHighlights();
6446         fromX = fromY = -1;
6447         DrawPosition(TRUE, boards[currentMove]);
6448         return;
6449     }
6450
6451     // off-board moves should not be highlighted
6452     if(x < 0 || x < 0) ClearHighlights();
6453
6454     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6455         SetHighlights(fromX, fromY, toX, toY);
6456         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6457             // [HGM] super: promotion to captured piece selected from holdings
6458             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6459             promotionChoice = TRUE;
6460             // kludge follows to temporarily execute move on display, without promoting yet
6461             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6462             boards[currentMove][toY][toX] = p;
6463             DrawPosition(FALSE, boards[currentMove]);
6464             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6465             boards[currentMove][toY][toX] = q;
6466             DisplayMessage("Click in holdings to choose piece", "");
6467             return;
6468         }
6469         PromotionPopUp();
6470     } else {
6471         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6472         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6473         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6474         fromX = fromY = -1;
6475     }
6476     appData.animate = saveAnimate;
6477     if (appData.animate || appData.animateDragging) {
6478         /* Undo animation damage if needed */
6479         DrawPosition(FALSE, NULL);
6480     }
6481 }
6482
6483 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6484 {   // front-end-free part taken out of PieceMenuPopup
6485     int whichMenu; int xSqr, ySqr;
6486
6487     if(seekGraphUp) { // [HGM] seekgraph
6488         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6489         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6490         return -2;
6491     }
6492
6493     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6494          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6495         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6496         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6497         if(action == Press)   {
6498             originalFlip = flipView;
6499             flipView = !flipView; // temporarily flip board to see game from partners perspective
6500             DrawPosition(TRUE, partnerBoard);
6501             DisplayMessage(partnerStatus, "");
6502             partnerUp = TRUE;
6503         } else if(action == Release) {
6504             flipView = originalFlip;
6505             DrawPosition(TRUE, boards[currentMove]);
6506             partnerUp = FALSE;
6507         }
6508         return -2;
6509     }
6510
6511     xSqr = EventToSquare(x, BOARD_WIDTH);
6512     ySqr = EventToSquare(y, BOARD_HEIGHT);
6513     if (action == Release) UnLoadPV(); // [HGM] pv
6514     if (action != Press) return -2; // return code to be ignored
6515     switch (gameMode) {
6516       case IcsExamining:
6517         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6518       case EditPosition:
6519         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6520         if (xSqr < 0 || ySqr < 0) return -1;\r
6521         whichMenu = 0; // edit-position menu
6522         break;
6523       case IcsObserving:
6524         if(!appData.icsEngineAnalyze) return -1;
6525       case IcsPlayingWhite:
6526       case IcsPlayingBlack:
6527         if(!appData.zippyPlay) goto noZip;
6528       case AnalyzeMode:
6529       case AnalyzeFile:
6530       case MachinePlaysWhite:
6531       case MachinePlaysBlack:
6532       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6533         if (!appData.dropMenu) {
6534           LoadPV(x, y);
6535           return 2; // flag front-end to grab mouse events
6536         }
6537         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6538            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6539       case EditGame:
6540       noZip:
6541         if (xSqr < 0 || ySqr < 0) return -1;
6542         if (!appData.dropMenu || appData.testLegality &&
6543             gameInfo.variant != VariantBughouse &&
6544             gameInfo.variant != VariantCrazyhouse) return -1;
6545         whichMenu = 1; // drop menu
6546         break;
6547       default:
6548         return -1;
6549     }
6550
6551     if (((*fromX = xSqr) < 0) ||
6552         ((*fromY = ySqr) < 0)) {
6553         *fromX = *fromY = -1;
6554         return -1;
6555     }
6556     if (flipView)
6557       *fromX = BOARD_WIDTH - 1 - *fromX;
6558     else
6559       *fromY = BOARD_HEIGHT - 1 - *fromY;
6560
6561     return whichMenu;
6562 }
6563
6564 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6565 {
6566 //    char * hint = lastHint;
6567     FrontEndProgramStats stats;
6568
6569     stats.which = cps == &first ? 0 : 1;
6570     stats.depth = cpstats->depth;
6571     stats.nodes = cpstats->nodes;
6572     stats.score = cpstats->score;
6573     stats.time = cpstats->time;
6574     stats.pv = cpstats->movelist;
6575     stats.hint = lastHint;
6576     stats.an_move_index = 0;
6577     stats.an_move_count = 0;
6578
6579     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6580         stats.hint = cpstats->move_name;
6581         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6582         stats.an_move_count = cpstats->nr_moves;
6583     }
6584
6585     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6586
6587     SetProgramStats( &stats );
6588 }
6589
6590 void
6591 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6592 {       // count all piece types
6593         int p, f, r;
6594         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6595         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6596         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6597                 p = board[r][f];
6598                 pCnt[p]++;
6599                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6600                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6601                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6602                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6603                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6604                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6605         }
6606 }
6607
6608 int
6609 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6610 {
6611         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6612         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6613                    
6614         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6615         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6616         if(myPawns == 2 && nMine == 3) // KPP
6617             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6618         if(myPawns == 1 && nMine == 2) // KP
6619             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6620         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6621             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6622         if(myPawns) return FALSE;
6623         if(pCnt[WhiteRook+side])
6624             return pCnt[BlackRook-side] || 
6625                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6626                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6627                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6628         if(pCnt[WhiteCannon+side]) {
6629             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6630             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6631         }
6632         if(pCnt[WhiteKnight+side])
6633             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6634         return FALSE;
6635 }
6636
6637 int
6638 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6639 {
6640         VariantClass v = gameInfo.variant;
6641
6642         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6643         if(v == VariantShatranj) return TRUE; // always winnable through baring
6644         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6645         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6646
6647         if(v == VariantXiangqi) {
6648                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6649
6650                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6651                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6652                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6653                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6654                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6655                 if(stale) // we have at least one last-rank P plus perhaps C
6656                     return majors // KPKX
6657                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6658                 else // KCA*E*
6659                     return pCnt[WhiteFerz+side] // KCAK
6660                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6661                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6662                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6663
6664         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6665                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6666                 
6667                 if(nMine == 1) return FALSE; // bare King
6668                 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
6669                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6670                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6671                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6672                 if(pCnt[WhiteKnight+side])
6673                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] + 
6674                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6675                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6676                 if(nBishops)
6677                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6678                 if(pCnt[WhiteAlfil+side])
6679                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6680                 if(pCnt[WhiteWazir+side])
6681                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6682         }
6683
6684         return TRUE;
6685 }
6686
6687 int
6688 Adjudicate(ChessProgramState *cps)
6689 {       // [HGM] some adjudications useful with buggy engines
6690         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6691         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6692         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6693         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6694         int k, count = 0; static int bare = 1;
6695         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6696         Boolean canAdjudicate = !appData.icsActive;
6697
6698         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6699         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6700             if( appData.testLegality )
6701             {   /* [HGM] Some more adjudications for obstinate engines */
6702                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6703                 static int moveCount = 6;
6704                 ChessMove result;
6705                 char *reason = NULL;
6706
6707                 /* Count what is on board. */
6708                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6709
6710                 /* Some material-based adjudications that have to be made before stalemate test */
6711                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6712                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6713                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6714                      if(canAdjudicate && appData.checkMates) {
6715                          if(engineOpponent)
6716                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6717                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6718                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6719                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6720                          return 1;
6721                      }
6722                 }
6723
6724                 /* Bare King in Shatranj (loses) or Losers (wins) */
6725                 if( nrW == 1 || nrB == 1) {
6726                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6727                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6728                      if(canAdjudicate && appData.checkMates) {
6729                          if(engineOpponent)
6730                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6731                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6732                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6733                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6734                          return 1;
6735                      }
6736                   } else
6737                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6738                   {    /* bare King */
6739                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6740                         if(canAdjudicate && appData.checkMates) {
6741                             /* but only adjudicate if adjudication enabled */
6742                             if(engineOpponent)
6743                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6744                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6745                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, 
6746                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6747                             return 1;
6748                         }
6749                   }
6750                 } else bare = 1;
6751
6752
6753             // don't wait for engine to announce game end if we can judge ourselves
6754             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6755               case MT_CHECK:
6756                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6757                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6758                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6759                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6760                             checkCnt++;
6761                         if(checkCnt >= 2) {
6762                             reason = "Xboard adjudication: 3rd check";
6763                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6764                             break;
6765                         }
6766                     }
6767                 }
6768               case MT_NONE:
6769               default:
6770                 break;
6771               case MT_STALEMATE:
6772               case MT_STAINMATE:
6773                 reason = "Xboard adjudication: Stalemate";
6774                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6775                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6776                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6777                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6778                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6779                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6780                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6781                                                                         EP_CHECKMATE : EP_WINS);
6782                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6783                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6784                 }
6785                 break;
6786               case MT_CHECKMATE:
6787                 reason = "Xboard adjudication: Checkmate";
6788                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6789                 break;
6790             }
6791
6792                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6793                     case EP_STALEMATE:
6794                         result = GameIsDrawn; break;
6795                     case EP_CHECKMATE:
6796                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6797                     case EP_WINS:
6798                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6799                     default:
6800                         result = (ChessMove) 0;
6801                 }
6802                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6803                     if(engineOpponent)
6804                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6805                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6806                     GameEnds( result, reason, GE_XBOARD );
6807                     return 1;
6808                 }
6809
6810                 /* Next absolutely insufficient mating material. */
6811                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6812                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6813                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6814
6815                      /* always flag draws, for judging claims */
6816                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6817
6818                      if(canAdjudicate && appData.materialDraws) {
6819                          /* but only adjudicate them if adjudication enabled */
6820                          if(engineOpponent) {
6821                            SendToProgram("force\n", engineOpponent); // suppress reply
6822                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6823                          }
6824                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6825                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6826                          return 1;
6827                      }
6828                 }
6829
6830                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6831                 if(gameInfo.variant == VariantXiangqi ?
6832                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6833                  : nrW + nrB == 4 && 
6834                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6835                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6836                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6837                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6838                    ) ) {
6839                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6840                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6841                           if(engineOpponent) {
6842                             SendToProgram("force\n", engineOpponent); // suppress reply
6843                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6844                           }
6845                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6846                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6847                           return 1;
6848                      }
6849                 } else moveCount = 6;
6850             }
6851         }
6852           
6853         if (appData.debugMode) { int i;
6854             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6855                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6856                     appData.drawRepeats);
6857             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6858               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6859             
6860         }
6861
6862         // Repetition draws and 50-move rule can be applied independently of legality testing
6863
6864                 /* Check for rep-draws */
6865                 count = 0;
6866                 for(k = forwardMostMove-2;
6867                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6868                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6869                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6870                     k-=2)
6871                 {   int rights=0;
6872                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6873                         /* compare castling rights */
6874                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6875                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6876                                 rights++; /* King lost rights, while rook still had them */
6877                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6878                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6879                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6880                                    rights++; /* but at least one rook lost them */
6881                         }
6882                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6883                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6884                                 rights++; 
6885                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6886                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6887                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6888                                    rights++;
6889                         }
6890                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6891                             && appData.drawRepeats > 1) {
6892                              /* adjudicate after user-specified nr of repeats */
6893                              int result = GameIsDrawn;
6894                              char *details = "XBoard adjudication: repetition draw";
6895                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6896                                 // [HGM] xiangqi: check for forbidden perpetuals
6897                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6898                                 for(m=forwardMostMove; m>k; m-=2) {
6899                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6900                                         ourPerpetual = 0; // the current mover did not always check
6901                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6902                                         hisPerpetual = 0; // the opponent did not always check
6903                                 }
6904                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6905                                                                         ourPerpetual, hisPerpetual);
6906                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6907                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6908                                     details = "Xboard adjudication: perpetual checking";
6909                                 } else
6910                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6911                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6912                                 } else
6913                                 // Now check for perpetual chases
6914                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6915                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6916                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6917                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6918                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6919                                         details = "Xboard adjudication: perpetual chasing";
6920                                     } else
6921                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6922                                         break; // Abort repetition-checking loop.
6923                                 }
6924                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6925                              }
6926                              if(engineOpponent) {
6927                                SendToProgram("force\n", engineOpponent); // suppress reply
6928                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6929                              }
6930                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6931                              GameEnds( result, details, GE_XBOARD );
6932                              return 1;
6933                         }
6934                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6935                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6936                     }
6937                 }
6938
6939                 /* Now we test for 50-move draws. Determine ply count */
6940                 count = forwardMostMove;
6941                 /* look for last irreversble move */
6942                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6943                     count--;
6944                 /* if we hit starting position, add initial plies */
6945                 if( count == backwardMostMove )
6946                     count -= initialRulePlies;
6947                 count = forwardMostMove - count; 
6948                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6949                         // adjust reversible move counter for checks in Xiangqi
6950                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6951                         if(i < backwardMostMove) i = backwardMostMove;
6952                         while(i <= forwardMostMove) {
6953                                 lastCheck = inCheck; // check evasion does not count
6954                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6955                                 if(inCheck || lastCheck) count--; // check does not count
6956                                 i++;
6957                         }
6958                 }
6959                 if( count >= 100)
6960                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6961                          /* this is used to judge if draw claims are legal */
6962                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
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, "Xboard adjudication: 50-move rule", GE_XBOARD );
6969                          return 1;
6970                 }
6971
6972                 /* if draw offer is pending, treat it as a draw claim
6973                  * when draw condition present, to allow engines a way to
6974                  * claim draws before making their move to avoid a race
6975                  * condition occurring after their move
6976                  */
6977                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6978                          char *p = NULL;
6979                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6980                              p = "Draw claim: 50-move rule";
6981                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6982                              p = "Draw claim: 3-fold repetition";
6983                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6984                              p = "Draw claim: insufficient mating material";
6985                          if( p != NULL && canAdjudicate) {
6986                              if(engineOpponent) {
6987                                SendToProgram("force\n", engineOpponent); // suppress reply
6988                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6989                              }
6990                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6991                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6992                              return 1;
6993                          }
6994                 }
6995
6996                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6997                     if(engineOpponent) {
6998                       SendToProgram("force\n", engineOpponent); // suppress reply
6999                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7000                     }
7001                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7002                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7003                     return 1;
7004                 }
7005         return 0;
7006 }
7007
7008 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7009 {   // [HGM] book: this routine intercepts moves to simulate book replies
7010     char *bookHit = NULL;
7011
7012     //first determine if the incoming move brings opponent into his book
7013     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7014         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7015     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7016     if(bookHit != NULL && !cps->bookSuspend) {
7017         // make sure opponent is not going to reply after receiving move to book position
7018         SendToProgram("force\n", cps);
7019         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7020     }
7021     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7022     // now arrange restart after book miss
7023     if(bookHit) {
7024         // after a book hit we never send 'go', and the code after the call to this routine
7025         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7026         char buf[MSG_SIZ];
7027         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7028         SendToProgram(buf, cps);
7029         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7030     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7031         SendToProgram("go\n", cps);
7032         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7033     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7034         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7035             SendToProgram("go\n", cps); 
7036         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7037     }
7038     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7039 }
7040
7041 char *savedMessage;
7042 ChessProgramState *savedState;
7043 void DeferredBookMove(void)
7044 {
7045         if(savedState->lastPing != savedState->lastPong)
7046                     ScheduleDelayedEvent(DeferredBookMove, 10);
7047         else
7048         HandleMachineMove(savedMessage, savedState);
7049 }
7050
7051 void
7052 HandleMachineMove(message, cps)
7053      char *message;
7054      ChessProgramState *cps;
7055 {
7056     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7057     char realname[MSG_SIZ];
7058     int fromX, fromY, toX, toY;
7059     ChessMove moveType;
7060     char promoChar;
7061     char *p;
7062     int machineWhite;
7063     char *bookHit;
7064
7065     cps->userError = 0;
7066
7067 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7068     /*
7069      * Kludge to ignore BEL characters
7070      */
7071     while (*message == '\007') message++;
7072
7073     /*
7074      * [HGM] engine debug message: ignore lines starting with '#' character
7075      */
7076     if(cps->debug && *message == '#') return;
7077
7078     /*
7079      * Look for book output
7080      */
7081     if (cps == &first && bookRequested) {
7082         if (message[0] == '\t' || message[0] == ' ') {
7083             /* Part of the book output is here; append it */
7084             strcat(bookOutput, message);
7085             strcat(bookOutput, "  \n");
7086             return;
7087         } else if (bookOutput[0] != NULLCHAR) {
7088             /* All of book output has arrived; display it */
7089             char *p = bookOutput;
7090             while (*p != NULLCHAR) {
7091                 if (*p == '\t') *p = ' ';
7092                 p++;
7093             }
7094             DisplayInformation(bookOutput);
7095             bookRequested = FALSE;
7096             /* Fall through to parse the current output */
7097         }
7098     }
7099
7100     /*
7101      * Look for machine move.
7102      */
7103     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7104         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7105     {
7106         /* This method is only useful on engines that support ping */
7107         if (cps->lastPing != cps->lastPong) {
7108           if (gameMode == BeginningOfGame) {
7109             /* Extra move from before last new; ignore */
7110             if (appData.debugMode) {
7111                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7112             }
7113           } else {
7114             if (appData.debugMode) {
7115                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7116                         cps->which, gameMode);
7117             }
7118
7119             SendToProgram("undo\n", cps);
7120           }
7121           return;
7122         }
7123
7124         switch (gameMode) {
7125           case BeginningOfGame:
7126             /* Extra move from before last reset; ignore */
7127             if (appData.debugMode) {
7128                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7129             }
7130             return;
7131
7132           case EndOfGame:
7133           case IcsIdle:
7134           default:
7135             /* Extra move after we tried to stop.  The mode test is
7136                not a reliable way of detecting this problem, but it's
7137                the best we can do on engines that don't support ping.
7138             */
7139             if (appData.debugMode) {
7140                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7141                         cps->which, gameMode);
7142             }
7143             SendToProgram("undo\n", cps);
7144             return;
7145
7146           case MachinePlaysWhite:
7147           case IcsPlayingWhite:
7148             machineWhite = TRUE;
7149             break;
7150
7151           case MachinePlaysBlack:
7152           case IcsPlayingBlack:
7153             machineWhite = FALSE;
7154             break;
7155
7156           case TwoMachinesPlay:
7157             machineWhite = (cps->twoMachinesColor[0] == 'w');
7158             break;
7159         }
7160         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7161             if (appData.debugMode) {
7162                 fprintf(debugFP,
7163                         "Ignoring move out of turn by %s, gameMode %d"
7164                         ", forwardMost %d\n",
7165                         cps->which, gameMode, forwardMostMove);
7166             }
7167             return;
7168         }
7169
7170     if (appData.debugMode) { int f = forwardMostMove;
7171         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7172                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7173                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7174     }
7175         if(cps->alphaRank) AlphaRank(machineMove, 4);
7176         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7177                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7178             /* Machine move could not be parsed; ignore it. */
7179             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7180                     machineMove, cps->which);
7181             DisplayError(buf1, 0);
7182             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7183                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7184             if (gameMode == TwoMachinesPlay) {
7185               GameEnds(machineWhite ? BlackWins : WhiteWins,
7186                        buf1, GE_XBOARD);
7187             }
7188             return;
7189         }
7190
7191         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7192         /* So we have to redo legality test with true e.p. status here,  */
7193         /* to make sure an illegal e.p. capture does not slip through,   */
7194         /* to cause a forfeit on a justified illegal-move complaint      */
7195         /* of the opponent.                                              */
7196         if( gameMode==TwoMachinesPlay && appData.testLegality
7197             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7198                                                               ) {
7199            ChessMove moveType;
7200            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7201                              fromY, fromX, toY, toX, promoChar);
7202             if (appData.debugMode) {
7203                 int i;
7204                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7205                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7206                 fprintf(debugFP, "castling rights\n");
7207             }
7208             if(moveType == IllegalMove) {
7209                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7210                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7211                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7212                            buf1, GE_XBOARD);
7213                 return;
7214            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7215            /* [HGM] Kludge to handle engines that send FRC-style castling
7216               when they shouldn't (like TSCP-Gothic) */
7217            switch(moveType) {
7218              case WhiteASideCastleFR:
7219              case BlackASideCastleFR:
7220                toX+=2;
7221                currentMoveString[2]++;
7222                break;
7223              case WhiteHSideCastleFR:
7224              case BlackHSideCastleFR:
7225                toX--;
7226                currentMoveString[2]--;
7227                break;
7228              default: ; // nothing to do, but suppresses warning of pedantic compilers
7229            }
7230         }
7231         hintRequested = FALSE;
7232         lastHint[0] = NULLCHAR;
7233         bookRequested = FALSE;
7234         /* Program may be pondering now */
7235         cps->maybeThinking = TRUE;
7236         if (cps->sendTime == 2) cps->sendTime = 1;
7237         if (cps->offeredDraw) cps->offeredDraw--;
7238
7239         /* currentMoveString is set as a side-effect of ParseOneMove */
7240         strcpy(machineMove, currentMoveString);
7241         strcat(machineMove, "\n");
7242         strcpy(moveList[forwardMostMove], machineMove);
7243
7244         /* [AS] Save move info*/
7245         pvInfoList[ forwardMostMove ].score = programStats.score;
7246         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7247         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7248
7249         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7250
7251         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7252         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7253             int count = 0;
7254
7255             while( count < adjudicateLossPlies ) {
7256                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7257
7258                 if( count & 1 ) {
7259                     score = -score; /* Flip score for winning side */
7260                 }
7261
7262                 if( score > adjudicateLossThreshold ) {
7263                     break;
7264                 }
7265
7266                 count++;
7267             }
7268
7269             if( count >= adjudicateLossPlies ) {
7270                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7271
7272                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7273                     "Xboard adjudication", 
7274                     GE_XBOARD );
7275
7276                 return;
7277             }
7278         }
7279
7280         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7281
7282 #if ZIPPY
7283         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7284             first.initDone) {
7285           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7286                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7287                 SendToICS("draw ");
7288                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7289           }
7290           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7291           ics_user_moved = 1;
7292           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7293                 char buf[3*MSG_SIZ];
7294
7295                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7296                         programStats.score / 100.,
7297                         programStats.depth,
7298                         programStats.time / 100.,
7299                         (unsigned int)programStats.nodes,
7300                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7301                         programStats.movelist);
7302                 SendToICS(buf);
7303 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7304           }
7305         }
7306 #endif
7307
7308         /* [AS] Clear stats for next move */
7309         ClearProgramStats();
7310         thinkOutput[0] = NULLCHAR;
7311         hiddenThinkOutputState = 0;
7312
7313         bookHit = NULL;
7314         if (gameMode == TwoMachinesPlay) {
7315             /* [HGM] relaying draw offers moved to after reception of move */
7316             /* and interpreting offer as claim if it brings draw condition */
7317             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7318                 SendToProgram("draw\n", cps->other);
7319             }
7320             if (cps->other->sendTime) {
7321                 SendTimeRemaining(cps->other,
7322                                   cps->other->twoMachinesColor[0] == 'w');
7323             }
7324             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7325             if (firstMove && !bookHit) {
7326                 firstMove = FALSE;
7327                 if (cps->other->useColors) {
7328                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7329                 }
7330                 SendToProgram("go\n", cps->other);
7331             }
7332             cps->other->maybeThinking = TRUE;
7333         }
7334
7335         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7336         
7337         if (!pausing && appData.ringBellAfterMoves) {
7338             RingBell();
7339         }
7340
7341         /* 
7342          * Reenable menu items that were disabled while
7343          * machine was thinking
7344          */
7345         if (gameMode != TwoMachinesPlay)
7346             SetUserThinkingEnables();
7347
7348         // [HGM] book: after book hit opponent has received move and is now in force mode
7349         // force the book reply into it, and then fake that it outputted this move by jumping
7350         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7351         if(bookHit) {
7352                 static char bookMove[MSG_SIZ]; // a bit generous?
7353
7354                 strcpy(bookMove, "move ");
7355                 strcat(bookMove, bookHit);
7356                 message = bookMove;
7357                 cps = cps->other;
7358                 programStats.nodes = programStats.depth = programStats.time = 
7359                 programStats.score = programStats.got_only_move = 0;
7360                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7361
7362                 if(cps->lastPing != cps->lastPong) {
7363                     savedMessage = message; // args for deferred call
7364                     savedState = cps;
7365                     ScheduleDelayedEvent(DeferredBookMove, 10);
7366                     return;
7367                 }
7368                 goto FakeBookMove;
7369         }
7370
7371         return;
7372     }
7373
7374     /* Set special modes for chess engines.  Later something general
7375      *  could be added here; for now there is just one kludge feature,
7376      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7377      *  when "xboard" is given as an interactive command.
7378      */
7379     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7380         cps->useSigint = FALSE;
7381         cps->useSigterm = FALSE;
7382     }
7383     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7384       ParseFeatures(message+8, cps);
7385       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7386     }
7387
7388     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7389      * want this, I was asked to put it in, and obliged.
7390      */
7391     if (!strncmp(message, "setboard ", 9)) {
7392         Board initial_position;
7393
7394         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7395
7396         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7397             DisplayError(_("Bad FEN received from engine"), 0);
7398             return ;
7399         } else {
7400            Reset(TRUE, FALSE);
7401            CopyBoard(boards[0], initial_position);
7402            initialRulePlies = FENrulePlies;
7403            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7404            else gameMode = MachinePlaysBlack;                 
7405            DrawPosition(FALSE, boards[currentMove]);
7406         }
7407         return;
7408     }
7409
7410     /*
7411      * Look for communication commands
7412      */
7413     if (!strncmp(message, "telluser ", 9)) {
7414         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7415         DisplayNote(message + 9);
7416         return;
7417     }
7418     if (!strncmp(message, "tellusererror ", 14)) {
7419         cps->userError = 1;
7420         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7421         DisplayError(message + 14, 0);
7422         return;
7423     }
7424     if (!strncmp(message, "tellopponent ", 13)) {
7425       if (appData.icsActive) {
7426         if (loggedOn) {
7427           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7428           SendToICS(buf1);
7429         }
7430       } else {
7431         DisplayNote(message + 13);
7432       }
7433       return;
7434     }
7435     if (!strncmp(message, "tellothers ", 11)) {
7436       if (appData.icsActive) {
7437         if (loggedOn) {
7438           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7439           SendToICS(buf1);
7440         }
7441       }
7442       return;
7443     }
7444     if (!strncmp(message, "tellall ", 8)) {
7445       if (appData.icsActive) {
7446         if (loggedOn) {
7447           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7448           SendToICS(buf1);
7449         }
7450       } else {
7451         DisplayNote(message + 8);
7452       }
7453       return;
7454     }
7455     if (strncmp(message, "warning", 7) == 0) {
7456         /* Undocumented feature, use tellusererror in new code */
7457         DisplayError(message, 0);
7458         return;
7459     }
7460     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7461         strcpy(realname, cps->tidy);
7462         strcat(realname, " query");
7463         AskQuestion(realname, buf2, buf1, cps->pr);
7464         return;
7465     }
7466     /* Commands from the engine directly to ICS.  We don't allow these to be 
7467      *  sent until we are logged on. Crafty kibitzes have been known to 
7468      *  interfere with the login process.
7469      */
7470     if (loggedOn) {
7471         if (!strncmp(message, "tellics ", 8)) {
7472             SendToICS(message + 8);
7473             SendToICS("\n");
7474             return;
7475         }
7476         if (!strncmp(message, "tellicsnoalias ", 15)) {
7477             SendToICS(ics_prefix);
7478             SendToICS(message + 15);
7479             SendToICS("\n");
7480             return;
7481         }
7482         /* The following are for backward compatibility only */
7483         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7484             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7485             SendToICS(ics_prefix);
7486             SendToICS(message);
7487             SendToICS("\n");
7488             return;
7489         }
7490     }
7491     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7492         return;
7493     }
7494     /*
7495      * If the move is illegal, cancel it and redraw the board.
7496      * Also deal with other error cases.  Matching is rather loose
7497      * here to accommodate engines written before the spec.
7498      */
7499     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7500         strncmp(message, "Error", 5) == 0) {
7501         if (StrStr(message, "name") || 
7502             StrStr(message, "rating") || StrStr(message, "?") ||
7503             StrStr(message, "result") || StrStr(message, "board") ||
7504             StrStr(message, "bk") || StrStr(message, "computer") ||
7505             StrStr(message, "variant") || StrStr(message, "hint") ||
7506             StrStr(message, "random") || StrStr(message, "depth") ||
7507             StrStr(message, "accepted")) {
7508             return;
7509         }
7510         if (StrStr(message, "protover")) {
7511           /* Program is responding to input, so it's apparently done
7512              initializing, and this error message indicates it is
7513              protocol version 1.  So we don't need to wait any longer
7514              for it to initialize and send feature commands. */
7515           FeatureDone(cps, 1);
7516           cps->protocolVersion = 1;
7517           return;
7518         }
7519         cps->maybeThinking = FALSE;
7520
7521         if (StrStr(message, "draw")) {
7522             /* Program doesn't have "draw" command */
7523             cps->sendDrawOffers = 0;
7524             return;
7525         }
7526         if (cps->sendTime != 1 &&
7527             (StrStr(message, "time") || StrStr(message, "otim"))) {
7528           /* Program apparently doesn't have "time" or "otim" command */
7529           cps->sendTime = 0;
7530           return;
7531         }
7532         if (StrStr(message, "analyze")) {
7533             cps->analysisSupport = FALSE;
7534             cps->analyzing = FALSE;
7535             Reset(FALSE, TRUE);
7536             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7537             DisplayError(buf2, 0);
7538             return;
7539         }
7540         if (StrStr(message, "(no matching move)st")) {
7541           /* Special kludge for GNU Chess 4 only */
7542           cps->stKludge = TRUE;
7543           SendTimeControl(cps, movesPerSession, timeControl,
7544                           timeIncrement, appData.searchDepth,
7545                           searchTime);
7546           return;
7547         }
7548         if (StrStr(message, "(no matching move)sd")) {
7549           /* Special kludge for GNU Chess 4 only */
7550           cps->sdKludge = TRUE;
7551           SendTimeControl(cps, movesPerSession, timeControl,
7552                           timeIncrement, appData.searchDepth,
7553                           searchTime);
7554           return;
7555         }
7556         if (!StrStr(message, "llegal")) {
7557             return;
7558         }
7559         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7560             gameMode == IcsIdle) return;
7561         if (forwardMostMove <= backwardMostMove) return;
7562         if (pausing) PauseEvent();
7563       if(appData.forceIllegal) {
7564             // [HGM] illegal: machine refused move; force position after move into it
7565           SendToProgram("force\n", cps);
7566           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7567                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7568                 // when black is to move, while there might be nothing on a2 or black
7569                 // might already have the move. So send the board as if white has the move.
7570                 // But first we must change the stm of the engine, as it refused the last move
7571                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7572                 if(WhiteOnMove(forwardMostMove)) {
7573                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7574                     SendBoard(cps, forwardMostMove); // kludgeless board
7575                 } else {
7576                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7577                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7578                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7579                 }
7580           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7581             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7582                  gameMode == TwoMachinesPlay)
7583               SendToProgram("go\n", cps);
7584             return;
7585       } else
7586         if (gameMode == PlayFromGameFile) {
7587             /* Stop reading this game file */
7588             gameMode = EditGame;
7589             ModeHighlight();
7590         }
7591         currentMove = forwardMostMove-1;
7592         DisplayMove(currentMove-1); /* before DisplayMoveError */
7593         SwitchClocks(forwardMostMove-1); // [HGM] race
7594         DisplayBothClocks();
7595         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7596                 parseList[currentMove], cps->which);
7597         DisplayMoveError(buf1);
7598         DrawPosition(FALSE, boards[currentMove]);
7599
7600         /* [HGM] illegal-move claim should forfeit game when Xboard */
7601         /* only passes fully legal moves                            */
7602         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7603             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7604                                 "False illegal-move claim", GE_XBOARD );
7605         }
7606         return;
7607     }
7608     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7609         /* Program has a broken "time" command that
7610            outputs a string not ending in newline.
7611            Don't use it. */
7612         cps->sendTime = 0;
7613     }
7614     
7615     /*
7616      * If chess program startup fails, exit with an error message.
7617      * Attempts to recover here are futile.
7618      */
7619     if ((StrStr(message, "unknown host") != NULL)
7620         || (StrStr(message, "No remote directory") != NULL)
7621         || (StrStr(message, "not found") != NULL)
7622         || (StrStr(message, "No such file") != NULL)
7623         || (StrStr(message, "can't alloc") != NULL)
7624         || (StrStr(message, "Permission denied") != NULL)) {
7625
7626         cps->maybeThinking = FALSE;
7627         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7628                 cps->which, cps->program, cps->host, message);
7629         RemoveInputSource(cps->isr);
7630         DisplayFatalError(buf1, 0, 1);
7631         return;
7632     }
7633     
7634     /* 
7635      * Look for hint output
7636      */
7637     if (sscanf(message, "Hint: %s", buf1) == 1) {
7638         if (cps == &first && hintRequested) {
7639             hintRequested = FALSE;
7640             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7641                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7642                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7643                                     PosFlags(forwardMostMove),
7644                                     fromY, fromX, toY, toX, promoChar, buf1);
7645                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7646                 DisplayInformation(buf2);
7647             } else {
7648                 /* Hint move could not be parsed!? */
7649               snprintf(buf2, sizeof(buf2),
7650                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7651                         buf1, cps->which);
7652                 DisplayError(buf2, 0);
7653             }
7654         } else {
7655             strcpy(lastHint, buf1);
7656         }
7657         return;
7658     }
7659
7660     /*
7661      * Ignore other messages if game is not in progress
7662      */
7663     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7664         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7665
7666     /*
7667      * look for win, lose, draw, or draw offer
7668      */
7669     if (strncmp(message, "1-0", 3) == 0) {
7670         char *p, *q, *r = "";
7671         p = strchr(message, '{');
7672         if (p) {
7673             q = strchr(p, '}');
7674             if (q) {
7675                 *q = NULLCHAR;
7676                 r = p + 1;
7677             }
7678         }
7679         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7680         return;
7681     } else if (strncmp(message, "0-1", 3) == 0) {
7682         char *p, *q, *r = "";
7683         p = strchr(message, '{');
7684         if (p) {
7685             q = strchr(p, '}');
7686             if (q) {
7687                 *q = NULLCHAR;
7688                 r = p + 1;
7689             }
7690         }
7691         /* Kludge for Arasan 4.1 bug */
7692         if (strcmp(r, "Black resigns") == 0) {
7693             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7694             return;
7695         }
7696         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7697         return;
7698     } else if (strncmp(message, "1/2", 3) == 0) {
7699         char *p, *q, *r = "";
7700         p = strchr(message, '{');
7701         if (p) {
7702             q = strchr(p, '}');
7703             if (q) {
7704                 *q = NULLCHAR;
7705                 r = p + 1;
7706             }
7707         }
7708             
7709         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7710         return;
7711
7712     } else if (strncmp(message, "White resign", 12) == 0) {
7713         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7714         return;
7715     } else if (strncmp(message, "Black resign", 12) == 0) {
7716         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7717         return;
7718     } else if (strncmp(message, "White matches", 13) == 0 ||
7719                strncmp(message, "Black matches", 13) == 0   ) {
7720         /* [HGM] ignore GNUShogi noises */
7721         return;
7722     } else if (strncmp(message, "White", 5) == 0 &&
7723                message[5] != '(' &&
7724                StrStr(message, "Black") == NULL) {
7725         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7726         return;
7727     } else if (strncmp(message, "Black", 5) == 0 &&
7728                message[5] != '(') {
7729         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7730         return;
7731     } else if (strcmp(message, "resign") == 0 ||
7732                strcmp(message, "computer resigns") == 0) {
7733         switch (gameMode) {
7734           case MachinePlaysBlack:
7735           case IcsPlayingBlack:
7736             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7737             break;
7738           case MachinePlaysWhite:
7739           case IcsPlayingWhite:
7740             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7741             break;
7742           case TwoMachinesPlay:
7743             if (cps->twoMachinesColor[0] == 'w')
7744               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7745             else
7746               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7747             break;
7748           default:
7749             /* can't happen */
7750             break;
7751         }
7752         return;
7753     } else if (strncmp(message, "opponent mates", 14) == 0) {
7754         switch (gameMode) {
7755           case MachinePlaysBlack:
7756           case IcsPlayingBlack:
7757             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7758             break;
7759           case MachinePlaysWhite:
7760           case IcsPlayingWhite:
7761             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7762             break;
7763           case TwoMachinesPlay:
7764             if (cps->twoMachinesColor[0] == 'w')
7765               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7766             else
7767               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7768             break;
7769           default:
7770             /* can't happen */
7771             break;
7772         }
7773         return;
7774     } else if (strncmp(message, "computer mates", 14) == 0) {
7775         switch (gameMode) {
7776           case MachinePlaysBlack:
7777           case IcsPlayingBlack:
7778             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7779             break;
7780           case MachinePlaysWhite:
7781           case IcsPlayingWhite:
7782             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7783             break;
7784           case TwoMachinesPlay:
7785             if (cps->twoMachinesColor[0] == 'w')
7786               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7787             else
7788               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7789             break;
7790           default:
7791             /* can't happen */
7792             break;
7793         }
7794         return;
7795     } else if (strncmp(message, "checkmate", 9) == 0) {
7796         if (WhiteOnMove(forwardMostMove)) {
7797             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7798         } else {
7799             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7800         }
7801         return;
7802     } else if (strstr(message, "Draw") != NULL ||
7803                strstr(message, "game is a draw") != NULL) {
7804         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7805         return;
7806     } else if (strstr(message, "offer") != NULL &&
7807                strstr(message, "draw") != NULL) {
7808 #if ZIPPY
7809         if (appData.zippyPlay && first.initDone) {
7810             /* Relay offer to ICS */
7811             SendToICS(ics_prefix);
7812             SendToICS("draw\n");
7813         }
7814 #endif
7815         cps->offeredDraw = 2; /* valid until this engine moves twice */
7816         if (gameMode == TwoMachinesPlay) {
7817             if (cps->other->offeredDraw) {
7818                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7819             /* [HGM] in two-machine mode we delay relaying draw offer      */
7820             /* until after we also have move, to see if it is really claim */
7821             }
7822         } else if (gameMode == MachinePlaysWhite ||
7823                    gameMode == MachinePlaysBlack) {
7824           if (userOfferedDraw) {
7825             DisplayInformation(_("Machine accepts your draw offer"));
7826             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7827           } else {
7828             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7829           }
7830         }
7831     }
7832
7833     
7834     /*
7835      * Look for thinking output
7836      */
7837     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7838           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7839                                 ) {
7840         int plylev, mvleft, mvtot, curscore, time;
7841         char mvname[MOVE_LEN];
7842         u64 nodes; // [DM]
7843         char plyext;
7844         int ignore = FALSE;
7845         int prefixHint = FALSE;
7846         mvname[0] = NULLCHAR;
7847
7848         switch (gameMode) {
7849           case MachinePlaysBlack:
7850           case IcsPlayingBlack:
7851             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7852             break;
7853           case MachinePlaysWhite:
7854           case IcsPlayingWhite:
7855             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7856             break;
7857           case AnalyzeMode:
7858           case AnalyzeFile:
7859             break;
7860           case IcsObserving: /* [DM] icsEngineAnalyze */
7861             if (!appData.icsEngineAnalyze) ignore = TRUE;
7862             break;
7863           case TwoMachinesPlay:
7864             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7865                 ignore = TRUE;
7866             }
7867             break;
7868           default:
7869             ignore = TRUE;
7870             break;
7871         }
7872
7873         if (!ignore) {
7874             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7875             buf1[0] = NULLCHAR;
7876             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7877                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7878
7879                 if (plyext != ' ' && plyext != '\t') {
7880                     time *= 100;
7881                 }
7882
7883                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7884                 if( cps->scoreIsAbsolute && 
7885                     ( gameMode == MachinePlaysBlack ||
7886                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7887                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7888                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7889                      !WhiteOnMove(currentMove)
7890                     ) )
7891                 {
7892                     curscore = -curscore;
7893                 }
7894
7895
7896                 tempStats.depth = plylev;
7897                 tempStats.nodes = nodes;
7898                 tempStats.time = time;
7899                 tempStats.score = curscore;
7900                 tempStats.got_only_move = 0;
7901
7902                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7903                         int ticklen;
7904
7905                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7906                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7907                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7908                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7909                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7910                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7911                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7912                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7913                 }
7914
7915                 /* Buffer overflow protection */
7916                 if (buf1[0] != NULLCHAR) {
7917                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7918                         && appData.debugMode) {
7919                         fprintf(debugFP,
7920                                 "PV is too long; using the first %u bytes.\n",
7921                                 (unsigned) sizeof(tempStats.movelist) - 1);
7922                     }
7923
7924                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7925                 } else {
7926                     sprintf(tempStats.movelist, " no PV\n");
7927                 }
7928
7929                 if (tempStats.seen_stat) {
7930                     tempStats.ok_to_send = 1;
7931                 }
7932
7933                 if (strchr(tempStats.movelist, '(') != NULL) {
7934                     tempStats.line_is_book = 1;
7935                     tempStats.nr_moves = 0;
7936                     tempStats.moves_left = 0;
7937                 } else {
7938                     tempStats.line_is_book = 0;
7939                 }
7940
7941                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7942                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7943
7944                 SendProgramStatsToFrontend( cps, &tempStats );
7945
7946                 /* 
7947                     [AS] Protect the thinkOutput buffer from overflow... this
7948                     is only useful if buf1 hasn't overflowed first!
7949                 */
7950                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7951                         plylev, 
7952                         (gameMode == TwoMachinesPlay ?
7953                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7954                         ((double) curscore) / 100.0,
7955                         prefixHint ? lastHint : "",
7956                         prefixHint ? " " : "" );
7957
7958                 if( buf1[0] != NULLCHAR ) {
7959                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7960
7961                     if( strlen(buf1) > max_len ) {
7962                         if( appData.debugMode) {
7963                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7964                         }
7965                         buf1[max_len+1] = '\0';
7966                     }
7967
7968                     strcat( thinkOutput, buf1 );
7969                 }
7970
7971                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7972                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7973                     DisplayMove(currentMove - 1);
7974                 }
7975                 return;
7976
7977             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7978                 /* crafty (9.25+) says "(only move) <move>"
7979                  * if there is only 1 legal move
7980                  */
7981                 sscanf(p, "(only move) %s", buf1);
7982                 sprintf(thinkOutput, "%s (only move)", buf1);
7983                 sprintf(programStats.movelist, "%s (only move)", buf1);
7984                 programStats.depth = 1;
7985                 programStats.nr_moves = 1;
7986                 programStats.moves_left = 1;
7987                 programStats.nodes = 1;
7988                 programStats.time = 1;
7989                 programStats.got_only_move = 1;
7990
7991                 /* Not really, but we also use this member to
7992                    mean "line isn't going to change" (Crafty
7993                    isn't searching, so stats won't change) */
7994                 programStats.line_is_book = 1;
7995
7996                 SendProgramStatsToFrontend( cps, &programStats );
7997                 
7998                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7999                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8000                     DisplayMove(currentMove - 1);
8001                 }
8002                 return;
8003             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8004                               &time, &nodes, &plylev, &mvleft,
8005                               &mvtot, mvname) >= 5) {
8006                 /* The stat01: line is from Crafty (9.29+) in response
8007                    to the "." command */
8008                 programStats.seen_stat = 1;
8009                 cps->maybeThinking = TRUE;
8010
8011                 if (programStats.got_only_move || !appData.periodicUpdates)
8012                   return;
8013
8014                 programStats.depth = plylev;
8015                 programStats.time = time;
8016                 programStats.nodes = nodes;
8017                 programStats.moves_left = mvleft;
8018                 programStats.nr_moves = mvtot;
8019                 strcpy(programStats.move_name, mvname);
8020                 programStats.ok_to_send = 1;
8021                 programStats.movelist[0] = '\0';
8022
8023                 SendProgramStatsToFrontend( cps, &programStats );
8024
8025                 return;
8026
8027             } else if (strncmp(message,"++",2) == 0) {
8028                 /* Crafty 9.29+ outputs this */
8029                 programStats.got_fail = 2;
8030                 return;
8031
8032             } else if (strncmp(message,"--",2) == 0) {
8033                 /* Crafty 9.29+ outputs this */
8034                 programStats.got_fail = 1;
8035                 return;
8036
8037             } else if (thinkOutput[0] != NULLCHAR &&
8038                        strncmp(message, "    ", 4) == 0) {
8039                 unsigned message_len;
8040
8041                 p = message;
8042                 while (*p && *p == ' ') p++;
8043
8044                 message_len = strlen( p );
8045
8046                 /* [AS] Avoid buffer overflow */
8047                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8048                     strcat(thinkOutput, " ");
8049                     strcat(thinkOutput, p);
8050                 }
8051
8052                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8053                     strcat(programStats.movelist, " ");
8054                     strcat(programStats.movelist, p);
8055                 }
8056
8057                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8058                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8059                     DisplayMove(currentMove - 1);
8060                 }
8061                 return;
8062             }
8063         }
8064         else {
8065             buf1[0] = NULLCHAR;
8066
8067             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8068                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8069             {
8070                 ChessProgramStats cpstats;
8071
8072                 if (plyext != ' ' && plyext != '\t') {
8073                     time *= 100;
8074                 }
8075
8076                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8077                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8078                     curscore = -curscore;
8079                 }
8080
8081                 cpstats.depth = plylev;
8082                 cpstats.nodes = nodes;
8083                 cpstats.time = time;
8084                 cpstats.score = curscore;
8085                 cpstats.got_only_move = 0;
8086                 cpstats.movelist[0] = '\0';
8087
8088                 if (buf1[0] != NULLCHAR) {
8089                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8090                 }
8091
8092                 cpstats.ok_to_send = 0;
8093                 cpstats.line_is_book = 0;
8094                 cpstats.nr_moves = 0;
8095                 cpstats.moves_left = 0;
8096
8097                 SendProgramStatsToFrontend( cps, &cpstats );
8098             }
8099         }
8100     }
8101 }
8102
8103
8104 /* Parse a game score from the character string "game", and
8105    record it as the history of the current game.  The game
8106    score is NOT assumed to start from the standard position. 
8107    The display is not updated in any way.
8108    */
8109 void
8110 ParseGameHistory(game)
8111      char *game;
8112 {
8113     ChessMove moveType;
8114     int fromX, fromY, toX, toY, boardIndex;
8115     char promoChar;
8116     char *p, *q;
8117     char buf[MSG_SIZ];
8118
8119     if (appData.debugMode)
8120       fprintf(debugFP, "Parsing game history: %s\n", game);
8121
8122     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8123     gameInfo.site = StrSave(appData.icsHost);
8124     gameInfo.date = PGNDate();
8125     gameInfo.round = StrSave("-");
8126
8127     /* Parse out names of players */
8128     while (*game == ' ') game++;
8129     p = buf;
8130     while (*game != ' ') *p++ = *game++;
8131     *p = NULLCHAR;
8132     gameInfo.white = StrSave(buf);
8133     while (*game == ' ') game++;
8134     p = buf;
8135     while (*game != ' ' && *game != '\n') *p++ = *game++;
8136     *p = NULLCHAR;
8137     gameInfo.black = StrSave(buf);
8138
8139     /* Parse moves */
8140     boardIndex = blackPlaysFirst ? 1 : 0;
8141     yynewstr(game);
8142     for (;;) {
8143         yyboardindex = boardIndex;
8144         moveType = (ChessMove) yylex();
8145         switch (moveType) {
8146           case IllegalMove:             /* maybe suicide chess, etc. */
8147   if (appData.debugMode) {
8148     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8149     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8150     setbuf(debugFP, NULL);
8151   }
8152           case WhitePromotionChancellor:
8153           case BlackPromotionChancellor:
8154           case WhitePromotionArchbishop:
8155           case BlackPromotionArchbishop:
8156           case WhitePromotionQueen:
8157           case BlackPromotionQueen:
8158           case WhitePromotionRook:
8159           case BlackPromotionRook:
8160           case WhitePromotionBishop:
8161           case BlackPromotionBishop:
8162           case WhitePromotionKnight:
8163           case BlackPromotionKnight:
8164           case WhitePromotionKing:
8165           case BlackPromotionKing:
8166           case NormalMove:
8167           case WhiteCapturesEnPassant:
8168           case BlackCapturesEnPassant:
8169           case WhiteKingSideCastle:
8170           case WhiteQueenSideCastle:
8171           case BlackKingSideCastle:
8172           case BlackQueenSideCastle:
8173           case WhiteKingSideCastleWild:
8174           case WhiteQueenSideCastleWild:
8175           case BlackKingSideCastleWild:
8176           case BlackQueenSideCastleWild:
8177           /* PUSH Fabien */
8178           case WhiteHSideCastleFR:
8179           case WhiteASideCastleFR:
8180           case BlackHSideCastleFR:
8181           case BlackASideCastleFR:
8182           /* POP Fabien */
8183             fromX = currentMoveString[0] - AAA;
8184             fromY = currentMoveString[1] - ONE;
8185             toX = currentMoveString[2] - AAA;
8186             toY = currentMoveString[3] - ONE;
8187             promoChar = currentMoveString[4];
8188             break;
8189           case WhiteDrop:
8190           case BlackDrop:
8191             fromX = moveType == WhiteDrop ?
8192               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8193             (int) CharToPiece(ToLower(currentMoveString[0]));
8194             fromY = DROP_RANK;
8195             toX = currentMoveString[2] - AAA;
8196             toY = currentMoveString[3] - ONE;
8197             promoChar = NULLCHAR;
8198             break;
8199           case AmbiguousMove:
8200             /* bug? */
8201             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8202   if (appData.debugMode) {
8203     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8204     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8205     setbuf(debugFP, NULL);
8206   }
8207             DisplayError(buf, 0);
8208             return;
8209           case ImpossibleMove:
8210             /* bug? */
8211             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8212   if (appData.debugMode) {
8213     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8214     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8215     setbuf(debugFP, NULL);
8216   }
8217             DisplayError(buf, 0);
8218             return;
8219           case (ChessMove) 0:   /* end of file */
8220             if (boardIndex < backwardMostMove) {
8221                 /* Oops, gap.  How did that happen? */
8222                 DisplayError(_("Gap in move list"), 0);
8223                 return;
8224             }
8225             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8226             if (boardIndex > forwardMostMove) {
8227                 forwardMostMove = boardIndex;
8228             }
8229             return;
8230           case ElapsedTime:
8231             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8232                 strcat(parseList[boardIndex-1], " ");
8233                 strcat(parseList[boardIndex-1], yy_text);
8234             }
8235             continue;
8236           case Comment:
8237           case PGNTag:
8238           case NAG:
8239           default:
8240             /* ignore */
8241             continue;
8242           case WhiteWins:
8243           case BlackWins:
8244           case GameIsDrawn:
8245           case GameUnfinished:
8246             if (gameMode == IcsExamining) {
8247                 if (boardIndex < backwardMostMove) {
8248                     /* Oops, gap.  How did that happen? */
8249                     return;
8250                 }
8251                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8252                 return;
8253             }
8254             gameInfo.result = moveType;
8255             p = strchr(yy_text, '{');
8256             if (p == NULL) p = strchr(yy_text, '(');
8257             if (p == NULL) {
8258                 p = yy_text;
8259                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8260             } else {
8261                 q = strchr(p, *p == '{' ? '}' : ')');
8262                 if (q != NULL) *q = NULLCHAR;
8263                 p++;
8264             }
8265             gameInfo.resultDetails = StrSave(p);
8266             continue;
8267         }
8268         if (boardIndex >= forwardMostMove &&
8269             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8270             backwardMostMove = blackPlaysFirst ? 1 : 0;
8271             return;
8272         }
8273         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8274                                  fromY, fromX, toY, toX, promoChar,
8275                                  parseList[boardIndex]);
8276         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8277         /* currentMoveString is set as a side-effect of yylex */
8278         strcpy(moveList[boardIndex], currentMoveString);
8279         strcat(moveList[boardIndex], "\n");
8280         boardIndex++;
8281         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8282         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8283           case MT_NONE:
8284           case MT_STALEMATE:
8285           default:
8286             break;
8287           case MT_CHECK:
8288             if(gameInfo.variant != VariantShogi)
8289                 strcat(parseList[boardIndex - 1], "+");
8290             break;
8291           case MT_CHECKMATE:
8292           case MT_STAINMATE:
8293             strcat(parseList[boardIndex - 1], "#");
8294             break;
8295         }
8296     }
8297 }
8298
8299
8300 /* Apply a move to the given board  */
8301 void
8302 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8303      int fromX, fromY, toX, toY;
8304      int promoChar;
8305      Board board;
8306 {
8307   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8308   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8309
8310     /* [HGM] compute & store e.p. status and castling rights for new position */
8311     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8312     { int i;
8313
8314       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8315       oldEP = (signed char)board[EP_STATUS];
8316       board[EP_STATUS] = EP_NONE;
8317
8318       if( board[toY][toX] != EmptySquare ) 
8319            board[EP_STATUS] = EP_CAPTURE;  
8320
8321       if( board[fromY][fromX] == WhitePawn ) {
8322            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8323                board[EP_STATUS] = EP_PAWN_MOVE;
8324            if( toY-fromY==2) {
8325                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8326                         gameInfo.variant != VariantBerolina || toX < fromX)
8327                       board[EP_STATUS] = toX | berolina;
8328                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8329                         gameInfo.variant != VariantBerolina || toX > fromX) 
8330                       board[EP_STATUS] = toX;
8331            }
8332       } else 
8333       if( board[fromY][fromX] == BlackPawn ) {
8334            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8335                board[EP_STATUS] = EP_PAWN_MOVE; 
8336            if( toY-fromY== -2) {
8337                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8338                         gameInfo.variant != VariantBerolina || toX < fromX)
8339                       board[EP_STATUS] = toX | berolina;
8340                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8341                         gameInfo.variant != VariantBerolina || toX > fromX) 
8342                       board[EP_STATUS] = toX;
8343            }
8344        }
8345
8346        for(i=0; i<nrCastlingRights; i++) {
8347            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8348               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8349              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8350        }
8351
8352     }
8353
8354   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8355   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8356        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8357          
8358   if (fromX == toX && fromY == toY) return;
8359
8360   if (fromY == DROP_RANK) {
8361         /* must be first */
8362         piece = board[toY][toX] = (ChessSquare) fromX;
8363   } else {
8364      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8365      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8366      if(gameInfo.variant == VariantKnightmate)
8367          king += (int) WhiteUnicorn - (int) WhiteKing;
8368
8369     /* Code added by Tord: */
8370     /* FRC castling assumed when king captures friendly rook. */
8371     if (board[fromY][fromX] == WhiteKing &&
8372              board[toY][toX] == WhiteRook) {
8373       board[fromY][fromX] = EmptySquare;
8374       board[toY][toX] = EmptySquare;
8375       if(toX > fromX) {
8376         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8377       } else {
8378         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8379       }
8380     } else if (board[fromY][fromX] == BlackKing &&
8381                board[toY][toX] == BlackRook) {
8382       board[fromY][fromX] = EmptySquare;
8383       board[toY][toX] = EmptySquare;
8384       if(toX > fromX) {
8385         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8386       } else {
8387         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8388       }
8389     /* End of code added by Tord */
8390
8391     } else if (board[fromY][fromX] == king
8392         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8393         && toY == fromY && toX > fromX+1) {
8394         board[fromY][fromX] = EmptySquare;
8395         board[toY][toX] = king;
8396         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8397         board[fromY][BOARD_RGHT-1] = EmptySquare;
8398     } else if (board[fromY][fromX] == king
8399         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8400                && toY == fromY && toX < fromX-1) {
8401         board[fromY][fromX] = EmptySquare;
8402         board[toY][toX] = king;
8403         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8404         board[fromY][BOARD_LEFT] = EmptySquare;
8405     } else if (board[fromY][fromX] == WhitePawn
8406                && toY >= BOARD_HEIGHT-promoRank
8407                && gameInfo.variant != VariantXiangqi
8408                ) {
8409         /* white pawn promotion */
8410         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8411         if (board[toY][toX] == EmptySquare) {
8412             board[toY][toX] = WhiteQueen;
8413         }
8414         if(gameInfo.variant==VariantBughouse ||
8415            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8416             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8417         board[fromY][fromX] = EmptySquare;
8418     } else if ((fromY == BOARD_HEIGHT-4)
8419                && (toX != fromX)
8420                && gameInfo.variant != VariantXiangqi
8421                && gameInfo.variant != VariantBerolina
8422                && (board[fromY][fromX] == WhitePawn)
8423                && (board[toY][toX] == EmptySquare)) {
8424         board[fromY][fromX] = EmptySquare;
8425         board[toY][toX] = WhitePawn;
8426         captured = board[toY - 1][toX];
8427         board[toY - 1][toX] = EmptySquare;
8428     } else if ((fromY == BOARD_HEIGHT-4)
8429                && (toX == fromX)
8430                && gameInfo.variant == VariantBerolina
8431                && (board[fromY][fromX] == WhitePawn)
8432                && (board[toY][toX] == EmptySquare)) {
8433         board[fromY][fromX] = EmptySquare;
8434         board[toY][toX] = WhitePawn;
8435         if(oldEP & EP_BEROLIN_A) {
8436                 captured = board[fromY][fromX-1];
8437                 board[fromY][fromX-1] = EmptySquare;
8438         }else{  captured = board[fromY][fromX+1];
8439                 board[fromY][fromX+1] = EmptySquare;
8440         }
8441     } else if (board[fromY][fromX] == king
8442         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8443                && toY == fromY && toX > fromX+1) {
8444         board[fromY][fromX] = EmptySquare;
8445         board[toY][toX] = king;
8446         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8447         board[fromY][BOARD_RGHT-1] = EmptySquare;
8448     } else if (board[fromY][fromX] == king
8449         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8450                && toY == fromY && toX < fromX-1) {
8451         board[fromY][fromX] = EmptySquare;
8452         board[toY][toX] = king;
8453         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8454         board[fromY][BOARD_LEFT] = EmptySquare;
8455     } else if (fromY == 7 && fromX == 3
8456                && board[fromY][fromX] == BlackKing
8457                && toY == 7 && toX == 5) {
8458         board[fromY][fromX] = EmptySquare;
8459         board[toY][toX] = BlackKing;
8460         board[fromY][7] = EmptySquare;
8461         board[toY][4] = BlackRook;
8462     } else if (fromY == 7 && fromX == 3
8463                && board[fromY][fromX] == BlackKing
8464                && toY == 7 && toX == 1) {
8465         board[fromY][fromX] = EmptySquare;
8466         board[toY][toX] = BlackKing;
8467         board[fromY][0] = EmptySquare;
8468         board[toY][2] = BlackRook;
8469     } else if (board[fromY][fromX] == BlackPawn
8470                && toY < promoRank
8471                && gameInfo.variant != VariantXiangqi
8472                ) {
8473         /* black pawn promotion */
8474         board[toY][toX] = CharToPiece(ToLower(promoChar));
8475         if (board[toY][toX] == EmptySquare) {
8476             board[toY][toX] = BlackQueen;
8477         }
8478         if(gameInfo.variant==VariantBughouse ||
8479            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8480             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8481         board[fromY][fromX] = EmptySquare;
8482     } else if ((fromY == 3)
8483                && (toX != fromX)
8484                && gameInfo.variant != VariantXiangqi
8485                && gameInfo.variant != VariantBerolina
8486                && (board[fromY][fromX] == BlackPawn)
8487                && (board[toY][toX] == EmptySquare)) {
8488         board[fromY][fromX] = EmptySquare;
8489         board[toY][toX] = BlackPawn;
8490         captured = board[toY + 1][toX];
8491         board[toY + 1][toX] = EmptySquare;
8492     } else if ((fromY == 3)
8493                && (toX == fromX)
8494                && gameInfo.variant == VariantBerolina
8495                && (board[fromY][fromX] == BlackPawn)
8496                && (board[toY][toX] == EmptySquare)) {
8497         board[fromY][fromX] = EmptySquare;
8498         board[toY][toX] = BlackPawn;
8499         if(oldEP & EP_BEROLIN_A) {
8500                 captured = board[fromY][fromX-1];
8501                 board[fromY][fromX-1] = EmptySquare;
8502         }else{  captured = board[fromY][fromX+1];
8503                 board[fromY][fromX+1] = EmptySquare;
8504         }
8505     } else {
8506         board[toY][toX] = board[fromY][fromX];
8507         board[fromY][fromX] = EmptySquare;
8508     }
8509
8510     /* [HGM] now we promote for Shogi, if needed */
8511     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8512         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8513   }
8514
8515     if (gameInfo.holdingsWidth != 0) {
8516
8517       /* !!A lot more code needs to be written to support holdings  */
8518       /* [HGM] OK, so I have written it. Holdings are stored in the */
8519       /* penultimate board files, so they are automaticlly stored   */
8520       /* in the game history.                                       */
8521       if (fromY == DROP_RANK) {
8522         /* Delete from holdings, by decreasing count */
8523         /* and erasing image if necessary            */
8524         p = (int) fromX;
8525         if(p < (int) BlackPawn) { /* white drop */
8526              p -= (int)WhitePawn;
8527                  p = PieceToNumber((ChessSquare)p);
8528              if(p >= gameInfo.holdingsSize) p = 0;
8529              if(--board[p][BOARD_WIDTH-2] <= 0)
8530                   board[p][BOARD_WIDTH-1] = EmptySquare;
8531              if((int)board[p][BOARD_WIDTH-2] < 0)
8532                         board[p][BOARD_WIDTH-2] = 0;
8533         } else {                  /* black drop */
8534              p -= (int)BlackPawn;
8535                  p = PieceToNumber((ChessSquare)p);
8536              if(p >= gameInfo.holdingsSize) p = 0;
8537              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8538                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8539              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8540                         board[BOARD_HEIGHT-1-p][1] = 0;
8541         }
8542       }
8543       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8544           && gameInfo.variant != VariantBughouse        ) {
8545         /* [HGM] holdings: Add to holdings, if holdings exist */
8546         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8547                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8548                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8549         }
8550         p = (int) captured;
8551         if (p >= (int) BlackPawn) {
8552           p -= (int)BlackPawn;
8553           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8554                   /* in Shogi restore piece to its original  first */
8555                   captured = (ChessSquare) (DEMOTED captured);
8556                   p = DEMOTED p;
8557           }
8558           p = PieceToNumber((ChessSquare)p);
8559           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8560           board[p][BOARD_WIDTH-2]++;
8561           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8562         } else {
8563           p -= (int)WhitePawn;
8564           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8565                   captured = (ChessSquare) (DEMOTED captured);
8566                   p = DEMOTED p;
8567           }
8568           p = PieceToNumber((ChessSquare)p);
8569           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8570           board[BOARD_HEIGHT-1-p][1]++;
8571           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8572         }
8573       }
8574     } else if (gameInfo.variant == VariantAtomic) {
8575       if (captured != EmptySquare) {
8576         int y, x;
8577         for (y = toY-1; y <= toY+1; y++) {
8578           for (x = toX-1; x <= toX+1; x++) {
8579             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8580                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8581               board[y][x] = EmptySquare;
8582             }
8583           }
8584         }
8585         board[toY][toX] = EmptySquare;
8586       }
8587     }
8588     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8589         /* [HGM] Shogi promotions */
8590         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8591     }
8592
8593     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8594                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8595         // [HGM] superchess: take promotion piece out of holdings
8596         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8597         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8598             if(!--board[k][BOARD_WIDTH-2])
8599                 board[k][BOARD_WIDTH-1] = EmptySquare;
8600         } else {
8601             if(!--board[BOARD_HEIGHT-1-k][1])
8602                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8603         }
8604     }
8605
8606 }
8607
8608 /* Updates forwardMostMove */
8609 void
8610 MakeMove(fromX, fromY, toX, toY, promoChar)
8611      int fromX, fromY, toX, toY;
8612      int promoChar;
8613 {
8614 //    forwardMostMove++; // [HGM] bare: moved downstream
8615
8616     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8617         int timeLeft; static int lastLoadFlag=0; int king, piece;
8618         piece = boards[forwardMostMove][fromY][fromX];
8619         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8620         if(gameInfo.variant == VariantKnightmate)
8621             king += (int) WhiteUnicorn - (int) WhiteKing;
8622         if(forwardMostMove == 0) {
8623             if(blackPlaysFirst) 
8624                 fprintf(serverMoves, "%s;", second.tidy);
8625             fprintf(serverMoves, "%s;", first.tidy);
8626             if(!blackPlaysFirst) 
8627                 fprintf(serverMoves, "%s;", second.tidy);
8628         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8629         lastLoadFlag = loadFlag;
8630         // print base move
8631         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8632         // print castling suffix
8633         if( toY == fromY && piece == king ) {
8634             if(toX-fromX > 1)
8635                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8636             if(fromX-toX >1)
8637                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8638         }
8639         // e.p. suffix
8640         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8641              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8642              boards[forwardMostMove][toY][toX] == EmptySquare
8643              && fromX != toX && fromY != toY)
8644                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8645         // promotion suffix
8646         if(promoChar != NULLCHAR)
8647                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8648         if(!loadFlag) {
8649             fprintf(serverMoves, "/%d/%d",
8650                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8651             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8652             else                      timeLeft = blackTimeRemaining/1000;
8653             fprintf(serverMoves, "/%d", timeLeft);
8654         }
8655         fflush(serverMoves);
8656     }
8657
8658     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8659       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8660                         0, 1);
8661       return;
8662     }
8663     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8664     if (commentList[forwardMostMove+1] != NULL) {
8665         free(commentList[forwardMostMove+1]);
8666         commentList[forwardMostMove+1] = NULL;
8667     }
8668     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8669     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8670     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8671     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8672     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8673     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8674     gameInfo.result = GameUnfinished;
8675     if (gameInfo.resultDetails != NULL) {
8676         free(gameInfo.resultDetails);
8677         gameInfo.resultDetails = NULL;
8678     }
8679     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8680                               moveList[forwardMostMove - 1]);
8681     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8682                              PosFlags(forwardMostMove - 1),
8683                              fromY, fromX, toY, toX, promoChar,
8684                              parseList[forwardMostMove - 1]);
8685     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8686       case MT_NONE:
8687       case MT_STALEMATE:
8688       default:
8689         break;
8690       case MT_CHECK:
8691         if(gameInfo.variant != VariantShogi)
8692             strcat(parseList[forwardMostMove - 1], "+");
8693         break;
8694       case MT_CHECKMATE:
8695       case MT_STAINMATE:
8696         strcat(parseList[forwardMostMove - 1], "#");
8697         break;
8698     }
8699     if (appData.debugMode) {
8700         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8701     }
8702
8703 }
8704
8705 /* Updates currentMove if not pausing */
8706 void
8707 ShowMove(fromX, fromY, toX, toY)
8708 {
8709     int instant = (gameMode == PlayFromGameFile) ?
8710         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8711     if(appData.noGUI) return;
8712     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8713         if (!instant) {
8714             if (forwardMostMove == currentMove + 1) {
8715                 AnimateMove(boards[forwardMostMove - 1],
8716                             fromX, fromY, toX, toY);
8717             }
8718             if (appData.highlightLastMove) {
8719                 SetHighlights(fromX, fromY, toX, toY);
8720             }
8721         }
8722         currentMove = forwardMostMove;
8723     }
8724
8725     if (instant) return;
8726
8727     DisplayMove(currentMove - 1);
8728     DrawPosition(FALSE, boards[currentMove]);
8729     DisplayBothClocks();
8730     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8731 }
8732
8733 void SendEgtPath(ChessProgramState *cps)
8734 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8735         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8736
8737         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8738
8739         while(*p) {
8740             char c, *q = name+1, *r, *s;
8741
8742             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8743             while(*p && *p != ',') *q++ = *p++;
8744             *q++ = ':'; *q = 0;
8745             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8746                 strcmp(name, ",nalimov:") == 0 ) {
8747                 // take nalimov path from the menu-changeable option first, if it is defined
8748                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8749                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8750             } else
8751             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8752                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8753                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8754                 s = r = StrStr(s, ":") + 1; // beginning of path info
8755                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8756                 c = *r; *r = 0;             // temporarily null-terminate path info
8757                     *--q = 0;               // strip of trailig ':' from name
8758                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8759                 *r = c;
8760                 SendToProgram(buf,cps);     // send egtbpath command for this format
8761             }
8762             if(*p == ',') p++; // read away comma to position for next format name
8763         }
8764 }
8765
8766 void
8767 InitChessProgram(cps, setup)
8768      ChessProgramState *cps;
8769      int setup; /* [HGM] needed to setup FRC opening position */
8770 {
8771     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8772     if (appData.noChessProgram) return;
8773     hintRequested = FALSE;
8774     bookRequested = FALSE;
8775
8776     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8777     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8778     if(cps->memSize) { /* [HGM] memory */
8779         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8780         SendToProgram(buf, cps);
8781     }
8782     SendEgtPath(cps); /* [HGM] EGT */
8783     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8784         sprintf(buf, "cores %d\n", appData.smpCores);
8785         SendToProgram(buf, cps);
8786     }
8787
8788     SendToProgram(cps->initString, cps);
8789     if (gameInfo.variant != VariantNormal &&
8790         gameInfo.variant != VariantLoadable
8791         /* [HGM] also send variant if board size non-standard */
8792         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8793                                             ) {
8794       char *v = VariantName(gameInfo.variant);
8795       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8796         /* [HGM] in protocol 1 we have to assume all variants valid */
8797         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8798         DisplayFatalError(buf, 0, 1);
8799         return;
8800       }
8801
8802       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8803       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8804       if( gameInfo.variant == VariantXiangqi )
8805            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8806       if( gameInfo.variant == VariantShogi )
8807            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8808       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8809            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8810       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8811                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8812            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8813       if( gameInfo.variant == VariantCourier )
8814            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8815       if( gameInfo.variant == VariantSuper )
8816            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8817       if( gameInfo.variant == VariantGreat )
8818            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8819
8820       if(overruled) {
8821            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8822                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8823            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8824            if(StrStr(cps->variants, b) == NULL) { 
8825                // specific sized variant not known, check if general sizing allowed
8826                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8827                    if(StrStr(cps->variants, "boardsize") == NULL) {
8828                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8829                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8830                        DisplayFatalError(buf, 0, 1);
8831                        return;
8832                    }
8833                    /* [HGM] here we really should compare with the maximum supported board size */
8834                }
8835            }
8836       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8837       sprintf(buf, "variant %s\n", b);
8838       SendToProgram(buf, cps);
8839     }
8840     currentlyInitializedVariant = gameInfo.variant;
8841
8842     /* [HGM] send opening position in FRC to first engine */
8843     if(setup) {
8844           SendToProgram("force\n", cps);
8845           SendBoard(cps, 0);
8846           /* engine is now in force mode! Set flag to wake it up after first move. */
8847           setboardSpoiledMachineBlack = 1;
8848     }
8849
8850     if (cps->sendICS) {
8851       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8852       SendToProgram(buf, cps);
8853     }
8854     cps->maybeThinking = FALSE;
8855     cps->offeredDraw = 0;
8856     if (!appData.icsActive) {
8857         SendTimeControl(cps, movesPerSession, timeControl,
8858                         timeIncrement, appData.searchDepth,
8859                         searchTime);
8860     }
8861     if (appData.showThinking 
8862         // [HGM] thinking: four options require thinking output to be sent
8863         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8864                                 ) {
8865         SendToProgram("post\n", cps);
8866     }
8867     SendToProgram("hard\n", cps);
8868     if (!appData.ponderNextMove) {
8869         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8870            it without being sure what state we are in first.  "hard"
8871            is not a toggle, so that one is OK.
8872          */
8873         SendToProgram("easy\n", cps);
8874     }
8875     if (cps->usePing) {
8876       sprintf(buf, "ping %d\n", ++cps->lastPing);
8877       SendToProgram(buf, cps);
8878     }
8879     cps->initDone = TRUE;
8880 }   
8881
8882
8883 void
8884 StartChessProgram(cps)
8885      ChessProgramState *cps;
8886 {
8887     char buf[MSG_SIZ];
8888     int err;
8889
8890     if (appData.noChessProgram) return;
8891     cps->initDone = FALSE;
8892
8893     if (strcmp(cps->host, "localhost") == 0) {
8894         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8895     } else if (*appData.remoteShell == NULLCHAR) {
8896         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8897     } else {
8898         if (*appData.remoteUser == NULLCHAR) {
8899           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8900                     cps->program);
8901         } else {
8902           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8903                     cps->host, appData.remoteUser, cps->program);
8904         }
8905         err = StartChildProcess(buf, "", &cps->pr);
8906     }
8907     
8908     if (err != 0) {
8909         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8910         DisplayFatalError(buf, err, 1);
8911         cps->pr = NoProc;
8912         cps->isr = NULL;
8913         return;
8914     }
8915     
8916     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8917     if (cps->protocolVersion > 1) {
8918       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8919       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8920       cps->comboCnt = 0;  //                and values of combo boxes
8921       SendToProgram(buf, cps);
8922     } else {
8923       SendToProgram("xboard\n", cps);
8924     }
8925 }
8926
8927
8928 void
8929 TwoMachinesEventIfReady P((void))
8930 {
8931   if (first.lastPing != first.lastPong) {
8932     DisplayMessage("", _("Waiting for first chess program"));
8933     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8934     return;
8935   }
8936   if (second.lastPing != second.lastPong) {
8937     DisplayMessage("", _("Waiting for second chess program"));
8938     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8939     return;
8940   }
8941   ThawUI();
8942   TwoMachinesEvent();
8943 }
8944
8945 void
8946 NextMatchGame P((void))
8947 {
8948     int index; /* [HGM] autoinc: step load index during match */
8949     Reset(FALSE, TRUE);
8950     if (*appData.loadGameFile != NULLCHAR) {
8951         index = appData.loadGameIndex;
8952         if(index < 0) { // [HGM] autoinc
8953             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8954             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8955         } 
8956         LoadGameFromFile(appData.loadGameFile,
8957                          index,
8958                          appData.loadGameFile, FALSE);
8959     } else if (*appData.loadPositionFile != NULLCHAR) {
8960         index = appData.loadPositionIndex;
8961         if(index < 0) { // [HGM] autoinc
8962             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8963             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8964         } 
8965         LoadPositionFromFile(appData.loadPositionFile,
8966                              index,
8967                              appData.loadPositionFile);
8968     }
8969     TwoMachinesEventIfReady();
8970 }
8971
8972 void UserAdjudicationEvent( int result )
8973 {
8974     ChessMove gameResult = GameIsDrawn;
8975
8976     if( result > 0 ) {
8977         gameResult = WhiteWins;
8978     }
8979     else if( result < 0 ) {
8980         gameResult = BlackWins;
8981     }
8982
8983     if( gameMode == TwoMachinesPlay ) {
8984         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8985     }
8986 }
8987
8988
8989 // [HGM] save: calculate checksum of game to make games easily identifiable
8990 int StringCheckSum(char *s)
8991 {
8992         int i = 0;
8993         if(s==NULL) return 0;
8994         while(*s) i = i*259 + *s++;
8995         return i;
8996 }
8997
8998 int GameCheckSum()
8999 {
9000         int i, sum=0;
9001         for(i=backwardMostMove; i<forwardMostMove; i++) {
9002                 sum += pvInfoList[i].depth;
9003                 sum += StringCheckSum(parseList[i]);
9004                 sum += StringCheckSum(commentList[i]);
9005                 sum *= 261;
9006         }
9007         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9008         return sum + StringCheckSum(commentList[i]);
9009 } // end of save patch
9010
9011 void
9012 GameEnds(result, resultDetails, whosays)
9013      ChessMove result;
9014      char *resultDetails;
9015      int whosays;
9016 {
9017     GameMode nextGameMode;
9018     int isIcsGame;
9019     char buf[MSG_SIZ], popupRequested = 0;
9020
9021     if(endingGame) return; /* [HGM] crash: forbid recursion */
9022     endingGame = 1;
9023     if(twoBoards) { // [HGM] dual: switch back to one board
9024         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9025         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9026     }
9027     if (appData.debugMode) {
9028       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9029               result, resultDetails ? resultDetails : "(null)", whosays);
9030     }
9031
9032     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9033
9034     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9035         /* If we are playing on ICS, the server decides when the
9036            game is over, but the engine can offer to draw, claim 
9037            a draw, or resign. 
9038          */
9039 #if ZIPPY
9040         if (appData.zippyPlay && first.initDone) {
9041             if (result == GameIsDrawn) {
9042                 /* In case draw still needs to be claimed */
9043                 SendToICS(ics_prefix);
9044                 SendToICS("draw\n");
9045             } else if (StrCaseStr(resultDetails, "resign")) {
9046                 SendToICS(ics_prefix);
9047                 SendToICS("resign\n");
9048             }
9049         }
9050 #endif
9051         endingGame = 0; /* [HGM] crash */
9052         return;
9053     }
9054
9055     /* If we're loading the game from a file, stop */
9056     if (whosays == GE_FILE) {
9057       (void) StopLoadGameTimer();
9058       gameFileFP = NULL;
9059     }
9060
9061     /* Cancel draw offers */
9062     first.offeredDraw = second.offeredDraw = 0;
9063
9064     /* If this is an ICS game, only ICS can really say it's done;
9065        if not, anyone can. */
9066     isIcsGame = (gameMode == IcsPlayingWhite || 
9067                  gameMode == IcsPlayingBlack || 
9068                  gameMode == IcsObserving    || 
9069                  gameMode == IcsExamining);
9070
9071     if (!isIcsGame || whosays == GE_ICS) {
9072         /* OK -- not an ICS game, or ICS said it was done */
9073         StopClocks();
9074         if (!isIcsGame && !appData.noChessProgram) 
9075           SetUserThinkingEnables();
9076     
9077         /* [HGM] if a machine claims the game end we verify this claim */
9078         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9079             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9080                 char claimer;
9081                 ChessMove trueResult = (ChessMove) -1;
9082
9083                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9084                                             first.twoMachinesColor[0] :
9085                                             second.twoMachinesColor[0] ;
9086
9087                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9088                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9089                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9090                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9091                 } else
9092                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9093                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9094                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9095                 } else
9096                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9097                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9098                 }
9099
9100                 // now verify win claims, but not in drop games, as we don't understand those yet
9101                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9102                                                  || gameInfo.variant == VariantGreat) &&
9103                     (result == WhiteWins && claimer == 'w' ||
9104                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9105                       if (appData.debugMode) {
9106                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9107                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9108                       }
9109                       if(result != trueResult) {
9110                               sprintf(buf, "False win claim: '%s'", resultDetails);
9111                               result = claimer == 'w' ? BlackWins : WhiteWins;
9112                               resultDetails = buf;
9113                       }
9114                 } else
9115                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9116                     && (forwardMostMove <= backwardMostMove ||
9117                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9118                         (claimer=='b')==(forwardMostMove&1))
9119                                                                                   ) {
9120                       /* [HGM] verify: draws that were not flagged are false claims */
9121                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9122                       result = claimer == 'w' ? BlackWins : WhiteWins;
9123                       resultDetails = buf;
9124                 }
9125                 /* (Claiming a loss is accepted no questions asked!) */
9126             }
9127             /* [HGM] bare: don't allow bare King to win */
9128             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9129                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9130                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9131                && result != GameIsDrawn)
9132             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9133                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9134                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9135                         if(p >= 0 && p <= (int)WhiteKing) k++;
9136                 }
9137                 if (appData.debugMode) {
9138                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9139                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9140                 }
9141                 if(k <= 1) {
9142                         result = GameIsDrawn;
9143                         sprintf(buf, "%s but bare king", resultDetails);
9144                         resultDetails = buf;
9145                 }
9146             }
9147         }
9148
9149
9150         if(serverMoves != NULL && !loadFlag) { char c = '=';
9151             if(result==WhiteWins) c = '+';
9152             if(result==BlackWins) c = '-';
9153             if(resultDetails != NULL)
9154                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9155         }
9156         if (resultDetails != NULL) {
9157             gameInfo.result = result;
9158             gameInfo.resultDetails = StrSave(resultDetails);
9159
9160             /* display last move only if game was not loaded from file */
9161             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9162                 DisplayMove(currentMove - 1);
9163     
9164             if (forwardMostMove != 0) {
9165                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9166                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9167                                                                 ) {
9168                     if (*appData.saveGameFile != NULLCHAR) {
9169                         SaveGameToFile(appData.saveGameFile, TRUE);
9170                     } else if (appData.autoSaveGames) {
9171                         AutoSaveGame();
9172                     }
9173                     if (*appData.savePositionFile != NULLCHAR) {
9174                         SavePositionToFile(appData.savePositionFile);
9175                     }
9176                 }
9177             }
9178
9179             /* Tell program how game ended in case it is learning */
9180             /* [HGM] Moved this to after saving the PGN, just in case */
9181             /* engine died and we got here through time loss. In that */
9182             /* case we will get a fatal error writing the pipe, which */
9183             /* would otherwise lose us the PGN.                       */
9184             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9185             /* output during GameEnds should never be fatal anymore   */
9186             if (gameMode == MachinePlaysWhite ||
9187                 gameMode == MachinePlaysBlack ||
9188                 gameMode == TwoMachinesPlay ||
9189                 gameMode == IcsPlayingWhite ||
9190                 gameMode == IcsPlayingBlack ||
9191                 gameMode == BeginningOfGame) {
9192                 char buf[MSG_SIZ];
9193                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9194                         resultDetails);
9195                 if (first.pr != NoProc) {
9196                     SendToProgram(buf, &first);
9197                 }
9198                 if (second.pr != NoProc &&
9199                     gameMode == TwoMachinesPlay) {
9200                     SendToProgram(buf, &second);
9201                 }
9202             }
9203         }
9204
9205         if (appData.icsActive) {
9206             if (appData.quietPlay &&
9207                 (gameMode == IcsPlayingWhite ||
9208                  gameMode == IcsPlayingBlack)) {
9209                 SendToICS(ics_prefix);
9210                 SendToICS("set shout 1\n");
9211             }
9212             nextGameMode = IcsIdle;
9213             ics_user_moved = FALSE;
9214             /* clean up premove.  It's ugly when the game has ended and the
9215              * premove highlights are still on the board.
9216              */
9217             if (gotPremove) {
9218               gotPremove = FALSE;
9219               ClearPremoveHighlights();
9220               DrawPosition(FALSE, boards[currentMove]);
9221             }
9222             if (whosays == GE_ICS) {
9223                 switch (result) {
9224                 case WhiteWins:
9225                     if (gameMode == IcsPlayingWhite)
9226                         PlayIcsWinSound();
9227                     else if(gameMode == IcsPlayingBlack)
9228                         PlayIcsLossSound();
9229                     break;
9230                 case BlackWins:
9231                     if (gameMode == IcsPlayingBlack)
9232                         PlayIcsWinSound();
9233                     else if(gameMode == IcsPlayingWhite)
9234                         PlayIcsLossSound();
9235                     break;
9236                 case GameIsDrawn:
9237                     PlayIcsDrawSound();
9238                     break;
9239                 default:
9240                     PlayIcsUnfinishedSound();
9241                 }
9242             }
9243         } else if (gameMode == EditGame ||
9244                    gameMode == PlayFromGameFile || 
9245                    gameMode == AnalyzeMode || 
9246                    gameMode == AnalyzeFile) {
9247             nextGameMode = gameMode;
9248         } else {
9249             nextGameMode = EndOfGame;
9250         }
9251         pausing = FALSE;
9252         ModeHighlight();
9253     } else {
9254         nextGameMode = gameMode;
9255     }
9256
9257     if (appData.noChessProgram) {
9258         gameMode = nextGameMode;
9259         ModeHighlight();
9260         endingGame = 0; /* [HGM] crash */
9261         return;
9262     }
9263
9264     if (first.reuse) {
9265         /* Put first chess program into idle state */
9266         if (first.pr != NoProc &&
9267             (gameMode == MachinePlaysWhite ||
9268              gameMode == MachinePlaysBlack ||
9269              gameMode == TwoMachinesPlay ||
9270              gameMode == IcsPlayingWhite ||
9271              gameMode == IcsPlayingBlack ||
9272              gameMode == BeginningOfGame)) {
9273             SendToProgram("force\n", &first);
9274             if (first.usePing) {
9275               char buf[MSG_SIZ];
9276               sprintf(buf, "ping %d\n", ++first.lastPing);
9277               SendToProgram(buf, &first);
9278             }
9279         }
9280     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9281         /* Kill off first chess program */
9282         if (first.isr != NULL)
9283           RemoveInputSource(first.isr);
9284         first.isr = NULL;
9285     
9286         if (first.pr != NoProc) {
9287             ExitAnalyzeMode();
9288             DoSleep( appData.delayBeforeQuit );
9289             SendToProgram("quit\n", &first);
9290             DoSleep( appData.delayAfterQuit );
9291             DestroyChildProcess(first.pr, first.useSigterm);
9292         }
9293         first.pr = NoProc;
9294     }
9295     if (second.reuse) {
9296         /* Put second chess program into idle state */
9297         if (second.pr != NoProc &&
9298             gameMode == TwoMachinesPlay) {
9299             SendToProgram("force\n", &second);
9300             if (second.usePing) {
9301               char buf[MSG_SIZ];
9302               sprintf(buf, "ping %d\n", ++second.lastPing);
9303               SendToProgram(buf, &second);
9304             }
9305         }
9306     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9307         /* Kill off second chess program */
9308         if (second.isr != NULL)
9309           RemoveInputSource(second.isr);
9310         second.isr = NULL;
9311     
9312         if (second.pr != NoProc) {
9313             DoSleep( appData.delayBeforeQuit );
9314             SendToProgram("quit\n", &second);
9315             DoSleep( appData.delayAfterQuit );
9316             DestroyChildProcess(second.pr, second.useSigterm);
9317         }
9318         second.pr = NoProc;
9319     }
9320
9321     if (matchMode && gameMode == TwoMachinesPlay) {
9322         switch (result) {
9323         case WhiteWins:
9324           if (first.twoMachinesColor[0] == 'w') {
9325             first.matchWins++;
9326           } else {
9327             second.matchWins++;
9328           }
9329           break;
9330         case BlackWins:
9331           if (first.twoMachinesColor[0] == 'b') {
9332             first.matchWins++;
9333           } else {
9334             second.matchWins++;
9335           }
9336           break;
9337         default:
9338           break;
9339         }
9340         if (matchGame < appData.matchGames) {
9341             char *tmp;
9342             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9343                 tmp = first.twoMachinesColor;
9344                 first.twoMachinesColor = second.twoMachinesColor;
9345                 second.twoMachinesColor = tmp;
9346             }
9347             gameMode = nextGameMode;
9348             matchGame++;
9349             if(appData.matchPause>10000 || appData.matchPause<10)
9350                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9351             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9352             endingGame = 0; /* [HGM] crash */
9353             return;
9354         } else {
9355             gameMode = nextGameMode;
9356             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9357                     first.tidy, second.tidy,
9358                     first.matchWins, second.matchWins,
9359                     appData.matchGames - (first.matchWins + second.matchWins));
9360             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9361         }
9362     }
9363     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9364         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9365       ExitAnalyzeMode();
9366     gameMode = nextGameMode;
9367     ModeHighlight();
9368     endingGame = 0;  /* [HGM] crash */
9369     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9370       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9371         matchMode = FALSE; appData.matchGames = matchGame = 0;
9372         DisplayNote(buf);
9373       }
9374     }
9375 }
9376
9377 /* Assumes program was just initialized (initString sent).
9378    Leaves program in force mode. */
9379 void
9380 FeedMovesToProgram(cps, upto) 
9381      ChessProgramState *cps;
9382      int upto;
9383 {
9384     int i;
9385     
9386     if (appData.debugMode)
9387       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9388               startedFromSetupPosition ? "position and " : "",
9389               backwardMostMove, upto, cps->which);
9390     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9391         // [HGM] variantswitch: make engine aware of new variant
9392         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9393                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9394         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9395         SendToProgram(buf, cps);
9396         currentlyInitializedVariant = gameInfo.variant;
9397     }
9398     SendToProgram("force\n", cps);
9399     if (startedFromSetupPosition) {
9400         SendBoard(cps, backwardMostMove);
9401     if (appData.debugMode) {
9402         fprintf(debugFP, "feedMoves\n");
9403     }
9404     }
9405     for (i = backwardMostMove; i < upto; i++) {
9406         SendMoveToProgram(i, cps);
9407     }
9408 }
9409
9410
9411 void
9412 ResurrectChessProgram()
9413 {
9414      /* The chess program may have exited.
9415         If so, restart it and feed it all the moves made so far. */
9416
9417     if (appData.noChessProgram || first.pr != NoProc) return;
9418     
9419     StartChessProgram(&first);
9420     InitChessProgram(&first, FALSE);
9421     FeedMovesToProgram(&first, currentMove);
9422
9423     if (!first.sendTime) {
9424         /* can't tell gnuchess what its clock should read,
9425            so we bow to its notion. */
9426         ResetClocks();
9427         timeRemaining[0][currentMove] = whiteTimeRemaining;
9428         timeRemaining[1][currentMove] = blackTimeRemaining;
9429     }
9430
9431     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9432                 appData.icsEngineAnalyze) && first.analysisSupport) {
9433       SendToProgram("analyze\n", &first);
9434       first.analyzing = TRUE;
9435     }
9436 }
9437
9438 /*
9439  * Button procedures
9440  */
9441 void
9442 Reset(redraw, init)
9443      int redraw, init;
9444 {
9445     int i;
9446
9447     if (appData.debugMode) {
9448         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9449                 redraw, init, gameMode);
9450     }
9451     CleanupTail(); // [HGM] vari: delete any stored variations
9452     pausing = pauseExamInvalid = FALSE;
9453     startedFromSetupPosition = blackPlaysFirst = FALSE;
9454     firstMove = TRUE;
9455     whiteFlag = blackFlag = FALSE;
9456     userOfferedDraw = FALSE;
9457     hintRequested = bookRequested = FALSE;
9458     first.maybeThinking = FALSE;
9459     second.maybeThinking = FALSE;
9460     first.bookSuspend = FALSE; // [HGM] book
9461     second.bookSuspend = FALSE;
9462     thinkOutput[0] = NULLCHAR;
9463     lastHint[0] = NULLCHAR;
9464     ClearGameInfo(&gameInfo);
9465     gameInfo.variant = StringToVariant(appData.variant);
9466     ics_user_moved = ics_clock_paused = FALSE;
9467     ics_getting_history = H_FALSE;
9468     ics_gamenum = -1;
9469     white_holding[0] = black_holding[0] = NULLCHAR;
9470     ClearProgramStats();
9471     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9472     
9473     ResetFrontEnd();
9474     ClearHighlights();
9475     flipView = appData.flipView;
9476     ClearPremoveHighlights();
9477     gotPremove = FALSE;
9478     alarmSounded = FALSE;
9479
9480     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9481     if(appData.serverMovesName != NULL) {
9482         /* [HGM] prepare to make moves file for broadcasting */
9483         clock_t t = clock();
9484         if(serverMoves != NULL) fclose(serverMoves);
9485         serverMoves = fopen(appData.serverMovesName, "r");
9486         if(serverMoves != NULL) {
9487             fclose(serverMoves);
9488             /* delay 15 sec before overwriting, so all clients can see end */
9489             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9490         }
9491         serverMoves = fopen(appData.serverMovesName, "w");
9492     }
9493
9494     ExitAnalyzeMode();
9495     gameMode = BeginningOfGame;
9496     ModeHighlight();
9497     if(appData.icsActive) gameInfo.variant = VariantNormal;
9498     currentMove = forwardMostMove = backwardMostMove = 0;
9499     InitPosition(redraw);
9500     for (i = 0; i < MAX_MOVES; i++) {
9501         if (commentList[i] != NULL) {
9502             free(commentList[i]);
9503             commentList[i] = NULL;
9504         }
9505     }
9506     ResetClocks();
9507     timeRemaining[0][0] = whiteTimeRemaining;
9508     timeRemaining[1][0] = blackTimeRemaining;
9509     if (first.pr == NULL) {
9510         StartChessProgram(&first);
9511     }
9512     if (init) {
9513             InitChessProgram(&first, startedFromSetupPosition);
9514     }
9515     DisplayTitle("");
9516     DisplayMessage("", "");
9517     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9518     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9519 }
9520
9521 void
9522 AutoPlayGameLoop()
9523 {
9524     for (;;) {
9525         if (!AutoPlayOneMove())
9526           return;
9527         if (matchMode || appData.timeDelay == 0)
9528           continue;
9529         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9530           return;
9531         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9532         break;
9533     }
9534 }
9535
9536
9537 int
9538 AutoPlayOneMove()
9539 {
9540     int fromX, fromY, toX, toY;
9541
9542     if (appData.debugMode) {
9543       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9544     }
9545
9546     if (gameMode != PlayFromGameFile)
9547       return FALSE;
9548
9549     if (currentMove >= forwardMostMove) {
9550       gameMode = EditGame;
9551       ModeHighlight();
9552
9553       /* [AS] Clear current move marker at the end of a game */
9554       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9555
9556       return FALSE;
9557     }
9558     
9559     toX = moveList[currentMove][2] - AAA;
9560     toY = moveList[currentMove][3] - ONE;
9561
9562     if (moveList[currentMove][1] == '@') {
9563         if (appData.highlightLastMove) {
9564             SetHighlights(-1, -1, toX, toY);
9565         }
9566     } else {
9567         fromX = moveList[currentMove][0] - AAA;
9568         fromY = moveList[currentMove][1] - ONE;
9569
9570         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9571
9572         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9573
9574         if (appData.highlightLastMove) {
9575             SetHighlights(fromX, fromY, toX, toY);
9576         }
9577     }
9578     DisplayMove(currentMove);
9579     SendMoveToProgram(currentMove++, &first);
9580     DisplayBothClocks();
9581     DrawPosition(FALSE, boards[currentMove]);
9582     // [HGM] PV info: always display, routine tests if empty
9583     DisplayComment(currentMove - 1, commentList[currentMove]);
9584     return TRUE;
9585 }
9586
9587
9588 int
9589 LoadGameOneMove(readAhead)
9590      ChessMove readAhead;
9591 {
9592     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9593     char promoChar = NULLCHAR;
9594     ChessMove moveType;
9595     char move[MSG_SIZ];
9596     char *p, *q;
9597     
9598     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9599         gameMode != AnalyzeMode && gameMode != Training) {
9600         gameFileFP = NULL;
9601         return FALSE;
9602     }
9603     
9604     yyboardindex = forwardMostMove;
9605     if (readAhead != (ChessMove)0) {
9606       moveType = readAhead;
9607     } else {
9608       if (gameFileFP == NULL)
9609           return FALSE;
9610       moveType = (ChessMove) yylex();
9611     }
9612     
9613     done = FALSE;
9614     switch (moveType) {
9615       case Comment:
9616         if (appData.debugMode) 
9617           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9618         p = yy_text;
9619
9620         /* append the comment but don't display it */
9621         AppendComment(currentMove, p, FALSE);
9622         return TRUE;
9623
9624       case WhiteCapturesEnPassant:
9625       case BlackCapturesEnPassant:
9626       case WhitePromotionChancellor:
9627       case BlackPromotionChancellor:
9628       case WhitePromotionArchbishop:
9629       case BlackPromotionArchbishop:
9630       case WhitePromotionCentaur:
9631       case BlackPromotionCentaur:
9632       case WhitePromotionQueen:
9633       case BlackPromotionQueen:
9634       case WhitePromotionRook:
9635       case BlackPromotionRook:
9636       case WhitePromotionBishop:
9637       case BlackPromotionBishop:
9638       case WhitePromotionKnight:
9639       case BlackPromotionKnight:
9640       case WhitePromotionKing:
9641       case BlackPromotionKing:
9642       case NormalMove:
9643       case WhiteKingSideCastle:
9644       case WhiteQueenSideCastle:
9645       case BlackKingSideCastle:
9646       case BlackQueenSideCastle:
9647       case WhiteKingSideCastleWild:
9648       case WhiteQueenSideCastleWild:
9649       case BlackKingSideCastleWild:
9650       case BlackQueenSideCastleWild:
9651       /* PUSH Fabien */
9652       case WhiteHSideCastleFR:
9653       case WhiteASideCastleFR:
9654       case BlackHSideCastleFR:
9655       case BlackASideCastleFR:
9656       /* POP Fabien */
9657         if (appData.debugMode)
9658           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9659         fromX = currentMoveString[0] - AAA;
9660         fromY = currentMoveString[1] - ONE;
9661         toX = currentMoveString[2] - AAA;
9662         toY = currentMoveString[3] - ONE;
9663         promoChar = currentMoveString[4];
9664         break;
9665
9666       case WhiteDrop:
9667       case BlackDrop:
9668         if (appData.debugMode)
9669           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9670         fromX = moveType == WhiteDrop ?
9671           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9672         (int) CharToPiece(ToLower(currentMoveString[0]));
9673         fromY = DROP_RANK;
9674         toX = currentMoveString[2] - AAA;
9675         toY = currentMoveString[3] - ONE;
9676         break;
9677
9678       case WhiteWins:
9679       case BlackWins:
9680       case GameIsDrawn:
9681       case GameUnfinished:
9682         if (appData.debugMode)
9683           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9684         p = strchr(yy_text, '{');
9685         if (p == NULL) p = strchr(yy_text, '(');
9686         if (p == NULL) {
9687             p = yy_text;
9688             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9689         } else {
9690             q = strchr(p, *p == '{' ? '}' : ')');
9691             if (q != NULL) *q = NULLCHAR;
9692             p++;
9693         }
9694         GameEnds(moveType, p, GE_FILE);
9695         done = TRUE;
9696         if (cmailMsgLoaded) {
9697             ClearHighlights();
9698             flipView = WhiteOnMove(currentMove);
9699             if (moveType == GameUnfinished) flipView = !flipView;
9700             if (appData.debugMode)
9701               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9702         }
9703         break;
9704
9705       case (ChessMove) 0:       /* end of file */
9706         if (appData.debugMode)
9707           fprintf(debugFP, "Parser hit end of file\n");
9708         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9709           case MT_NONE:
9710           case MT_CHECK:
9711             break;
9712           case MT_CHECKMATE:
9713           case MT_STAINMATE:
9714             if (WhiteOnMove(currentMove)) {
9715                 GameEnds(BlackWins, "Black mates", GE_FILE);
9716             } else {
9717                 GameEnds(WhiteWins, "White mates", GE_FILE);
9718             }
9719             break;
9720           case MT_STALEMATE:
9721             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9722             break;
9723         }
9724         done = TRUE;
9725         break;
9726
9727       case MoveNumberOne:
9728         if (lastLoadGameStart == GNUChessGame) {
9729             /* GNUChessGames have numbers, but they aren't move numbers */
9730             if (appData.debugMode)
9731               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9732                       yy_text, (int) moveType);
9733             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9734         }
9735         /* else fall thru */
9736
9737       case XBoardGame:
9738       case GNUChessGame:
9739       case PGNTag:
9740         /* Reached start of next game in file */
9741         if (appData.debugMode)
9742           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9743         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9744           case MT_NONE:
9745           case MT_CHECK:
9746             break;
9747           case MT_CHECKMATE:
9748           case MT_STAINMATE:
9749             if (WhiteOnMove(currentMove)) {
9750                 GameEnds(BlackWins, "Black mates", GE_FILE);
9751             } else {
9752                 GameEnds(WhiteWins, "White mates", GE_FILE);
9753             }
9754             break;
9755           case MT_STALEMATE:
9756             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9757             break;
9758         }
9759         done = TRUE;
9760         break;
9761
9762       case PositionDiagram:     /* should not happen; ignore */
9763       case ElapsedTime:         /* ignore */
9764       case NAG:                 /* ignore */
9765         if (appData.debugMode)
9766           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9767                   yy_text, (int) moveType);
9768         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9769
9770       case IllegalMove:
9771         if (appData.testLegality) {
9772             if (appData.debugMode)
9773               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9774             sprintf(move, _("Illegal move: %d.%s%s"),
9775                     (forwardMostMove / 2) + 1,
9776                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9777             DisplayError(move, 0);
9778             done = TRUE;
9779         } else {
9780             if (appData.debugMode)
9781               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9782                       yy_text, currentMoveString);
9783             fromX = currentMoveString[0] - AAA;
9784             fromY = currentMoveString[1] - ONE;
9785             toX = currentMoveString[2] - AAA;
9786             toY = currentMoveString[3] - ONE;
9787             promoChar = currentMoveString[4];
9788         }
9789         break;
9790
9791       case AmbiguousMove:
9792         if (appData.debugMode)
9793           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9794         sprintf(move, _("Ambiguous move: %d.%s%s"),
9795                 (forwardMostMove / 2) + 1,
9796                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9797         DisplayError(move, 0);
9798         done = TRUE;
9799         break;
9800
9801       default:
9802       case ImpossibleMove:
9803         if (appData.debugMode)
9804           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9805         sprintf(move, _("Illegal move: %d.%s%s"),
9806                 (forwardMostMove / 2) + 1,
9807                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9808         DisplayError(move, 0);
9809         done = TRUE;
9810         break;
9811     }
9812
9813     if (done) {
9814         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9815             DrawPosition(FALSE, boards[currentMove]);
9816             DisplayBothClocks();
9817             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9818               DisplayComment(currentMove - 1, commentList[currentMove]);
9819         }
9820         (void) StopLoadGameTimer();
9821         gameFileFP = NULL;
9822         cmailOldMove = forwardMostMove;
9823         return FALSE;
9824     } else {
9825         /* currentMoveString is set as a side-effect of yylex */
9826         strcat(currentMoveString, "\n");
9827         strcpy(moveList[forwardMostMove], currentMoveString);
9828         
9829         thinkOutput[0] = NULLCHAR;
9830         MakeMove(fromX, fromY, toX, toY, promoChar);
9831         currentMove = forwardMostMove;
9832         return TRUE;
9833     }
9834 }
9835
9836 /* Load the nth game from the given file */
9837 int
9838 LoadGameFromFile(filename, n, title, useList)
9839      char *filename;
9840      int n;
9841      char *title;
9842      /*Boolean*/ int useList;
9843 {
9844     FILE *f;
9845     char buf[MSG_SIZ];
9846
9847     if (strcmp(filename, "-") == 0) {
9848         f = stdin;
9849         title = "stdin";
9850     } else {
9851         f = fopen(filename, "rb");
9852         if (f == NULL) {
9853           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9854             DisplayError(buf, errno);
9855             return FALSE;
9856         }
9857     }
9858     if (fseek(f, 0, 0) == -1) {
9859         /* f is not seekable; probably a pipe */
9860         useList = FALSE;
9861     }
9862     if (useList && n == 0) {
9863         int error = GameListBuild(f);
9864         if (error) {
9865             DisplayError(_("Cannot build game list"), error);
9866         } else if (!ListEmpty(&gameList) &&
9867                    ((ListGame *) gameList.tailPred)->number > 1) {
9868             GameListPopUp(f, title);
9869             return TRUE;
9870         }
9871         GameListDestroy();
9872         n = 1;
9873     }
9874     if (n == 0) n = 1;
9875     return LoadGame(f, n, title, FALSE);
9876 }
9877
9878
9879 void
9880 MakeRegisteredMove()
9881 {
9882     int fromX, fromY, toX, toY;
9883     char promoChar;
9884     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9885         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9886           case CMAIL_MOVE:
9887           case CMAIL_DRAW:
9888             if (appData.debugMode)
9889               fprintf(debugFP, "Restoring %s for game %d\n",
9890                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9891     
9892             thinkOutput[0] = NULLCHAR;
9893             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9894             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9895             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9896             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9897             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9898             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9899             MakeMove(fromX, fromY, toX, toY, promoChar);
9900             ShowMove(fromX, fromY, toX, toY);
9901               
9902             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9903               case MT_NONE:
9904               case MT_CHECK:
9905                 break;
9906                 
9907               case MT_CHECKMATE:
9908               case MT_STAINMATE:
9909                 if (WhiteOnMove(currentMove)) {
9910                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9911                 } else {
9912                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9913                 }
9914                 break;
9915                 
9916               case MT_STALEMATE:
9917                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9918                 break;
9919             }
9920
9921             break;
9922             
9923           case CMAIL_RESIGN:
9924             if (WhiteOnMove(currentMove)) {
9925                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9926             } else {
9927                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9928             }
9929             break;
9930             
9931           case CMAIL_ACCEPT:
9932             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9933             break;
9934               
9935           default:
9936             break;
9937         }
9938     }
9939
9940     return;
9941 }
9942
9943 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9944 int
9945 CmailLoadGame(f, gameNumber, title, useList)
9946      FILE *f;
9947      int gameNumber;
9948      char *title;
9949      int useList;
9950 {
9951     int retVal;
9952
9953     if (gameNumber > nCmailGames) {
9954         DisplayError(_("No more games in this message"), 0);
9955         return FALSE;
9956     }
9957     if (f == lastLoadGameFP) {
9958         int offset = gameNumber - lastLoadGameNumber;
9959         if (offset == 0) {
9960             cmailMsg[0] = NULLCHAR;
9961             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9962                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9963                 nCmailMovesRegistered--;
9964             }
9965             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9966             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9967                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9968             }
9969         } else {
9970             if (! RegisterMove()) return FALSE;
9971         }
9972     }
9973
9974     retVal = LoadGame(f, gameNumber, title, useList);
9975
9976     /* Make move registered during previous look at this game, if any */
9977     MakeRegisteredMove();
9978
9979     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9980         commentList[currentMove]
9981           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9982         DisplayComment(currentMove - 1, commentList[currentMove]);
9983     }
9984
9985     return retVal;
9986 }
9987
9988 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9989 int
9990 ReloadGame(offset)
9991      int offset;
9992 {
9993     int gameNumber = lastLoadGameNumber + offset;
9994     if (lastLoadGameFP == NULL) {
9995         DisplayError(_("No game has been loaded yet"), 0);
9996         return FALSE;
9997     }
9998     if (gameNumber <= 0) {
9999         DisplayError(_("Can't back up any further"), 0);
10000         return FALSE;
10001     }
10002     if (cmailMsgLoaded) {
10003         return CmailLoadGame(lastLoadGameFP, gameNumber,
10004                              lastLoadGameTitle, lastLoadGameUseList);
10005     } else {
10006         return LoadGame(lastLoadGameFP, gameNumber,
10007                         lastLoadGameTitle, lastLoadGameUseList);
10008     }
10009 }
10010
10011
10012
10013 /* Load the nth game from open file f */
10014 int
10015 LoadGame(f, gameNumber, title, useList)
10016      FILE *f;
10017      int gameNumber;
10018      char *title;
10019      int useList;
10020 {
10021     ChessMove cm;
10022     char buf[MSG_SIZ];
10023     int gn = gameNumber;
10024     ListGame *lg = NULL;
10025     int numPGNTags = 0;
10026     int err;
10027     GameMode oldGameMode;
10028     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10029
10030     if (appData.debugMode) 
10031         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10032
10033     if (gameMode == Training )
10034         SetTrainingModeOff();
10035
10036     oldGameMode = gameMode;
10037     if (gameMode != BeginningOfGame) {
10038       Reset(FALSE, TRUE);
10039     }
10040
10041     gameFileFP = f;
10042     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10043         fclose(lastLoadGameFP);
10044     }
10045
10046     if (useList) {
10047         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10048         
10049         if (lg) {
10050             fseek(f, lg->offset, 0);
10051             GameListHighlight(gameNumber);
10052             gn = 1;
10053         }
10054         else {
10055             DisplayError(_("Game number out of range"), 0);
10056             return FALSE;
10057         }
10058     } else {
10059         GameListDestroy();
10060         if (fseek(f, 0, 0) == -1) {
10061             if (f == lastLoadGameFP ?
10062                 gameNumber == lastLoadGameNumber + 1 :
10063                 gameNumber == 1) {
10064                 gn = 1;
10065             } else {
10066                 DisplayError(_("Can't seek on game file"), 0);
10067                 return FALSE;
10068             }
10069         }
10070     }
10071     lastLoadGameFP = f;
10072     lastLoadGameNumber = gameNumber;
10073     strcpy(lastLoadGameTitle, title);
10074     lastLoadGameUseList = useList;
10075
10076     yynewfile(f);
10077
10078     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10079       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10080                 lg->gameInfo.black);
10081             DisplayTitle(buf);
10082     } else if (*title != NULLCHAR) {
10083         if (gameNumber > 1) {
10084             sprintf(buf, "%s %d", title, gameNumber);
10085             DisplayTitle(buf);
10086         } else {
10087             DisplayTitle(title);
10088         }
10089     }
10090
10091     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10092         gameMode = PlayFromGameFile;
10093         ModeHighlight();
10094     }
10095
10096     currentMove = forwardMostMove = backwardMostMove = 0;
10097     CopyBoard(boards[0], initialPosition);
10098     StopClocks();
10099
10100     /*
10101      * Skip the first gn-1 games in the file.
10102      * Also skip over anything that precedes an identifiable 
10103      * start of game marker, to avoid being confused by 
10104      * garbage at the start of the file.  Currently 
10105      * recognized start of game markers are the move number "1",
10106      * the pattern "gnuchess .* game", the pattern
10107      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10108      * A game that starts with one of the latter two patterns
10109      * will also have a move number 1, possibly
10110      * following a position diagram.
10111      * 5-4-02: Let's try being more lenient and allowing a game to
10112      * start with an unnumbered move.  Does that break anything?
10113      */
10114     cm = lastLoadGameStart = (ChessMove) 0;
10115     while (gn > 0) {
10116         yyboardindex = forwardMostMove;
10117         cm = (ChessMove) yylex();
10118         switch (cm) {
10119           case (ChessMove) 0:
10120             if (cmailMsgLoaded) {
10121                 nCmailGames = CMAIL_MAX_GAMES - gn;
10122             } else {
10123                 Reset(TRUE, TRUE);
10124                 DisplayError(_("Game not found in file"), 0);
10125             }
10126             return FALSE;
10127
10128           case GNUChessGame:
10129           case XBoardGame:
10130             gn--;
10131             lastLoadGameStart = cm;
10132             break;
10133             
10134           case MoveNumberOne:
10135             switch (lastLoadGameStart) {
10136               case GNUChessGame:
10137               case XBoardGame:
10138               case PGNTag:
10139                 break;
10140               case MoveNumberOne:
10141               case (ChessMove) 0:
10142                 gn--;           /* count this game */
10143                 lastLoadGameStart = cm;
10144                 break;
10145               default:
10146                 /* impossible */
10147                 break;
10148             }
10149             break;
10150
10151           case PGNTag:
10152             switch (lastLoadGameStart) {
10153               case GNUChessGame:
10154               case PGNTag:
10155               case MoveNumberOne:
10156               case (ChessMove) 0:
10157                 gn--;           /* count this game */
10158                 lastLoadGameStart = cm;
10159                 break;
10160               case XBoardGame:
10161                 lastLoadGameStart = cm; /* game counted already */
10162                 break;
10163               default:
10164                 /* impossible */
10165                 break;
10166             }
10167             if (gn > 0) {
10168                 do {
10169                     yyboardindex = forwardMostMove;
10170                     cm = (ChessMove) yylex();
10171                 } while (cm == PGNTag || cm == Comment);
10172             }
10173             break;
10174
10175           case WhiteWins:
10176           case BlackWins:
10177           case GameIsDrawn:
10178             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10179                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10180                     != CMAIL_OLD_RESULT) {
10181                     nCmailResults ++ ;
10182                     cmailResult[  CMAIL_MAX_GAMES
10183                                 - gn - 1] = CMAIL_OLD_RESULT;
10184                 }
10185             }
10186             break;
10187
10188           case NormalMove:
10189             /* Only a NormalMove can be at the start of a game
10190              * without a position diagram. */
10191             if (lastLoadGameStart == (ChessMove) 0) {
10192               gn--;
10193               lastLoadGameStart = MoveNumberOne;
10194             }
10195             break;
10196
10197           default:
10198             break;
10199         }
10200     }
10201     
10202     if (appData.debugMode)
10203       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10204
10205     if (cm == XBoardGame) {
10206         /* Skip any header junk before position diagram and/or move 1 */
10207         for (;;) {
10208             yyboardindex = forwardMostMove;
10209             cm = (ChessMove) yylex();
10210
10211             if (cm == (ChessMove) 0 ||
10212                 cm == GNUChessGame || cm == XBoardGame) {
10213                 /* Empty game; pretend end-of-file and handle later */
10214                 cm = (ChessMove) 0;
10215                 break;
10216             }
10217
10218             if (cm == MoveNumberOne || cm == PositionDiagram ||
10219                 cm == PGNTag || cm == Comment)
10220               break;
10221         }
10222     } else if (cm == GNUChessGame) {
10223         if (gameInfo.event != NULL) {
10224             free(gameInfo.event);
10225         }
10226         gameInfo.event = StrSave(yy_text);
10227     }   
10228
10229     startedFromSetupPosition = FALSE;
10230     while (cm == PGNTag) {
10231         if (appData.debugMode) 
10232           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10233         err = ParsePGNTag(yy_text, &gameInfo);
10234         if (!err) numPGNTags++;
10235
10236         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10237         if(gameInfo.variant != oldVariant) {
10238             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10239             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10240             InitPosition(TRUE);
10241             oldVariant = gameInfo.variant;
10242             if (appData.debugMode) 
10243               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10244         }
10245
10246
10247         if (gameInfo.fen != NULL) {
10248           Board initial_position;
10249           startedFromSetupPosition = TRUE;
10250           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10251             Reset(TRUE, TRUE);
10252             DisplayError(_("Bad FEN position in file"), 0);
10253             return FALSE;
10254           }
10255           CopyBoard(boards[0], initial_position);
10256           if (blackPlaysFirst) {
10257             currentMove = forwardMostMove = backwardMostMove = 1;
10258             CopyBoard(boards[1], initial_position);
10259             strcpy(moveList[0], "");
10260             strcpy(parseList[0], "");
10261             timeRemaining[0][1] = whiteTimeRemaining;
10262             timeRemaining[1][1] = blackTimeRemaining;
10263             if (commentList[0] != NULL) {
10264               commentList[1] = commentList[0];
10265               commentList[0] = NULL;
10266             }
10267           } else {
10268             currentMove = forwardMostMove = backwardMostMove = 0;
10269           }
10270           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10271           {   int i;
10272               initialRulePlies = FENrulePlies;
10273               for( i=0; i< nrCastlingRights; i++ )
10274                   initialRights[i] = initial_position[CASTLING][i];
10275           }
10276           yyboardindex = forwardMostMove;
10277           free(gameInfo.fen);
10278           gameInfo.fen = NULL;
10279         }
10280
10281         yyboardindex = forwardMostMove;
10282         cm = (ChessMove) yylex();
10283
10284         /* Handle comments interspersed among the tags */
10285         while (cm == Comment) {
10286             char *p;
10287             if (appData.debugMode) 
10288               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10289             p = yy_text;
10290             AppendComment(currentMove, p, FALSE);
10291             yyboardindex = forwardMostMove;
10292             cm = (ChessMove) yylex();
10293         }
10294     }
10295
10296     /* don't rely on existence of Event tag since if game was
10297      * pasted from clipboard the Event tag may not exist
10298      */
10299     if (numPGNTags > 0){
10300         char *tags;
10301         if (gameInfo.variant == VariantNormal) {
10302           VariantClass v = StringToVariant(gameInfo.event);
10303           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10304           if(v < VariantShogi) gameInfo.variant = v;
10305         }
10306         if (!matchMode) {
10307           if( appData.autoDisplayTags ) {
10308             tags = PGNTags(&gameInfo);
10309             TagsPopUp(tags, CmailMsg());
10310             free(tags);
10311           }
10312         }
10313     } else {
10314         /* Make something up, but don't display it now */
10315         SetGameInfo();
10316         TagsPopDown();
10317     }
10318
10319     if (cm == PositionDiagram) {
10320         int i, j;
10321         char *p;
10322         Board initial_position;
10323
10324         if (appData.debugMode)
10325           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10326
10327         if (!startedFromSetupPosition) {
10328             p = yy_text;
10329             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10330               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10331                 switch (*p) {
10332                   case '[':
10333                   case '-':
10334                   case ' ':
10335                   case '\t':
10336                   case '\n':
10337                   case '\r':
10338                     break;
10339                   default:
10340                     initial_position[i][j++] = CharToPiece(*p);
10341                     break;
10342                 }
10343             while (*p == ' ' || *p == '\t' ||
10344                    *p == '\n' || *p == '\r') p++;
10345         
10346             if (strncmp(p, "black", strlen("black"))==0)
10347               blackPlaysFirst = TRUE;
10348             else
10349               blackPlaysFirst = FALSE;
10350             startedFromSetupPosition = TRUE;
10351         
10352             CopyBoard(boards[0], initial_position);
10353             if (blackPlaysFirst) {
10354                 currentMove = forwardMostMove = backwardMostMove = 1;
10355                 CopyBoard(boards[1], initial_position);
10356                 strcpy(moveList[0], "");
10357                 strcpy(parseList[0], "");
10358                 timeRemaining[0][1] = whiteTimeRemaining;
10359                 timeRemaining[1][1] = blackTimeRemaining;
10360                 if (commentList[0] != NULL) {
10361                     commentList[1] = commentList[0];
10362                     commentList[0] = NULL;
10363                 }
10364             } else {
10365                 currentMove = forwardMostMove = backwardMostMove = 0;
10366             }
10367         }
10368         yyboardindex = forwardMostMove;
10369         cm = (ChessMove) yylex();
10370     }
10371
10372     if (first.pr == NoProc) {
10373         StartChessProgram(&first);
10374     }
10375     InitChessProgram(&first, FALSE);
10376     SendToProgram("force\n", &first);
10377     if (startedFromSetupPosition) {
10378         SendBoard(&first, forwardMostMove);
10379     if (appData.debugMode) {
10380         fprintf(debugFP, "Load Game\n");
10381     }
10382         DisplayBothClocks();
10383     }      
10384
10385     /* [HGM] server: flag to write setup moves in broadcast file as one */
10386     loadFlag = appData.suppressLoadMoves;
10387
10388     while (cm == Comment) {
10389         char *p;
10390         if (appData.debugMode) 
10391           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10392         p = yy_text;
10393         AppendComment(currentMove, p, FALSE);
10394         yyboardindex = forwardMostMove;
10395         cm = (ChessMove) yylex();
10396     }
10397
10398     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10399         cm == WhiteWins || cm == BlackWins ||
10400         cm == GameIsDrawn || cm == GameUnfinished) {
10401         DisplayMessage("", _("No moves in game"));
10402         if (cmailMsgLoaded) {
10403             if (appData.debugMode)
10404               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10405             ClearHighlights();
10406             flipView = FALSE;
10407         }
10408         DrawPosition(FALSE, boards[currentMove]);
10409         DisplayBothClocks();
10410         gameMode = EditGame;
10411         ModeHighlight();
10412         gameFileFP = NULL;
10413         cmailOldMove = 0;
10414         return TRUE;
10415     }
10416
10417     // [HGM] PV info: routine tests if comment empty
10418     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10419         DisplayComment(currentMove - 1, commentList[currentMove]);
10420     }
10421     if (!matchMode && appData.timeDelay != 0) 
10422       DrawPosition(FALSE, boards[currentMove]);
10423
10424     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10425       programStats.ok_to_send = 1;
10426     }
10427
10428     /* if the first token after the PGN tags is a move
10429      * and not move number 1, retrieve it from the parser 
10430      */
10431     if (cm != MoveNumberOne)
10432         LoadGameOneMove(cm);
10433
10434     /* load the remaining moves from the file */
10435     while (LoadGameOneMove((ChessMove)0)) {
10436       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10437       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10438     }
10439
10440     /* rewind to the start of the game */
10441     currentMove = backwardMostMove;
10442
10443     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10444
10445     if (oldGameMode == AnalyzeFile ||
10446         oldGameMode == AnalyzeMode) {
10447       AnalyzeFileEvent();
10448     }
10449
10450     if (matchMode || appData.timeDelay == 0) {
10451       ToEndEvent();
10452       gameMode = EditGame;
10453       ModeHighlight();
10454     } else if (appData.timeDelay > 0) {
10455       AutoPlayGameLoop();
10456     }
10457
10458     if (appData.debugMode) 
10459         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10460
10461     loadFlag = 0; /* [HGM] true game starts */
10462     return TRUE;
10463 }
10464
10465 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10466 int
10467 ReloadPosition(offset)
10468      int offset;
10469 {
10470     int positionNumber = lastLoadPositionNumber + offset;
10471     if (lastLoadPositionFP == NULL) {
10472         DisplayError(_("No position has been loaded yet"), 0);
10473         return FALSE;
10474     }
10475     if (positionNumber <= 0) {
10476         DisplayError(_("Can't back up any further"), 0);
10477         return FALSE;
10478     }
10479     return LoadPosition(lastLoadPositionFP, positionNumber,
10480                         lastLoadPositionTitle);
10481 }
10482
10483 /* Load the nth position from the given file */
10484 int
10485 LoadPositionFromFile(filename, n, title)
10486      char *filename;
10487      int n;
10488      char *title;
10489 {
10490     FILE *f;
10491     char buf[MSG_SIZ];
10492
10493     if (strcmp(filename, "-") == 0) {
10494         return LoadPosition(stdin, n, "stdin");
10495     } else {
10496         f = fopen(filename, "rb");
10497         if (f == NULL) {
10498             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10499             DisplayError(buf, errno);
10500             return FALSE;
10501         } else {
10502             return LoadPosition(f, n, title);
10503         }
10504     }
10505 }
10506
10507 /* Load the nth position from the given open file, and close it */
10508 int
10509 LoadPosition(f, positionNumber, title)
10510      FILE *f;
10511      int positionNumber;
10512      char *title;
10513 {
10514     char *p, line[MSG_SIZ];
10515     Board initial_position;
10516     int i, j, fenMode, pn;
10517     
10518     if (gameMode == Training )
10519         SetTrainingModeOff();
10520
10521     if (gameMode != BeginningOfGame) {
10522         Reset(FALSE, TRUE);
10523     }
10524     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10525         fclose(lastLoadPositionFP);
10526     }
10527     if (positionNumber == 0) positionNumber = 1;
10528     lastLoadPositionFP = f;
10529     lastLoadPositionNumber = positionNumber;
10530     strcpy(lastLoadPositionTitle, title);
10531     if (first.pr == NoProc) {
10532       StartChessProgram(&first);
10533       InitChessProgram(&first, FALSE);
10534     }    
10535     pn = positionNumber;
10536     if (positionNumber < 0) {
10537         /* Negative position number means to seek to that byte offset */
10538         if (fseek(f, -positionNumber, 0) == -1) {
10539             DisplayError(_("Can't seek on position file"), 0);
10540             return FALSE;
10541         };
10542         pn = 1;
10543     } else {
10544         if (fseek(f, 0, 0) == -1) {
10545             if (f == lastLoadPositionFP ?
10546                 positionNumber == lastLoadPositionNumber + 1 :
10547                 positionNumber == 1) {
10548                 pn = 1;
10549             } else {
10550                 DisplayError(_("Can't seek on position file"), 0);
10551                 return FALSE;
10552             }
10553         }
10554     }
10555     /* See if this file is FEN or old-style xboard */
10556     if (fgets(line, MSG_SIZ, f) == NULL) {
10557         DisplayError(_("Position not found in file"), 0);
10558         return FALSE;
10559     }
10560     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10561     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10562
10563     if (pn >= 2) {
10564         if (fenMode || line[0] == '#') pn--;
10565         while (pn > 0) {
10566             /* skip positions before number pn */
10567             if (fgets(line, MSG_SIZ, f) == NULL) {
10568                 Reset(TRUE, TRUE);
10569                 DisplayError(_("Position not found in file"), 0);
10570                 return FALSE;
10571             }
10572             if (fenMode || line[0] == '#') pn--;
10573         }
10574     }
10575
10576     if (fenMode) {
10577         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10578             DisplayError(_("Bad FEN position in file"), 0);
10579             return FALSE;
10580         }
10581     } else {
10582         (void) fgets(line, MSG_SIZ, f);
10583         (void) fgets(line, MSG_SIZ, f);
10584     
10585         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10586             (void) fgets(line, MSG_SIZ, f);
10587             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10588                 if (*p == ' ')
10589                   continue;
10590                 initial_position[i][j++] = CharToPiece(*p);
10591             }
10592         }
10593     
10594         blackPlaysFirst = FALSE;
10595         if (!feof(f)) {
10596             (void) fgets(line, MSG_SIZ, f);
10597             if (strncmp(line, "black", strlen("black"))==0)
10598               blackPlaysFirst = TRUE;
10599         }
10600     }
10601     startedFromSetupPosition = TRUE;
10602     
10603     SendToProgram("force\n", &first);
10604     CopyBoard(boards[0], initial_position);
10605     if (blackPlaysFirst) {
10606         currentMove = forwardMostMove = backwardMostMove = 1;
10607         strcpy(moveList[0], "");
10608         strcpy(parseList[0], "");
10609         CopyBoard(boards[1], initial_position);
10610         DisplayMessage("", _("Black to play"));
10611     } else {
10612         currentMove = forwardMostMove = backwardMostMove = 0;
10613         DisplayMessage("", _("White to play"));
10614     }
10615     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10616     SendBoard(&first, forwardMostMove);
10617     if (appData.debugMode) {
10618 int i, j;
10619   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10620   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10621         fprintf(debugFP, "Load Position\n");
10622     }
10623
10624     if (positionNumber > 1) {
10625         sprintf(line, "%s %d", title, positionNumber);
10626         DisplayTitle(line);
10627     } else {
10628         DisplayTitle(title);
10629     }
10630     gameMode = EditGame;
10631     ModeHighlight();
10632     ResetClocks();
10633     timeRemaining[0][1] = whiteTimeRemaining;
10634     timeRemaining[1][1] = blackTimeRemaining;
10635     DrawPosition(FALSE, boards[currentMove]);
10636    
10637     return TRUE;
10638 }
10639
10640
10641 void
10642 CopyPlayerNameIntoFileName(dest, src)
10643      char **dest, *src;
10644 {
10645     while (*src != NULLCHAR && *src != ',') {
10646         if (*src == ' ') {
10647             *(*dest)++ = '_';
10648             src++;
10649         } else {
10650             *(*dest)++ = *src++;
10651         }
10652     }
10653 }
10654
10655 char *DefaultFileName(ext)
10656      char *ext;
10657 {
10658     static char def[MSG_SIZ];
10659     char *p;
10660
10661     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10662         p = def;
10663         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10664         *p++ = '-';
10665         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10666         *p++ = '.';
10667         strcpy(p, ext);
10668     } else {
10669         def[0] = NULLCHAR;
10670     }
10671     return def;
10672 }
10673
10674 /* Save the current game to the given file */
10675 int
10676 SaveGameToFile(filename, append)
10677      char *filename;
10678      int append;
10679 {
10680     FILE *f;
10681     char buf[MSG_SIZ];
10682
10683     if (strcmp(filename, "-") == 0) {
10684         return SaveGame(stdout, 0, NULL);
10685     } else {
10686         f = fopen(filename, append ? "a" : "w");
10687         if (f == NULL) {
10688             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10689             DisplayError(buf, errno);
10690             return FALSE;
10691         } else {
10692             return SaveGame(f, 0, NULL);
10693         }
10694     }
10695 }
10696
10697 char *
10698 SavePart(str)
10699      char *str;
10700 {
10701     static char buf[MSG_SIZ];
10702     char *p;
10703     
10704     p = strchr(str, ' ');
10705     if (p == NULL) return str;
10706     strncpy(buf, str, p - str);
10707     buf[p - str] = NULLCHAR;
10708     return buf;
10709 }
10710
10711 #define PGN_MAX_LINE 75
10712
10713 #define PGN_SIDE_WHITE  0
10714 #define PGN_SIDE_BLACK  1
10715
10716 /* [AS] */
10717 static int FindFirstMoveOutOfBook( int side )
10718 {
10719     int result = -1;
10720
10721     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10722         int index = backwardMostMove;
10723         int has_book_hit = 0;
10724
10725         if( (index % 2) != side ) {
10726             index++;
10727         }
10728
10729         while( index < forwardMostMove ) {
10730             /* Check to see if engine is in book */
10731             int depth = pvInfoList[index].depth;
10732             int score = pvInfoList[index].score;
10733             int in_book = 0;
10734
10735             if( depth <= 2 ) {
10736                 in_book = 1;
10737             }
10738             else if( score == 0 && depth == 63 ) {
10739                 in_book = 1; /* Zappa */
10740             }
10741             else if( score == 2 && depth == 99 ) {
10742                 in_book = 1; /* Abrok */
10743             }
10744
10745             has_book_hit += in_book;
10746
10747             if( ! in_book ) {
10748                 result = index;
10749
10750                 break;
10751             }
10752
10753             index += 2;
10754         }
10755     }
10756
10757     return result;
10758 }
10759
10760 /* [AS] */
10761 void GetOutOfBookInfo( char * buf )
10762 {
10763     int oob[2];
10764     int i;
10765     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10766
10767     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10768     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10769
10770     *buf = '\0';
10771
10772     if( oob[0] >= 0 || oob[1] >= 0 ) {
10773         for( i=0; i<2; i++ ) {
10774             int idx = oob[i];
10775
10776             if( idx >= 0 ) {
10777                 if( i > 0 && oob[0] >= 0 ) {
10778                     strcat( buf, "   " );
10779                 }
10780
10781                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10782                 sprintf( buf+strlen(buf), "%s%.2f", 
10783                     pvInfoList[idx].score >= 0 ? "+" : "",
10784                     pvInfoList[idx].score / 100.0 );
10785             }
10786         }
10787     }
10788 }
10789
10790 /* Save game in PGN style and close the file */
10791 int
10792 SaveGamePGN(f)
10793      FILE *f;
10794 {
10795     int i, offset, linelen, newblock;
10796     time_t tm;
10797 //    char *movetext;
10798     char numtext[32];
10799     int movelen, numlen, blank;
10800     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10801
10802     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10803     
10804     tm = time((time_t *) NULL);
10805     
10806     PrintPGNTags(f, &gameInfo);
10807     
10808     if (backwardMostMove > 0 || startedFromSetupPosition) {
10809         char *fen = PositionToFEN(backwardMostMove, NULL);
10810         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10811         fprintf(f, "\n{--------------\n");
10812         PrintPosition(f, backwardMostMove);
10813         fprintf(f, "--------------}\n");
10814         free(fen);
10815     }
10816     else {
10817         /* [AS] Out of book annotation */
10818         if( appData.saveOutOfBookInfo ) {
10819             char buf[64];
10820
10821             GetOutOfBookInfo( buf );
10822
10823             if( buf[0] != '\0' ) {
10824                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10825             }
10826         }
10827
10828         fprintf(f, "\n");
10829     }
10830
10831     i = backwardMostMove;
10832     linelen = 0;
10833     newblock = TRUE;
10834
10835     while (i < forwardMostMove) {
10836         /* Print comments preceding this move */
10837         if (commentList[i] != NULL) {
10838             if (linelen > 0) fprintf(f, "\n");
10839             fprintf(f, "%s", commentList[i]);
10840             linelen = 0;
10841             newblock = TRUE;
10842         }
10843
10844         /* Format move number */
10845         if ((i % 2) == 0) {
10846             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10847         } else {
10848             if (newblock) {
10849                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10850             } else {
10851                 numtext[0] = NULLCHAR;
10852             }
10853         }
10854         numlen = strlen(numtext);
10855         newblock = FALSE;
10856
10857         /* Print move number */
10858         blank = linelen > 0 && numlen > 0;
10859         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10860             fprintf(f, "\n");
10861             linelen = 0;
10862             blank = 0;
10863         }
10864         if (blank) {
10865             fprintf(f, " ");
10866             linelen++;
10867         }
10868         fprintf(f, "%s", numtext);
10869         linelen += numlen;
10870
10871         /* Get move */
10872         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10873         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10874
10875         /* Print move */
10876         blank = linelen > 0 && movelen > 0;
10877         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10878             fprintf(f, "\n");
10879             linelen = 0;
10880             blank = 0;
10881         }
10882         if (blank) {
10883             fprintf(f, " ");
10884             linelen++;
10885         }
10886         fprintf(f, "%s", move_buffer);
10887         linelen += movelen;
10888
10889         /* [AS] Add PV info if present */
10890         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10891             /* [HGM] add time */
10892             char buf[MSG_SIZ]; int seconds;
10893
10894             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10895
10896             if( seconds <= 0) buf[0] = 0; else
10897             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10898                 seconds = (seconds + 4)/10; // round to full seconds
10899                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10900                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10901             }
10902
10903             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10904                 pvInfoList[i].score >= 0 ? "+" : "",
10905                 pvInfoList[i].score / 100.0,
10906                 pvInfoList[i].depth,
10907                 buf );
10908
10909             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10910
10911             /* Print score/depth */
10912             blank = linelen > 0 && movelen > 0;
10913             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10914                 fprintf(f, "\n");
10915                 linelen = 0;
10916                 blank = 0;
10917             }
10918             if (blank) {
10919                 fprintf(f, " ");
10920                 linelen++;
10921             }
10922             fprintf(f, "%s", move_buffer);
10923             linelen += movelen;
10924         }
10925
10926         i++;
10927     }
10928     
10929     /* Start a new line */
10930     if (linelen > 0) fprintf(f, "\n");
10931
10932     /* Print comments after last move */
10933     if (commentList[i] != NULL) {
10934         fprintf(f, "%s\n", commentList[i]);
10935     }
10936
10937     /* Print result */
10938     if (gameInfo.resultDetails != NULL &&
10939         gameInfo.resultDetails[0] != NULLCHAR) {
10940         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10941                 PGNResult(gameInfo.result));
10942     } else {
10943         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10944     }
10945
10946     fclose(f);
10947     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10948     return TRUE;
10949 }
10950
10951 /* Save game in old style and close the file */
10952 int
10953 SaveGameOldStyle(f)
10954      FILE *f;
10955 {
10956     int i, offset;
10957     time_t tm;
10958     
10959     tm = time((time_t *) NULL);
10960     
10961     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10962     PrintOpponents(f);
10963     
10964     if (backwardMostMove > 0 || startedFromSetupPosition) {
10965         fprintf(f, "\n[--------------\n");
10966         PrintPosition(f, backwardMostMove);
10967         fprintf(f, "--------------]\n");
10968     } else {
10969         fprintf(f, "\n");
10970     }
10971
10972     i = backwardMostMove;
10973     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10974
10975     while (i < forwardMostMove) {
10976         if (commentList[i] != NULL) {
10977             fprintf(f, "[%s]\n", commentList[i]);
10978         }
10979
10980         if ((i % 2) == 1) {
10981             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10982             i++;
10983         } else {
10984             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10985             i++;
10986             if (commentList[i] != NULL) {
10987                 fprintf(f, "\n");
10988                 continue;
10989             }
10990             if (i >= forwardMostMove) {
10991                 fprintf(f, "\n");
10992                 break;
10993             }
10994             fprintf(f, "%s\n", parseList[i]);
10995             i++;
10996         }
10997     }
10998     
10999     if (commentList[i] != NULL) {
11000         fprintf(f, "[%s]\n", commentList[i]);
11001     }
11002
11003     /* This isn't really the old style, but it's close enough */
11004     if (gameInfo.resultDetails != NULL &&
11005         gameInfo.resultDetails[0] != NULLCHAR) {
11006         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11007                 gameInfo.resultDetails);
11008     } else {
11009         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11010     }
11011
11012     fclose(f);
11013     return TRUE;
11014 }
11015
11016 /* Save the current game to open file f and close the file */
11017 int
11018 SaveGame(f, dummy, dummy2)
11019      FILE *f;
11020      int dummy;
11021      char *dummy2;
11022 {
11023     if (gameMode == EditPosition) EditPositionDone(TRUE);
11024     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11025     if (appData.oldSaveStyle)
11026       return SaveGameOldStyle(f);
11027     else
11028       return SaveGamePGN(f);
11029 }
11030
11031 /* Save the current position to the given file */
11032 int
11033 SavePositionToFile(filename)
11034      char *filename;
11035 {
11036     FILE *f;
11037     char buf[MSG_SIZ];
11038
11039     if (strcmp(filename, "-") == 0) {
11040         return SavePosition(stdout, 0, NULL);
11041     } else {
11042         f = fopen(filename, "a");
11043         if (f == NULL) {
11044             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11045             DisplayError(buf, errno);
11046             return FALSE;
11047         } else {
11048             SavePosition(f, 0, NULL);
11049             return TRUE;
11050         }
11051     }
11052 }
11053
11054 /* Save the current position to the given open file and close the file */
11055 int
11056 SavePosition(f, dummy, dummy2)
11057      FILE *f;
11058      int dummy;
11059      char *dummy2;
11060 {
11061     time_t tm;
11062     char *fen;
11063     
11064     if (gameMode == EditPosition) EditPositionDone(TRUE);
11065     if (appData.oldSaveStyle) {
11066         tm = time((time_t *) NULL);
11067     
11068         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11069         PrintOpponents(f);
11070         fprintf(f, "[--------------\n");
11071         PrintPosition(f, currentMove);
11072         fprintf(f, "--------------]\n");
11073     } else {
11074         fen = PositionToFEN(currentMove, NULL);
11075         fprintf(f, "%s\n", fen);
11076         free(fen);
11077     }
11078     fclose(f);
11079     return TRUE;
11080 }
11081
11082 void
11083 ReloadCmailMsgEvent(unregister)
11084      int unregister;
11085 {
11086 #if !WIN32
11087     static char *inFilename = NULL;
11088     static char *outFilename;
11089     int i;
11090     struct stat inbuf, outbuf;
11091     int status;
11092     
11093     /* Any registered moves are unregistered if unregister is set, */
11094     /* i.e. invoked by the signal handler */
11095     if (unregister) {
11096         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11097             cmailMoveRegistered[i] = FALSE;
11098             if (cmailCommentList[i] != NULL) {
11099                 free(cmailCommentList[i]);
11100                 cmailCommentList[i] = NULL;
11101             }
11102         }
11103         nCmailMovesRegistered = 0;
11104     }
11105
11106     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11107         cmailResult[i] = CMAIL_NOT_RESULT;
11108     }
11109     nCmailResults = 0;
11110
11111     if (inFilename == NULL) {
11112         /* Because the filenames are static they only get malloced once  */
11113         /* and they never get freed                                      */
11114         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11115         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11116
11117         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11118         sprintf(outFilename, "%s.out", appData.cmailGameName);
11119     }
11120     
11121     status = stat(outFilename, &outbuf);
11122     if (status < 0) {
11123         cmailMailedMove = FALSE;
11124     } else {
11125         status = stat(inFilename, &inbuf);
11126         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11127     }
11128     
11129     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11130        counts the games, notes how each one terminated, etc.
11131        
11132        It would be nice to remove this kludge and instead gather all
11133        the information while building the game list.  (And to keep it
11134        in the game list nodes instead of having a bunch of fixed-size
11135        parallel arrays.)  Note this will require getting each game's
11136        termination from the PGN tags, as the game list builder does
11137        not process the game moves.  --mann
11138        */
11139     cmailMsgLoaded = TRUE;
11140     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11141     
11142     /* Load first game in the file or popup game menu */
11143     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11144
11145 #endif /* !WIN32 */
11146     return;
11147 }
11148
11149 int
11150 RegisterMove()
11151 {
11152     FILE *f;
11153     char string[MSG_SIZ];
11154
11155     if (   cmailMailedMove
11156         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11157         return TRUE;            /* Allow free viewing  */
11158     }
11159
11160     /* Unregister move to ensure that we don't leave RegisterMove        */
11161     /* with the move registered when the conditions for registering no   */
11162     /* longer hold                                                       */
11163     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11164         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11165         nCmailMovesRegistered --;
11166
11167         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11168           {
11169               free(cmailCommentList[lastLoadGameNumber - 1]);
11170               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11171           }
11172     }
11173
11174     if (cmailOldMove == -1) {
11175         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11176         return FALSE;
11177     }
11178
11179     if (currentMove > cmailOldMove + 1) {
11180         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11181         return FALSE;
11182     }
11183
11184     if (currentMove < cmailOldMove) {
11185         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11186         return FALSE;
11187     }
11188
11189     if (forwardMostMove > currentMove) {
11190         /* Silently truncate extra moves */
11191         TruncateGame();
11192     }
11193
11194     if (   (currentMove == cmailOldMove + 1)
11195         || (   (currentMove == cmailOldMove)
11196             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11197                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11198         if (gameInfo.result != GameUnfinished) {
11199             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11200         }
11201
11202         if (commentList[currentMove] != NULL) {
11203             cmailCommentList[lastLoadGameNumber - 1]
11204               = StrSave(commentList[currentMove]);
11205         }
11206         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11207
11208         if (appData.debugMode)
11209           fprintf(debugFP, "Saving %s for game %d\n",
11210                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11211
11212         sprintf(string,
11213                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11214         
11215         f = fopen(string, "w");
11216         if (appData.oldSaveStyle) {
11217             SaveGameOldStyle(f); /* also closes the file */
11218             
11219             sprintf(string, "%s.pos.out", appData.cmailGameName);
11220             f = fopen(string, "w");
11221             SavePosition(f, 0, NULL); /* also closes the file */
11222         } else {
11223             fprintf(f, "{--------------\n");
11224             PrintPosition(f, currentMove);
11225             fprintf(f, "--------------}\n\n");
11226             
11227             SaveGame(f, 0, NULL); /* also closes the file*/
11228         }
11229         
11230         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11231         nCmailMovesRegistered ++;
11232     } else if (nCmailGames == 1) {
11233         DisplayError(_("You have not made a move yet"), 0);
11234         return FALSE;
11235     }
11236
11237     return TRUE;
11238 }
11239
11240 void
11241 MailMoveEvent()
11242 {
11243 #if !WIN32
11244     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11245     FILE *commandOutput;
11246     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11247     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11248     int nBuffers;
11249     int i;
11250     int archived;
11251     char *arcDir;
11252
11253     if (! cmailMsgLoaded) {
11254         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11255         return;
11256     }
11257
11258     if (nCmailGames == nCmailResults) {
11259         DisplayError(_("No unfinished games"), 0);
11260         return;
11261     }
11262
11263 #if CMAIL_PROHIBIT_REMAIL
11264     if (cmailMailedMove) {
11265         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);
11266         DisplayError(msg, 0);
11267         return;
11268     }
11269 #endif
11270
11271     if (! (cmailMailedMove || RegisterMove())) return;
11272     
11273     if (   cmailMailedMove
11274         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11275         sprintf(string, partCommandString,
11276                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11277         commandOutput = popen(string, "r");
11278
11279         if (commandOutput == NULL) {
11280             DisplayError(_("Failed to invoke cmail"), 0);
11281         } else {
11282             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11283                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11284             }
11285             if (nBuffers > 1) {
11286                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11287                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11288                 nBytes = MSG_SIZ - 1;
11289             } else {
11290                 (void) memcpy(msg, buffer, nBytes);
11291             }
11292             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11293
11294             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11295                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11296
11297                 archived = TRUE;
11298                 for (i = 0; i < nCmailGames; i ++) {
11299                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11300                         archived = FALSE;
11301                     }
11302                 }
11303                 if (   archived
11304                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11305                         != NULL)) {
11306                     sprintf(buffer, "%s/%s.%s.archive",
11307                             arcDir,
11308                             appData.cmailGameName,
11309                             gameInfo.date);
11310                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11311                     cmailMsgLoaded = FALSE;
11312                 }
11313             }
11314
11315             DisplayInformation(msg);
11316             pclose(commandOutput);
11317         }
11318     } else {
11319         if ((*cmailMsg) != '\0') {
11320             DisplayInformation(cmailMsg);
11321         }
11322     }
11323
11324     return;
11325 #endif /* !WIN32 */
11326 }
11327
11328 char *
11329 CmailMsg()
11330 {
11331 #if WIN32
11332     return NULL;
11333 #else
11334     int  prependComma = 0;
11335     char number[5];
11336     char string[MSG_SIZ];       /* Space for game-list */
11337     int  i;
11338     
11339     if (!cmailMsgLoaded) return "";
11340
11341     if (cmailMailedMove) {
11342         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11343     } else {
11344         /* Create a list of games left */
11345         sprintf(string, "[");
11346         for (i = 0; i < nCmailGames; i ++) {
11347             if (! (   cmailMoveRegistered[i]
11348                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11349                 if (prependComma) {
11350                     sprintf(number, ",%d", i + 1);
11351                 } else {
11352                     sprintf(number, "%d", i + 1);
11353                     prependComma = 1;
11354                 }
11355                 
11356                 strcat(string, number);
11357             }
11358         }
11359         strcat(string, "]");
11360
11361         if (nCmailMovesRegistered + nCmailResults == 0) {
11362             switch (nCmailGames) {
11363               case 1:
11364                 sprintf(cmailMsg,
11365                         _("Still need to make move for game\n"));
11366                 break;
11367                 
11368               case 2:
11369                 sprintf(cmailMsg,
11370                         _("Still need to make moves for both games\n"));
11371                 break;
11372                 
11373               default:
11374                 sprintf(cmailMsg,
11375                         _("Still need to make moves for all %d games\n"),
11376                         nCmailGames);
11377                 break;
11378             }
11379         } else {
11380             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11381               case 1:
11382                 sprintf(cmailMsg,
11383                         _("Still need to make a move for game %s\n"),
11384                         string);
11385                 break;
11386                 
11387               case 0:
11388                 if (nCmailResults == nCmailGames) {
11389                     sprintf(cmailMsg, _("No unfinished games\n"));
11390                 } else {
11391                     sprintf(cmailMsg, _("Ready to send mail\n"));
11392                 }
11393                 break;
11394                 
11395               default:
11396                 sprintf(cmailMsg,
11397                         _("Still need to make moves for games %s\n"),
11398                         string);
11399             }
11400         }
11401     }
11402     return cmailMsg;
11403 #endif /* WIN32 */
11404 }
11405
11406 void
11407 ResetGameEvent()
11408 {
11409     if (gameMode == Training)
11410       SetTrainingModeOff();
11411
11412     Reset(TRUE, TRUE);
11413     cmailMsgLoaded = FALSE;
11414     if (appData.icsActive) {
11415       SendToICS(ics_prefix);
11416       SendToICS("refresh\n");
11417     }
11418 }
11419
11420 void
11421 ExitEvent(status)
11422      int status;
11423 {
11424     exiting++;
11425     if (exiting > 2) {
11426       /* Give up on clean exit */
11427       exit(status);
11428     }
11429     if (exiting > 1) {
11430       /* Keep trying for clean exit */
11431       return;
11432     }
11433
11434     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11435
11436     if (telnetISR != NULL) {
11437       RemoveInputSource(telnetISR);
11438     }
11439     if (icsPR != NoProc) {
11440       DestroyChildProcess(icsPR, TRUE);
11441     }
11442
11443     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11444     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11445
11446     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11447     /* make sure this other one finishes before killing it!                  */
11448     if(endingGame) { int count = 0;
11449         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11450         while(endingGame && count++ < 10) DoSleep(1);
11451         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11452     }
11453
11454     /* Kill off chess programs */
11455     if (first.pr != NoProc) {
11456         ExitAnalyzeMode();
11457         
11458         DoSleep( appData.delayBeforeQuit );
11459         SendToProgram("quit\n", &first);
11460         DoSleep( appData.delayAfterQuit );
11461         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11462     }
11463     if (second.pr != NoProc) {
11464         DoSleep( appData.delayBeforeQuit );
11465         SendToProgram("quit\n", &second);
11466         DoSleep( appData.delayAfterQuit );
11467         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11468     }
11469     if (first.isr != NULL) {
11470         RemoveInputSource(first.isr);
11471     }
11472     if (second.isr != NULL) {
11473         RemoveInputSource(second.isr);
11474     }
11475
11476     ShutDownFrontEnd();
11477     exit(status);
11478 }
11479
11480 void
11481 PauseEvent()
11482 {
11483     if (appData.debugMode)
11484         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11485     if (pausing) {
11486         pausing = FALSE;
11487         ModeHighlight();
11488         if (gameMode == MachinePlaysWhite ||
11489             gameMode == MachinePlaysBlack) {
11490             StartClocks();
11491         } else {
11492             DisplayBothClocks();
11493         }
11494         if (gameMode == PlayFromGameFile) {
11495             if (appData.timeDelay >= 0) 
11496                 AutoPlayGameLoop();
11497         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11498             Reset(FALSE, TRUE);
11499             SendToICS(ics_prefix);
11500             SendToICS("refresh\n");
11501         } else if (currentMove < forwardMostMove) {
11502             ForwardInner(forwardMostMove);
11503         }
11504         pauseExamInvalid = FALSE;
11505     } else {
11506         switch (gameMode) {
11507           default:
11508             return;
11509           case IcsExamining:
11510             pauseExamForwardMostMove = forwardMostMove;
11511             pauseExamInvalid = FALSE;
11512             /* fall through */
11513           case IcsObserving:
11514           case IcsPlayingWhite:
11515           case IcsPlayingBlack:
11516             pausing = TRUE;
11517             ModeHighlight();
11518             return;
11519           case PlayFromGameFile:
11520             (void) StopLoadGameTimer();
11521             pausing = TRUE;
11522             ModeHighlight();
11523             break;
11524           case BeginningOfGame:
11525             if (appData.icsActive) return;
11526             /* else fall through */
11527           case MachinePlaysWhite:
11528           case MachinePlaysBlack:
11529           case TwoMachinesPlay:
11530             if (forwardMostMove == 0)
11531               return;           /* don't pause if no one has moved */
11532             if ((gameMode == MachinePlaysWhite &&
11533                  !WhiteOnMove(forwardMostMove)) ||
11534                 (gameMode == MachinePlaysBlack &&
11535                  WhiteOnMove(forwardMostMove))) {
11536                 StopClocks();
11537             }
11538             pausing = TRUE;
11539             ModeHighlight();
11540             break;
11541         }
11542     }
11543 }
11544
11545 void
11546 EditCommentEvent()
11547 {
11548     char title[MSG_SIZ];
11549
11550     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11551         strcpy(title, _("Edit comment"));
11552     } else {
11553         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11554                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11555                 parseList[currentMove - 1]);
11556     }
11557
11558     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11559 }
11560
11561
11562 void
11563 EditTagsEvent()
11564 {
11565     char *tags = PGNTags(&gameInfo);
11566     EditTagsPopUp(tags);
11567     free(tags);
11568 }
11569
11570 void
11571 AnalyzeModeEvent()
11572 {
11573     if (appData.noChessProgram || gameMode == AnalyzeMode)
11574       return;
11575
11576     if (gameMode != AnalyzeFile) {
11577         if (!appData.icsEngineAnalyze) {
11578                EditGameEvent();
11579                if (gameMode != EditGame) return;
11580         }
11581         ResurrectChessProgram();
11582         SendToProgram("analyze\n", &first);
11583         first.analyzing = TRUE;
11584         /*first.maybeThinking = TRUE;*/
11585         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11586         EngineOutputPopUp();
11587     }
11588     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11589     pausing = FALSE;
11590     ModeHighlight();
11591     SetGameInfo();
11592
11593     StartAnalysisClock();
11594     GetTimeMark(&lastNodeCountTime);
11595     lastNodeCount = 0;
11596 }
11597
11598 void
11599 AnalyzeFileEvent()
11600 {
11601     if (appData.noChessProgram || gameMode == AnalyzeFile)
11602       return;
11603
11604     if (gameMode != AnalyzeMode) {
11605         EditGameEvent();
11606         if (gameMode != EditGame) return;
11607         ResurrectChessProgram();
11608         SendToProgram("analyze\n", &first);
11609         first.analyzing = TRUE;
11610         /*first.maybeThinking = TRUE;*/
11611         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11612         EngineOutputPopUp();
11613     }
11614     gameMode = AnalyzeFile;
11615     pausing = FALSE;
11616     ModeHighlight();
11617     SetGameInfo();
11618
11619     StartAnalysisClock();
11620     GetTimeMark(&lastNodeCountTime);
11621     lastNodeCount = 0;
11622 }
11623
11624 void
11625 MachineWhiteEvent()
11626 {
11627     char buf[MSG_SIZ];
11628     char *bookHit = NULL;
11629
11630     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11631       return;
11632
11633
11634     if (gameMode == PlayFromGameFile || 
11635         gameMode == TwoMachinesPlay  || 
11636         gameMode == Training         || 
11637         gameMode == AnalyzeMode      || 
11638         gameMode == EndOfGame)
11639         EditGameEvent();
11640
11641     if (gameMode == EditPosition) 
11642         EditPositionDone(TRUE);
11643
11644     if (!WhiteOnMove(currentMove)) {
11645         DisplayError(_("It is not White's turn"), 0);
11646         return;
11647     }
11648   
11649     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11650       ExitAnalyzeMode();
11651
11652     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11653         gameMode == AnalyzeFile)
11654         TruncateGame();
11655
11656     ResurrectChessProgram();    /* in case it isn't running */
11657     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11658         gameMode = MachinePlaysWhite;
11659         ResetClocks();
11660     } else
11661     gameMode = MachinePlaysWhite;
11662     pausing = FALSE;
11663     ModeHighlight();
11664     SetGameInfo();
11665     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11666     DisplayTitle(buf);
11667     if (first.sendName) {
11668       sprintf(buf, "name %s\n", gameInfo.black);
11669       SendToProgram(buf, &first);
11670     }
11671     if (first.sendTime) {
11672       if (first.useColors) {
11673         SendToProgram("black\n", &first); /*gnu kludge*/
11674       }
11675       SendTimeRemaining(&first, TRUE);
11676     }
11677     if (first.useColors) {
11678       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11679     }
11680     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11681     SetMachineThinkingEnables();
11682     first.maybeThinking = TRUE;
11683     StartClocks();
11684     firstMove = FALSE;
11685
11686     if (appData.autoFlipView && !flipView) {
11687       flipView = !flipView;
11688       DrawPosition(FALSE, NULL);
11689       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11690     }
11691
11692     if(bookHit) { // [HGM] book: simulate book reply
11693         static char bookMove[MSG_SIZ]; // a bit generous?
11694
11695         programStats.nodes = programStats.depth = programStats.time = 
11696         programStats.score = programStats.got_only_move = 0;
11697         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11698
11699         strcpy(bookMove, "move ");
11700         strcat(bookMove, bookHit);
11701         HandleMachineMove(bookMove, &first);
11702     }
11703 }
11704
11705 void
11706 MachineBlackEvent()
11707 {
11708     char buf[MSG_SIZ];
11709    char *bookHit = NULL;
11710
11711     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11712         return;
11713
11714
11715     if (gameMode == PlayFromGameFile || 
11716         gameMode == TwoMachinesPlay  || 
11717         gameMode == Training         || 
11718         gameMode == AnalyzeMode      || 
11719         gameMode == EndOfGame)
11720         EditGameEvent();
11721
11722     if (gameMode == EditPosition) 
11723         EditPositionDone(TRUE);
11724
11725     if (WhiteOnMove(currentMove)) {
11726         DisplayError(_("It is not Black's turn"), 0);
11727         return;
11728     }
11729     
11730     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11731       ExitAnalyzeMode();
11732
11733     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11734         gameMode == AnalyzeFile)
11735         TruncateGame();
11736
11737     ResurrectChessProgram();    /* in case it isn't running */
11738     gameMode = MachinePlaysBlack;
11739     pausing = FALSE;
11740     ModeHighlight();
11741     SetGameInfo();
11742     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11743     DisplayTitle(buf);
11744     if (first.sendName) {
11745       sprintf(buf, "name %s\n", gameInfo.white);
11746       SendToProgram(buf, &first);
11747     }
11748     if (first.sendTime) {
11749       if (first.useColors) {
11750         SendToProgram("white\n", &first); /*gnu kludge*/
11751       }
11752       SendTimeRemaining(&first, FALSE);
11753     }
11754     if (first.useColors) {
11755       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11756     }
11757     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11758     SetMachineThinkingEnables();
11759     first.maybeThinking = TRUE;
11760     StartClocks();
11761
11762     if (appData.autoFlipView && flipView) {
11763       flipView = !flipView;
11764       DrawPosition(FALSE, NULL);
11765       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11766     }
11767     if(bookHit) { // [HGM] book: simulate book reply
11768         static char bookMove[MSG_SIZ]; // a bit generous?
11769
11770         programStats.nodes = programStats.depth = programStats.time = 
11771         programStats.score = programStats.got_only_move = 0;
11772         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11773
11774         strcpy(bookMove, "move ");
11775         strcat(bookMove, bookHit);
11776         HandleMachineMove(bookMove, &first);
11777     }
11778 }
11779
11780
11781 void
11782 DisplayTwoMachinesTitle()
11783 {
11784     char buf[MSG_SIZ];
11785     if (appData.matchGames > 0) {
11786         if (first.twoMachinesColor[0] == 'w') {
11787             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11788                     gameInfo.white, gameInfo.black,
11789                     first.matchWins, second.matchWins,
11790                     matchGame - 1 - (first.matchWins + second.matchWins));
11791         } else {
11792             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11793                     gameInfo.white, gameInfo.black,
11794                     second.matchWins, first.matchWins,
11795                     matchGame - 1 - (first.matchWins + second.matchWins));
11796         }
11797     } else {
11798         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11799     }
11800     DisplayTitle(buf);
11801 }
11802
11803 void
11804 TwoMachinesEvent P((void))
11805 {
11806     int i;
11807     char buf[MSG_SIZ];
11808     ChessProgramState *onmove;
11809     char *bookHit = NULL;
11810     
11811     if (appData.noChessProgram) return;
11812
11813     switch (gameMode) {
11814       case TwoMachinesPlay:
11815         return;
11816       case MachinePlaysWhite:
11817       case MachinePlaysBlack:
11818         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11819             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11820             return;
11821         }
11822         /* fall through */
11823       case BeginningOfGame:
11824       case PlayFromGameFile:
11825       case EndOfGame:
11826         EditGameEvent();
11827         if (gameMode != EditGame) return;
11828         break;
11829       case EditPosition:
11830         EditPositionDone(TRUE);
11831         break;
11832       case AnalyzeMode:
11833       case AnalyzeFile:
11834         ExitAnalyzeMode();
11835         break;
11836       case EditGame:
11837       default:
11838         break;
11839     }
11840
11841 //    forwardMostMove = currentMove;
11842     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11843     ResurrectChessProgram();    /* in case first program isn't running */
11844
11845     if (second.pr == NULL) {
11846         StartChessProgram(&second);
11847         if (second.protocolVersion == 1) {
11848           TwoMachinesEventIfReady();
11849         } else {
11850           /* kludge: allow timeout for initial "feature" command */
11851           FreezeUI();
11852           DisplayMessage("", _("Starting second chess program"));
11853           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11854         }
11855         return;
11856     }
11857     DisplayMessage("", "");
11858     InitChessProgram(&second, FALSE);
11859     SendToProgram("force\n", &second);
11860     if (startedFromSetupPosition) {
11861         SendBoard(&second, backwardMostMove);
11862     if (appData.debugMode) {
11863         fprintf(debugFP, "Two Machines\n");
11864     }
11865     }
11866     for (i = backwardMostMove; i < forwardMostMove; i++) {
11867         SendMoveToProgram(i, &second);
11868     }
11869
11870     gameMode = TwoMachinesPlay;
11871     pausing = FALSE;
11872     ModeHighlight();
11873     SetGameInfo();
11874     DisplayTwoMachinesTitle();
11875     firstMove = TRUE;
11876     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11877         onmove = &first;
11878     } else {
11879         onmove = &second;
11880     }
11881
11882     SendToProgram(first.computerString, &first);
11883     if (first.sendName) {
11884       sprintf(buf, "name %s\n", second.tidy);
11885       SendToProgram(buf, &first);
11886     }
11887     SendToProgram(second.computerString, &second);
11888     if (second.sendName) {
11889       sprintf(buf, "name %s\n", first.tidy);
11890       SendToProgram(buf, &second);
11891     }
11892
11893     ResetClocks();
11894     if (!first.sendTime || !second.sendTime) {
11895         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11896         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11897     }
11898     if (onmove->sendTime) {
11899       if (onmove->useColors) {
11900         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11901       }
11902       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11903     }
11904     if (onmove->useColors) {
11905       SendToProgram(onmove->twoMachinesColor, onmove);
11906     }
11907     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11908 //    SendToProgram("go\n", onmove);
11909     onmove->maybeThinking = TRUE;
11910     SetMachineThinkingEnables();
11911
11912     StartClocks();
11913
11914     if(bookHit) { // [HGM] book: simulate book reply
11915         static char bookMove[MSG_SIZ]; // a bit generous?
11916
11917         programStats.nodes = programStats.depth = programStats.time = 
11918         programStats.score = programStats.got_only_move = 0;
11919         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11920
11921         strcpy(bookMove, "move ");
11922         strcat(bookMove, bookHit);
11923         savedMessage = bookMove; // args for deferred call
11924         savedState = onmove;
11925         ScheduleDelayedEvent(DeferredBookMove, 1);
11926     }
11927 }
11928
11929 void
11930 TrainingEvent()
11931 {
11932     if (gameMode == Training) {
11933       SetTrainingModeOff();
11934       gameMode = PlayFromGameFile;
11935       DisplayMessage("", _("Training mode off"));
11936     } else {
11937       gameMode = Training;
11938       animateTraining = appData.animate;
11939
11940       /* make sure we are not already at the end of the game */
11941       if (currentMove < forwardMostMove) {
11942         SetTrainingModeOn();
11943         DisplayMessage("", _("Training mode on"));
11944       } else {
11945         gameMode = PlayFromGameFile;
11946         DisplayError(_("Already at end of game"), 0);
11947       }
11948     }
11949     ModeHighlight();
11950 }
11951
11952 void
11953 IcsClientEvent()
11954 {
11955     if (!appData.icsActive) return;
11956     switch (gameMode) {
11957       case IcsPlayingWhite:
11958       case IcsPlayingBlack:
11959       case IcsObserving:
11960       case IcsIdle:
11961       case BeginningOfGame:
11962       case IcsExamining:
11963         return;
11964
11965       case EditGame:
11966         break;
11967
11968       case EditPosition:
11969         EditPositionDone(TRUE);
11970         break;
11971
11972       case AnalyzeMode:
11973       case AnalyzeFile:
11974         ExitAnalyzeMode();
11975         break;
11976         
11977       default:
11978         EditGameEvent();
11979         break;
11980     }
11981
11982     gameMode = IcsIdle;
11983     ModeHighlight();
11984     return;
11985 }
11986
11987
11988 void
11989 EditGameEvent()
11990 {
11991     int i;
11992
11993     switch (gameMode) {
11994       case Training:
11995         SetTrainingModeOff();
11996         break;
11997       case MachinePlaysWhite:
11998       case MachinePlaysBlack:
11999       case BeginningOfGame:
12000         SendToProgram("force\n", &first);
12001         SetUserThinkingEnables();
12002         break;
12003       case PlayFromGameFile:
12004         (void) StopLoadGameTimer();
12005         if (gameFileFP != NULL) {
12006             gameFileFP = NULL;
12007         }
12008         break;
12009       case EditPosition:
12010         EditPositionDone(TRUE);
12011         break;
12012       case AnalyzeMode:
12013       case AnalyzeFile:
12014         ExitAnalyzeMode();
12015         SendToProgram("force\n", &first);
12016         break;
12017       case TwoMachinesPlay:
12018         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
12019         ResurrectChessProgram();
12020         SetUserThinkingEnables();
12021         break;
12022       case EndOfGame:
12023         ResurrectChessProgram();
12024         break;
12025       case IcsPlayingBlack:
12026       case IcsPlayingWhite:
12027         DisplayError(_("Warning: You are still playing a game"), 0);
12028         break;
12029       case IcsObserving:
12030         DisplayError(_("Warning: You are still observing a game"), 0);
12031         break;
12032       case IcsExamining:
12033         DisplayError(_("Warning: You are still examining a game"), 0);
12034         break;
12035       case IcsIdle:
12036         break;
12037       case EditGame:
12038       default:
12039         return;
12040     }
12041     
12042     pausing = FALSE;
12043     StopClocks();
12044     first.offeredDraw = second.offeredDraw = 0;
12045
12046     if (gameMode == PlayFromGameFile) {
12047         whiteTimeRemaining = timeRemaining[0][currentMove];
12048         blackTimeRemaining = timeRemaining[1][currentMove];
12049         DisplayTitle("");
12050     }
12051
12052     if (gameMode == MachinePlaysWhite ||
12053         gameMode == MachinePlaysBlack ||
12054         gameMode == TwoMachinesPlay ||
12055         gameMode == EndOfGame) {
12056         i = forwardMostMove;
12057         while (i > currentMove) {
12058             SendToProgram("undo\n", &first);
12059             i--;
12060         }
12061         whiteTimeRemaining = timeRemaining[0][currentMove];
12062         blackTimeRemaining = timeRemaining[1][currentMove];
12063         DisplayBothClocks();
12064         if (whiteFlag || blackFlag) {
12065             whiteFlag = blackFlag = 0;
12066         }
12067         DisplayTitle("");
12068     }           
12069     
12070     gameMode = EditGame;
12071     ModeHighlight();
12072     SetGameInfo();
12073 }
12074
12075
12076 void
12077 EditPositionEvent()
12078 {
12079     if (gameMode == EditPosition) {
12080         EditGameEvent();
12081         return;
12082     }
12083     
12084     EditGameEvent();
12085     if (gameMode != EditGame) return;
12086     
12087     gameMode = EditPosition;
12088     ModeHighlight();
12089     SetGameInfo();
12090     if (currentMove > 0)
12091       CopyBoard(boards[0], boards[currentMove]);
12092     
12093     blackPlaysFirst = !WhiteOnMove(currentMove);
12094     ResetClocks();
12095     currentMove = forwardMostMove = backwardMostMove = 0;
12096     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12097     DisplayMove(-1);
12098 }
12099
12100 void
12101 ExitAnalyzeMode()
12102 {
12103     /* [DM] icsEngineAnalyze - possible call from other functions */
12104     if (appData.icsEngineAnalyze) {
12105         appData.icsEngineAnalyze = FALSE;
12106
12107         DisplayMessage("",_("Close ICS engine analyze..."));
12108     }
12109     if (first.analysisSupport && first.analyzing) {
12110       SendToProgram("exit\n", &first);
12111       first.analyzing = FALSE;
12112     }
12113     thinkOutput[0] = NULLCHAR;
12114 }
12115
12116 void
12117 EditPositionDone(Boolean fakeRights)
12118 {
12119     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12120
12121     startedFromSetupPosition = TRUE;
12122     InitChessProgram(&first, FALSE);
12123     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12124       boards[0][EP_STATUS] = EP_NONE;
12125       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12126     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12127         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12128         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12129       } else boards[0][CASTLING][2] = NoRights;
12130     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12131         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12132         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12133       } else boards[0][CASTLING][5] = NoRights;
12134     }
12135     SendToProgram("force\n", &first);
12136     if (blackPlaysFirst) {
12137         strcpy(moveList[0], "");
12138         strcpy(parseList[0], "");
12139         currentMove = forwardMostMove = backwardMostMove = 1;
12140         CopyBoard(boards[1], boards[0]);
12141     } else {
12142         currentMove = forwardMostMove = backwardMostMove = 0;
12143     }
12144     SendBoard(&first, forwardMostMove);
12145     if (appData.debugMode) {
12146         fprintf(debugFP, "EditPosDone\n");
12147     }
12148     DisplayTitle("");
12149     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12150     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12151     gameMode = EditGame;
12152     ModeHighlight();
12153     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12154     ClearHighlights(); /* [AS] */
12155 }
12156
12157 /* Pause for `ms' milliseconds */
12158 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12159 void
12160 TimeDelay(ms)
12161      long ms;
12162 {
12163     TimeMark m1, m2;
12164
12165     GetTimeMark(&m1);
12166     do {
12167         GetTimeMark(&m2);
12168     } while (SubtractTimeMarks(&m2, &m1) < ms);
12169 }
12170
12171 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12172 void
12173 SendMultiLineToICS(buf)
12174      char *buf;
12175 {
12176     char temp[MSG_SIZ+1], *p;
12177     int len;
12178
12179     len = strlen(buf);
12180     if (len > MSG_SIZ)
12181       len = MSG_SIZ;
12182   
12183     strncpy(temp, buf, len);
12184     temp[len] = 0;
12185
12186     p = temp;
12187     while (*p) {
12188         if (*p == '\n' || *p == '\r')
12189           *p = ' ';
12190         ++p;
12191     }
12192
12193     strcat(temp, "\n");
12194     SendToICS(temp);
12195     SendToPlayer(temp, strlen(temp));
12196 }
12197
12198 void
12199 SetWhiteToPlayEvent()
12200 {
12201     if (gameMode == EditPosition) {
12202         blackPlaysFirst = FALSE;
12203         DisplayBothClocks();    /* works because currentMove is 0 */
12204     } else if (gameMode == IcsExamining) {
12205         SendToICS(ics_prefix);
12206         SendToICS("tomove white\n");
12207     }
12208 }
12209
12210 void
12211 SetBlackToPlayEvent()
12212 {
12213     if (gameMode == EditPosition) {
12214         blackPlaysFirst = TRUE;
12215         currentMove = 1;        /* kludge */
12216         DisplayBothClocks();
12217         currentMove = 0;
12218     } else if (gameMode == IcsExamining) {
12219         SendToICS(ics_prefix);
12220         SendToICS("tomove black\n");
12221     }
12222 }
12223
12224 void
12225 EditPositionMenuEvent(selection, x, y)
12226      ChessSquare selection;
12227      int x, y;
12228 {
12229     char buf[MSG_SIZ];
12230     ChessSquare piece = boards[0][y][x];
12231
12232     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12233
12234     switch (selection) {
12235       case ClearBoard:
12236         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12237             SendToICS(ics_prefix);
12238             SendToICS("bsetup clear\n");
12239         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12240             SendToICS(ics_prefix);
12241             SendToICS("clearboard\n");
12242         } else {
12243             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12244                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12245                 for (y = 0; y < BOARD_HEIGHT; y++) {
12246                     if (gameMode == IcsExamining) {
12247                         if (boards[currentMove][y][x] != EmptySquare) {
12248                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12249                                     AAA + x, ONE + y);
12250                             SendToICS(buf);
12251                         }
12252                     } else {
12253                         boards[0][y][x] = p;
12254                     }
12255                 }
12256             }
12257         }
12258         if (gameMode == EditPosition) {
12259             DrawPosition(FALSE, boards[0]);
12260         }
12261         break;
12262
12263       case WhitePlay:
12264         SetWhiteToPlayEvent();
12265         break;
12266
12267       case BlackPlay:
12268         SetBlackToPlayEvent();
12269         break;
12270
12271       case EmptySquare:
12272         if (gameMode == IcsExamining) {
12273             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12274             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12275             SendToICS(buf);
12276         } else {
12277             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12278                 if(x == BOARD_LEFT-2) {
12279                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12280                     boards[0][y][1] = 0;
12281                 } else
12282                 if(x == BOARD_RGHT+1) {
12283                     if(y >= gameInfo.holdingsSize) break;
12284                     boards[0][y][BOARD_WIDTH-2] = 0;
12285                 } else break;
12286             }
12287             boards[0][y][x] = EmptySquare;
12288             DrawPosition(FALSE, boards[0]);
12289         }
12290         break;
12291
12292       case PromotePiece:
12293         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12294            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12295             selection = (ChessSquare) (PROMOTED piece);
12296         } else if(piece == EmptySquare) selection = WhiteSilver;
12297         else selection = (ChessSquare)((int)piece - 1);
12298         goto defaultlabel;
12299
12300       case DemotePiece:
12301         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12302            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12303             selection = (ChessSquare) (DEMOTED piece);
12304         } else if(piece == EmptySquare) selection = BlackSilver;
12305         else selection = (ChessSquare)((int)piece + 1);       
12306         goto defaultlabel;
12307
12308       case WhiteQueen:
12309       case BlackQueen:
12310         if(gameInfo.variant == VariantShatranj ||
12311            gameInfo.variant == VariantXiangqi  ||
12312            gameInfo.variant == VariantCourier  ||
12313            gameInfo.variant == VariantMakruk     )
12314             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12315         goto defaultlabel;
12316
12317       case WhiteKing:
12318       case BlackKing:
12319         if(gameInfo.variant == VariantXiangqi)
12320             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12321         if(gameInfo.variant == VariantKnightmate)
12322             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12323       default:
12324         defaultlabel:
12325         if (gameMode == IcsExamining) {
12326             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12327             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12328                     PieceToChar(selection), AAA + x, ONE + y);
12329             SendToICS(buf);
12330         } else {
12331             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12332                 int n;
12333                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12334                     n = PieceToNumber(selection - BlackPawn);
12335                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12336                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12337                     boards[0][BOARD_HEIGHT-1-n][1]++;
12338                 } else
12339                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12340                     n = PieceToNumber(selection);
12341                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12342                     boards[0][n][BOARD_WIDTH-1] = selection;
12343                     boards[0][n][BOARD_WIDTH-2]++;
12344                 }
12345             } else
12346             boards[0][y][x] = selection;
12347             DrawPosition(TRUE, boards[0]);
12348         }
12349         break;
12350     }
12351 }
12352
12353
12354 void
12355 DropMenuEvent(selection, x, y)
12356      ChessSquare selection;
12357      int x, y;
12358 {
12359     ChessMove moveType;
12360
12361     switch (gameMode) {
12362       case IcsPlayingWhite:
12363       case MachinePlaysBlack:
12364         if (!WhiteOnMove(currentMove)) {
12365             DisplayMoveError(_("It is Black's turn"));
12366             return;
12367         }
12368         moveType = WhiteDrop;
12369         break;
12370       case IcsPlayingBlack:
12371       case MachinePlaysWhite:
12372         if (WhiteOnMove(currentMove)) {
12373             DisplayMoveError(_("It is White's turn"));
12374             return;
12375         }
12376         moveType = BlackDrop;
12377         break;
12378       case EditGame:
12379         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12380         break;
12381       default:
12382         return;
12383     }
12384
12385     if (moveType == BlackDrop && selection < BlackPawn) {
12386       selection = (ChessSquare) ((int) selection
12387                                  + (int) BlackPawn - (int) WhitePawn);
12388     }
12389     if (boards[currentMove][y][x] != EmptySquare) {
12390         DisplayMoveError(_("That square is occupied"));
12391         return;
12392     }
12393
12394     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12395 }
12396
12397 void
12398 AcceptEvent()
12399 {
12400     /* Accept a pending offer of any kind from opponent */
12401     
12402     if (appData.icsActive) {
12403         SendToICS(ics_prefix);
12404         SendToICS("accept\n");
12405     } else if (cmailMsgLoaded) {
12406         if (currentMove == cmailOldMove &&
12407             commentList[cmailOldMove] != NULL &&
12408             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12409                    "Black offers a draw" : "White offers a draw")) {
12410             TruncateGame();
12411             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12412             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12413         } else {
12414             DisplayError(_("There is no pending offer on this move"), 0);
12415             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12416         }
12417     } else {
12418         /* Not used for offers from chess program */
12419     }
12420 }
12421
12422 void
12423 DeclineEvent()
12424 {
12425     /* Decline a pending offer of any kind from opponent */
12426     
12427     if (appData.icsActive) {
12428         SendToICS(ics_prefix);
12429         SendToICS("decline\n");
12430     } else if (cmailMsgLoaded) {
12431         if (currentMove == cmailOldMove &&
12432             commentList[cmailOldMove] != NULL &&
12433             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12434                    "Black offers a draw" : "White offers a draw")) {
12435 #ifdef NOTDEF
12436             AppendComment(cmailOldMove, "Draw declined", TRUE);
12437             DisplayComment(cmailOldMove - 1, "Draw declined");
12438 #endif /*NOTDEF*/
12439         } else {
12440             DisplayError(_("There is no pending offer on this move"), 0);
12441         }
12442     } else {
12443         /* Not used for offers from chess program */
12444     }
12445 }
12446
12447 void
12448 RematchEvent()
12449 {
12450     /* Issue ICS rematch command */
12451     if (appData.icsActive) {
12452         SendToICS(ics_prefix);
12453         SendToICS("rematch\n");
12454     }
12455 }
12456
12457 void
12458 CallFlagEvent()
12459 {
12460     /* Call your opponent's flag (claim a win on time) */
12461     if (appData.icsActive) {
12462         SendToICS(ics_prefix);
12463         SendToICS("flag\n");
12464     } else {
12465         switch (gameMode) {
12466           default:
12467             return;
12468           case MachinePlaysWhite:
12469             if (whiteFlag) {
12470                 if (blackFlag)
12471                   GameEnds(GameIsDrawn, "Both players ran out of time",
12472                            GE_PLAYER);
12473                 else
12474                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12475             } else {
12476                 DisplayError(_("Your opponent is not out of time"), 0);
12477             }
12478             break;
12479           case MachinePlaysBlack:
12480             if (blackFlag) {
12481                 if (whiteFlag)
12482                   GameEnds(GameIsDrawn, "Both players ran out of time",
12483                            GE_PLAYER);
12484                 else
12485                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12486             } else {
12487                 DisplayError(_("Your opponent is not out of time"), 0);
12488             }
12489             break;
12490         }
12491     }
12492 }
12493
12494 void
12495 DrawEvent()
12496 {
12497     /* Offer draw or accept pending draw offer from opponent */
12498     
12499     if (appData.icsActive) {
12500         /* Note: tournament rules require draw offers to be
12501            made after you make your move but before you punch
12502            your clock.  Currently ICS doesn't let you do that;
12503            instead, you immediately punch your clock after making
12504            a move, but you can offer a draw at any time. */
12505         
12506         SendToICS(ics_prefix);
12507         SendToICS("draw\n");
12508         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12509     } else if (cmailMsgLoaded) {
12510         if (currentMove == cmailOldMove &&
12511             commentList[cmailOldMove] != NULL &&
12512             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12513                    "Black offers a draw" : "White offers a draw")) {
12514             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12515             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12516         } else if (currentMove == cmailOldMove + 1) {
12517             char *offer = WhiteOnMove(cmailOldMove) ?
12518               "White offers a draw" : "Black offers a draw";
12519             AppendComment(currentMove, offer, TRUE);
12520             DisplayComment(currentMove - 1, offer);
12521             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12522         } else {
12523             DisplayError(_("You must make your move before offering a draw"), 0);
12524             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12525         }
12526     } else if (first.offeredDraw) {
12527         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12528     } else {
12529         if (first.sendDrawOffers) {
12530             SendToProgram("draw\n", &first);
12531             userOfferedDraw = TRUE;
12532         }
12533     }
12534 }
12535
12536 void
12537 AdjournEvent()
12538 {
12539     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12540     
12541     if (appData.icsActive) {
12542         SendToICS(ics_prefix);
12543         SendToICS("adjourn\n");
12544     } else {
12545         /* Currently GNU Chess doesn't offer or accept Adjourns */
12546     }
12547 }
12548
12549
12550 void
12551 AbortEvent()
12552 {
12553     /* Offer Abort or accept pending Abort offer from opponent */
12554     
12555     if (appData.icsActive) {
12556         SendToICS(ics_prefix);
12557         SendToICS("abort\n");
12558     } else {
12559         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12560     }
12561 }
12562
12563 void
12564 ResignEvent()
12565 {
12566     /* Resign.  You can do this even if it's not your turn. */
12567     
12568     if (appData.icsActive) {
12569         SendToICS(ics_prefix);
12570         SendToICS("resign\n");
12571     } else {
12572         switch (gameMode) {
12573           case MachinePlaysWhite:
12574             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12575             break;
12576           case MachinePlaysBlack:
12577             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12578             break;
12579           case EditGame:
12580             if (cmailMsgLoaded) {
12581                 TruncateGame();
12582                 if (WhiteOnMove(cmailOldMove)) {
12583                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12584                 } else {
12585                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12586                 }
12587                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12588             }
12589             break;
12590           default:
12591             break;
12592         }
12593     }
12594 }
12595
12596
12597 void
12598 StopObservingEvent()
12599 {
12600     /* Stop observing current games */
12601     SendToICS(ics_prefix);
12602     SendToICS("unobserve\n");
12603 }
12604
12605 void
12606 StopExaminingEvent()
12607 {
12608     /* Stop observing current game */
12609     SendToICS(ics_prefix);
12610     SendToICS("unexamine\n");
12611 }
12612
12613 void
12614 ForwardInner(target)
12615      int target;
12616 {
12617     int limit;
12618
12619     if (appData.debugMode)
12620         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12621                 target, currentMove, forwardMostMove);
12622
12623     if (gameMode == EditPosition)
12624       return;
12625
12626     if (gameMode == PlayFromGameFile && !pausing)
12627       PauseEvent();
12628     
12629     if (gameMode == IcsExamining && pausing)
12630       limit = pauseExamForwardMostMove;
12631     else
12632       limit = forwardMostMove;
12633     
12634     if (target > limit) target = limit;
12635
12636     if (target > 0 && moveList[target - 1][0]) {
12637         int fromX, fromY, toX, toY;
12638         toX = moveList[target - 1][2] - AAA;
12639         toY = moveList[target - 1][3] - ONE;
12640         if (moveList[target - 1][1] == '@') {
12641             if (appData.highlightLastMove) {
12642                 SetHighlights(-1, -1, toX, toY);
12643             }
12644         } else {
12645             fromX = moveList[target - 1][0] - AAA;
12646             fromY = moveList[target - 1][1] - ONE;
12647             if (target == currentMove + 1) {
12648                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12649             }
12650             if (appData.highlightLastMove) {
12651                 SetHighlights(fromX, fromY, toX, toY);
12652             }
12653         }
12654     }
12655     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12656         gameMode == Training || gameMode == PlayFromGameFile || 
12657         gameMode == AnalyzeFile) {
12658         while (currentMove < target) {
12659             SendMoveToProgram(currentMove++, &first);
12660         }
12661     } else {
12662         currentMove = target;
12663     }
12664     
12665     if (gameMode == EditGame || gameMode == EndOfGame) {
12666         whiteTimeRemaining = timeRemaining[0][currentMove];
12667         blackTimeRemaining = timeRemaining[1][currentMove];
12668     }
12669     DisplayBothClocks();
12670     DisplayMove(currentMove - 1);
12671     DrawPosition(FALSE, boards[currentMove]);
12672     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12673     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12674         DisplayComment(currentMove - 1, commentList[currentMove]);
12675     }
12676 }
12677
12678
12679 void
12680 ForwardEvent()
12681 {
12682     if (gameMode == IcsExamining && !pausing) {
12683         SendToICS(ics_prefix);
12684         SendToICS("forward\n");
12685     } else {
12686         ForwardInner(currentMove + 1);
12687     }
12688 }
12689
12690 void
12691 ToEndEvent()
12692 {
12693     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12694         /* to optimze, we temporarily turn off analysis mode while we feed
12695          * the remaining moves to the engine. Otherwise we get analysis output
12696          * after each move.
12697          */ 
12698         if (first.analysisSupport) {
12699           SendToProgram("exit\nforce\n", &first);
12700           first.analyzing = FALSE;
12701         }
12702     }
12703         
12704     if (gameMode == IcsExamining && !pausing) {
12705         SendToICS(ics_prefix);
12706         SendToICS("forward 999999\n");
12707     } else {
12708         ForwardInner(forwardMostMove);
12709     }
12710
12711     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12712         /* we have fed all the moves, so reactivate analysis mode */
12713         SendToProgram("analyze\n", &first);
12714         first.analyzing = TRUE;
12715         /*first.maybeThinking = TRUE;*/
12716         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12717     }
12718 }
12719
12720 void
12721 BackwardInner(target)
12722      int target;
12723 {
12724     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12725
12726     if (appData.debugMode)
12727         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12728                 target, currentMove, forwardMostMove);
12729
12730     if (gameMode == EditPosition) return;
12731     if (currentMove <= backwardMostMove) {
12732         ClearHighlights();
12733         DrawPosition(full_redraw, boards[currentMove]);
12734         return;
12735     }
12736     if (gameMode == PlayFromGameFile && !pausing)
12737       PauseEvent();
12738     
12739     if (moveList[target][0]) {
12740         int fromX, fromY, toX, toY;
12741         toX = moveList[target][2] - AAA;
12742         toY = moveList[target][3] - ONE;
12743         if (moveList[target][1] == '@') {
12744             if (appData.highlightLastMove) {
12745                 SetHighlights(-1, -1, toX, toY);
12746             }
12747         } else {
12748             fromX = moveList[target][0] - AAA;
12749             fromY = moveList[target][1] - ONE;
12750             if (target == currentMove - 1) {
12751                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12752             }
12753             if (appData.highlightLastMove) {
12754                 SetHighlights(fromX, fromY, toX, toY);
12755             }
12756         }
12757     }
12758     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12759         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12760         while (currentMove > target) {
12761             SendToProgram("undo\n", &first);
12762             currentMove--;
12763         }
12764     } else {
12765         currentMove = target;
12766     }
12767     
12768     if (gameMode == EditGame || gameMode == EndOfGame) {
12769         whiteTimeRemaining = timeRemaining[0][currentMove];
12770         blackTimeRemaining = timeRemaining[1][currentMove];
12771     }
12772     DisplayBothClocks();
12773     DisplayMove(currentMove - 1);
12774     DrawPosition(full_redraw, boards[currentMove]);
12775     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12776     // [HGM] PV info: routine tests if comment empty
12777     DisplayComment(currentMove - 1, commentList[currentMove]);
12778 }
12779
12780 void
12781 BackwardEvent()
12782 {
12783     if (gameMode == IcsExamining && !pausing) {
12784         SendToICS(ics_prefix);
12785         SendToICS("backward\n");
12786     } else {
12787         BackwardInner(currentMove - 1);
12788     }
12789 }
12790
12791 void
12792 ToStartEvent()
12793 {
12794     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12795         /* to optimize, we temporarily turn off analysis mode while we undo
12796          * all the moves. Otherwise we get analysis output after each undo.
12797          */ 
12798         if (first.analysisSupport) {
12799           SendToProgram("exit\nforce\n", &first);
12800           first.analyzing = FALSE;
12801         }
12802     }
12803
12804     if (gameMode == IcsExamining && !pausing) {
12805         SendToICS(ics_prefix);
12806         SendToICS("backward 999999\n");
12807     } else {
12808         BackwardInner(backwardMostMove);
12809     }
12810
12811     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12812         /* we have fed all the moves, so reactivate analysis mode */
12813         SendToProgram("analyze\n", &first);
12814         first.analyzing = TRUE;
12815         /*first.maybeThinking = TRUE;*/
12816         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12817     }
12818 }
12819
12820 void
12821 ToNrEvent(int to)
12822 {
12823   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12824   if (to >= forwardMostMove) to = forwardMostMove;
12825   if (to <= backwardMostMove) to = backwardMostMove;
12826   if (to < currentMove) {
12827     BackwardInner(to);
12828   } else {
12829     ForwardInner(to);
12830   }
12831 }
12832
12833 void
12834 RevertEvent(Boolean annotate)
12835 {
12836     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12837         return;
12838     }
12839     if (gameMode != IcsExamining) {
12840         DisplayError(_("You are not examining a game"), 0);
12841         return;
12842     }
12843     if (pausing) {
12844         DisplayError(_("You can't revert while pausing"), 0);
12845         return;
12846     }
12847     SendToICS(ics_prefix);
12848     SendToICS("revert\n");
12849 }
12850
12851 void
12852 RetractMoveEvent()
12853 {
12854     switch (gameMode) {
12855       case MachinePlaysWhite:
12856       case MachinePlaysBlack:
12857         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12858             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12859             return;
12860         }
12861         if (forwardMostMove < 2) return;
12862         currentMove = forwardMostMove = forwardMostMove - 2;
12863         whiteTimeRemaining = timeRemaining[0][currentMove];
12864         blackTimeRemaining = timeRemaining[1][currentMove];
12865         DisplayBothClocks();
12866         DisplayMove(currentMove - 1);
12867         ClearHighlights();/*!! could figure this out*/
12868         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12869         SendToProgram("remove\n", &first);
12870         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12871         break;
12872
12873       case BeginningOfGame:
12874       default:
12875         break;
12876
12877       case IcsPlayingWhite:
12878       case IcsPlayingBlack:
12879         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12880             SendToICS(ics_prefix);
12881             SendToICS("takeback 2\n");
12882         } else {
12883             SendToICS(ics_prefix);
12884             SendToICS("takeback 1\n");
12885         }
12886         break;
12887     }
12888 }
12889
12890 void
12891 MoveNowEvent()
12892 {
12893     ChessProgramState *cps;
12894
12895     switch (gameMode) {
12896       case MachinePlaysWhite:
12897         if (!WhiteOnMove(forwardMostMove)) {
12898             DisplayError(_("It is your turn"), 0);
12899             return;
12900         }
12901         cps = &first;
12902         break;
12903       case MachinePlaysBlack:
12904         if (WhiteOnMove(forwardMostMove)) {
12905             DisplayError(_("It is your turn"), 0);
12906             return;
12907         }
12908         cps = &first;
12909         break;
12910       case TwoMachinesPlay:
12911         if (WhiteOnMove(forwardMostMove) ==
12912             (first.twoMachinesColor[0] == 'w')) {
12913             cps = &first;
12914         } else {
12915             cps = &second;
12916         }
12917         break;
12918       case BeginningOfGame:
12919       default:
12920         return;
12921     }
12922     SendToProgram("?\n", cps);
12923 }
12924
12925 void
12926 TruncateGameEvent()
12927 {
12928     EditGameEvent();
12929     if (gameMode != EditGame) return;
12930     TruncateGame();
12931 }
12932
12933 void
12934 TruncateGame()
12935 {
12936     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12937     if (forwardMostMove > currentMove) {
12938         if (gameInfo.resultDetails != NULL) {
12939             free(gameInfo.resultDetails);
12940             gameInfo.resultDetails = NULL;
12941             gameInfo.result = GameUnfinished;
12942         }
12943         forwardMostMove = currentMove;
12944         HistorySet(parseList, backwardMostMove, forwardMostMove,
12945                    currentMove-1);
12946     }
12947 }
12948
12949 void
12950 HintEvent()
12951 {
12952     if (appData.noChessProgram) return;
12953     switch (gameMode) {
12954       case MachinePlaysWhite:
12955         if (WhiteOnMove(forwardMostMove)) {
12956             DisplayError(_("Wait until your turn"), 0);
12957             return;
12958         }
12959         break;
12960       case BeginningOfGame:
12961       case MachinePlaysBlack:
12962         if (!WhiteOnMove(forwardMostMove)) {
12963             DisplayError(_("Wait until your turn"), 0);
12964             return;
12965         }
12966         break;
12967       default:
12968         DisplayError(_("No hint available"), 0);
12969         return;
12970     }
12971     SendToProgram("hint\n", &first);
12972     hintRequested = TRUE;
12973 }
12974
12975 void
12976 BookEvent()
12977 {
12978     if (appData.noChessProgram) return;
12979     switch (gameMode) {
12980       case MachinePlaysWhite:
12981         if (WhiteOnMove(forwardMostMove)) {
12982             DisplayError(_("Wait until your turn"), 0);
12983             return;
12984         }
12985         break;
12986       case BeginningOfGame:
12987       case MachinePlaysBlack:
12988         if (!WhiteOnMove(forwardMostMove)) {
12989             DisplayError(_("Wait until your turn"), 0);
12990             return;
12991         }
12992         break;
12993       case EditPosition:
12994         EditPositionDone(TRUE);
12995         break;
12996       case TwoMachinesPlay:
12997         return;
12998       default:
12999         break;
13000     }
13001     SendToProgram("bk\n", &first);
13002     bookOutput[0] = NULLCHAR;
13003     bookRequested = TRUE;
13004 }
13005
13006 void
13007 AboutGameEvent()
13008 {
13009     char *tags = PGNTags(&gameInfo);
13010     TagsPopUp(tags, CmailMsg());
13011     free(tags);
13012 }
13013
13014 /* end button procedures */
13015
13016 void
13017 PrintPosition(fp, move)
13018      FILE *fp;
13019      int move;
13020 {
13021     int i, j;
13022     
13023     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13024         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13025             char c = PieceToChar(boards[move][i][j]);
13026             fputc(c == 'x' ? '.' : c, fp);
13027             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13028         }
13029     }
13030     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13031       fprintf(fp, "white to play\n");
13032     else
13033       fprintf(fp, "black to play\n");
13034 }
13035
13036 void
13037 PrintOpponents(fp)
13038      FILE *fp;
13039 {
13040     if (gameInfo.white != NULL) {
13041         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13042     } else {
13043         fprintf(fp, "\n");
13044     }
13045 }
13046
13047 /* Find last component of program's own name, using some heuristics */
13048 void
13049 TidyProgramName(prog, host, buf)
13050      char *prog, *host, buf[MSG_SIZ];
13051 {
13052     char *p, *q;
13053     int local = (strcmp(host, "localhost") == 0);
13054     while (!local && (p = strchr(prog, ';')) != NULL) {
13055         p++;
13056         while (*p == ' ') p++;
13057         prog = p;
13058     }
13059     if (*prog == '"' || *prog == '\'') {
13060         q = strchr(prog + 1, *prog);
13061     } else {
13062         q = strchr(prog, ' ');
13063     }
13064     if (q == NULL) q = prog + strlen(prog);
13065     p = q;
13066     while (p >= prog && *p != '/' && *p != '\\') p--;
13067     p++;
13068     if(p == prog && *p == '"') p++;
13069     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13070     memcpy(buf, p, q - p);
13071     buf[q - p] = NULLCHAR;
13072     if (!local) {
13073         strcat(buf, "@");
13074         strcat(buf, host);
13075     }
13076 }
13077
13078 char *
13079 TimeControlTagValue()
13080 {
13081     char buf[MSG_SIZ];
13082     if (!appData.clockMode) {
13083         strcpy(buf, "-");
13084     } else if (movesPerSession > 0) {
13085         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13086     } else if (timeIncrement == 0) {
13087         sprintf(buf, "%ld", timeControl/1000);
13088     } else {
13089         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13090     }
13091     return StrSave(buf);
13092 }
13093
13094 void
13095 SetGameInfo()
13096 {
13097     /* This routine is used only for certain modes */
13098     VariantClass v = gameInfo.variant;
13099     ChessMove r = GameUnfinished;
13100     char *p = NULL;
13101
13102     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13103         r = gameInfo.result; 
13104         p = gameInfo.resultDetails; 
13105         gameInfo.resultDetails = NULL;
13106     }
13107     ClearGameInfo(&gameInfo);
13108     gameInfo.variant = v;
13109
13110     switch (gameMode) {
13111       case MachinePlaysWhite:
13112         gameInfo.event = StrSave( appData.pgnEventHeader );
13113         gameInfo.site = StrSave(HostName());
13114         gameInfo.date = PGNDate();
13115         gameInfo.round = StrSave("-");
13116         gameInfo.white = StrSave(first.tidy);
13117         gameInfo.black = StrSave(UserName());
13118         gameInfo.timeControl = TimeControlTagValue();
13119         break;
13120
13121       case MachinePlaysBlack:
13122         gameInfo.event = StrSave( appData.pgnEventHeader );
13123         gameInfo.site = StrSave(HostName());
13124         gameInfo.date = PGNDate();
13125         gameInfo.round = StrSave("-");
13126         gameInfo.white = StrSave(UserName());
13127         gameInfo.black = StrSave(first.tidy);
13128         gameInfo.timeControl = TimeControlTagValue();
13129         break;
13130
13131       case TwoMachinesPlay:
13132         gameInfo.event = StrSave( appData.pgnEventHeader );
13133         gameInfo.site = StrSave(HostName());
13134         gameInfo.date = PGNDate();
13135         if (matchGame > 0) {
13136             char buf[MSG_SIZ];
13137             sprintf(buf, "%d", matchGame);
13138             gameInfo.round = StrSave(buf);
13139         } else {
13140             gameInfo.round = StrSave("-");
13141         }
13142         if (first.twoMachinesColor[0] == 'w') {
13143             gameInfo.white = StrSave(first.tidy);
13144             gameInfo.black = StrSave(second.tidy);
13145         } else {
13146             gameInfo.white = StrSave(second.tidy);
13147             gameInfo.black = StrSave(first.tidy);
13148         }
13149         gameInfo.timeControl = TimeControlTagValue();
13150         break;
13151
13152       case EditGame:
13153         gameInfo.event = StrSave("Edited game");
13154         gameInfo.site = StrSave(HostName());
13155         gameInfo.date = PGNDate();
13156         gameInfo.round = StrSave("-");
13157         gameInfo.white = StrSave("-");
13158         gameInfo.black = StrSave("-");
13159         gameInfo.result = r;
13160         gameInfo.resultDetails = p;
13161         break;
13162
13163       case EditPosition:
13164         gameInfo.event = StrSave("Edited position");
13165         gameInfo.site = StrSave(HostName());
13166         gameInfo.date = PGNDate();
13167         gameInfo.round = StrSave("-");
13168         gameInfo.white = StrSave("-");
13169         gameInfo.black = StrSave("-");
13170         break;
13171
13172       case IcsPlayingWhite:
13173       case IcsPlayingBlack:
13174       case IcsObserving:
13175       case IcsExamining:
13176         break;
13177
13178       case PlayFromGameFile:
13179         gameInfo.event = StrSave("Game from non-PGN file");
13180         gameInfo.site = StrSave(HostName());
13181         gameInfo.date = PGNDate();
13182         gameInfo.round = StrSave("-");
13183         gameInfo.white = StrSave("?");
13184         gameInfo.black = StrSave("?");
13185         break;
13186
13187       default:
13188         break;
13189     }
13190 }
13191
13192 void
13193 ReplaceComment(index, text)
13194      int index;
13195      char *text;
13196 {
13197     int len;
13198
13199     while (*text == '\n') text++;
13200     len = strlen(text);
13201     while (len > 0 && text[len - 1] == '\n') len--;
13202
13203     if (commentList[index] != NULL)
13204       free(commentList[index]);
13205
13206     if (len == 0) {
13207         commentList[index] = NULL;
13208         return;
13209     }
13210   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13211       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13212       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13213     commentList[index] = (char *) malloc(len + 2);
13214     strncpy(commentList[index], text, len);
13215     commentList[index][len] = '\n';
13216     commentList[index][len + 1] = NULLCHAR;
13217   } else { 
13218     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13219     char *p;
13220     commentList[index] = (char *) malloc(len + 6);
13221     strcpy(commentList[index], "{\n");
13222     strncpy(commentList[index]+2, text, len);
13223     commentList[index][len+2] = NULLCHAR;
13224     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13225     strcat(commentList[index], "\n}\n");
13226   }
13227 }
13228
13229 void
13230 CrushCRs(text)
13231      char *text;
13232 {
13233   char *p = text;
13234   char *q = text;
13235   char ch;
13236
13237   do {
13238     ch = *p++;
13239     if (ch == '\r') continue;
13240     *q++ = ch;
13241   } while (ch != '\0');
13242 }
13243
13244 void
13245 AppendComment(index, text, addBraces)
13246      int index;
13247      char *text;
13248      Boolean addBraces; // [HGM] braces: tells if we should add {}
13249 {
13250     int oldlen, len;
13251     char *old;
13252
13253 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13254     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13255
13256     CrushCRs(text);
13257     while (*text == '\n') text++;
13258     len = strlen(text);
13259     while (len > 0 && text[len - 1] == '\n') len--;
13260
13261     if (len == 0) return;
13262
13263     if (commentList[index] != NULL) {
13264         old = commentList[index];
13265         oldlen = strlen(old);
13266         while(commentList[index][oldlen-1] ==  '\n')
13267           commentList[index][--oldlen] = NULLCHAR;
13268         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13269         strcpy(commentList[index], old);
13270         free(old);
13271         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13272         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13273           if(addBraces) addBraces = FALSE; else { text++; len--; }
13274           while (*text == '\n') { text++; len--; }
13275           commentList[index][--oldlen] = NULLCHAR;
13276       }
13277         if(addBraces) strcat(commentList[index], "\n{\n");
13278         else          strcat(commentList[index], "\n");
13279         strcat(commentList[index], text);
13280         if(addBraces) strcat(commentList[index], "\n}\n");
13281         else          strcat(commentList[index], "\n");
13282     } else {
13283         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13284         if(addBraces)
13285              strcpy(commentList[index], "{\n");
13286         else commentList[index][0] = NULLCHAR;
13287         strcat(commentList[index], text);
13288         strcat(commentList[index], "\n");
13289         if(addBraces) strcat(commentList[index], "}\n");
13290     }
13291 }
13292
13293 static char * FindStr( char * text, char * sub_text )
13294 {
13295     char * result = strstr( text, sub_text );
13296
13297     if( result != NULL ) {
13298         result += strlen( sub_text );
13299     }
13300
13301     return result;
13302 }
13303
13304 /* [AS] Try to extract PV info from PGN comment */
13305 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13306 char *GetInfoFromComment( int index, char * text )
13307 {
13308     char * sep = text;
13309
13310     if( text != NULL && index > 0 ) {
13311         int score = 0;
13312         int depth = 0;
13313         int time = -1, sec = 0, deci;
13314         char * s_eval = FindStr( text, "[%eval " );
13315         char * s_emt = FindStr( text, "[%emt " );
13316
13317         if( s_eval != NULL || s_emt != NULL ) {
13318             /* New style */
13319             char delim;
13320
13321             if( s_eval != NULL ) {
13322                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13323                     return text;
13324                 }
13325
13326                 if( delim != ']' ) {
13327                     return text;
13328                 }
13329             }
13330
13331             if( s_emt != NULL ) {
13332             }
13333                 return text;
13334         }
13335         else {
13336             /* We expect something like: [+|-]nnn.nn/dd */
13337             int score_lo = 0;
13338
13339             if(*text != '{') return text; // [HGM] braces: must be normal comment
13340
13341             sep = strchr( text, '/' );
13342             if( sep == NULL || sep < (text+4) ) {
13343                 return text;
13344             }
13345
13346             time = -1; sec = -1; deci = -1;
13347             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13348                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13349                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13350                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13351                 return text;
13352             }
13353
13354             if( score_lo < 0 || score_lo >= 100 ) {
13355                 return text;
13356             }
13357
13358             if(sec >= 0) time = 600*time + 10*sec; else
13359             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13360
13361             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13362
13363             /* [HGM] PV time: now locate end of PV info */
13364             while( *++sep >= '0' && *sep <= '9'); // strip depth
13365             if(time >= 0)
13366             while( *++sep >= '0' && *sep <= '9'); // strip time
13367             if(sec >= 0)
13368             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13369             if(deci >= 0)
13370             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13371             while(*sep == ' ') sep++;
13372         }
13373
13374         if( depth <= 0 ) {
13375             return text;
13376         }
13377
13378         if( time < 0 ) {
13379             time = -1;
13380         }
13381
13382         pvInfoList[index-1].depth = depth;
13383         pvInfoList[index-1].score = score;
13384         pvInfoList[index-1].time  = 10*time; // centi-sec
13385         if(*sep == '}') *sep = 0; else *--sep = '{';
13386     }
13387     return sep;
13388 }
13389
13390 void
13391 SendToProgram(message, cps)
13392      char *message;
13393      ChessProgramState *cps;
13394 {
13395     int count, outCount, error;
13396     char buf[MSG_SIZ];
13397
13398     if (cps->pr == NULL) return;
13399     Attention(cps);
13400     
13401     if (appData.debugMode) {
13402         TimeMark now;
13403         GetTimeMark(&now);
13404         fprintf(debugFP, "%ld >%-6s: %s", 
13405                 SubtractTimeMarks(&now, &programStartTime),
13406                 cps->which, message);
13407     }
13408     
13409     count = strlen(message);
13410     outCount = OutputToProcess(cps->pr, message, count, &error);
13411     if (outCount < count && !exiting 
13412                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13413         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13414         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13415             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13416                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13417                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13418             } else {
13419                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13420             }
13421             gameInfo.resultDetails = StrSave(buf);
13422         }
13423         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13424     }
13425 }
13426
13427 void
13428 ReceiveFromProgram(isr, closure, message, count, error)
13429      InputSourceRef isr;
13430      VOIDSTAR closure;
13431      char *message;
13432      int count;
13433      int error;
13434 {
13435     char *end_str;
13436     char buf[MSG_SIZ];
13437     ChessProgramState *cps = (ChessProgramState *)closure;
13438
13439     if (isr != cps->isr) return; /* Killed intentionally */
13440     if (count <= 0) {
13441         if (count == 0) {
13442             sprintf(buf,
13443                     _("Error: %s chess program (%s) exited unexpectedly"),
13444                     cps->which, cps->program);
13445         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13446                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13447                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13448                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13449                 } else {
13450                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13451                 }
13452                 gameInfo.resultDetails = StrSave(buf);
13453             }
13454             RemoveInputSource(cps->isr);
13455             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13456         } else {
13457             sprintf(buf,
13458                     _("Error reading from %s chess program (%s)"),
13459                     cps->which, cps->program);
13460             RemoveInputSource(cps->isr);
13461
13462             /* [AS] Program is misbehaving badly... kill it */
13463             if( count == -2 ) {
13464                 DestroyChildProcess( cps->pr, 9 );
13465                 cps->pr = NoProc;
13466             }
13467
13468             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13469         }
13470         return;
13471     }
13472     
13473     if ((end_str = strchr(message, '\r')) != NULL)
13474       *end_str = NULLCHAR;
13475     if ((end_str = strchr(message, '\n')) != NULL)
13476       *end_str = NULLCHAR;
13477     
13478     if (appData.debugMode) {
13479         TimeMark now; int print = 1;
13480         char *quote = ""; char c; int i;
13481
13482         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13483                 char start = message[0];
13484                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13485                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13486                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13487                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13488                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13489                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13490                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13491                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13492                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13493                     print = (appData.engineComments >= 2);
13494                 }
13495                 message[0] = start; // restore original message
13496         }
13497         if(print) {
13498                 GetTimeMark(&now);
13499                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13500                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13501                         quote,
13502                         message);
13503         }
13504     }
13505
13506     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13507     if (appData.icsEngineAnalyze) {
13508         if (strstr(message, "whisper") != NULL ||
13509              strstr(message, "kibitz") != NULL || 
13510             strstr(message, "tellics") != NULL) return;
13511     }
13512
13513     HandleMachineMove(message, cps);
13514 }
13515
13516
13517 void
13518 SendTimeControl(cps, mps, tc, inc, sd, st)
13519      ChessProgramState *cps;
13520      int mps, inc, sd, st;
13521      long tc;
13522 {
13523     char buf[MSG_SIZ];
13524     int seconds;
13525
13526     if( timeControl_2 > 0 ) {
13527         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13528             tc = timeControl_2;
13529         }
13530     }
13531     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13532     inc /= cps->timeOdds;
13533     st  /= cps->timeOdds;
13534
13535     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13536
13537     if (st > 0) {
13538       /* Set exact time per move, normally using st command */
13539       if (cps->stKludge) {
13540         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13541         seconds = st % 60;
13542         if (seconds == 0) {
13543           sprintf(buf, "level 1 %d\n", st/60);
13544         } else {
13545           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13546         }
13547       } else {
13548         sprintf(buf, "st %d\n", st);
13549       }
13550     } else {
13551       /* Set conventional or incremental time control, using level command */
13552       if (seconds == 0) {
13553         /* Note old gnuchess bug -- minutes:seconds used to not work.
13554            Fixed in later versions, but still avoid :seconds
13555            when seconds is 0. */
13556         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13557       } else {
13558         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13559                 seconds, inc/1000);
13560       }
13561     }
13562     SendToProgram(buf, cps);
13563
13564     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13565     /* Orthogonally, limit search to given depth */
13566     if (sd > 0) {
13567       if (cps->sdKludge) {
13568         sprintf(buf, "depth\n%d\n", sd);
13569       } else {
13570         sprintf(buf, "sd %d\n", sd);
13571       }
13572       SendToProgram(buf, cps);
13573     }
13574
13575     if(cps->nps > 0) { /* [HGM] nps */
13576         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13577         else {
13578                 sprintf(buf, "nps %d\n", cps->nps);
13579               SendToProgram(buf, cps);
13580         }
13581     }
13582 }
13583
13584 ChessProgramState *WhitePlayer()
13585 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13586 {
13587     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13588        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13589         return &second;
13590     return &first;
13591 }
13592
13593 void
13594 SendTimeRemaining(cps, machineWhite)
13595      ChessProgramState *cps;
13596      int /*boolean*/ machineWhite;
13597 {
13598     char message[MSG_SIZ];
13599     long time, otime;
13600
13601     /* Note: this routine must be called when the clocks are stopped
13602        or when they have *just* been set or switched; otherwise
13603        it will be off by the time since the current tick started.
13604     */
13605     if (machineWhite) {
13606         time = whiteTimeRemaining / 10;
13607         otime = blackTimeRemaining / 10;
13608     } else {
13609         time = blackTimeRemaining / 10;
13610         otime = whiteTimeRemaining / 10;
13611     }
13612     /* [HGM] translate opponent's time by time-odds factor */
13613     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13614     if (appData.debugMode) {
13615         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13616     }
13617
13618     if (time <= 0) time = 1;
13619     if (otime <= 0) otime = 1;
13620     
13621     sprintf(message, "time %ld\n", time);
13622     SendToProgram(message, cps);
13623
13624     sprintf(message, "otim %ld\n", otime);
13625     SendToProgram(message, cps);
13626 }
13627
13628 int
13629 BoolFeature(p, name, loc, cps)
13630      char **p;
13631      char *name;
13632      int *loc;
13633      ChessProgramState *cps;
13634 {
13635   char buf[MSG_SIZ];
13636   int len = strlen(name);
13637   int val;
13638   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13639     (*p) += len + 1;
13640     sscanf(*p, "%d", &val);
13641     *loc = (val != 0);
13642     while (**p && **p != ' ') (*p)++;
13643     sprintf(buf, "accepted %s\n", name);
13644     SendToProgram(buf, cps);
13645     return TRUE;
13646   }
13647   return FALSE;
13648 }
13649
13650 int
13651 IntFeature(p, name, loc, cps)
13652      char **p;
13653      char *name;
13654      int *loc;
13655      ChessProgramState *cps;
13656 {
13657   char buf[MSG_SIZ];
13658   int len = strlen(name);
13659   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13660     (*p) += len + 1;
13661     sscanf(*p, "%d", loc);
13662     while (**p && **p != ' ') (*p)++;
13663     sprintf(buf, "accepted %s\n", name);
13664     SendToProgram(buf, cps);
13665     return TRUE;
13666   }
13667   return FALSE;
13668 }
13669
13670 int
13671 StringFeature(p, name, loc, cps)
13672      char **p;
13673      char *name;
13674      char loc[];
13675      ChessProgramState *cps;
13676 {
13677   char buf[MSG_SIZ];
13678   int len = strlen(name);
13679   if (strncmp((*p), name, len) == 0
13680       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13681     (*p) += len + 2;
13682     sscanf(*p, "%[^\"]", loc);
13683     while (**p && **p != '\"') (*p)++;
13684     if (**p == '\"') (*p)++;
13685     sprintf(buf, "accepted %s\n", name);
13686     SendToProgram(buf, cps);
13687     return TRUE;
13688   }
13689   return FALSE;
13690 }
13691
13692 int 
13693 ParseOption(Option *opt, ChessProgramState *cps)
13694 // [HGM] options: process the string that defines an engine option, and determine
13695 // name, type, default value, and allowed value range
13696 {
13697         char *p, *q, buf[MSG_SIZ];
13698         int n, min = (-1)<<31, max = 1<<31, def;
13699
13700         if(p = strstr(opt->name, " -spin ")) {
13701             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13702             if(max < min) max = min; // enforce consistency
13703             if(def < min) def = min;
13704             if(def > max) def = max;
13705             opt->value = def;
13706             opt->min = min;
13707             opt->max = max;
13708             opt->type = Spin;
13709         } else if((p = strstr(opt->name, " -slider "))) {
13710             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13711             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13712             if(max < min) max = min; // enforce consistency
13713             if(def < min) def = min;
13714             if(def > max) def = max;
13715             opt->value = def;
13716             opt->min = min;
13717             opt->max = max;
13718             opt->type = Spin; // Slider;
13719         } else if((p = strstr(opt->name, " -string "))) {
13720             opt->textValue = p+9;
13721             opt->type = TextBox;
13722         } else if((p = strstr(opt->name, " -file "))) {
13723             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13724             opt->textValue = p+7;
13725             opt->type = TextBox; // FileName;
13726         } else if((p = strstr(opt->name, " -path "))) {
13727             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13728             opt->textValue = p+7;
13729             opt->type = TextBox; // PathName;
13730         } else if(p = strstr(opt->name, " -check ")) {
13731             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13732             opt->value = (def != 0);
13733             opt->type = CheckBox;
13734         } else if(p = strstr(opt->name, " -combo ")) {
13735             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13736             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13737             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13738             opt->value = n = 0;
13739             while(q = StrStr(q, " /// ")) {
13740                 n++; *q = 0;    // count choices, and null-terminate each of them
13741                 q += 5;
13742                 if(*q == '*') { // remember default, which is marked with * prefix
13743                     q++;
13744                     opt->value = n;
13745                 }
13746                 cps->comboList[cps->comboCnt++] = q;
13747             }
13748             cps->comboList[cps->comboCnt++] = NULL;
13749             opt->max = n + 1;
13750             opt->type = ComboBox;
13751         } else if(p = strstr(opt->name, " -button")) {
13752             opt->type = Button;
13753         } else if(p = strstr(opt->name, " -save")) {
13754             opt->type = SaveButton;
13755         } else return FALSE;
13756         *p = 0; // terminate option name
13757         // now look if the command-line options define a setting for this engine option.
13758         if(cps->optionSettings && cps->optionSettings[0])
13759             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13760         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13761                 sprintf(buf, "option %s", p);
13762                 if(p = strstr(buf, ",")) *p = 0;
13763                 strcat(buf, "\n");
13764                 SendToProgram(buf, cps);
13765         }
13766         return TRUE;
13767 }
13768
13769 void
13770 FeatureDone(cps, val)
13771      ChessProgramState* cps;
13772      int val;
13773 {
13774   DelayedEventCallback cb = GetDelayedEvent();
13775   if ((cb == InitBackEnd3 && cps == &first) ||
13776       (cb == TwoMachinesEventIfReady && cps == &second)) {
13777     CancelDelayedEvent();
13778     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13779   }
13780   cps->initDone = val;
13781 }
13782
13783 /* Parse feature command from engine */
13784 void
13785 ParseFeatures(args, cps)
13786      char* args;
13787      ChessProgramState *cps;  
13788 {
13789   char *p = args;
13790   char *q;
13791   int val;
13792   char buf[MSG_SIZ];
13793
13794   for (;;) {
13795     while (*p == ' ') p++;
13796     if (*p == NULLCHAR) return;
13797
13798     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13799     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13800     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13801     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13802     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13803     if (BoolFeature(&p, "reuse", &val, cps)) {
13804       /* Engine can disable reuse, but can't enable it if user said no */
13805       if (!val) cps->reuse = FALSE;
13806       continue;
13807     }
13808     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13809     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13810       if (gameMode == TwoMachinesPlay) {
13811         DisplayTwoMachinesTitle();
13812       } else {
13813         DisplayTitle("");
13814       }
13815       continue;
13816     }
13817     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13818     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13819     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13820     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13821     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13822     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13823     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13824     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13825     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13826     if (IntFeature(&p, "done", &val, cps)) {
13827       FeatureDone(cps, val);
13828       continue;
13829     }
13830     /* Added by Tord: */
13831     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13832     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13833     /* End of additions by Tord */
13834
13835     /* [HGM] added features: */
13836     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13837     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13838     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13839     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13840     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13841     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13842     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13843         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13844             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13845             SendToProgram(buf, cps);
13846             continue;
13847         }
13848         if(cps->nrOptions >= MAX_OPTIONS) {
13849             cps->nrOptions--;
13850             sprintf(buf, "%s engine has too many options\n", cps->which);
13851             DisplayError(buf, 0);
13852         }
13853         continue;
13854     }
13855     /* End of additions by HGM */
13856
13857     /* unknown feature: complain and skip */
13858     q = p;
13859     while (*q && *q != '=') q++;
13860     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13861     SendToProgram(buf, cps);
13862     p = q;
13863     if (*p == '=') {
13864       p++;
13865       if (*p == '\"') {
13866         p++;
13867         while (*p && *p != '\"') p++;
13868         if (*p == '\"') p++;
13869       } else {
13870         while (*p && *p != ' ') p++;
13871       }
13872     }
13873   }
13874
13875 }
13876
13877 void
13878 PeriodicUpdatesEvent(newState)
13879      int newState;
13880 {
13881     if (newState == appData.periodicUpdates)
13882       return;
13883
13884     appData.periodicUpdates=newState;
13885
13886     /* Display type changes, so update it now */
13887 //    DisplayAnalysis();
13888
13889     /* Get the ball rolling again... */
13890     if (newState) {
13891         AnalysisPeriodicEvent(1);
13892         StartAnalysisClock();
13893     }
13894 }
13895
13896 void
13897 PonderNextMoveEvent(newState)
13898      int newState;
13899 {
13900     if (newState == appData.ponderNextMove) return;
13901     if (gameMode == EditPosition) EditPositionDone(TRUE);
13902     if (newState) {
13903         SendToProgram("hard\n", &first);
13904         if (gameMode == TwoMachinesPlay) {
13905             SendToProgram("hard\n", &second);
13906         }
13907     } else {
13908         SendToProgram("easy\n", &first);
13909         thinkOutput[0] = NULLCHAR;
13910         if (gameMode == TwoMachinesPlay) {
13911             SendToProgram("easy\n", &second);
13912         }
13913     }
13914     appData.ponderNextMove = newState;
13915 }
13916
13917 void
13918 NewSettingEvent(option, feature, command, value)
13919      char *command;
13920      int option, value, *feature;
13921 {
13922     char buf[MSG_SIZ];
13923
13924     if (gameMode == EditPosition) EditPositionDone(TRUE);
13925     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13926     if(feature == NULL || *feature) SendToProgram(buf, &first);
13927     if (gameMode == TwoMachinesPlay) {
13928         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13929     }
13930 }
13931
13932 void
13933 ShowThinkingEvent()
13934 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13935 {
13936     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13937     int newState = appData.showThinking
13938         // [HGM] thinking: other features now need thinking output as well
13939         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13940     
13941     if (oldState == newState) return;
13942     oldState = newState;
13943     if (gameMode == EditPosition) EditPositionDone(TRUE);
13944     if (oldState) {
13945         SendToProgram("post\n", &first);
13946         if (gameMode == TwoMachinesPlay) {
13947             SendToProgram("post\n", &second);
13948         }
13949     } else {
13950         SendToProgram("nopost\n", &first);
13951         thinkOutput[0] = NULLCHAR;
13952         if (gameMode == TwoMachinesPlay) {
13953             SendToProgram("nopost\n", &second);
13954         }
13955     }
13956 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13957 }
13958
13959 void
13960 AskQuestionEvent(title, question, replyPrefix, which)
13961      char *title; char *question; char *replyPrefix; char *which;
13962 {
13963   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13964   if (pr == NoProc) return;
13965   AskQuestion(title, question, replyPrefix, pr);
13966 }
13967
13968 void
13969 DisplayMove(moveNumber)
13970      int moveNumber;
13971 {
13972     char message[MSG_SIZ];
13973     char res[MSG_SIZ];
13974     char cpThinkOutput[MSG_SIZ];
13975
13976     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13977     
13978     if (moveNumber == forwardMostMove - 1 || 
13979         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13980
13981         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13982
13983         if (strchr(cpThinkOutput, '\n')) {
13984             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13985         }
13986     } else {
13987         *cpThinkOutput = NULLCHAR;
13988     }
13989
13990     /* [AS] Hide thinking from human user */
13991     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13992         *cpThinkOutput = NULLCHAR;
13993         if( thinkOutput[0] != NULLCHAR ) {
13994             int i;
13995
13996             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13997                 cpThinkOutput[i] = '.';
13998             }
13999             cpThinkOutput[i] = NULLCHAR;
14000             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14001         }
14002     }
14003
14004     if (moveNumber == forwardMostMove - 1 &&
14005         gameInfo.resultDetails != NULL) {
14006         if (gameInfo.resultDetails[0] == NULLCHAR) {
14007             sprintf(res, " %s", PGNResult(gameInfo.result));
14008         } else {
14009             sprintf(res, " {%s} %s",
14010                     gameInfo.resultDetails, PGNResult(gameInfo.result));
14011         }
14012     } else {
14013         res[0] = NULLCHAR;
14014     }
14015
14016     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14017         DisplayMessage(res, cpThinkOutput);
14018     } else {
14019         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
14020                 WhiteOnMove(moveNumber) ? " " : ".. ",
14021                 parseList[moveNumber], res);
14022         DisplayMessage(message, cpThinkOutput);
14023     }
14024 }
14025
14026 void
14027 DisplayComment(moveNumber, text)
14028      int moveNumber;
14029      char *text;
14030 {
14031     char title[MSG_SIZ];
14032     char buf[8000]; // comment can be long!
14033     int score, depth;
14034     
14035     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14036       strcpy(title, "Comment");
14037     } else {
14038       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14039               WhiteOnMove(moveNumber) ? " " : ".. ",
14040               parseList[moveNumber]);
14041     }
14042     // [HGM] PV info: display PV info together with (or as) comment
14043     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14044       if(text == NULL) text = "";                                           
14045       score = pvInfoList[moveNumber].score;
14046       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14047               depth, (pvInfoList[moveNumber].time+50)/100, text);
14048       text = buf;
14049     }
14050     if (text != NULL && (appData.autoDisplayComment || commentUp))
14051         CommentPopUp(title, text);
14052 }
14053
14054 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14055  * might be busy thinking or pondering.  It can be omitted if your
14056  * gnuchess is configured to stop thinking immediately on any user
14057  * input.  However, that gnuchess feature depends on the FIONREAD
14058  * ioctl, which does not work properly on some flavors of Unix.
14059  */
14060 void
14061 Attention(cps)
14062      ChessProgramState *cps;
14063 {
14064 #if ATTENTION
14065     if (!cps->useSigint) return;
14066     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14067     switch (gameMode) {
14068       case MachinePlaysWhite:
14069       case MachinePlaysBlack:
14070       case TwoMachinesPlay:
14071       case IcsPlayingWhite:
14072       case IcsPlayingBlack:
14073       case AnalyzeMode:
14074       case AnalyzeFile:
14075         /* Skip if we know it isn't thinking */
14076         if (!cps->maybeThinking) return;
14077         if (appData.debugMode)
14078           fprintf(debugFP, "Interrupting %s\n", cps->which);
14079         InterruptChildProcess(cps->pr);
14080         cps->maybeThinking = FALSE;
14081         break;
14082       default:
14083         break;
14084     }
14085 #endif /*ATTENTION*/
14086 }
14087
14088 int
14089 CheckFlags()
14090 {
14091     if (whiteTimeRemaining <= 0) {
14092         if (!whiteFlag) {
14093             whiteFlag = TRUE;
14094             if (appData.icsActive) {
14095                 if (appData.autoCallFlag &&
14096                     gameMode == IcsPlayingBlack && !blackFlag) {
14097                   SendToICS(ics_prefix);
14098                   SendToICS("flag\n");
14099                 }
14100             } else {
14101                 if (blackFlag) {
14102                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14103                 } else {
14104                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14105                     if (appData.autoCallFlag) {
14106                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14107                         return TRUE;
14108                     }
14109                 }
14110             }
14111         }
14112     }
14113     if (blackTimeRemaining <= 0) {
14114         if (!blackFlag) {
14115             blackFlag = TRUE;
14116             if (appData.icsActive) {
14117                 if (appData.autoCallFlag &&
14118                     gameMode == IcsPlayingWhite && !whiteFlag) {
14119                   SendToICS(ics_prefix);
14120                   SendToICS("flag\n");
14121                 }
14122             } else {
14123                 if (whiteFlag) {
14124                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14125                 } else {
14126                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14127                     if (appData.autoCallFlag) {
14128                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14129                         return TRUE;
14130                     }
14131                 }
14132             }
14133         }
14134     }
14135     return FALSE;
14136 }
14137
14138 void
14139 CheckTimeControl()
14140 {
14141     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14142         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14143
14144     /*
14145      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14146      */
14147     if ( !WhiteOnMove(forwardMostMove) )
14148         /* White made time control */
14149         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14150         /* [HGM] time odds: correct new time quota for time odds! */
14151                                             / WhitePlayer()->timeOdds;
14152       else
14153         /* Black made time control */
14154         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14155                                             / WhitePlayer()->other->timeOdds;
14156 }
14157
14158 void
14159 DisplayBothClocks()
14160 {
14161     int wom = gameMode == EditPosition ?
14162       !blackPlaysFirst : WhiteOnMove(currentMove);
14163     DisplayWhiteClock(whiteTimeRemaining, wom);
14164     DisplayBlackClock(blackTimeRemaining, !wom);
14165 }
14166
14167
14168 /* Timekeeping seems to be a portability nightmare.  I think everyone
14169    has ftime(), but I'm really not sure, so I'm including some ifdefs
14170    to use other calls if you don't.  Clocks will be less accurate if
14171    you have neither ftime nor gettimeofday.
14172 */
14173
14174 /* VS 2008 requires the #include outside of the function */
14175 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14176 #include <sys/timeb.h>
14177 #endif
14178
14179 /* Get the current time as a TimeMark */
14180 void
14181 GetTimeMark(tm)
14182      TimeMark *tm;
14183 {
14184 #if HAVE_GETTIMEOFDAY
14185
14186     struct timeval timeVal;
14187     struct timezone timeZone;
14188
14189     gettimeofday(&timeVal, &timeZone);
14190     tm->sec = (long) timeVal.tv_sec; 
14191     tm->ms = (int) (timeVal.tv_usec / 1000L);
14192
14193 #else /*!HAVE_GETTIMEOFDAY*/
14194 #if HAVE_FTIME
14195
14196 // include <sys/timeb.h> / moved to just above start of function
14197     struct timeb timeB;
14198
14199     ftime(&timeB);
14200     tm->sec = (long) timeB.time;
14201     tm->ms = (int) timeB.millitm;
14202
14203 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14204     tm->sec = (long) time(NULL);
14205     tm->ms = 0;
14206 #endif
14207 #endif
14208 }
14209
14210 /* Return the difference in milliseconds between two
14211    time marks.  We assume the difference will fit in a long!
14212 */
14213 long
14214 SubtractTimeMarks(tm2, tm1)
14215      TimeMark *tm2, *tm1;
14216 {
14217     return 1000L*(tm2->sec - tm1->sec) +
14218            (long) (tm2->ms - tm1->ms);
14219 }
14220
14221
14222 /*
14223  * Code to manage the game clocks.
14224  *
14225  * In tournament play, black starts the clock and then white makes a move.
14226  * We give the human user a slight advantage if he is playing white---the
14227  * clocks don't run until he makes his first move, so it takes zero time.
14228  * Also, we don't account for network lag, so we could get out of sync
14229  * with GNU Chess's clock -- but then, referees are always right.  
14230  */
14231
14232 static TimeMark tickStartTM;
14233 static long intendedTickLength;
14234
14235 long
14236 NextTickLength(timeRemaining)
14237      long timeRemaining;
14238 {
14239     long nominalTickLength, nextTickLength;
14240
14241     if (timeRemaining > 0L && timeRemaining <= 10000L)
14242       nominalTickLength = 100L;
14243     else
14244       nominalTickLength = 1000L;
14245     nextTickLength = timeRemaining % nominalTickLength;
14246     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14247
14248     return nextTickLength;
14249 }
14250
14251 /* Adjust clock one minute up or down */
14252 void
14253 AdjustClock(Boolean which, int dir)
14254 {
14255     if(which) blackTimeRemaining += 60000*dir;
14256     else      whiteTimeRemaining += 60000*dir;
14257     DisplayBothClocks();
14258 }
14259
14260 /* Stop clocks and reset to a fresh time control */
14261 void
14262 ResetClocks() 
14263 {
14264     (void) StopClockTimer();
14265     if (appData.icsActive) {
14266         whiteTimeRemaining = blackTimeRemaining = 0;
14267     } else if (searchTime) {
14268         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14269         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14270     } else { /* [HGM] correct new time quote for time odds */
14271         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14272         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14273     }
14274     if (whiteFlag || blackFlag) {
14275         DisplayTitle("");
14276         whiteFlag = blackFlag = FALSE;
14277     }
14278     DisplayBothClocks();
14279 }
14280
14281 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14282
14283 /* Decrement running clock by amount of time that has passed */
14284 void
14285 DecrementClocks()
14286 {
14287     long timeRemaining;
14288     long lastTickLength, fudge;
14289     TimeMark now;
14290
14291     if (!appData.clockMode) return;
14292     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14293         
14294     GetTimeMark(&now);
14295
14296     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14297
14298     /* Fudge if we woke up a little too soon */
14299     fudge = intendedTickLength - lastTickLength;
14300     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14301
14302     if (WhiteOnMove(forwardMostMove)) {
14303         if(whiteNPS >= 0) lastTickLength = 0;
14304         timeRemaining = whiteTimeRemaining -= lastTickLength;
14305         DisplayWhiteClock(whiteTimeRemaining - fudge,
14306                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14307     } else {
14308         if(blackNPS >= 0) lastTickLength = 0;
14309         timeRemaining = blackTimeRemaining -= lastTickLength;
14310         DisplayBlackClock(blackTimeRemaining - fudge,
14311                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14312     }
14313
14314     if (CheckFlags()) return;
14315         
14316     tickStartTM = now;
14317     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14318     StartClockTimer(intendedTickLength);
14319
14320     /* if the time remaining has fallen below the alarm threshold, sound the
14321      * alarm. if the alarm has sounded and (due to a takeback or time control
14322      * with increment) the time remaining has increased to a level above the
14323      * threshold, reset the alarm so it can sound again. 
14324      */
14325     
14326     if (appData.icsActive && appData.icsAlarm) {
14327
14328         /* make sure we are dealing with the user's clock */
14329         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14330                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14331            )) return;
14332
14333         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14334             alarmSounded = FALSE;
14335         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14336             PlayAlarmSound();
14337             alarmSounded = TRUE;
14338         }
14339     }
14340 }
14341
14342
14343 /* A player has just moved, so stop the previously running
14344    clock and (if in clock mode) start the other one.
14345    We redisplay both clocks in case we're in ICS mode, because
14346    ICS gives us an update to both clocks after every move.
14347    Note that this routine is called *after* forwardMostMove
14348    is updated, so the last fractional tick must be subtracted
14349    from the color that is *not* on move now.
14350 */
14351 void
14352 SwitchClocks(int newMoveNr)
14353 {
14354     long lastTickLength;
14355     TimeMark now;
14356     int flagged = FALSE;
14357
14358     GetTimeMark(&now);
14359
14360     if (StopClockTimer() && appData.clockMode) {
14361         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14362         if (!WhiteOnMove(forwardMostMove)) {
14363             if(blackNPS >= 0) lastTickLength = 0;
14364             blackTimeRemaining -= lastTickLength;
14365            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14366 //         if(pvInfoList[forwardMostMove-1].time == -1)
14367                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14368                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14369         } else {
14370            if(whiteNPS >= 0) lastTickLength = 0;
14371            whiteTimeRemaining -= lastTickLength;
14372            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14373 //         if(pvInfoList[forwardMostMove-1].time == -1)
14374                  pvInfoList[forwardMostMove-1].time = 
14375                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14376         }
14377         flagged = CheckFlags();
14378     }
14379     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14380     CheckTimeControl();
14381
14382     if (flagged || !appData.clockMode) return;
14383
14384     switch (gameMode) {
14385       case MachinePlaysBlack:
14386       case MachinePlaysWhite:
14387       case BeginningOfGame:
14388         if (pausing) return;
14389         break;
14390
14391       case EditGame:
14392       case PlayFromGameFile:
14393       case IcsExamining:
14394         return;
14395
14396       default:
14397         break;
14398     }
14399
14400     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14401         if(WhiteOnMove(forwardMostMove))
14402              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14403         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14404     }
14405
14406     tickStartTM = now;
14407     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14408       whiteTimeRemaining : blackTimeRemaining);
14409     StartClockTimer(intendedTickLength);
14410 }
14411         
14412
14413 /* Stop both clocks */
14414 void
14415 StopClocks()
14416 {       
14417     long lastTickLength;
14418     TimeMark now;
14419
14420     if (!StopClockTimer()) return;
14421     if (!appData.clockMode) return;
14422
14423     GetTimeMark(&now);
14424
14425     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14426     if (WhiteOnMove(forwardMostMove)) {
14427         if(whiteNPS >= 0) lastTickLength = 0;
14428         whiteTimeRemaining -= lastTickLength;
14429         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14430     } else {
14431         if(blackNPS >= 0) lastTickLength = 0;
14432         blackTimeRemaining -= lastTickLength;
14433         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14434     }
14435     CheckFlags();
14436 }
14437         
14438 /* Start clock of player on move.  Time may have been reset, so
14439    if clock is already running, stop and restart it. */
14440 void
14441 StartClocks()
14442 {
14443     (void) StopClockTimer(); /* in case it was running already */
14444     DisplayBothClocks();
14445     if (CheckFlags()) return;
14446
14447     if (!appData.clockMode) return;
14448     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14449
14450     GetTimeMark(&tickStartTM);
14451     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14452       whiteTimeRemaining : blackTimeRemaining);
14453
14454    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14455     whiteNPS = blackNPS = -1; 
14456     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14457        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14458         whiteNPS = first.nps;
14459     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14460        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14461         blackNPS = first.nps;
14462     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14463         whiteNPS = second.nps;
14464     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14465         blackNPS = second.nps;
14466     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14467
14468     StartClockTimer(intendedTickLength);
14469 }
14470
14471 char *
14472 TimeString(ms)
14473      long ms;
14474 {
14475     long second, minute, hour, day;
14476     char *sign = "";
14477     static char buf[32];
14478     
14479     if (ms > 0 && ms <= 9900) {
14480       /* convert milliseconds to tenths, rounding up */
14481       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14482
14483       sprintf(buf, " %03.1f ", tenths/10.0);
14484       return buf;
14485     }
14486
14487     /* convert milliseconds to seconds, rounding up */
14488     /* use floating point to avoid strangeness of integer division
14489        with negative dividends on many machines */
14490     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14491
14492     if (second < 0) {
14493         sign = "-";
14494         second = -second;
14495     }
14496     
14497     day = second / (60 * 60 * 24);
14498     second = second % (60 * 60 * 24);
14499     hour = second / (60 * 60);
14500     second = second % (60 * 60);
14501     minute = second / 60;
14502     second = second % 60;
14503     
14504     if (day > 0)
14505       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14506               sign, day, hour, minute, second);
14507     else if (hour > 0)
14508       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14509     else
14510       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14511     
14512     return buf;
14513 }
14514
14515
14516 /*
14517  * This is necessary because some C libraries aren't ANSI C compliant yet.
14518  */
14519 char *
14520 StrStr(string, match)
14521      char *string, *match;
14522 {
14523     int i, length;
14524     
14525     length = strlen(match);
14526     
14527     for (i = strlen(string) - length; i >= 0; i--, string++)
14528       if (!strncmp(match, string, length))
14529         return string;
14530     
14531     return NULL;
14532 }
14533
14534 char *
14535 StrCaseStr(string, match)
14536      char *string, *match;
14537 {
14538     int i, j, length;
14539     
14540     length = strlen(match);
14541     
14542     for (i = strlen(string) - length; i >= 0; i--, string++) {
14543         for (j = 0; j < length; j++) {
14544             if (ToLower(match[j]) != ToLower(string[j]))
14545               break;
14546         }
14547         if (j == length) return string;
14548     }
14549
14550     return NULL;
14551 }
14552
14553 #ifndef _amigados
14554 int
14555 StrCaseCmp(s1, s2)
14556      char *s1, *s2;
14557 {
14558     char c1, c2;
14559     
14560     for (;;) {
14561         c1 = ToLower(*s1++);
14562         c2 = ToLower(*s2++);
14563         if (c1 > c2) return 1;
14564         if (c1 < c2) return -1;
14565         if (c1 == NULLCHAR) return 0;
14566     }
14567 }
14568
14569
14570 int
14571 ToLower(c)
14572      int c;
14573 {
14574     return isupper(c) ? tolower(c) : c;
14575 }
14576
14577
14578 int
14579 ToUpper(c)
14580      int c;
14581 {
14582     return islower(c) ? toupper(c) : c;
14583 }
14584 #endif /* !_amigados    */
14585
14586 char *
14587 StrSave(s)
14588      char *s;
14589 {
14590     char *ret;
14591
14592     if ((ret = (char *) malloc(strlen(s) + 1))) {
14593         strcpy(ret, s);
14594     }
14595     return ret;
14596 }
14597
14598 char *
14599 StrSavePtr(s, savePtr)
14600      char *s, **savePtr;
14601 {
14602     if (*savePtr) {
14603         free(*savePtr);
14604     }
14605     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14606         strcpy(*savePtr, s);
14607     }
14608     return(*savePtr);
14609 }
14610
14611 char *
14612 PGNDate()
14613 {
14614     time_t clock;
14615     struct tm *tm;
14616     char buf[MSG_SIZ];
14617
14618     clock = time((time_t *)NULL);
14619     tm = localtime(&clock);
14620     sprintf(buf, "%04d.%02d.%02d",
14621             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14622     return StrSave(buf);
14623 }
14624
14625
14626 char *
14627 PositionToFEN(move, overrideCastling)
14628      int move;
14629      char *overrideCastling;
14630 {
14631     int i, j, fromX, fromY, toX, toY;
14632     int whiteToPlay;
14633     char buf[128];
14634     char *p, *q;
14635     int emptycount;
14636     ChessSquare piece;
14637
14638     whiteToPlay = (gameMode == EditPosition) ?
14639       !blackPlaysFirst : (move % 2 == 0);
14640     p = buf;
14641
14642     /* Piece placement data */
14643     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14644         emptycount = 0;
14645         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14646             if (boards[move][i][j] == EmptySquare) {
14647                 emptycount++;
14648             } else { ChessSquare piece = boards[move][i][j];
14649                 if (emptycount > 0) {
14650                     if(emptycount<10) /* [HGM] can be >= 10 */
14651                         *p++ = '0' + emptycount;
14652                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14653                     emptycount = 0;
14654                 }
14655                 if(PieceToChar(piece) == '+') {
14656                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14657                     *p++ = '+';
14658                     piece = (ChessSquare)(DEMOTED piece);
14659                 } 
14660                 *p++ = PieceToChar(piece);
14661                 if(p[-1] == '~') {
14662                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14663                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14664                     *p++ = '~';
14665                 }
14666             }
14667         }
14668         if (emptycount > 0) {
14669             if(emptycount<10) /* [HGM] can be >= 10 */
14670                 *p++ = '0' + emptycount;
14671             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14672             emptycount = 0;
14673         }
14674         *p++ = '/';
14675     }
14676     *(p - 1) = ' ';
14677
14678     /* [HGM] print Crazyhouse or Shogi holdings */
14679     if( gameInfo.holdingsWidth ) {
14680         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14681         q = p;
14682         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14683             piece = boards[move][i][BOARD_WIDTH-1];
14684             if( piece != EmptySquare )
14685               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14686                   *p++ = PieceToChar(piece);
14687         }
14688         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14689             piece = boards[move][BOARD_HEIGHT-i-1][0];
14690             if( piece != EmptySquare )
14691               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14692                   *p++ = PieceToChar(piece);
14693         }
14694
14695         if( q == p ) *p++ = '-';
14696         *p++ = ']';
14697         *p++ = ' ';
14698     }
14699
14700     /* Active color */
14701     *p++ = whiteToPlay ? 'w' : 'b';
14702     *p++ = ' ';
14703
14704   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14705     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14706   } else {
14707   if(nrCastlingRights) {
14708      q = p;
14709      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14710        /* [HGM] write directly from rights */
14711            if(boards[move][CASTLING][2] != NoRights &&
14712               boards[move][CASTLING][0] != NoRights   )
14713                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14714            if(boards[move][CASTLING][2] != NoRights &&
14715               boards[move][CASTLING][1] != NoRights   )
14716                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14717            if(boards[move][CASTLING][5] != NoRights &&
14718               boards[move][CASTLING][3] != NoRights   )
14719                 *p++ = boards[move][CASTLING][3] + AAA;
14720            if(boards[move][CASTLING][5] != NoRights &&
14721               boards[move][CASTLING][4] != NoRights   )
14722                 *p++ = boards[move][CASTLING][4] + AAA;
14723      } else {
14724
14725         /* [HGM] write true castling rights */
14726         if( nrCastlingRights == 6 ) {
14727             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14728                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14729             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14730                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14731             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14732                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14733             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14734                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14735         }
14736      }
14737      if (q == p) *p++ = '-'; /* No castling rights */
14738      *p++ = ' ';
14739   }
14740
14741   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14742      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14743     /* En passant target square */
14744     if (move > backwardMostMove) {
14745         fromX = moveList[move - 1][0] - AAA;
14746         fromY = moveList[move - 1][1] - ONE;
14747         toX = moveList[move - 1][2] - AAA;
14748         toY = moveList[move - 1][3] - ONE;
14749         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14750             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14751             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14752             fromX == toX) {
14753             /* 2-square pawn move just happened */
14754             *p++ = toX + AAA;
14755             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14756         } else {
14757             *p++ = '-';
14758         }
14759     } else if(move == backwardMostMove) {
14760         // [HGM] perhaps we should always do it like this, and forget the above?
14761         if((signed char)boards[move][EP_STATUS] >= 0) {
14762             *p++ = boards[move][EP_STATUS] + AAA;
14763             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14764         } else {
14765             *p++ = '-';
14766         }
14767     } else {
14768         *p++ = '-';
14769     }
14770     *p++ = ' ';
14771   }
14772   }
14773
14774     /* [HGM] find reversible plies */
14775     {   int i = 0, j=move;
14776
14777         if (appData.debugMode) { int k;
14778             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14779             for(k=backwardMostMove; k<=forwardMostMove; k++)
14780                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14781
14782         }
14783
14784         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14785         if( j == backwardMostMove ) i += initialRulePlies;
14786         sprintf(p, "%d ", i);
14787         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14788     }
14789     /* Fullmove number */
14790     sprintf(p, "%d", (move / 2) + 1);
14791     
14792     return StrSave(buf);
14793 }
14794
14795 Boolean
14796 ParseFEN(board, blackPlaysFirst, fen)
14797     Board board;
14798      int *blackPlaysFirst;
14799      char *fen;
14800 {
14801     int i, j;
14802     char *p, c;
14803     int emptycount;
14804     ChessSquare piece;
14805
14806     p = fen;
14807
14808     /* [HGM] by default clear Crazyhouse holdings, if present */
14809     if(gameInfo.holdingsWidth) {
14810        for(i=0; i<BOARD_HEIGHT; i++) {
14811            board[i][0]             = EmptySquare; /* black holdings */
14812            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14813            board[i][1]             = (ChessSquare) 0; /* black counts */
14814            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14815        }
14816     }
14817
14818     /* Piece placement data */
14819     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14820         j = 0;
14821         for (;;) {
14822             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14823                 if (*p == '/') p++;
14824                 emptycount = gameInfo.boardWidth - j;
14825                 while (emptycount--)
14826                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14827                 break;
14828 #if(BOARD_FILES >= 10)
14829             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14830                 p++; emptycount=10;
14831                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14832                 while (emptycount--)
14833                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14834 #endif
14835             } else if (isdigit(*p)) {
14836                 emptycount = *p++ - '0';
14837                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14838                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14839                 while (emptycount--)
14840                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14841             } else if (*p == '+' || isalpha(*p)) {
14842                 if (j >= gameInfo.boardWidth) return FALSE;
14843                 if(*p=='+') {
14844                     piece = CharToPiece(*++p);
14845                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14846                     piece = (ChessSquare) (PROMOTED piece ); p++;
14847                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14848                 } else piece = CharToPiece(*p++);
14849
14850                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14851                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14852                     piece = (ChessSquare) (PROMOTED piece);
14853                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14854                     p++;
14855                 }
14856                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14857             } else {
14858                 return FALSE;
14859             }
14860         }
14861     }
14862     while (*p == '/' || *p == ' ') p++;
14863
14864     /* [HGM] look for Crazyhouse holdings here */
14865     while(*p==' ') p++;
14866     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14867         if(*p == '[') p++;
14868         if(*p == '-' ) *p++; /* empty holdings */ else {
14869             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14870             /* if we would allow FEN reading to set board size, we would   */
14871             /* have to add holdings and shift the board read so far here   */
14872             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14873                 *p++;
14874                 if((int) piece >= (int) BlackPawn ) {
14875                     i = (int)piece - (int)BlackPawn;
14876                     i = PieceToNumber((ChessSquare)i);
14877                     if( i >= gameInfo.holdingsSize ) return FALSE;
14878                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14879                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14880                 } else {
14881                     i = (int)piece - (int)WhitePawn;
14882                     i = PieceToNumber((ChessSquare)i);
14883                     if( i >= gameInfo.holdingsSize ) return FALSE;
14884                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14885                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14886                 }
14887             }
14888         }
14889         if(*p == ']') *p++;
14890     }
14891
14892     while(*p == ' ') p++;
14893
14894     /* Active color */
14895     c = *p++;
14896     if(appData.colorNickNames) {
14897       if( c == appData.colorNickNames[0] ) c = 'w'; else
14898       if( c == appData.colorNickNames[1] ) c = 'b';
14899     }
14900     switch (c) {
14901       case 'w':
14902         *blackPlaysFirst = FALSE;
14903         break;
14904       case 'b': 
14905         *blackPlaysFirst = TRUE;
14906         break;
14907       default:
14908         return FALSE;
14909     }
14910
14911     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14912     /* return the extra info in global variiables             */
14913
14914     /* set defaults in case FEN is incomplete */
14915     board[EP_STATUS] = EP_UNKNOWN;
14916     for(i=0; i<nrCastlingRights; i++ ) {
14917         board[CASTLING][i] =
14918             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14919     }   /* assume possible unless obviously impossible */
14920     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14921     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14922     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14923                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14924     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14925     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14926     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14927                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14928     FENrulePlies = 0;
14929
14930     while(*p==' ') p++;
14931     if(nrCastlingRights) {
14932       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14933           /* castling indicator present, so default becomes no castlings */
14934           for(i=0; i<nrCastlingRights; i++ ) {
14935                  board[CASTLING][i] = NoRights;
14936           }
14937       }
14938       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14939              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14940              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14941              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14942         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14943
14944         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14945             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14946             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14947         }
14948         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14949             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14950         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14951                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14952         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14953                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14954         switch(c) {
14955           case'K':
14956               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14957               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14958               board[CASTLING][2] = whiteKingFile;
14959               break;
14960           case'Q':
14961               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14962               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14963               board[CASTLING][2] = whiteKingFile;
14964               break;
14965           case'k':
14966               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14967               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14968               board[CASTLING][5] = blackKingFile;
14969               break;
14970           case'q':
14971               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14972               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14973               board[CASTLING][5] = blackKingFile;
14974           case '-':
14975               break;
14976           default: /* FRC castlings */
14977               if(c >= 'a') { /* black rights */
14978                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14979                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14980                   if(i == BOARD_RGHT) break;
14981                   board[CASTLING][5] = i;
14982                   c -= AAA;
14983                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14984                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14985                   if(c > i)
14986                       board[CASTLING][3] = c;
14987                   else
14988                       board[CASTLING][4] = c;
14989               } else { /* white rights */
14990                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14991                     if(board[0][i] == WhiteKing) break;
14992                   if(i == BOARD_RGHT) break;
14993                   board[CASTLING][2] = i;
14994                   c -= AAA - 'a' + 'A';
14995                   if(board[0][c] >= WhiteKing) break;
14996                   if(c > i)
14997                       board[CASTLING][0] = c;
14998                   else
14999                       board[CASTLING][1] = c;
15000               }
15001         }
15002       }
15003       for(i=0; i<nrCastlingRights; i++)
15004         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15005     if (appData.debugMode) {
15006         fprintf(debugFP, "FEN castling rights:");
15007         for(i=0; i<nrCastlingRights; i++)
15008         fprintf(debugFP, " %d", board[CASTLING][i]);
15009         fprintf(debugFP, "\n");
15010     }
15011
15012       while(*p==' ') p++;
15013     }
15014
15015     /* read e.p. field in games that know e.p. capture */
15016     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15017        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
15018       if(*p=='-') {
15019         p++; board[EP_STATUS] = EP_NONE;
15020       } else {
15021          char c = *p++ - AAA;
15022
15023          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15024          if(*p >= '0' && *p <='9') *p++;
15025          board[EP_STATUS] = c;
15026       }
15027     }
15028
15029
15030     if(sscanf(p, "%d", &i) == 1) {
15031         FENrulePlies = i; /* 50-move ply counter */
15032         /* (The move number is still ignored)    */
15033     }
15034
15035     return TRUE;
15036 }
15037       
15038 void
15039 EditPositionPasteFEN(char *fen)
15040 {
15041   if (fen != NULL) {
15042     Board initial_position;
15043
15044     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15045       DisplayError(_("Bad FEN position in clipboard"), 0);
15046       return ;
15047     } else {
15048       int savedBlackPlaysFirst = blackPlaysFirst;
15049       EditPositionEvent();
15050       blackPlaysFirst = savedBlackPlaysFirst;
15051       CopyBoard(boards[0], initial_position);
15052       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15053       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15054       DisplayBothClocks();
15055       DrawPosition(FALSE, boards[currentMove]);
15056     }
15057   }
15058 }
15059
15060 static char cseq[12] = "\\   ";
15061
15062 Boolean set_cont_sequence(char *new_seq)
15063 {
15064     int len;
15065     Boolean ret;
15066
15067     // handle bad attempts to set the sequence
15068         if (!new_seq)
15069                 return 0; // acceptable error - no debug
15070
15071     len = strlen(new_seq);
15072     ret = (len > 0) && (len < sizeof(cseq));
15073     if (ret)
15074         strcpy(cseq, new_seq);
15075     else if (appData.debugMode)
15076         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15077     return ret;
15078 }
15079
15080 /*
15081     reformat a source message so words don't cross the width boundary.  internal
15082     newlines are not removed.  returns the wrapped size (no null character unless
15083     included in source message).  If dest is NULL, only calculate the size required
15084     for the dest buffer.  lp argument indicats line position upon entry, and it's
15085     passed back upon exit.
15086 */
15087 int wrap(char *dest, char *src, int count, int width, int *lp)
15088 {
15089     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15090
15091     cseq_len = strlen(cseq);
15092     old_line = line = *lp;
15093     ansi = len = clen = 0;
15094
15095     for (i=0; i < count; i++)
15096     {
15097         if (src[i] == '\033')
15098             ansi = 1;
15099
15100         // if we hit the width, back up
15101         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15102         {
15103             // store i & len in case the word is too long
15104             old_i = i, old_len = len;
15105
15106             // find the end of the last word
15107             while (i && src[i] != ' ' && src[i] != '\n')
15108             {
15109                 i--;
15110                 len--;
15111             }
15112
15113             // word too long?  restore i & len before splitting it
15114             if ((old_i-i+clen) >= width)
15115             {
15116                 i = old_i;
15117                 len = old_len;
15118             }
15119
15120             // extra space?
15121             if (i && src[i-1] == ' ')
15122                 len--;
15123
15124             if (src[i] != ' ' && src[i] != '\n')
15125             {
15126                 i--;
15127                 if (len)
15128                     len--;
15129             }
15130
15131             // now append the newline and continuation sequence
15132             if (dest)
15133                 dest[len] = '\n';
15134             len++;
15135             if (dest)
15136                 strncpy(dest+len, cseq, cseq_len);
15137             len += cseq_len;
15138             line = cseq_len;
15139             clen = cseq_len;
15140             continue;
15141         }
15142
15143         if (dest)
15144             dest[len] = src[i];
15145         len++;
15146         if (!ansi)
15147             line++;
15148         if (src[i] == '\n')
15149             line = 0;
15150         if (src[i] == 'm')
15151             ansi = 0;
15152     }
15153     if (dest && appData.debugMode)
15154     {
15155         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15156             count, width, line, len, *lp);
15157         show_bytes(debugFP, src, count);
15158         fprintf(debugFP, "\ndest: ");
15159         show_bytes(debugFP, dest, len);
15160         fprintf(debugFP, "\n");
15161     }
15162     *lp = dest ? line : old_line;
15163
15164     return len;
15165 }
15166
15167 // [HGM] vari: routines for shelving variations
15168
15169 void 
15170 PushTail(int firstMove, int lastMove)
15171 {
15172         int i, j, nrMoves = lastMove - firstMove;
15173
15174         if(appData.icsActive) { // only in local mode
15175                 forwardMostMove = currentMove; // mimic old ICS behavior
15176                 return;
15177         }
15178         if(storedGames >= MAX_VARIATIONS-1) return;
15179
15180         // push current tail of game on stack
15181         savedResult[storedGames] = gameInfo.result;
15182         savedDetails[storedGames] = gameInfo.resultDetails;
15183         gameInfo.resultDetails = NULL;
15184         savedFirst[storedGames] = firstMove;
15185         savedLast [storedGames] = lastMove;
15186         savedFramePtr[storedGames] = framePtr;
15187         framePtr -= nrMoves; // reserve space for the boards
15188         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15189             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15190             for(j=0; j<MOVE_LEN; j++)
15191                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15192             for(j=0; j<2*MOVE_LEN; j++)
15193                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15194             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15195             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15196             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15197             pvInfoList[firstMove+i-1].depth = 0;
15198             commentList[framePtr+i] = commentList[firstMove+i];
15199             commentList[firstMove+i] = NULL;
15200         }
15201
15202         storedGames++;
15203         forwardMostMove = firstMove; // truncate game so we can start variation
15204         if(storedGames == 1) GreyRevert(FALSE);
15205 }
15206
15207 Boolean
15208 PopTail(Boolean annotate)
15209 {
15210         int i, j, nrMoves;
15211         char buf[8000], moveBuf[20];
15212
15213         if(appData.icsActive) return FALSE; // only in local mode
15214         if(!storedGames) return FALSE; // sanity
15215         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15216
15217         storedGames--;
15218         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15219         nrMoves = savedLast[storedGames] - currentMove;
15220         if(annotate) {
15221                 int cnt = 10;
15222                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15223                 else strcpy(buf, "(");
15224                 for(i=currentMove; i<forwardMostMove; i++) {
15225                         if(WhiteOnMove(i))
15226                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15227                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15228                         strcat(buf, moveBuf);
15229                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15230                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15231                 }
15232                 strcat(buf, ")");
15233         }
15234         for(i=1; i<=nrMoves; i++) { // copy last variation back
15235             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15236             for(j=0; j<MOVE_LEN; j++)
15237                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15238             for(j=0; j<2*MOVE_LEN; j++)
15239                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15240             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15241             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15242             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15243             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15244             commentList[currentMove+i] = commentList[framePtr+i];
15245             commentList[framePtr+i] = NULL;
15246         }
15247         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15248         framePtr = savedFramePtr[storedGames];
15249         gameInfo.result = savedResult[storedGames];
15250         if(gameInfo.resultDetails != NULL) {
15251             free(gameInfo.resultDetails);
15252       }
15253         gameInfo.resultDetails = savedDetails[storedGames];
15254         forwardMostMove = currentMove + nrMoves;
15255         if(storedGames == 0) GreyRevert(TRUE);
15256         return TRUE;
15257 }
15258
15259 void 
15260 CleanupTail()
15261 {       // remove all shelved variations
15262         int i;
15263         for(i=0; i<storedGames; i++) {
15264             if(savedDetails[i])
15265                 free(savedDetails[i]);
15266             savedDetails[i] = NULL;
15267         }
15268         for(i=framePtr; i<MAX_MOVES; i++) {
15269                 if(commentList[i]) free(commentList[i]);
15270                 commentList[i] = NULL;
15271         }
15272         framePtr = MAX_MOVES-1;
15273         storedGames = 0;
15274 }
15275
15276 void
15277 LoadVariation(int index, char *text)
15278 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15279         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15280         int level = 0, move;
15281
15282         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15283         // first find outermost bracketing variation
15284         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15285             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15286                 if(*p == '{') wait = '}'; else
15287                 if(*p == '[') wait = ']'; else
15288                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15289                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15290             }
15291             if(*p == wait) wait = NULLCHAR; // closing ]} found
15292             p++;
15293         }
15294         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15295         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15296         end[1] = NULLCHAR; // clip off comment beyond variation
15297         ToNrEvent(currentMove-1);
15298         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15299         // kludge: use ParsePV() to append variation to game
15300         move = currentMove;
15301         ParsePV(start, TRUE);
15302         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15303         ClearPremoveHighlights();
15304         CommentPopDown();
15305         ToNrEvent(currentMove+1);
15306 }
15307