Let second click on piece make only capture, with -oneClickMove
[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((void));
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
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
254 int chattingPartner;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
256
257 /* States for ics_getting_history */
258 #define H_FALSE 0
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
264
265 /* whosays values for GameEnds */
266 #define GE_ICS 0
267 #define GE_ENGINE 1
268 #define GE_PLAYER 2
269 #define GE_FILE 3
270 #define GE_XBOARD 4
271 #define GE_ENGINE1 5
272 #define GE_ENGINE2 6
273
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
276
277 /* Different types of move when calling RegisterMove */
278 #define CMAIL_MOVE   0
279 #define CMAIL_RESIGN 1
280 #define CMAIL_DRAW   2
281 #define CMAIL_ACCEPT 3
282
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
287
288 /* Telnet protocol constants */
289 #define TN_WILL 0373
290 #define TN_WONT 0374
291 #define TN_DO   0375
292 #define TN_DONT 0376
293 #define TN_IAC  0377
294 #define TN_ECHO 0001
295 #define TN_SGA  0003
296 #define TN_PORT 23
297
298 /* [AS] */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
300 {
301     assert( dst != NULL );
302     assert( src != NULL );
303     assert( count > 0 );
304
305     strncpy( dst, src, count );
306     dst[ count-1 ] = '\0';
307     return dst;
308 }
309
310 /* Some compiler can't cast u64 to double
311  * This function do the job for us:
312
313  * We use the highest bit for cast, this only
314  * works if the highest bit is not
315  * in use (This should not happen)
316  *
317  * We used this for all compiler
318  */
319 double
320 u64ToDouble(u64 value)
321 {
322   double r;
323   u64 tmp = value & u64Const(0x7fffffffffffffff);
324   r = (double)(s64)tmp;
325   if (value & u64Const(0x8000000000000000))
326        r +=  9.2233720368547758080e18; /* 2^63 */
327  return r;
328 }
329
330 /* Fake up flags for now, as we aren't keeping track of castling
331    availability yet. [HGM] Change of logic: the flag now only
332    indicates the type of castlings allowed by the rule of the game.
333    The actual rights themselves are maintained in the array
334    castlingRights, as part of the game history, and are not probed
335    by this function.
336  */
337 int
338 PosFlags(index)
339 {
340   int flags = F_ALL_CASTLE_OK;
341   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342   switch (gameInfo.variant) {
343   case VariantSuicide:
344     flags &= ~F_ALL_CASTLE_OK;
345   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346     flags |= F_IGNORE_CHECK;
347   case VariantLosers:
348     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
349     break;
350   case VariantAtomic:
351     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
352     break;
353   case VariantKriegspiel:
354     flags |= F_KRIEGSPIEL_CAPTURE;
355     break;
356   case VariantCapaRandom: 
357   case VariantFischeRandom:
358     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359   case VariantNoCastle:
360   case VariantShatranj:
361   case VariantCourier:
362   case VariantMakruk:
363     flags &= ~F_ALL_CASTLE_OK;
364     break;
365   default:
366     break;
367   }
368   return flags;
369 }
370
371 FILE *gameFileFP, *debugFP;
372
373 /* 
374     [AS] Note: sometimes, the sscanf() function is used to parse the input
375     into a fixed-size buffer. Because of this, we must be prepared to
376     receive strings as long as the size of the input buffer, which is currently
377     set to 4K for Windows and 8K for the rest.
378     So, we must either allocate sufficiently large buffers here, or
379     reduce the size of the input buffer in the input reading part.
380 */
381
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
385
386 ChessProgramState first, second;
387
388 /* premove variables */
389 int premoveToX = 0;
390 int premoveToY = 0;
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
394 int gotPremove = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
397
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
400
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
428 int movesPerSession;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
433 int matchGame = 0;
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
437
438 /* animateTraining preserves the state of appData.animate
439  * when Training mode is activated. This allows the
440  * response to be animated when appData.animate == TRUE and
441  * appData.animateDragging == TRUE.
442  */
443 Boolean animateTraining;
444
445 GameInfo gameInfo;
446
447 AppData appData;
448
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char  initialRights[BOARD_FILES];
453 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int   initialRulePlies, FENrulePlies;
455 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
456 int loadFlag = 0; 
457 int shuffleOpenings;
458 int mute; // mute all sounds
459
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
463 int storedGames = 0;
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
469
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
473
474 ChessSquare  FIDEArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackKing, BlackKnight, BlackRook }
486 };
487
488 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
489     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491     { BlackRook, BlackMan, BlackBishop, BlackQueen,
492         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
493 };
494
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
500 };
501
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackMan, BlackFerz,
513         BlackKing, BlackMan, BlackKnight, BlackRook }
514 };
515
516
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
523 };
524
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
530 };
531
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
534         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
536         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
537 };
538
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
541         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
543         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
544 };
545
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
548         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
550         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
551 };
552
553 #ifdef GOTHIC
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
556         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
558         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !GOTHIC
561 #define GothicArray CapablancaArray
562 #endif // !GOTHIC
563
564 #ifdef FALCON
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
567         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
569         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
570 };
571 #else // !FALCON
572 #define FalconArray CapablancaArray
573 #endif // !FALCON
574
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
581
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
588 };
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
592
593
594 Board initialPosition;
595
596
597 /* Convert str to a rating. Checks for special cases of "----",
598
599    "++++", etc. Also strips ()'s */
600 int
601 string_to_rating(str)
602   char *str;
603 {
604   while(*str && !isdigit(*str)) ++str;
605   if (!*str)
606     return 0;   /* One of the special "no rating" cases */
607   else
608     return atoi(str);
609 }
610
611 void
612 ClearProgramStats()
613 {
614     /* Init programStats */
615     programStats.movelist[0] = 0;
616     programStats.depth = 0;
617     programStats.nr_moves = 0;
618     programStats.moves_left = 0;
619     programStats.nodes = 0;
620     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
621     programStats.score = 0;
622     programStats.got_only_move = 0;
623     programStats.got_fail = 0;
624     programStats.line_is_book = 0;
625 }
626
627 void
628 InitBackEnd1()
629 {
630     int matched, min, sec;
631
632     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
633
634     GetTimeMark(&programStartTime);
635     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
636
637     ClearProgramStats();
638     programStats.ok_to_send = 1;
639     programStats.seen_stat = 0;
640
641     /*
642      * Initialize game list
643      */
644     ListNew(&gameList);
645
646
647     /*
648      * Internet chess server status
649      */
650     if (appData.icsActive) {
651         appData.matchMode = FALSE;
652         appData.matchGames = 0;
653 #if ZIPPY       
654         appData.noChessProgram = !appData.zippyPlay;
655 #else
656         appData.zippyPlay = FALSE;
657         appData.zippyTalk = FALSE;
658         appData.noChessProgram = TRUE;
659 #endif
660         if (*appData.icsHelper != NULLCHAR) {
661             appData.useTelnet = TRUE;
662             appData.telnetProgram = appData.icsHelper;
663         }
664     } else {
665         appData.zippyTalk = appData.zippyPlay = FALSE;
666     }
667
668     /* [AS] Initialize pv info list [HGM] and game state */
669     {
670         int i, j;
671
672         for( i=0; i<=framePtr; i++ ) {
673             pvInfoList[i].depth = -1;
674             boards[i][EP_STATUS] = EP_NONE;
675             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
676         }
677     }
678
679     /*
680      * Parse timeControl resource
681      */
682     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683                           appData.movesPerSession)) {
684         char buf[MSG_SIZ];
685         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686         DisplayFatalError(buf, 0, 2);
687     }
688
689     /*
690      * Parse searchTime resource
691      */
692     if (*appData.searchTime != NULLCHAR) {
693         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
694         if (matched == 1) {
695             searchTime = min * 60;
696         } else if (matched == 2) {
697             searchTime = min * 60 + sec;
698         } else {
699             char buf[MSG_SIZ];
700             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701             DisplayFatalError(buf, 0, 2);
702         }
703     }
704
705     /* [AS] Adjudication threshold */
706     adjudicateLossThreshold = appData.adjudicateLossThreshold;
707     
708     first.which = "first";
709     second.which = "second";
710     first.maybeThinking = second.maybeThinking = FALSE;
711     first.pr = second.pr = NoProc;
712     first.isr = second.isr = NULL;
713     first.sendTime = second.sendTime = 2;
714     first.sendDrawOffers = 1;
715     if (appData.firstPlaysBlack) {
716         first.twoMachinesColor = "black\n";
717         second.twoMachinesColor = "white\n";
718     } else {
719         first.twoMachinesColor = "white\n";
720         second.twoMachinesColor = "black\n";
721     }
722     first.program = appData.firstChessProgram;
723     second.program = appData.secondChessProgram;
724     first.host = appData.firstHost;
725     second.host = appData.secondHost;
726     first.dir = appData.firstDirectory;
727     second.dir = appData.secondDirectory;
728     first.other = &second;
729     second.other = &first;
730     first.initString = appData.initString;
731     second.initString = appData.secondInitString;
732     first.computerString = appData.firstComputerString;
733     second.computerString = appData.secondComputerString;
734     first.useSigint = second.useSigint = TRUE;
735     first.useSigterm = second.useSigterm = TRUE;
736     first.reuse = appData.reuseFirst;
737     second.reuse = appData.reuseSecond;
738     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
739     second.nps = appData.secondNPS;
740     first.useSetboard = second.useSetboard = FALSE;
741     first.useSAN = second.useSAN = FALSE;
742     first.usePing = second.usePing = FALSE;
743     first.lastPing = second.lastPing = 0;
744     first.lastPong = second.lastPong = 0;
745     first.usePlayother = second.usePlayother = FALSE;
746     first.useColors = second.useColors = TRUE;
747     first.useUsermove = second.useUsermove = FALSE;
748     first.sendICS = second.sendICS = FALSE;
749     first.sendName = second.sendName = appData.icsActive;
750     first.sdKludge = second.sdKludge = FALSE;
751     first.stKludge = second.stKludge = FALSE;
752     TidyProgramName(first.program, first.host, first.tidy);
753     TidyProgramName(second.program, second.host, second.tidy);
754     first.matchWins = second.matchWins = 0;
755     strcpy(first.variants, appData.variant);
756     strcpy(second.variants, appData.variant);
757     first.analysisSupport = second.analysisSupport = 2; /* detect */
758     first.analyzing = second.analyzing = FALSE;
759     first.initDone = second.initDone = FALSE;
760
761     /* New features added by Tord: */
762     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764     /* End of new features added by Tord. */
765     first.fenOverride  = appData.fenOverride1;
766     second.fenOverride = appData.fenOverride2;
767
768     /* [HGM] time odds: set factor for each machine */
769     first.timeOdds  = appData.firstTimeOdds;
770     second.timeOdds = appData.secondTimeOdds;
771     { float norm = 1;
772         if(appData.timeOddsMode) {
773             norm = first.timeOdds;
774             if(norm > second.timeOdds) norm = second.timeOdds;
775         }
776         first.timeOdds /= norm;
777         second.timeOdds /= norm;
778     }
779
780     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781     first.accumulateTC = appData.firstAccumulateTC;
782     second.accumulateTC = appData.secondAccumulateTC;
783     first.maxNrOfSessions = second.maxNrOfSessions = 1;
784
785     /* [HGM] debug */
786     first.debug = second.debug = FALSE;
787     first.supportsNPS = second.supportsNPS = UNKNOWN;
788
789     /* [HGM] options */
790     first.optionSettings  = appData.firstOptions;
791     second.optionSettings = appData.secondOptions;
792
793     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795     first.isUCI = appData.firstIsUCI; /* [AS] */
796     second.isUCI = appData.secondIsUCI; /* [AS] */
797     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
799
800     if (appData.firstProtocolVersion > PROTOVER ||
801         appData.firstProtocolVersion < 1) {
802       char buf[MSG_SIZ];
803       sprintf(buf, _("protocol version %d not supported"),
804               appData.firstProtocolVersion);
805       DisplayFatalError(buf, 0, 2);
806     } else {
807       first.protocolVersion = appData.firstProtocolVersion;
808     }
809
810     if (appData.secondProtocolVersion > PROTOVER ||
811         appData.secondProtocolVersion < 1) {
812       char buf[MSG_SIZ];
813       sprintf(buf, _("protocol version %d not supported"),
814               appData.secondProtocolVersion);
815       DisplayFatalError(buf, 0, 2);
816     } else {
817       second.protocolVersion = appData.secondProtocolVersion;
818     }
819
820     if (appData.icsActive) {
821         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
822 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824         appData.clockMode = FALSE;
825         first.sendTime = second.sendTime = 0;
826     }
827     
828 #if ZIPPY
829     /* Override some settings from environment variables, for backward
830        compatibility.  Unfortunately it's not feasible to have the env
831        vars just set defaults, at least in xboard.  Ugh.
832     */
833     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
834       ZippyInit();
835     }
836 #endif
837     
838     if (appData.noChessProgram) {
839         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840         sprintf(programVersion, "%s", PACKAGE_STRING);
841     } else {
842       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
845     }
846
847     if (!appData.icsActive) {
848       char buf[MSG_SIZ];
849       /* Check for variants that are supported only in ICS mode,
850          or not at all.  Some that are accepted here nevertheless
851          have bugs; see comments below.
852       */
853       VariantClass variant = StringToVariant(appData.variant);
854       switch (variant) {
855       case VariantBughouse:     /* need four players and two boards */
856       case VariantKriegspiel:   /* need to hide pieces and move details */
857       /* case VariantFischeRandom: (Fabien: moved below) */
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859         DisplayFatalError(buf, 0, 2);
860         return;
861
862       case VariantUnknown:
863       case VariantLoadable:
864       case Variant29:
865       case Variant30:
866       case Variant31:
867       case Variant32:
868       case Variant33:
869       case Variant34:
870       case Variant35:
871       case Variant36:
872       default:
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);
874         DisplayFatalError(buf, 0, 2);
875         return;
876
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */
879       case VariantGothic:     /* [HGM] should work */
880       case VariantCapablanca: /* [HGM] should work */
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */
882       case VariantShogi:      /* [HGM] drops not tested for legality */
883       case VariantKnightmate: /* [HGM] should work */
884       case VariantCylinder:   /* [HGM] untested */
885       case VariantFalcon:     /* [HGM] untested */
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887                                  offboard interposition not understood */
888       case VariantNormal:     /* definitely works! */
889       case VariantWildCastle: /* pieces not automatically shuffled */
890       case VariantNoCastle:   /* pieces not automatically shuffled */
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892       case VariantLosers:     /* should work except for win condition,
893                                  and doesn't know captures are mandatory */
894       case VariantSuicide:    /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantGiveaway:   /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantTwoKings:   /* should work */
899       case VariantAtomic:     /* should work except for win condition */
900       case Variant3Check:     /* should work except for win condition */
901       case VariantShatranj:   /* should work except for all win conditions */
902       case VariantMakruk:     /* should work except for daw countdown */
903       case VariantBerolina:   /* might work if TestLegality is off */
904       case VariantCapaRandom: /* should work */
905       case VariantJanus:      /* should work */
906       case VariantSuper:      /* experimental */
907       case VariantGreat:      /* experimental, requires legality testing to be off */
908         break;
909       }
910     }
911
912     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
913     InitEngineUCI( installDir, &second );
914 }
915
916 int NextIntegerFromString( char ** str, long * value )
917 {
918     int result = -1;
919     char * s = *str;
920
921     while( *s == ' ' || *s == '\t' ) {
922         s++;
923     }
924
925     *value = 0;
926
927     if( *s >= '0' && *s <= '9' ) {
928         while( *s >= '0' && *s <= '9' ) {
929             *value = *value * 10 + (*s - '0');
930             s++;
931         }
932
933         result = 0;
934     }
935
936     *str = s;
937
938     return result;
939 }
940
941 int NextTimeControlFromString( char ** str, long * value )
942 {
943     long temp;
944     int result = NextIntegerFromString( str, &temp );
945
946     if( result == 0 ) {
947         *value = temp * 60; /* Minutes */
948         if( **str == ':' ) {
949             (*str)++;
950             result = NextIntegerFromString( str, &temp );
951             *value += temp; /* Seconds */
952         }
953     }
954
955     return result;
956 }
957
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
960     int result = -1; long temp, temp2;
961
962     if(**str != '+') return -1; // old params remain in force!
963     (*str)++;
964     if( NextTimeControlFromString( str, &temp ) ) return -1;
965
966     if(**str != '/') {
967         /* time only: incremental or sudden-death time control */
968         if(**str == '+') { /* increment follows; read it */
969             (*str)++;
970             if(result = NextIntegerFromString( str, &temp2)) return -1;
971             *inc = temp2 * 1000;
972         } else *inc = 0;
973         *moves = 0; *tc = temp * 1000; 
974         return 0;
975     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
976
977     (*str)++; /* classical time control */
978     result = NextTimeControlFromString( str, &temp2);
979     if(result == 0) {
980         *moves = temp/60;
981         *tc    = temp2 * 1000;
982         *inc   = 0;
983     }
984     return result;
985 }
986
987 int GetTimeQuota(int movenr)
988 {   /* [HGM] get time to add from the multi-session time-control string */
989     int moves=1; /* kludge to force reading of first session */
990     long time, increment;
991     char *s = fullTimeControlString;
992
993     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994     do {
995         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997         if(movenr == -1) return time;    /* last move before new session     */
998         if(!moves) return increment;     /* current session is incremental   */
999         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000     } while(movenr >= -1);               /* try again for next session       */
1001
1002     return 0; // no new time quota on this move
1003 }
1004
1005 int
1006 ParseTimeControl(tc, ti, mps)
1007      char *tc;
1008      int ti;
1009      int mps;
1010 {
1011   long tc1;
1012   long tc2;
1013   char buf[MSG_SIZ];
1014   
1015   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1016   if(ti > 0) {
1017     if(mps)
1018       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019     else sprintf(buf, "+%s+%d", tc, ti);
1020   } else {
1021     if(mps)
1022              sprintf(buf, "+%d/%s", mps, tc);
1023     else sprintf(buf, "+%s", tc);
1024   }
1025   fullTimeControlString = StrSave(buf);
1026   
1027   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1028     return FALSE;
1029   }
1030   
1031   if( *tc == '/' ) {
1032     /* Parse second time control */
1033     tc++;
1034     
1035     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1036       return FALSE;
1037     }
1038     
1039     if( tc2 == 0 ) {
1040       return FALSE;
1041     }
1042     
1043     timeControl_2 = tc2 * 1000;
1044   }
1045   else {
1046     timeControl_2 = 0;
1047   }
1048   
1049   if( tc1 == 0 ) {
1050     return FALSE;
1051   }
1052   
1053   timeControl = tc1 * 1000;
1054   
1055   if (ti >= 0) {
1056     timeIncrement = ti * 1000;  /* convert to ms */
1057     movesPerSession = 0;
1058   } else {
1059     timeIncrement = 0;
1060     movesPerSession = mps;
1061   }
1062   return TRUE;
1063 }
1064
1065 void
1066 InitBackEnd2()
1067 {
1068     if (appData.debugMode) {
1069         fprintf(debugFP, "%s\n", programVersion);
1070     }
1071
1072     set_cont_sequence(appData.wrapContSeq);
1073     if (appData.matchGames > 0) {
1074         appData.matchMode = TRUE;
1075     } else if (appData.matchMode) {
1076         appData.matchGames = 1;
1077     }
1078     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079         appData.matchGames = appData.sameColorGames;
1080     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1083     }
1084     Reset(TRUE, FALSE);
1085     if (appData.noChessProgram || first.protocolVersion == 1) {
1086       InitBackEnd3();
1087     } else {
1088       /* kludge: allow timeout for initial "feature" commands */
1089       FreezeUI();
1090       DisplayMessage("", _("Starting chess program"));
1091       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1092     }
1093 }
1094
1095 void
1096 InitBackEnd3 P((void))
1097 {
1098     GameMode initialMode;
1099     char buf[MSG_SIZ];
1100     int err;
1101
1102     InitChessProgram(&first, startedFromSetupPosition);
1103
1104
1105     if (appData.icsActive) {
1106 #ifdef WIN32
1107         /* [DM] Make a console window if needed [HGM] merged ifs */
1108         ConsoleCreate(); 
1109 #endif
1110         err = establish();
1111         if (err != 0) {
1112             if (*appData.icsCommPort != NULLCHAR) {
1113                 sprintf(buf, _("Could not open comm port %s"),  
1114                         appData.icsCommPort);
1115             } else {
1116                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1117                         appData.icsHost, appData.icsPort);
1118             }
1119             DisplayFatalError(buf, err, 1);
1120             return;
1121         }
1122         SetICSMode();
1123         telnetISR =
1124           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1125         fromUserISR =
1126           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1127         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1128             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1129     } else if (appData.noChessProgram) {
1130         SetNCPMode();
1131     } else {
1132         SetGNUMode();
1133     }
1134
1135     if (*appData.cmailGameName != NULLCHAR) {
1136         SetCmailMode();
1137         OpenLoopback(&cmailPR);
1138         cmailISR =
1139           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1140     }
1141     
1142     ThawUI();
1143     DisplayMessage("", "");
1144     if (StrCaseCmp(appData.initialMode, "") == 0) {
1145       initialMode = BeginningOfGame;
1146     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1147       initialMode = TwoMachinesPlay;
1148     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1149       initialMode = AnalyzeFile; 
1150     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1151       initialMode = AnalyzeMode;
1152     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1153       initialMode = MachinePlaysWhite;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1155       initialMode = MachinePlaysBlack;
1156     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1157       initialMode = EditGame;
1158     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1159       initialMode = EditPosition;
1160     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1161       initialMode = Training;
1162     } else {
1163       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1164       DisplayFatalError(buf, 0, 2);
1165       return;
1166     }
1167
1168     if (appData.matchMode) {
1169         /* Set up machine vs. machine match */
1170         if (appData.noChessProgram) {
1171             DisplayFatalError(_("Can't have a match with no chess programs"),
1172                               0, 2);
1173             return;
1174         }
1175         matchMode = TRUE;
1176         matchGame = 1;
1177         if (*appData.loadGameFile != NULLCHAR) {
1178             int index = appData.loadGameIndex; // [HGM] autoinc
1179             if(index<0) lastIndex = index = 1;
1180             if (!LoadGameFromFile(appData.loadGameFile,
1181                                   index,
1182                                   appData.loadGameFile, FALSE)) {
1183                 DisplayFatalError(_("Bad game file"), 0, 1);
1184                 return;
1185             }
1186         } else if (*appData.loadPositionFile != NULLCHAR) {
1187             int index = appData.loadPositionIndex; // [HGM] autoinc
1188             if(index<0) lastIndex = index = 1;
1189             if (!LoadPositionFromFile(appData.loadPositionFile,
1190                                       index,
1191                                       appData.loadPositionFile)) {
1192                 DisplayFatalError(_("Bad position file"), 0, 1);
1193                 return;
1194             }
1195         }
1196         TwoMachinesEvent();
1197     } else if (*appData.cmailGameName != NULLCHAR) {
1198         /* Set up cmail mode */
1199         ReloadCmailMsgEvent(TRUE);
1200     } else {
1201         /* Set up other modes */
1202         if (initialMode == AnalyzeFile) {
1203           if (*appData.loadGameFile == NULLCHAR) {
1204             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1205             return;
1206           }
1207         }
1208         if (*appData.loadGameFile != NULLCHAR) {
1209             (void) LoadGameFromFile(appData.loadGameFile,
1210                                     appData.loadGameIndex,
1211                                     appData.loadGameFile, TRUE);
1212         } else if (*appData.loadPositionFile != NULLCHAR) {
1213             (void) LoadPositionFromFile(appData.loadPositionFile,
1214                                         appData.loadPositionIndex,
1215                                         appData.loadPositionFile);
1216             /* [HGM] try to make self-starting even after FEN load */
1217             /* to allow automatic setup of fairy variants with wtm */
1218             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1219                 gameMode = BeginningOfGame;
1220                 setboardSpoiledMachineBlack = 1;
1221             }
1222             /* [HGM] loadPos: make that every new game uses the setup */
1223             /* from file as long as we do not switch variant          */
1224             if(!blackPlaysFirst) {
1225                 startedFromPositionFile = TRUE;
1226                 CopyBoard(filePosition, boards[0]);
1227             }
1228         }
1229         if (initialMode == AnalyzeMode) {
1230           if (appData.noChessProgram) {
1231             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1236             return;
1237           }
1238           AnalyzeModeEvent();
1239         } else if (initialMode == AnalyzeFile) {
1240           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1241           ShowThinkingEvent();
1242           AnalyzeFileEvent();
1243           AnalysisPeriodicEvent(1);
1244         } else if (initialMode == MachinePlaysWhite) {
1245           if (appData.noChessProgram) {
1246             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1247                               0, 2);
1248             return;
1249           }
1250           if (appData.icsActive) {
1251             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1252                               0, 2);
1253             return;
1254           }
1255           MachineWhiteEvent();
1256         } else if (initialMode == MachinePlaysBlack) {
1257           if (appData.noChessProgram) {
1258             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1259                               0, 2);
1260             return;
1261           }
1262           if (appData.icsActive) {
1263             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1264                               0, 2);
1265             return;
1266           }
1267           MachineBlackEvent();
1268         } else if (initialMode == TwoMachinesPlay) {
1269           if (appData.noChessProgram) {
1270             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1271                               0, 2);
1272             return;
1273           }
1274           if (appData.icsActive) {
1275             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1276                               0, 2);
1277             return;
1278           }
1279           TwoMachinesEvent();
1280         } else if (initialMode == EditGame) {
1281           EditGameEvent();
1282         } else if (initialMode == EditPosition) {
1283           EditPositionEvent();
1284         } else if (initialMode == Training) {
1285           if (*appData.loadGameFile == NULLCHAR) {
1286             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1287             return;
1288           }
1289           TrainingEvent();
1290         }
1291     }
1292 }
1293
1294 /*
1295  * Establish will establish a contact to a remote host.port.
1296  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1297  *  used to talk to the host.
1298  * Returns 0 if okay, error code if not.
1299  */
1300 int
1301 establish()
1302 {
1303     char buf[MSG_SIZ];
1304
1305     if (*appData.icsCommPort != NULLCHAR) {
1306         /* Talk to the host through a serial comm port */
1307         return OpenCommPort(appData.icsCommPort, &icsPR);
1308
1309     } else if (*appData.gateway != NULLCHAR) {
1310         if (*appData.remoteShell == NULLCHAR) {
1311             /* Use the rcmd protocol to run telnet program on a gateway host */
1312             snprintf(buf, sizeof(buf), "%s %s %s",
1313                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1314             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1315
1316         } else {
1317             /* Use the rsh program to run telnet program on a gateway host */
1318             if (*appData.remoteUser == NULLCHAR) {
1319                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1320                         appData.gateway, appData.telnetProgram,
1321                         appData.icsHost, appData.icsPort);
1322             } else {
1323                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1324                         appData.remoteShell, appData.gateway, 
1325                         appData.remoteUser, appData.telnetProgram,
1326                         appData.icsHost, appData.icsPort);
1327             }
1328             return StartChildProcess(buf, "", &icsPR);
1329
1330         }
1331     } else if (appData.useTelnet) {
1332         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1333
1334     } else {
1335         /* TCP socket interface differs somewhat between
1336            Unix and NT; handle details in the front end.
1337            */
1338         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1339     }
1340 }
1341
1342 void
1343 show_bytes(fp, buf, count)
1344      FILE *fp;
1345      char *buf;
1346      int count;
1347 {
1348     while (count--) {
1349         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1350             fprintf(fp, "\\%03o", *buf & 0xff);
1351         } else {
1352             putc(*buf, fp);
1353         }
1354         buf++;
1355     }
1356     fflush(fp);
1357 }
1358
1359 /* Returns an errno value */
1360 int
1361 OutputMaybeTelnet(pr, message, count, outError)
1362      ProcRef pr;
1363      char *message;
1364      int count;
1365      int *outError;
1366 {
1367     char buf[8192], *p, *q, *buflim;
1368     int left, newcount, outcount;
1369
1370     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1371         *appData.gateway != NULLCHAR) {
1372         if (appData.debugMode) {
1373             fprintf(debugFP, ">ICS: ");
1374             show_bytes(debugFP, message, count);
1375             fprintf(debugFP, "\n");
1376         }
1377         return OutputToProcess(pr, message, count, outError);
1378     }
1379
1380     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1381     p = message;
1382     q = buf;
1383     left = count;
1384     newcount = 0;
1385     while (left) {
1386         if (q >= buflim) {
1387             if (appData.debugMode) {
1388                 fprintf(debugFP, ">ICS: ");
1389                 show_bytes(debugFP, buf, newcount);
1390                 fprintf(debugFP, "\n");
1391             }
1392             outcount = OutputToProcess(pr, buf, newcount, outError);
1393             if (outcount < newcount) return -1; /* to be sure */
1394             q = buf;
1395             newcount = 0;
1396         }
1397         if (*p == '\n') {
1398             *q++ = '\r';
1399             newcount++;
1400         } else if (((unsigned char) *p) == TN_IAC) {
1401             *q++ = (char) TN_IAC;
1402             newcount ++;
1403         }
1404         *q++ = *p++;
1405         newcount++;
1406         left--;
1407     }
1408     if (appData.debugMode) {
1409         fprintf(debugFP, ">ICS: ");
1410         show_bytes(debugFP, buf, newcount);
1411         fprintf(debugFP, "\n");
1412     }
1413     outcount = OutputToProcess(pr, buf, newcount, outError);
1414     if (outcount < newcount) return -1; /* to be sure */
1415     return count;
1416 }
1417
1418 void
1419 read_from_player(isr, closure, message, count, error)
1420      InputSourceRef isr;
1421      VOIDSTAR closure;
1422      char *message;
1423      int count;
1424      int error;
1425 {
1426     int outError, outCount;
1427     static int gotEof = 0;
1428
1429     /* Pass data read from player on to ICS */
1430     if (count > 0) {
1431         gotEof = 0;
1432         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1433         if (outCount < count) {
1434             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1435         }
1436     } else if (count < 0) {
1437         RemoveInputSource(isr);
1438         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1439     } else if (gotEof++ > 0) {
1440         RemoveInputSource(isr);
1441         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1442     }
1443 }
1444
1445 void
1446 KeepAlive()
1447 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1448     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1449     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1450     SendToICS("date\n");
1451     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1452 }
1453
1454 /* added routine for printf style output to ics */
1455 void ics_printf(char *format, ...)
1456 {
1457     char buffer[MSG_SIZ];
1458     va_list args;
1459
1460     va_start(args, format);
1461     vsnprintf(buffer, sizeof(buffer), format, args);
1462     buffer[sizeof(buffer)-1] = '\0';
1463     SendToICS(buffer);
1464     va_end(args);
1465 }
1466
1467 void
1468 SendToICS(s)
1469      char *s;
1470 {
1471     int count, outCount, outError;
1472
1473     if (icsPR == NULL) return;
1474
1475     count = strlen(s);
1476     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1477     if (outCount < count) {
1478         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1479     }
1480 }
1481
1482 /* This is used for sending logon scripts to the ICS. Sending
1483    without a delay causes problems when using timestamp on ICC
1484    (at least on my machine). */
1485 void
1486 SendToICSDelayed(s,msdelay)
1487      char *s;
1488      long msdelay;
1489 {
1490     int count, outCount, outError;
1491
1492     if (icsPR == NULL) return;
1493
1494     count = strlen(s);
1495     if (appData.debugMode) {
1496         fprintf(debugFP, ">ICS: ");
1497         show_bytes(debugFP, s, count);
1498         fprintf(debugFP, "\n");
1499     }
1500     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1501                                       msdelay);
1502     if (outCount < count) {
1503         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1504     }
1505 }
1506
1507
1508 /* Remove all highlighting escape sequences in s
1509    Also deletes any suffix starting with '(' 
1510    */
1511 char *
1512 StripHighlightAndTitle(s)
1513      char *s;
1514 {
1515     static char retbuf[MSG_SIZ];
1516     char *p = retbuf;
1517
1518     while (*s != NULLCHAR) {
1519         while (*s == '\033') {
1520             while (*s != NULLCHAR && !isalpha(*s)) s++;
1521             if (*s != NULLCHAR) s++;
1522         }
1523         while (*s != NULLCHAR && *s != '\033') {
1524             if (*s == '(' || *s == '[') {
1525                 *p = NULLCHAR;
1526                 return retbuf;
1527             }
1528             *p++ = *s++;
1529         }
1530     }
1531     *p = NULLCHAR;
1532     return retbuf;
1533 }
1534
1535 /* Remove all highlighting escape sequences in s */
1536 char *
1537 StripHighlight(s)
1538      char *s;
1539 {
1540     static char retbuf[MSG_SIZ];
1541     char *p = retbuf;
1542
1543     while (*s != NULLCHAR) {
1544         while (*s == '\033') {
1545             while (*s != NULLCHAR && !isalpha(*s)) s++;
1546             if (*s != NULLCHAR) s++;
1547         }
1548         while (*s != NULLCHAR && *s != '\033') {
1549             *p++ = *s++;
1550         }
1551     }
1552     *p = NULLCHAR;
1553     return retbuf;
1554 }
1555
1556 char *variantNames[] = VARIANT_NAMES;
1557 char *
1558 VariantName(v)
1559      VariantClass v;
1560 {
1561     return variantNames[v];
1562 }
1563
1564
1565 /* Identify a variant from the strings the chess servers use or the
1566    PGN Variant tag names we use. */
1567 VariantClass
1568 StringToVariant(e)
1569      char *e;
1570 {
1571     char *p;
1572     int wnum = -1;
1573     VariantClass v = VariantNormal;
1574     int i, found = FALSE;
1575     char buf[MSG_SIZ];
1576
1577     if (!e) return v;
1578
1579     /* [HGM] skip over optional board-size prefixes */
1580     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1581         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1582         while( *e++ != '_');
1583     }
1584
1585     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1586         v = VariantNormal;
1587         found = TRUE;
1588     } else
1589     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1590       if (StrCaseStr(e, variantNames[i])) {
1591         v = (VariantClass) i;
1592         found = TRUE;
1593         break;
1594       }
1595     }
1596
1597     if (!found) {
1598       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1599           || StrCaseStr(e, "wild/fr") 
1600           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1601         v = VariantFischeRandom;
1602       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1603                  (i = 1, p = StrCaseStr(e, "w"))) {
1604         p += i;
1605         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1606         if (isdigit(*p)) {
1607           wnum = atoi(p);
1608         } else {
1609           wnum = -1;
1610         }
1611         switch (wnum) {
1612         case 0: /* FICS only, actually */
1613         case 1:
1614           /* Castling legal even if K starts on d-file */
1615           v = VariantWildCastle;
1616           break;
1617         case 2:
1618         case 3:
1619         case 4:
1620           /* Castling illegal even if K & R happen to start in
1621              normal positions. */
1622           v = VariantNoCastle;
1623           break;
1624         case 5:
1625         case 7:
1626         case 8:
1627         case 10:
1628         case 11:
1629         case 12:
1630         case 13:
1631         case 14:
1632         case 15:
1633         case 18:
1634         case 19:
1635           /* Castling legal iff K & R start in normal positions */
1636           v = VariantNormal;
1637           break;
1638         case 6:
1639         case 20:
1640         case 21:
1641           /* Special wilds for position setup; unclear what to do here */
1642           v = VariantLoadable;
1643           break;
1644         case 9:
1645           /* Bizarre ICC game */
1646           v = VariantTwoKings;
1647           break;
1648         case 16:
1649           v = VariantKriegspiel;
1650           break;
1651         case 17:
1652           v = VariantLosers;
1653           break;
1654         case 22:
1655           v = VariantFischeRandom;
1656           break;
1657         case 23:
1658           v = VariantCrazyhouse;
1659           break;
1660         case 24:
1661           v = VariantBughouse;
1662           break;
1663         case 25:
1664           v = Variant3Check;
1665           break;
1666         case 26:
1667           /* Not quite the same as FICS suicide! */
1668           v = VariantGiveaway;
1669           break;
1670         case 27:
1671           v = VariantAtomic;
1672           break;
1673         case 28:
1674           v = VariantShatranj;
1675           break;
1676
1677         /* Temporary names for future ICC types.  The name *will* change in 
1678            the next xboard/WinBoard release after ICC defines it. */
1679         case 29:
1680           v = Variant29;
1681           break;
1682         case 30:
1683           v = Variant30;
1684           break;
1685         case 31:
1686           v = Variant31;
1687           break;
1688         case 32:
1689           v = Variant32;
1690           break;
1691         case 33:
1692           v = Variant33;
1693           break;
1694         case 34:
1695           v = Variant34;
1696           break;
1697         case 35:
1698           v = Variant35;
1699           break;
1700         case 36:
1701           v = Variant36;
1702           break;
1703         case 37:
1704           v = VariantShogi;
1705           break;
1706         case 38:
1707           v = VariantXiangqi;
1708           break;
1709         case 39:
1710           v = VariantCourier;
1711           break;
1712         case 40:
1713           v = VariantGothic;
1714           break;
1715         case 41:
1716           v = VariantCapablanca;
1717           break;
1718         case 42:
1719           v = VariantKnightmate;
1720           break;
1721         case 43:
1722           v = VariantFairy;
1723           break;
1724         case 44:
1725           v = VariantCylinder;
1726           break;
1727         case 45:
1728           v = VariantFalcon;
1729           break;
1730         case 46:
1731           v = VariantCapaRandom;
1732           break;
1733         case 47:
1734           v = VariantBerolina;
1735           break;
1736         case 48:
1737           v = VariantJanus;
1738           break;
1739         case 49:
1740           v = VariantSuper;
1741           break;
1742         case 50:
1743           v = VariantGreat;
1744           break;
1745         case -1:
1746           /* Found "wild" or "w" in the string but no number;
1747              must assume it's normal chess. */
1748           v = VariantNormal;
1749           break;
1750         default:
1751           sprintf(buf, _("Unknown wild type %d"), wnum);
1752           DisplayError(buf, 0);
1753           v = VariantUnknown;
1754           break;
1755         }
1756       }
1757     }
1758     if (appData.debugMode) {
1759       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1760               e, wnum, VariantName(v));
1761     }
1762     return v;
1763 }
1764
1765 static int leftover_start = 0, leftover_len = 0;
1766 char star_match[STAR_MATCH_N][MSG_SIZ];
1767
1768 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1769    advance *index beyond it, and set leftover_start to the new value of
1770    *index; else return FALSE.  If pattern contains the character '*', it
1771    matches any sequence of characters not containing '\r', '\n', or the
1772    character following the '*' (if any), and the matched sequence(s) are
1773    copied into star_match.
1774    */
1775 int
1776 looking_at(buf, index, pattern)
1777      char *buf;
1778      int *index;
1779      char *pattern;
1780 {
1781     char *bufp = &buf[*index], *patternp = pattern;
1782     int star_count = 0;
1783     char *matchp = star_match[0];
1784     
1785     for (;;) {
1786         if (*patternp == NULLCHAR) {
1787             *index = leftover_start = bufp - buf;
1788             *matchp = NULLCHAR;
1789             return TRUE;
1790         }
1791         if (*bufp == NULLCHAR) return FALSE;
1792         if (*patternp == '*') {
1793             if (*bufp == *(patternp + 1)) {
1794                 *matchp = NULLCHAR;
1795                 matchp = star_match[++star_count];
1796                 patternp += 2;
1797                 bufp++;
1798                 continue;
1799             } else if (*bufp == '\n' || *bufp == '\r') {
1800                 patternp++;
1801                 if (*patternp == NULLCHAR)
1802                   continue;
1803                 else
1804                   return FALSE;
1805             } else {
1806                 *matchp++ = *bufp++;
1807                 continue;
1808             }
1809         }
1810         if (*patternp != *bufp) return FALSE;
1811         patternp++;
1812         bufp++;
1813     }
1814 }
1815
1816 void
1817 SendToPlayer(data, length)
1818      char *data;
1819      int length;
1820 {
1821     int error, outCount;
1822     outCount = OutputToProcess(NoProc, data, length, &error);
1823     if (outCount < length) {
1824         DisplayFatalError(_("Error writing to display"), error, 1);
1825     }
1826 }
1827
1828 void
1829 PackHolding(packed, holding)
1830      char packed[];
1831      char *holding;
1832 {
1833     char *p = holding;
1834     char *q = packed;
1835     int runlength = 0;
1836     int curr = 9999;
1837     do {
1838         if (*p == curr) {
1839             runlength++;
1840         } else {
1841             switch (runlength) {
1842               case 0:
1843                 break;
1844               case 1:
1845                 *q++ = curr;
1846                 break;
1847               case 2:
1848                 *q++ = curr;
1849                 *q++ = curr;
1850                 break;
1851               default:
1852                 sprintf(q, "%d", runlength);
1853                 while (*q) q++;
1854                 *q++ = curr;
1855                 break;
1856             }
1857             runlength = 1;
1858             curr = *p;
1859         }
1860     } while (*p++);
1861     *q = NULLCHAR;
1862 }
1863
1864 /* Telnet protocol requests from the front end */
1865 void
1866 TelnetRequest(ddww, option)
1867      unsigned char ddww, option;
1868 {
1869     unsigned char msg[3];
1870     int outCount, outError;
1871
1872     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1873
1874     if (appData.debugMode) {
1875         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1876         switch (ddww) {
1877           case TN_DO:
1878             ddwwStr = "DO";
1879             break;
1880           case TN_DONT:
1881             ddwwStr = "DONT";
1882             break;
1883           case TN_WILL:
1884             ddwwStr = "WILL";
1885             break;
1886           case TN_WONT:
1887             ddwwStr = "WONT";
1888             break;
1889           default:
1890             ddwwStr = buf1;
1891             sprintf(buf1, "%d", ddww);
1892             break;
1893         }
1894         switch (option) {
1895           case TN_ECHO:
1896             optionStr = "ECHO";
1897             break;
1898           default:
1899             optionStr = buf2;
1900             sprintf(buf2, "%d", option);
1901             break;
1902         }
1903         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1904     }
1905     msg[0] = TN_IAC;
1906     msg[1] = ddww;
1907     msg[2] = option;
1908     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1909     if (outCount < 3) {
1910         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911     }
1912 }
1913
1914 void
1915 DoEcho()
1916 {
1917     if (!appData.icsActive) return;
1918     TelnetRequest(TN_DO, TN_ECHO);
1919 }
1920
1921 void
1922 DontEcho()
1923 {
1924     if (!appData.icsActive) return;
1925     TelnetRequest(TN_DONT, TN_ECHO);
1926 }
1927
1928 void
1929 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1930 {
1931     /* put the holdings sent to us by the server on the board holdings area */
1932     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1933     char p;
1934     ChessSquare piece;
1935
1936     if(gameInfo.holdingsWidth < 2)  return;
1937     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1938         return; // prevent overwriting by pre-board holdings
1939
1940     if( (int)lowestPiece >= BlackPawn ) {
1941         holdingsColumn = 0;
1942         countsColumn = 1;
1943         holdingsStartRow = BOARD_HEIGHT-1;
1944         direction = -1;
1945     } else {
1946         holdingsColumn = BOARD_WIDTH-1;
1947         countsColumn = BOARD_WIDTH-2;
1948         holdingsStartRow = 0;
1949         direction = 1;
1950     }
1951
1952     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1953         board[i][holdingsColumn] = EmptySquare;
1954         board[i][countsColumn]   = (ChessSquare) 0;
1955     }
1956     while( (p=*holdings++) != NULLCHAR ) {
1957         piece = CharToPiece( ToUpper(p) );
1958         if(piece == EmptySquare) continue;
1959         /*j = (int) piece - (int) WhitePawn;*/
1960         j = PieceToNumber(piece);
1961         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1962         if(j < 0) continue;               /* should not happen */
1963         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1964         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1965         board[holdingsStartRow+j*direction][countsColumn]++;
1966     }
1967 }
1968
1969
1970 void
1971 VariantSwitch(Board board, VariantClass newVariant)
1972 {
1973    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1974    Board oldBoard;
1975
1976    startedFromPositionFile = FALSE;
1977    if(gameInfo.variant == newVariant) return;
1978
1979    /* [HGM] This routine is called each time an assignment is made to
1980     * gameInfo.variant during a game, to make sure the board sizes
1981     * are set to match the new variant. If that means adding or deleting
1982     * holdings, we shift the playing board accordingly
1983     * This kludge is needed because in ICS observe mode, we get boards
1984     * of an ongoing game without knowing the variant, and learn about the
1985     * latter only later. This can be because of the move list we requested,
1986     * in which case the game history is refilled from the beginning anyway,
1987     * but also when receiving holdings of a crazyhouse game. In the latter
1988     * case we want to add those holdings to the already received position.
1989     */
1990
1991    
1992    if (appData.debugMode) {
1993      fprintf(debugFP, "Switch board from %s to %s\n",
1994              VariantName(gameInfo.variant), VariantName(newVariant));
1995      setbuf(debugFP, NULL);
1996    }
1997    shuffleOpenings = 0;       /* [HGM] shuffle */
1998    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1999    switch(newVariant) 
2000      {
2001      case VariantShogi:
2002        newWidth = 9;  newHeight = 9;
2003        gameInfo.holdingsSize = 7;
2004      case VariantBughouse:
2005      case VariantCrazyhouse:
2006        newHoldingsWidth = 2; break;
2007      case VariantGreat:
2008        newWidth = 10;
2009      case VariantSuper:
2010        newHoldingsWidth = 2;
2011        gameInfo.holdingsSize = 8;
2012        break;
2013      case VariantGothic:
2014      case VariantCapablanca:
2015      case VariantCapaRandom:
2016        newWidth = 10;
2017      default:
2018        newHoldingsWidth = gameInfo.holdingsSize = 0;
2019      };
2020    
2021    if(newWidth  != gameInfo.boardWidth  ||
2022       newHeight != gameInfo.boardHeight ||
2023       newHoldingsWidth != gameInfo.holdingsWidth ) {
2024      
2025      /* shift position to new playing area, if needed */
2026      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2027        for(i=0; i<BOARD_HEIGHT; i++) 
2028          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2029            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2030              board[i][j];
2031        for(i=0; i<newHeight; i++) {
2032          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2033          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2034        }
2035      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2036        for(i=0; i<BOARD_HEIGHT; i++)
2037          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2038            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2039              board[i][j];
2040      }
2041      gameInfo.boardWidth  = newWidth;
2042      gameInfo.boardHeight = newHeight;
2043      gameInfo.holdingsWidth = newHoldingsWidth;
2044      gameInfo.variant = newVariant;
2045      InitDrawingSizes(-2, 0);
2046    } else gameInfo.variant = newVariant;
2047    CopyBoard(oldBoard, board);   // remember correctly formatted board
2048      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2049    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2050 }
2051
2052 static int loggedOn = FALSE;
2053
2054 /*-- Game start info cache: --*/
2055 int gs_gamenum;
2056 char gs_kind[MSG_SIZ];
2057 static char player1Name[128] = "";
2058 static char player2Name[128] = "";
2059 static char cont_seq[] = "\n\\   ";
2060 static int player1Rating = -1;
2061 static int player2Rating = -1;
2062 /*----------------------------*/
2063
2064 ColorClass curColor = ColorNormal;
2065 int suppressKibitz = 0;
2066
2067 // [HGM] seekgraph
2068 Boolean soughtPending = FALSE;
2069 Boolean seekGraphUp;
2070 #define MAX_SEEK_ADS 200
2071 #define SQUARE 0x80
2072 char *seekAdList[MAX_SEEK_ADS];
2073 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2074 float tcList[MAX_SEEK_ADS];
2075 char colorList[MAX_SEEK_ADS];
2076 int nrOfSeekAds = 0;
2077 int minRating = 1010, maxRating = 2800;
2078 int hMargin = 10, vMargin = 20, h, w;
2079 extern int squareSize, lineGap;
2080
2081 void
2082 PlotSeekAd(int i)
2083 {
2084         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2085         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2086         if(r < minRating+100 && r >=0 ) r = minRating+100;
2087         if(r > maxRating) r = maxRating;
2088         if(tc < 1.) tc = 1.;
2089         if(tc > 95.) tc = 95.;
2090         x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2091         y = ((double)r - minRating)/(maxRating - minRating)
2092             * (h-vMargin-squareSize/8-1) + vMargin;
2093         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2094         if(strstr(seekAdList[i], " u ")) color = 1;
2095         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2096            !strstr(seekAdList[i], "bullet") &&
2097            !strstr(seekAdList[i], "blitz") &&
2098            !strstr(seekAdList[i], "standard") ) color = 2;
2099         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2100         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2101 }
2102
2103 void
2104 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2105 {
2106         char buf[MSG_SIZ], *ext = "";
2107         VariantClass v = StringToVariant(type);
2108         if(strstr(type, "wild")) {
2109             ext = type + 4; // append wild number
2110             if(v == VariantFischeRandom) type = "chess960"; else
2111             if(v == VariantLoadable) type = "setup"; else
2112             type = VariantName(v);
2113         }
2114         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2115         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2116             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2117             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2118             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2119             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2120             seekNrList[nrOfSeekAds] = nr;
2121             zList[nrOfSeekAds] = 0;
2122             seekAdList[nrOfSeekAds++] = StrSave(buf);
2123             if(plot) PlotSeekAd(nrOfSeekAds-1);
2124         }
2125 }
2126
2127 void
2128 EraseSeekDot(int i)
2129 {
2130     int x = xList[i], y = yList[i], d=squareSize/4, k;
2131     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2132     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2133     // now replot every dot that overlapped
2134     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2135         int xx = xList[k], yy = yList[k];
2136         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2137             DrawSeekDot(xx, yy, colorList[k]);
2138     }
2139 }
2140
2141 void
2142 RemoveSeekAd(int nr)
2143 {
2144         int i;
2145         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2146             EraseSeekDot(i);
2147             if(seekAdList[i]) free(seekAdList[i]);
2148             seekAdList[i] = seekAdList[--nrOfSeekAds];
2149             seekNrList[i] = seekNrList[nrOfSeekAds];
2150             ratingList[i] = ratingList[nrOfSeekAds];
2151             colorList[i]  = colorList[nrOfSeekAds];
2152             tcList[i] = tcList[nrOfSeekAds];
2153             xList[i]  = xList[nrOfSeekAds];
2154             yList[i]  = yList[nrOfSeekAds];
2155             zList[i]  = zList[nrOfSeekAds];
2156             seekAdList[nrOfSeekAds] = NULL;
2157             break;
2158         }
2159 }
2160
2161 Boolean
2162 MatchSoughtLine(char *line)
2163 {
2164     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2165     int nr, base, inc, u=0; char dummy;
2166
2167     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2168        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2169        (u=1) &&
2170        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2171         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2172         // match: compact and save the line
2173         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2174         return TRUE;
2175     }
2176     return FALSE;
2177 }
2178
2179 int
2180 DrawSeekGraph()
2181 {
2182     if(!seekGraphUp) return FALSE;
2183     int i;
2184     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2185     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2186
2187     DrawSeekBackground(0, 0, w, h);
2188     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2189     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2190     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2191         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2192         yy = h-1-yy;
2193         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2194         if(i%500 == 0) {
2195             char buf[MSG_SIZ];
2196             sprintf(buf, "%d", i);
2197             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2198         }
2199     }
2200     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2201     for(i=1; i<100; i+=(i<10?1:5)) {
2202         int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2203         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2204         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2205             char buf[MSG_SIZ];
2206             sprintf(buf, "%d", i);
2207             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2208         }
2209     }
2210     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2211     return TRUE;
2212 }
2213
2214 int SeekGraphClick(ClickType click, int x, int y, int moving)
2215 {
2216     static int lastDown = 0, displayed = 0, lastSecond;
2217     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2218         if(click == Release || moving) return FALSE;
2219         nrOfSeekAds = 0;
2220         soughtPending = TRUE;
2221         SendToICS(ics_prefix);
2222         SendToICS("sought\n"); // should this be "sought all"?
2223     } else { // issue challenge based on clicked ad
2224         int dist = 10000; int i, closest = 0, second = 0;
2225         for(i=0; i<nrOfSeekAds; i++) {
2226             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2227             if(d < dist) { dist = d; closest = i; }
2228             second += (d - zList[i] < 120); // count in-range ads
2229             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2230         }
2231         if(dist < 120) {
2232             char buf[MSG_SIZ];
2233             second = (second > 1);
2234             if(displayed != closest || second != lastSecond) {
2235                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2236                 lastSecond = second; displayed = closest;
2237             }
2238             sprintf(buf, "play %d\n", seekNrList[closest]);
2239             if(click == Press) {
2240                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2241                 lastDown = closest;
2242                 return TRUE;
2243             } // on press 'hit', only show info
2244             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2245             SendToICS(ics_prefix);
2246             SendToICS(buf); // should this be "sought all"?
2247         } else if(click == Release) { // release 'miss' is ignored
2248             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2249             if(moving == 2) { // right up-click
2250                 nrOfSeekAds = 0; // refresh graph
2251                 soughtPending = TRUE;
2252                 SendToICS(ics_prefix);
2253                 SendToICS("sought\n"); // should this be "sought all"?
2254             }
2255             return TRUE;
2256         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2257         // press miss or release hit 'pop down' seek graph
2258         seekGraphUp = FALSE;
2259         DrawPosition(TRUE, NULL);
2260     }
2261     return TRUE;
2262 }
2263
2264 void
2265 read_from_ics(isr, closure, data, count, error)
2266      InputSourceRef isr;
2267      VOIDSTAR closure;
2268      char *data;
2269      int count;
2270      int error;
2271 {
2272 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2273 #define STARTED_NONE 0
2274 #define STARTED_MOVES 1
2275 #define STARTED_BOARD 2
2276 #define STARTED_OBSERVE 3
2277 #define STARTED_HOLDINGS 4
2278 #define STARTED_CHATTER 5
2279 #define STARTED_COMMENT 6
2280 #define STARTED_MOVES_NOHIDE 7
2281     
2282     static int started = STARTED_NONE;
2283     static char parse[20000];
2284     static int parse_pos = 0;
2285     static char buf[BUF_SIZE + 1];
2286     static int firstTime = TRUE, intfSet = FALSE;
2287     static ColorClass prevColor = ColorNormal;
2288     static int savingComment = FALSE;
2289     static int cmatch = 0; // continuation sequence match
2290     char *bp;
2291     char str[500];
2292     int i, oldi;
2293     int buf_len;
2294     int next_out;
2295     int tkind;
2296     int backup;    /* [DM] For zippy color lines */
2297     char *p;
2298     char talker[MSG_SIZ]; // [HGM] chat
2299     int channel;
2300
2301     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2302
2303     if (appData.debugMode) {
2304       if (!error) {
2305         fprintf(debugFP, "<ICS: ");
2306         show_bytes(debugFP, data, count);
2307         fprintf(debugFP, "\n");
2308       }
2309     }
2310
2311     if (appData.debugMode) { int f = forwardMostMove;
2312         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2313                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2314                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2315     }
2316     if (count > 0) {
2317         /* If last read ended with a partial line that we couldn't parse,
2318            prepend it to the new read and try again. */
2319         if (leftover_len > 0) {
2320             for (i=0; i<leftover_len; i++)
2321               buf[i] = buf[leftover_start + i];
2322         }
2323
2324     /* copy new characters into the buffer */
2325     bp = buf + leftover_len;
2326     buf_len=leftover_len;
2327     for (i=0; i<count; i++)
2328     {
2329         // ignore these
2330         if (data[i] == '\r')
2331             continue;
2332
2333         // join lines split by ICS?
2334         if (!appData.noJoin)
2335         {
2336             /*
2337                 Joining just consists of finding matches against the
2338                 continuation sequence, and discarding that sequence
2339                 if found instead of copying it.  So, until a match
2340                 fails, there's nothing to do since it might be the
2341                 complete sequence, and thus, something we don't want
2342                 copied.
2343             */
2344             if (data[i] == cont_seq[cmatch])
2345             {
2346                 cmatch++;
2347                 if (cmatch == strlen(cont_seq))
2348                 {
2349                     cmatch = 0; // complete match.  just reset the counter
2350
2351                     /*
2352                         it's possible for the ICS to not include the space
2353                         at the end of the last word, making our [correct]
2354                         join operation fuse two separate words.  the server
2355                         does this when the space occurs at the width setting.
2356                     */
2357                     if (!buf_len || buf[buf_len-1] != ' ')
2358                     {
2359                         *bp++ = ' ';
2360                         buf_len++;
2361                     }
2362                 }
2363                 continue;
2364             }
2365             else if (cmatch)
2366             {
2367                 /*
2368                     match failed, so we have to copy what matched before
2369                     falling through and copying this character.  In reality,
2370                     this will only ever be just the newline character, but
2371                     it doesn't hurt to be precise.
2372                 */
2373                 strncpy(bp, cont_seq, cmatch);
2374                 bp += cmatch;
2375                 buf_len += cmatch;
2376                 cmatch = 0;
2377             }
2378         }
2379
2380         // copy this char
2381         *bp++ = data[i];
2382         buf_len++;
2383     }
2384
2385         buf[buf_len] = NULLCHAR;
2386 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2387         next_out = 0;
2388         leftover_start = 0;
2389         
2390         i = 0;
2391         while (i < buf_len) {
2392             /* Deal with part of the TELNET option negotiation
2393                protocol.  We refuse to do anything beyond the
2394                defaults, except that we allow the WILL ECHO option,
2395                which ICS uses to turn off password echoing when we are
2396                directly connected to it.  We reject this option
2397                if localLineEditing mode is on (always on in xboard)
2398                and we are talking to port 23, which might be a real
2399                telnet server that will try to keep WILL ECHO on permanently.
2400              */
2401             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2402                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2403                 unsigned char option;
2404                 oldi = i;
2405                 switch ((unsigned char) buf[++i]) {
2406                   case TN_WILL:
2407                     if (appData.debugMode)
2408                       fprintf(debugFP, "\n<WILL ");
2409                     switch (option = (unsigned char) buf[++i]) {
2410                       case TN_ECHO:
2411                         if (appData.debugMode)
2412                           fprintf(debugFP, "ECHO ");
2413                         /* Reply only if this is a change, according
2414                            to the protocol rules. */
2415                         if (remoteEchoOption) break;
2416                         if (appData.localLineEditing &&
2417                             atoi(appData.icsPort) == TN_PORT) {
2418                             TelnetRequest(TN_DONT, TN_ECHO);
2419                         } else {
2420                             EchoOff();
2421                             TelnetRequest(TN_DO, TN_ECHO);
2422                             remoteEchoOption = TRUE;
2423                         }
2424                         break;
2425                       default:
2426                         if (appData.debugMode)
2427                           fprintf(debugFP, "%d ", option);
2428                         /* Whatever this is, we don't want it. */
2429                         TelnetRequest(TN_DONT, option);
2430                         break;
2431                     }
2432                     break;
2433                   case TN_WONT:
2434                     if (appData.debugMode)
2435                       fprintf(debugFP, "\n<WONT ");
2436                     switch (option = (unsigned char) buf[++i]) {
2437                       case TN_ECHO:
2438                         if (appData.debugMode)
2439                           fprintf(debugFP, "ECHO ");
2440                         /* Reply only if this is a change, according
2441                            to the protocol rules. */
2442                         if (!remoteEchoOption) break;
2443                         EchoOn();
2444                         TelnetRequest(TN_DONT, TN_ECHO);
2445                         remoteEchoOption = FALSE;
2446                         break;
2447                       default:
2448                         if (appData.debugMode)
2449                           fprintf(debugFP, "%d ", (unsigned char) option);
2450                         /* Whatever this is, it must already be turned
2451                            off, because we never agree to turn on
2452                            anything non-default, so according to the
2453                            protocol rules, we don't reply. */
2454                         break;
2455                     }
2456                     break;
2457                   case TN_DO:
2458                     if (appData.debugMode)
2459                       fprintf(debugFP, "\n<DO ");
2460                     switch (option = (unsigned char) buf[++i]) {
2461                       default:
2462                         /* Whatever this is, we refuse to do it. */
2463                         if (appData.debugMode)
2464                           fprintf(debugFP, "%d ", option);
2465                         TelnetRequest(TN_WONT, option);
2466                         break;
2467                     }
2468                     break;
2469                   case TN_DONT:
2470                     if (appData.debugMode)
2471                       fprintf(debugFP, "\n<DONT ");
2472                     switch (option = (unsigned char) buf[++i]) {
2473                       default:
2474                         if (appData.debugMode)
2475                           fprintf(debugFP, "%d ", option);
2476                         /* Whatever this is, we are already not doing
2477                            it, because we never agree to do anything
2478                            non-default, so according to the protocol
2479                            rules, we don't reply. */
2480                         break;
2481                     }
2482                     break;
2483                   case TN_IAC:
2484                     if (appData.debugMode)
2485                       fprintf(debugFP, "\n<IAC ");
2486                     /* Doubled IAC; pass it through */
2487                     i--;
2488                     break;
2489                   default:
2490                     if (appData.debugMode)
2491                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2492                     /* Drop all other telnet commands on the floor */
2493                     break;
2494                 }
2495                 if (oldi > next_out)
2496                   SendToPlayer(&buf[next_out], oldi - next_out);
2497                 if (++i > next_out)
2498                   next_out = i;
2499                 continue;
2500             }
2501                 
2502             /* OK, this at least will *usually* work */
2503             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2504                 loggedOn = TRUE;
2505             }
2506             
2507             if (loggedOn && !intfSet) {
2508                 if (ics_type == ICS_ICC) {
2509                   sprintf(str,
2510                           "/set-quietly interface %s\n/set-quietly style 12\n",
2511                           programVersion);
2512                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2513                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2514                 } else if (ics_type == ICS_CHESSNET) {
2515                   sprintf(str, "/style 12\n");
2516                 } else {
2517                   strcpy(str, "alias $ @\n$set interface ");
2518                   strcat(str, programVersion);
2519                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2520                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2521                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2522 #ifdef WIN32
2523                   strcat(str, "$iset nohighlight 1\n");
2524 #endif
2525                   strcat(str, "$iset lock 1\n$style 12\n");
2526                 }
2527                 SendToICS(str);
2528                 NotifyFrontendLogin();
2529                 intfSet = TRUE;
2530             }
2531
2532             if (started == STARTED_COMMENT) {
2533                 /* Accumulate characters in comment */
2534                 parse[parse_pos++] = buf[i];
2535                 if (buf[i] == '\n') {
2536                     parse[parse_pos] = NULLCHAR;
2537                     if(chattingPartner>=0) {
2538                         char mess[MSG_SIZ];
2539                         sprintf(mess, "%s%s", talker, parse);
2540                         OutputChatMessage(chattingPartner, mess);
2541                         chattingPartner = -1;
2542                     } else
2543                     if(!suppressKibitz) // [HGM] kibitz
2544                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2545                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2546                         int nrDigit = 0, nrAlph = 0, j;
2547                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2548                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2549                         parse[parse_pos] = NULLCHAR;
2550                         // try to be smart: if it does not look like search info, it should go to
2551                         // ICS interaction window after all, not to engine-output window.
2552                         for(j=0; j<parse_pos; j++) { // count letters and digits
2553                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2554                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2555                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2556                         }
2557                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2558                             int depth=0; float score;
2559                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2560                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2561                                 pvInfoList[forwardMostMove-1].depth = depth;
2562                                 pvInfoList[forwardMostMove-1].score = 100*score;
2563                             }
2564                             OutputKibitz(suppressKibitz, parse);
2565                             next_out = i+1; // [HGM] suppress printing in ICS window
2566                         } else {
2567                             char tmp[MSG_SIZ];
2568                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2569                             SendToPlayer(tmp, strlen(tmp));
2570                         }
2571                     }
2572                     started = STARTED_NONE;
2573                 } else {
2574                     /* Don't match patterns against characters in comment */
2575                     i++;
2576                     continue;
2577                 }
2578             }
2579             if (started == STARTED_CHATTER) {
2580                 if (buf[i] != '\n') {
2581                     /* Don't match patterns against characters in chatter */
2582                     i++;
2583                     continue;
2584                 }
2585                 started = STARTED_NONE;
2586             }
2587
2588             /* Kludge to deal with rcmd protocol */
2589             if (firstTime && looking_at(buf, &i, "\001*")) {
2590                 DisplayFatalError(&buf[1], 0, 1);
2591                 continue;
2592             } else {
2593                 firstTime = FALSE;
2594             }
2595
2596             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2597                 ics_type = ICS_ICC;
2598                 ics_prefix = "/";
2599                 if (appData.debugMode)
2600                   fprintf(debugFP, "ics_type %d\n", ics_type);
2601                 continue;
2602             }
2603             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2604                 ics_type = ICS_FICS;
2605                 ics_prefix = "$";
2606                 if (appData.debugMode)
2607                   fprintf(debugFP, "ics_type %d\n", ics_type);
2608                 continue;
2609             }
2610             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2611                 ics_type = ICS_CHESSNET;
2612                 ics_prefix = "/";
2613                 if (appData.debugMode)
2614                   fprintf(debugFP, "ics_type %d\n", ics_type);
2615                 continue;
2616             }
2617
2618             if (!loggedOn &&
2619                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2620                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2621                  looking_at(buf, &i, "will be \"*\""))) {
2622               strcpy(ics_handle, star_match[0]);
2623               continue;
2624             }
2625
2626             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2627               char buf[MSG_SIZ];
2628               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2629               DisplayIcsInteractionTitle(buf);
2630               have_set_title = TRUE;
2631             }
2632
2633             /* skip finger notes */
2634             if (started == STARTED_NONE &&
2635                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2636                  (buf[i] == '1' && buf[i+1] == '0')) &&
2637                 buf[i+2] == ':' && buf[i+3] == ' ') {
2638               started = STARTED_CHATTER;
2639               i += 3;
2640               continue;
2641             }
2642
2643             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2644             if(appData.seekGraph) {
2645                 if(soughtPending && MatchSoughtLine(buf+i)) {
2646                     i = strstr(buf+i, "rated") - buf;
2647                     next_out = leftover_start = i;
2648                     started = STARTED_CHATTER;
2649                     suppressKibitz = TRUE;
2650                     continue;
2651                 }
2652                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2653                         && looking_at(buf, &i, "* ads displayed")) {
2654                     soughtPending = FALSE;
2655                     seekGraphUp = TRUE;
2656                     DrawSeekGraph();
2657                     continue;
2658                 }
2659                 if(appData.autoRefresh) {
2660                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2661                         int s = (ics_type == ICS_ICC); // ICC format differs
2662                         if(seekGraphUp)
2663                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2664                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2665                         looking_at(buf, &i, "*% "); // eat prompt
2666                         next_out = i; // suppress
2667                         continue;
2668                     }
2669                     if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2670                         char *p = star_match[0];
2671                         while(*p) {
2672                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2673                             while(*p && *p++ != ' '); // next
2674                         }
2675                         looking_at(buf, &i, "*% "); // eat prompt
2676                         next_out = i;
2677                         continue;
2678                     }
2679                 }
2680             }
2681
2682             /* skip formula vars */
2683             if (started == STARTED_NONE &&
2684                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2685               started = STARTED_CHATTER;
2686               i += 3;
2687               continue;
2688             }
2689
2690             oldi = i;
2691             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2692             if (appData.autoKibitz && started == STARTED_NONE && 
2693                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2694                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2695                 if(looking_at(buf, &i, "* kibitzes: ") &&
2696                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2697                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2698                         suppressKibitz = TRUE;
2699                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2700                                 && (gameMode == IcsPlayingWhite)) ||
2701                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2702                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2703                             started = STARTED_CHATTER; // own kibitz we simply discard
2704                         else {
2705                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2706                             parse_pos = 0; parse[0] = NULLCHAR;
2707                             savingComment = TRUE;
2708                             suppressKibitz = gameMode != IcsObserving ? 2 :
2709                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2710                         } 
2711                         continue;
2712                 } else
2713                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2714                     // suppress the acknowledgements of our own autoKibitz
2715                     char *p;
2716                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2717                     SendToPlayer(star_match[0], strlen(star_match[0]));
2718                     looking_at(buf, &i, "*% "); // eat prompt
2719                     next_out = i;
2720                 }
2721             } // [HGM] kibitz: end of patch
2722
2723 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2724
2725             // [HGM] chat: intercept tells by users for which we have an open chat window
2726             channel = -1;
2727             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2728                                            looking_at(buf, &i, "* whispers:") ||
2729                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2730                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2731                 int p;
2732                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2733                 chattingPartner = -1;
2734
2735                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2736                 for(p=0; p<MAX_CHAT; p++) {
2737                     if(channel == atoi(chatPartner[p])) {
2738                     talker[0] = '['; strcat(talker, "] ");
2739                     chattingPartner = p; break;
2740                     }
2741                 } else
2742                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2743                 for(p=0; p<MAX_CHAT; p++) {
2744                     if(!strcmp("WHISPER", chatPartner[p])) {
2745                         talker[0] = '['; strcat(talker, "] ");
2746                         chattingPartner = p; break;
2747                     }
2748                 }
2749                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2750                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2751                     talker[0] = 0;
2752                     chattingPartner = p; break;
2753                 }
2754                 if(chattingPartner<0) i = oldi; else {
2755                     started = STARTED_COMMENT;
2756                     parse_pos = 0; parse[0] = NULLCHAR;
2757                     savingComment = 3 + chattingPartner; // counts as TRUE
2758                     suppressKibitz = TRUE;
2759                 }
2760             } // [HGM] chat: end of patch
2761
2762             if (appData.zippyTalk || appData.zippyPlay) {
2763                 /* [DM] Backup address for color zippy lines */
2764                 backup = i;
2765 #if ZIPPY
2766        #ifdef WIN32
2767                if (loggedOn == TRUE)
2768                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2769                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2770        #else
2771                 if (ZippyControl(buf, &i) ||
2772                     ZippyConverse(buf, &i) ||
2773                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2774                       loggedOn = TRUE;
2775                       if (!appData.colorize) continue;
2776                 }
2777        #endif
2778 #endif
2779             } // [DM] 'else { ' deleted
2780                 if (
2781                     /* Regular tells and says */
2782                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2783                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2784                     looking_at(buf, &i, "* says: ") ||
2785                     /* Don't color "message" or "messages" output */
2786                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2787                     looking_at(buf, &i, "*. * at *:*: ") ||
2788                     looking_at(buf, &i, "--* (*:*): ") ||
2789                     /* Message notifications (same color as tells) */
2790                     looking_at(buf, &i, "* has left a message ") ||
2791                     looking_at(buf, &i, "* just sent you a message:\n") ||
2792                     /* Whispers and kibitzes */
2793                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2794                     looking_at(buf, &i, "* kibitzes: ") ||
2795                     /* Channel tells */
2796                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2797
2798                   if (tkind == 1 && strchr(star_match[0], ':')) {
2799                       /* Avoid "tells you:" spoofs in channels */
2800                      tkind = 3;
2801                   }
2802                   if (star_match[0][0] == NULLCHAR ||
2803                       strchr(star_match[0], ' ') ||
2804                       (tkind == 3 && strchr(star_match[1], ' '))) {
2805                     /* Reject bogus matches */
2806                     i = oldi;
2807                   } else {
2808                     if (appData.colorize) {
2809                       if (oldi > next_out) {
2810                         SendToPlayer(&buf[next_out], oldi - next_out);
2811                         next_out = oldi;
2812                       }
2813                       switch (tkind) {
2814                       case 1:
2815                         Colorize(ColorTell, FALSE);
2816                         curColor = ColorTell;
2817                         break;
2818                       case 2:
2819                         Colorize(ColorKibitz, FALSE);
2820                         curColor = ColorKibitz;
2821                         break;
2822                       case 3:
2823                         p = strrchr(star_match[1], '(');
2824                         if (p == NULL) {
2825                           p = star_match[1];
2826                         } else {
2827                           p++;
2828                         }
2829                         if (atoi(p) == 1) {
2830                           Colorize(ColorChannel1, FALSE);
2831                           curColor = ColorChannel1;
2832                         } else {
2833                           Colorize(ColorChannel, FALSE);
2834                           curColor = ColorChannel;
2835                         }
2836                         break;
2837                       case 5:
2838                         curColor = ColorNormal;
2839                         break;
2840                       }
2841                     }
2842                     if (started == STARTED_NONE && appData.autoComment &&
2843                         (gameMode == IcsObserving ||
2844                          gameMode == IcsPlayingWhite ||
2845                          gameMode == IcsPlayingBlack)) {
2846                       parse_pos = i - oldi;
2847                       memcpy(parse, &buf[oldi], parse_pos);
2848                       parse[parse_pos] = NULLCHAR;
2849                       started = STARTED_COMMENT;
2850                       savingComment = TRUE;
2851                     } else {
2852                       started = STARTED_CHATTER;
2853                       savingComment = FALSE;
2854                     }
2855                     loggedOn = TRUE;
2856                     continue;
2857                   }
2858                 }
2859
2860                 if (looking_at(buf, &i, "* s-shouts: ") ||
2861                     looking_at(buf, &i, "* c-shouts: ")) {
2862                     if (appData.colorize) {
2863                         if (oldi > next_out) {
2864                             SendToPlayer(&buf[next_out], oldi - next_out);
2865                             next_out = oldi;
2866                         }
2867                         Colorize(ColorSShout, FALSE);
2868                         curColor = ColorSShout;
2869                     }
2870                     loggedOn = TRUE;
2871                     started = STARTED_CHATTER;
2872                     continue;
2873                 }
2874
2875                 if (looking_at(buf, &i, "--->")) {
2876                     loggedOn = TRUE;
2877                     continue;
2878                 }
2879
2880                 if (looking_at(buf, &i, "* shouts: ") ||
2881                     looking_at(buf, &i, "--> ")) {
2882                     if (appData.colorize) {
2883                         if (oldi > next_out) {
2884                             SendToPlayer(&buf[next_out], oldi - next_out);
2885                             next_out = oldi;
2886                         }
2887                         Colorize(ColorShout, FALSE);
2888                         curColor = ColorShout;
2889                     }
2890                     loggedOn = TRUE;
2891                     started = STARTED_CHATTER;
2892                     continue;
2893                 }
2894
2895                 if (looking_at( buf, &i, "Challenge:")) {
2896                     if (appData.colorize) {
2897                         if (oldi > next_out) {
2898                             SendToPlayer(&buf[next_out], oldi - next_out);
2899                             next_out = oldi;
2900                         }
2901                         Colorize(ColorChallenge, FALSE);
2902                         curColor = ColorChallenge;
2903                     }
2904                     loggedOn = TRUE;
2905                     continue;
2906                 }
2907
2908                 if (looking_at(buf, &i, "* offers you") ||
2909                     looking_at(buf, &i, "* offers to be") ||
2910                     looking_at(buf, &i, "* would like to") ||
2911                     looking_at(buf, &i, "* requests to") ||
2912                     looking_at(buf, &i, "Your opponent offers") ||
2913                     looking_at(buf, &i, "Your opponent requests")) {
2914
2915                     if (appData.colorize) {
2916                         if (oldi > next_out) {
2917                             SendToPlayer(&buf[next_out], oldi - next_out);
2918                             next_out = oldi;
2919                         }
2920                         Colorize(ColorRequest, FALSE);
2921                         curColor = ColorRequest;
2922                     }
2923                     continue;
2924                 }
2925
2926                 if (looking_at(buf, &i, "* (*) seeking")) {
2927                     if (appData.colorize) {
2928                         if (oldi > next_out) {
2929                             SendToPlayer(&buf[next_out], oldi - next_out);
2930                             next_out = oldi;
2931                         }
2932                         Colorize(ColorSeek, FALSE);
2933                         curColor = ColorSeek;
2934                     }
2935                     continue;
2936             }
2937
2938             if (looking_at(buf, &i, "\\   ")) {
2939                 if (prevColor != ColorNormal) {
2940                     if (oldi > next_out) {
2941                         SendToPlayer(&buf[next_out], oldi - next_out);
2942                         next_out = oldi;
2943                     }
2944                     Colorize(prevColor, TRUE);
2945                     curColor = prevColor;
2946                 }
2947                 if (savingComment) {
2948                     parse_pos = i - oldi;
2949                     memcpy(parse, &buf[oldi], parse_pos);
2950                     parse[parse_pos] = NULLCHAR;
2951                     started = STARTED_COMMENT;
2952                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2953                         chattingPartner = savingComment - 3; // kludge to remember the box
2954                 } else {
2955                     started = STARTED_CHATTER;
2956                 }
2957                 continue;
2958             }
2959
2960             if (looking_at(buf, &i, "Black Strength :") ||
2961                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2962                 looking_at(buf, &i, "<10>") ||
2963                 looking_at(buf, &i, "#@#")) {
2964                 /* Wrong board style */
2965                 loggedOn = TRUE;
2966                 SendToICS(ics_prefix);
2967                 SendToICS("set style 12\n");
2968                 SendToICS(ics_prefix);
2969                 SendToICS("refresh\n");
2970                 continue;
2971             }
2972             
2973             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2974                 ICSInitScript();
2975                 have_sent_ICS_logon = 1;
2976                 continue;
2977             }
2978               
2979             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2980                 (looking_at(buf, &i, "\n<12> ") ||
2981                  looking_at(buf, &i, "<12> "))) {
2982                 loggedOn = TRUE;
2983                 if (oldi > next_out) {
2984                     SendToPlayer(&buf[next_out], oldi - next_out);
2985                 }
2986                 next_out = i;
2987                 started = STARTED_BOARD;
2988                 parse_pos = 0;
2989                 continue;
2990             }
2991
2992             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2993                 looking_at(buf, &i, "<b1> ")) {
2994                 if (oldi > next_out) {
2995                     SendToPlayer(&buf[next_out], oldi - next_out);
2996                 }
2997                 next_out = i;
2998                 started = STARTED_HOLDINGS;
2999                 parse_pos = 0;
3000                 continue;
3001             }
3002
3003             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3004                 loggedOn = TRUE;
3005                 /* Header for a move list -- first line */
3006
3007                 switch (ics_getting_history) {
3008                   case H_FALSE:
3009                     switch (gameMode) {
3010                       case IcsIdle:
3011                       case BeginningOfGame:
3012                         /* User typed "moves" or "oldmoves" while we
3013                            were idle.  Pretend we asked for these
3014                            moves and soak them up so user can step
3015                            through them and/or save them.
3016                            */
3017                         Reset(FALSE, TRUE);
3018                         gameMode = IcsObserving;
3019                         ModeHighlight();
3020                         ics_gamenum = -1;
3021                         ics_getting_history = H_GOT_UNREQ_HEADER;
3022                         break;
3023                       case EditGame: /*?*/
3024                       case EditPosition: /*?*/
3025                         /* Should above feature work in these modes too? */
3026                         /* For now it doesn't */
3027                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3028                         break;
3029                       default:
3030                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3031                         break;
3032                     }
3033                     break;
3034                   case H_REQUESTED:
3035                     /* Is this the right one? */
3036                     if (gameInfo.white && gameInfo.black &&
3037                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3038                         strcmp(gameInfo.black, star_match[2]) == 0) {
3039                         /* All is well */
3040                         ics_getting_history = H_GOT_REQ_HEADER;
3041                     }
3042                     break;
3043                   case H_GOT_REQ_HEADER:
3044                   case H_GOT_UNREQ_HEADER:
3045                   case H_GOT_UNWANTED_HEADER:
3046                   case H_GETTING_MOVES:
3047                     /* Should not happen */
3048                     DisplayError(_("Error gathering move list: two headers"), 0);
3049                     ics_getting_history = H_FALSE;
3050                     break;
3051                 }
3052
3053                 /* Save player ratings into gameInfo if needed */
3054                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3055                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3056                     (gameInfo.whiteRating == -1 ||
3057                      gameInfo.blackRating == -1)) {
3058
3059                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3060                     gameInfo.blackRating = string_to_rating(star_match[3]);
3061                     if (appData.debugMode)
3062                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3063                               gameInfo.whiteRating, gameInfo.blackRating);
3064                 }
3065                 continue;
3066             }
3067
3068             if (looking_at(buf, &i,
3069               "* * match, initial time: * minute*, increment: * second")) {
3070                 /* Header for a move list -- second line */
3071                 /* Initial board will follow if this is a wild game */
3072                 if (gameInfo.event != NULL) free(gameInfo.event);
3073                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3074                 gameInfo.event = StrSave(str);
3075                 /* [HGM] we switched variant. Translate boards if needed. */
3076                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3077                 continue;
3078             }
3079
3080             if (looking_at(buf, &i, "Move  ")) {
3081                 /* Beginning of a move list */
3082                 switch (ics_getting_history) {
3083                   case H_FALSE:
3084                     /* Normally should not happen */
3085                     /* Maybe user hit reset while we were parsing */
3086                     break;
3087                   case H_REQUESTED:
3088                     /* Happens if we are ignoring a move list that is not
3089                      * the one we just requested.  Common if the user
3090                      * tries to observe two games without turning off
3091                      * getMoveList */
3092                     break;
3093                   case H_GETTING_MOVES:
3094                     /* Should not happen */
3095                     DisplayError(_("Error gathering move list: nested"), 0);
3096                     ics_getting_history = H_FALSE;
3097                     break;
3098                   case H_GOT_REQ_HEADER:
3099                     ics_getting_history = H_GETTING_MOVES;
3100                     started = STARTED_MOVES;
3101                     parse_pos = 0;
3102                     if (oldi > next_out) {
3103                         SendToPlayer(&buf[next_out], oldi - next_out);
3104                     }
3105                     break;
3106                   case H_GOT_UNREQ_HEADER:
3107                     ics_getting_history = H_GETTING_MOVES;
3108                     started = STARTED_MOVES_NOHIDE;
3109                     parse_pos = 0;
3110                     break;
3111                   case H_GOT_UNWANTED_HEADER:
3112                     ics_getting_history = H_FALSE;
3113                     break;
3114                 }
3115                 continue;
3116             }                           
3117             
3118             if (looking_at(buf, &i, "% ") ||
3119                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3120                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3121                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3122                     soughtPending = FALSE;
3123                     seekGraphUp = TRUE;
3124                     DrawSeekGraph();
3125                 }
3126                 if(suppressKibitz) next_out = i;
3127                 savingComment = FALSE;
3128                 suppressKibitz = 0;
3129                 switch (started) {
3130                   case STARTED_MOVES:
3131                   case STARTED_MOVES_NOHIDE:
3132                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3133                     parse[parse_pos + i - oldi] = NULLCHAR;
3134                     ParseGameHistory(parse);
3135 #if ZIPPY
3136                     if (appData.zippyPlay && first.initDone) {
3137                         FeedMovesToProgram(&first, forwardMostMove);
3138                         if (gameMode == IcsPlayingWhite) {
3139                             if (WhiteOnMove(forwardMostMove)) {
3140                                 if (first.sendTime) {
3141                                   if (first.useColors) {
3142                                     SendToProgram("black\n", &first); 
3143                                   }
3144                                   SendTimeRemaining(&first, TRUE);
3145                                 }
3146                                 if (first.useColors) {
3147                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3148                                 }
3149                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3150                                 first.maybeThinking = TRUE;
3151                             } else {
3152                                 if (first.usePlayother) {
3153                                   if (first.sendTime) {
3154                                     SendTimeRemaining(&first, TRUE);
3155                                   }
3156                                   SendToProgram("playother\n", &first);
3157                                   firstMove = FALSE;
3158                                 } else {
3159                                   firstMove = TRUE;
3160                                 }
3161                             }
3162                         } else if (gameMode == IcsPlayingBlack) {
3163                             if (!WhiteOnMove(forwardMostMove)) {
3164                                 if (first.sendTime) {
3165                                   if (first.useColors) {
3166                                     SendToProgram("white\n", &first);
3167                                   }
3168                                   SendTimeRemaining(&first, FALSE);
3169                                 }
3170                                 if (first.useColors) {
3171                                   SendToProgram("black\n", &first);
3172                                 }
3173                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3174                                 first.maybeThinking = TRUE;
3175                             } else {
3176                                 if (first.usePlayother) {
3177                                   if (first.sendTime) {
3178                                     SendTimeRemaining(&first, FALSE);
3179                                   }
3180                                   SendToProgram("playother\n", &first);
3181                                   firstMove = FALSE;
3182                                 } else {
3183                                   firstMove = TRUE;
3184                                 }
3185                             }
3186                         }                       
3187                     }
3188 #endif
3189                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3190                         /* Moves came from oldmoves or moves command
3191                            while we weren't doing anything else.
3192                            */
3193                         currentMove = forwardMostMove;
3194                         ClearHighlights();/*!!could figure this out*/
3195                         flipView = appData.flipView;
3196                         DrawPosition(TRUE, boards[currentMove]);
3197                         DisplayBothClocks();
3198                         sprintf(str, "%s vs. %s",
3199                                 gameInfo.white, gameInfo.black);
3200                         DisplayTitle(str);
3201                         gameMode = IcsIdle;
3202                     } else {
3203                         /* Moves were history of an active game */
3204                         if (gameInfo.resultDetails != NULL) {
3205                             free(gameInfo.resultDetails);
3206                             gameInfo.resultDetails = NULL;
3207                         }
3208                     }
3209                     HistorySet(parseList, backwardMostMove,
3210                                forwardMostMove, currentMove-1);
3211                     DisplayMove(currentMove - 1);
3212                     if (started == STARTED_MOVES) next_out = i;
3213                     started = STARTED_NONE;
3214                     ics_getting_history = H_FALSE;
3215                     break;
3216
3217                   case STARTED_OBSERVE:
3218                     started = STARTED_NONE;
3219                     SendToICS(ics_prefix);
3220                     SendToICS("refresh\n");
3221                     break;
3222
3223                   default:
3224                     break;
3225                 }
3226                 if(bookHit) { // [HGM] book: simulate book reply
3227                     static char bookMove[MSG_SIZ]; // a bit generous?
3228
3229                     programStats.nodes = programStats.depth = programStats.time = 
3230                     programStats.score = programStats.got_only_move = 0;
3231                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3232
3233                     strcpy(bookMove, "move ");
3234                     strcat(bookMove, bookHit);
3235                     HandleMachineMove(bookMove, &first);
3236                 }
3237                 continue;
3238             }
3239             
3240             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3241                  started == STARTED_HOLDINGS ||
3242                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3243                 /* Accumulate characters in move list or board */
3244                 parse[parse_pos++] = buf[i];
3245             }
3246             
3247             /* Start of game messages.  Mostly we detect start of game
3248                when the first board image arrives.  On some versions
3249                of the ICS, though, we need to do a "refresh" after starting
3250                to observe in order to get the current board right away. */
3251             if (looking_at(buf, &i, "Adding game * to observation list")) {
3252                 started = STARTED_OBSERVE;
3253                 continue;
3254             }
3255
3256             /* Handle auto-observe */
3257             if (appData.autoObserve &&
3258                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3259                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3260                 char *player;
3261                 /* Choose the player that was highlighted, if any. */
3262                 if (star_match[0][0] == '\033' ||
3263                     star_match[1][0] != '\033') {
3264                     player = star_match[0];
3265                 } else {
3266                     player = star_match[2];
3267                 }
3268                 sprintf(str, "%sobserve %s\n",
3269                         ics_prefix, StripHighlightAndTitle(player));
3270                 SendToICS(str);
3271
3272                 /* Save ratings from notify string */
3273                 strcpy(player1Name, star_match[0]);
3274                 player1Rating = string_to_rating(star_match[1]);
3275                 strcpy(player2Name, star_match[2]);
3276                 player2Rating = string_to_rating(star_match[3]);
3277
3278                 if (appData.debugMode)
3279                   fprintf(debugFP, 
3280                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3281                           player1Name, player1Rating,
3282                           player2Name, player2Rating);
3283
3284                 continue;
3285             }
3286
3287             /* Deal with automatic examine mode after a game,
3288                and with IcsObserving -> IcsExamining transition */
3289             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3290                 looking_at(buf, &i, "has made you an examiner of game *")) {
3291
3292                 int gamenum = atoi(star_match[0]);
3293                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3294                     gamenum == ics_gamenum) {
3295                     /* We were already playing or observing this game;
3296                        no need to refetch history */
3297                     gameMode = IcsExamining;
3298                     if (pausing) {
3299                         pauseExamForwardMostMove = forwardMostMove;
3300                     } else if (currentMove < forwardMostMove) {
3301                         ForwardInner(forwardMostMove);
3302                     }
3303                 } else {
3304                     /* I don't think this case really can happen */
3305                     SendToICS(ics_prefix);
3306                     SendToICS("refresh\n");
3307                 }
3308                 continue;
3309             }    
3310             
3311             /* Error messages */
3312 //          if (ics_user_moved) {
3313             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3314                 if (looking_at(buf, &i, "Illegal move") ||
3315                     looking_at(buf, &i, "Not a legal move") ||
3316                     looking_at(buf, &i, "Your king is in check") ||
3317                     looking_at(buf, &i, "It isn't your turn") ||
3318                     looking_at(buf, &i, "It is not your move")) {
3319                     /* Illegal move */
3320                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3321                         currentMove = --forwardMostMove;
3322                         DisplayMove(currentMove - 1); /* before DMError */
3323                         DrawPosition(FALSE, boards[currentMove]);
3324                         SwitchClocks();
3325                         DisplayBothClocks();
3326                     }
3327                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3328                     ics_user_moved = 0;
3329                     continue;
3330                 }
3331             }
3332
3333             if (looking_at(buf, &i, "still have time") ||
3334                 looking_at(buf, &i, "not out of time") ||
3335                 looking_at(buf, &i, "either player is out of time") ||
3336                 looking_at(buf, &i, "has timeseal; checking")) {
3337                 /* We must have called his flag a little too soon */
3338                 whiteFlag = blackFlag = FALSE;
3339                 continue;
3340             }
3341
3342             if (looking_at(buf, &i, "added * seconds to") ||
3343                 looking_at(buf, &i, "seconds were added to")) {
3344                 /* Update the clocks */
3345                 SendToICS(ics_prefix);
3346                 SendToICS("refresh\n");
3347                 continue;
3348             }
3349
3350             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3351                 ics_clock_paused = TRUE;
3352                 StopClocks();
3353                 continue;
3354             }
3355
3356             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3357                 ics_clock_paused = FALSE;
3358                 StartClocks();
3359                 continue;
3360             }
3361
3362             /* Grab player ratings from the Creating: message.
3363                Note we have to check for the special case when
3364                the ICS inserts things like [white] or [black]. */
3365             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3366                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3367                 /* star_matches:
3368                    0    player 1 name (not necessarily white)
3369                    1    player 1 rating
3370                    2    empty, white, or black (IGNORED)
3371                    3    player 2 name (not necessarily black)
3372                    4    player 2 rating
3373                    
3374                    The names/ratings are sorted out when the game
3375                    actually starts (below).
3376                 */
3377                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3378                 player1Rating = string_to_rating(star_match[1]);
3379                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3380                 player2Rating = string_to_rating(star_match[4]);
3381
3382                 if (appData.debugMode)
3383                   fprintf(debugFP, 
3384                           "Ratings from 'Creating:' %s %d, %s %d\n",
3385                           player1Name, player1Rating,
3386                           player2Name, player2Rating);
3387
3388                 continue;
3389             }
3390             
3391             /* Improved generic start/end-of-game messages */
3392             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3393                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3394                 /* If tkind == 0: */
3395                 /* star_match[0] is the game number */
3396                 /*           [1] is the white player's name */
3397                 /*           [2] is the black player's name */
3398                 /* For end-of-game: */
3399                 /*           [3] is the reason for the game end */
3400                 /*           [4] is a PGN end game-token, preceded by " " */
3401                 /* For start-of-game: */
3402                 /*           [3] begins with "Creating" or "Continuing" */
3403                 /*           [4] is " *" or empty (don't care). */
3404                 int gamenum = atoi(star_match[0]);
3405                 char *whitename, *blackname, *why, *endtoken;
3406                 ChessMove endtype = (ChessMove) 0;
3407
3408                 if (tkind == 0) {
3409                   whitename = star_match[1];
3410                   blackname = star_match[2];
3411                   why = star_match[3];
3412                   endtoken = star_match[4];
3413                 } else {
3414                   whitename = star_match[1];
3415                   blackname = star_match[3];
3416                   why = star_match[5];
3417                   endtoken = star_match[6];
3418                 }
3419
3420                 /* Game start messages */
3421                 if (strncmp(why, "Creating ", 9) == 0 ||
3422                     strncmp(why, "Continuing ", 11) == 0) {
3423                     gs_gamenum = gamenum;
3424                     strcpy(gs_kind, strchr(why, ' ') + 1);
3425                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3426 #if ZIPPY
3427                     if (appData.zippyPlay) {
3428                         ZippyGameStart(whitename, blackname);
3429                     }
3430 #endif /*ZIPPY*/
3431                     continue;
3432                 }
3433
3434                 /* Game end messages */
3435                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3436                     ics_gamenum != gamenum) {
3437                     continue;
3438                 }
3439                 while (endtoken[0] == ' ') endtoken++;
3440                 switch (endtoken[0]) {
3441                   case '*':
3442                   default:
3443                     endtype = GameUnfinished;
3444                     break;
3445                   case '0':
3446                     endtype = BlackWins;
3447                     break;
3448                   case '1':
3449                     if (endtoken[1] == '/')
3450                       endtype = GameIsDrawn;
3451                     else
3452                       endtype = WhiteWins;
3453                     break;
3454                 }
3455                 GameEnds(endtype, why, GE_ICS);
3456 #if ZIPPY
3457                 if (appData.zippyPlay && first.initDone) {
3458                     ZippyGameEnd(endtype, why);
3459                     if (first.pr == NULL) {
3460                       /* Start the next process early so that we'll
3461                          be ready for the next challenge */
3462                       StartChessProgram(&first);
3463                     }
3464                     /* Send "new" early, in case this command takes
3465                        a long time to finish, so that we'll be ready
3466                        for the next challenge. */
3467                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3468                     Reset(TRUE, TRUE);
3469                 }
3470 #endif /*ZIPPY*/
3471                 continue;
3472             }
3473
3474             if (looking_at(buf, &i, "Removing game * from observation") ||
3475                 looking_at(buf, &i, "no longer observing game *") ||
3476                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3477                 if (gameMode == IcsObserving &&
3478                     atoi(star_match[0]) == ics_gamenum)
3479                   {
3480                       /* icsEngineAnalyze */
3481                       if (appData.icsEngineAnalyze) {
3482                             ExitAnalyzeMode();
3483                             ModeHighlight();
3484                       }
3485                       StopClocks();
3486                       gameMode = IcsIdle;
3487                       ics_gamenum = -1;
3488                       ics_user_moved = FALSE;
3489                   }
3490                 continue;
3491             }
3492
3493             if (looking_at(buf, &i, "no longer examining game *")) {
3494                 if (gameMode == IcsExamining &&
3495                     atoi(star_match[0]) == ics_gamenum)
3496                   {
3497                       gameMode = IcsIdle;
3498                       ics_gamenum = -1;
3499                       ics_user_moved = FALSE;
3500                   }
3501                 continue;
3502             }
3503
3504             /* Advance leftover_start past any newlines we find,
3505                so only partial lines can get reparsed */
3506             if (looking_at(buf, &i, "\n")) {
3507                 prevColor = curColor;
3508                 if (curColor != ColorNormal) {
3509                     if (oldi > next_out) {
3510                         SendToPlayer(&buf[next_out], oldi - next_out);
3511                         next_out = oldi;
3512                     }
3513                     Colorize(ColorNormal, FALSE);
3514                     curColor = ColorNormal;
3515                 }
3516                 if (started == STARTED_BOARD) {
3517                     started = STARTED_NONE;
3518                     parse[parse_pos] = NULLCHAR;
3519                     ParseBoard12(parse);
3520                     ics_user_moved = 0;
3521
3522                     /* Send premove here */
3523                     if (appData.premove) {
3524                       char str[MSG_SIZ];
3525                       if (currentMove == 0 &&
3526                           gameMode == IcsPlayingWhite &&
3527                           appData.premoveWhite) {
3528                         sprintf(str, "%s\n", appData.premoveWhiteText);
3529                         if (appData.debugMode)
3530                           fprintf(debugFP, "Sending premove:\n");
3531                         SendToICS(str);
3532                       } else if (currentMove == 1 &&
3533                                  gameMode == IcsPlayingBlack &&
3534                                  appData.premoveBlack) {
3535                         sprintf(str, "%s\n", appData.premoveBlackText);
3536                         if (appData.debugMode)
3537                           fprintf(debugFP, "Sending premove:\n");
3538                         SendToICS(str);
3539                       } else if (gotPremove) {
3540                         gotPremove = 0;
3541                         ClearPremoveHighlights();
3542                         if (appData.debugMode)
3543                           fprintf(debugFP, "Sending premove:\n");
3544                           UserMoveEvent(premoveFromX, premoveFromY, 
3545                                         premoveToX, premoveToY, 
3546                                         premovePromoChar);
3547                       }
3548                     }
3549
3550                     /* Usually suppress following prompt */
3551                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3552                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3553                         if (looking_at(buf, &i, "*% ")) {
3554                             savingComment = FALSE;
3555                             suppressKibitz = 0;
3556                         }
3557                     }
3558                     next_out = i;
3559                 } else if (started == STARTED_HOLDINGS) {
3560                     int gamenum;
3561                     char new_piece[MSG_SIZ];
3562                     started = STARTED_NONE;
3563                     parse[parse_pos] = NULLCHAR;
3564                     if (appData.debugMode)
3565                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3566                                                         parse, currentMove);
3567                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3568                         gamenum == ics_gamenum) {
3569                         if (gameInfo.variant == VariantNormal) {
3570                           /* [HGM] We seem to switch variant during a game!
3571                            * Presumably no holdings were displayed, so we have
3572                            * to move the position two files to the right to
3573                            * create room for them!
3574                            */
3575                           VariantClass newVariant;
3576                           switch(gameInfo.boardWidth) { // base guess on board width
3577                                 case 9:  newVariant = VariantShogi; break;
3578                                 case 10: newVariant = VariantGreat; break;
3579                                 default: newVariant = VariantCrazyhouse; break;
3580                           }
3581                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3582                           /* Get a move list just to see the header, which
3583                              will tell us whether this is really bug or zh */
3584                           if (ics_getting_history == H_FALSE) {
3585                             ics_getting_history = H_REQUESTED;
3586                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3587                             SendToICS(str);
3588                           }
3589                         }
3590                         new_piece[0] = NULLCHAR;
3591                         sscanf(parse, "game %d white [%s black [%s <- %s",
3592                                &gamenum, white_holding, black_holding,
3593                                new_piece);
3594                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3595                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3596                         /* [HGM] copy holdings to board holdings area */
3597                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3598                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3599                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3600 #if ZIPPY
3601                         if (appData.zippyPlay && first.initDone) {
3602                             ZippyHoldings(white_holding, black_holding,
3603                                           new_piece);
3604                         }
3605 #endif /*ZIPPY*/
3606                         if (tinyLayout || smallLayout) {
3607                             char wh[16], bh[16];
3608                             PackHolding(wh, white_holding);
3609                             PackHolding(bh, black_holding);
3610                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3611                                     gameInfo.white, gameInfo.black);
3612                         } else {
3613                             sprintf(str, "%s [%s] vs. %s [%s]",
3614                                     gameInfo.white, white_holding,
3615                                     gameInfo.black, black_holding);
3616                         }
3617
3618                         DrawPosition(FALSE, boards[currentMove]);
3619                         DisplayTitle(str);
3620                     }
3621                     /* Suppress following prompt */
3622                     if (looking_at(buf, &i, "*% ")) {
3623                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3624                         savingComment = FALSE;
3625                         suppressKibitz = 0;
3626                     }
3627                     next_out = i;
3628                 }
3629                 continue;
3630             }
3631
3632             i++;                /* skip unparsed character and loop back */
3633         }
3634         
3635         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3636 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3637 //          SendToPlayer(&buf[next_out], i - next_out);
3638             started != STARTED_HOLDINGS && leftover_start > next_out) {
3639             SendToPlayer(&buf[next_out], leftover_start - next_out);
3640             next_out = i;
3641         }
3642         
3643         leftover_len = buf_len - leftover_start;
3644         /* if buffer ends with something we couldn't parse,
3645            reparse it after appending the next read */
3646         
3647     } else if (count == 0) {
3648         RemoveInputSource(isr);
3649         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3650     } else {
3651         DisplayFatalError(_("Error reading from ICS"), error, 1);
3652     }
3653 }
3654
3655
3656 /* Board style 12 looks like this:
3657    
3658    <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
3659    
3660  * The "<12> " is stripped before it gets to this routine.  The two
3661  * trailing 0's (flip state and clock ticking) are later addition, and
3662  * some chess servers may not have them, or may have only the first.
3663  * Additional trailing fields may be added in the future.  
3664  */
3665
3666 #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"
3667
3668 #define RELATION_OBSERVING_PLAYED    0
3669 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3670 #define RELATION_PLAYING_MYMOVE      1
3671 #define RELATION_PLAYING_NOTMYMOVE  -1
3672 #define RELATION_EXAMINING           2
3673 #define RELATION_ISOLATED_BOARD     -3
3674 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3675
3676 void
3677 ParseBoard12(string)
3678      char *string;
3679
3680     GameMode newGameMode;
3681     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3682     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3683     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3684     char to_play, board_chars[200];
3685     char move_str[500], str[500], elapsed_time[500];
3686     char black[32], white[32];
3687     Board board;
3688     int prevMove = currentMove;
3689     int ticking = 2;
3690     ChessMove moveType;
3691     int fromX, fromY, toX, toY;
3692     char promoChar;
3693     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3694     char *bookHit = NULL; // [HGM] book
3695     Boolean weird = FALSE, reqFlag = FALSE;
3696
3697     fromX = fromY = toX = toY = -1;
3698     
3699     newGame = FALSE;
3700
3701     if (appData.debugMode)
3702       fprintf(debugFP, _("Parsing board: %s\n"), string);
3703
3704     move_str[0] = NULLCHAR;
3705     elapsed_time[0] = NULLCHAR;
3706     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3707         int  i = 0, j;
3708         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3709             if(string[i] == ' ') { ranks++; files = 0; }
3710             else files++;
3711             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3712             i++;
3713         }
3714         for(j = 0; j <i; j++) board_chars[j] = string[j];
3715         board_chars[i] = '\0';
3716         string += i + 1;
3717     }
3718     n = sscanf(string, PATTERN, &to_play, &double_push,
3719                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3720                &gamenum, white, black, &relation, &basetime, &increment,
3721                &white_stren, &black_stren, &white_time, &black_time,
3722                &moveNum, str, elapsed_time, move_str, &ics_flip,
3723                &ticking);
3724
3725     if (n < 21) {
3726         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3727         DisplayError(str, 0);
3728         return;
3729     }
3730
3731     /* Convert the move number to internal form */
3732     moveNum = (moveNum - 1) * 2;
3733     if (to_play == 'B') moveNum++;
3734     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3735       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3736                         0, 1);
3737       return;
3738     }
3739     
3740     switch (relation) {
3741       case RELATION_OBSERVING_PLAYED:
3742       case RELATION_OBSERVING_STATIC:
3743         if (gamenum == -1) {
3744             /* Old ICC buglet */
3745             relation = RELATION_OBSERVING_STATIC;
3746         }
3747         newGameMode = IcsObserving;
3748         break;
3749       case RELATION_PLAYING_MYMOVE:
3750       case RELATION_PLAYING_NOTMYMOVE:
3751         newGameMode =
3752           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3753             IcsPlayingWhite : IcsPlayingBlack;
3754         break;
3755       case RELATION_EXAMINING:
3756         newGameMode = IcsExamining;
3757         break;
3758       case RELATION_ISOLATED_BOARD:
3759       default:
3760         /* Just display this board.  If user was doing something else,
3761            we will forget about it until the next board comes. */ 
3762         newGameMode = IcsIdle;
3763         break;
3764       case RELATION_STARTING_POSITION:
3765         newGameMode = gameMode;
3766         break;
3767     }
3768     
3769     /* Modify behavior for initial board display on move listing
3770        of wild games.
3771        */
3772     switch (ics_getting_history) {
3773       case H_FALSE:
3774       case H_REQUESTED:
3775         break;
3776       case H_GOT_REQ_HEADER:
3777       case H_GOT_UNREQ_HEADER:
3778         /* This is the initial position of the current game */
3779         gamenum = ics_gamenum;
3780         moveNum = 0;            /* old ICS bug workaround */
3781         if (to_play == 'B') {
3782           startedFromSetupPosition = TRUE;
3783           blackPlaysFirst = TRUE;
3784           moveNum = 1;
3785           if (forwardMostMove == 0) forwardMostMove = 1;
3786           if (backwardMostMove == 0) backwardMostMove = 1;
3787           if (currentMove == 0) currentMove = 1;
3788         }
3789         newGameMode = gameMode;
3790         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3791         break;
3792       case H_GOT_UNWANTED_HEADER:
3793         /* This is an initial board that we don't want */
3794         return;
3795       case H_GETTING_MOVES:
3796         /* Should not happen */
3797         DisplayError(_("Error gathering move list: extra board"), 0);
3798         ics_getting_history = H_FALSE;
3799         return;
3800     }
3801
3802    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3803                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3804      /* [HGM] We seem to have switched variant unexpectedly
3805       * Try to guess new variant from board size
3806       */
3807           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3808           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3809           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3810           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3811           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3812           if(!weird) newVariant = VariantNormal;
3813           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3814           /* Get a move list just to see the header, which
3815              will tell us whether this is really bug or zh */
3816           if (ics_getting_history == H_FALSE) {
3817             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3818             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3819             SendToICS(str);
3820           }
3821     }
3822     
3823     /* Take action if this is the first board of a new game, or of a
3824        different game than is currently being displayed.  */
3825     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3826         relation == RELATION_ISOLATED_BOARD) {
3827         
3828         /* Forget the old game and get the history (if any) of the new one */
3829         if (gameMode != BeginningOfGame) {
3830           Reset(TRUE, TRUE);
3831         }
3832         newGame = TRUE;
3833         if (appData.autoRaiseBoard) BoardToTop();
3834         prevMove = -3;
3835         if (gamenum == -1) {
3836             newGameMode = IcsIdle;
3837         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3838                    appData.getMoveList && !reqFlag) {
3839             /* Need to get game history */
3840             ics_getting_history = H_REQUESTED;
3841             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3842             SendToICS(str);
3843         }
3844         
3845         /* Initially flip the board to have black on the bottom if playing
3846            black or if the ICS flip flag is set, but let the user change
3847            it with the Flip View button. */
3848         flipView = appData.autoFlipView ? 
3849           (newGameMode == IcsPlayingBlack) || ics_flip :
3850           appData.flipView;
3851         
3852         /* Done with values from previous mode; copy in new ones */
3853         gameMode = newGameMode;
3854         ModeHighlight();
3855         ics_gamenum = gamenum;
3856         if (gamenum == gs_gamenum) {
3857             int klen = strlen(gs_kind);
3858             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3859             sprintf(str, "ICS %s", gs_kind);
3860             gameInfo.event = StrSave(str);
3861         } else {
3862             gameInfo.event = StrSave("ICS game");
3863         }
3864         gameInfo.site = StrSave(appData.icsHost);
3865         gameInfo.date = PGNDate();
3866         gameInfo.round = StrSave("-");
3867         gameInfo.white = StrSave(white);
3868         gameInfo.black = StrSave(black);
3869         timeControl = basetime * 60 * 1000;
3870         timeControl_2 = 0;
3871         timeIncrement = increment * 1000;
3872         movesPerSession = 0;
3873         gameInfo.timeControl = TimeControlTagValue();
3874         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3875   if (appData.debugMode) {
3876     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3877     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3878     setbuf(debugFP, NULL);
3879   }
3880
3881         gameInfo.outOfBook = NULL;
3882         
3883         /* Do we have the ratings? */
3884         if (strcmp(player1Name, white) == 0 &&
3885             strcmp(player2Name, black) == 0) {
3886             if (appData.debugMode)
3887               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3888                       player1Rating, player2Rating);
3889             gameInfo.whiteRating = player1Rating;
3890             gameInfo.blackRating = player2Rating;
3891         } else if (strcmp(player2Name, white) == 0 &&
3892                    strcmp(player1Name, black) == 0) {
3893             if (appData.debugMode)
3894               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3895                       player2Rating, player1Rating);
3896             gameInfo.whiteRating = player2Rating;
3897             gameInfo.blackRating = player1Rating;
3898         }
3899         player1Name[0] = player2Name[0] = NULLCHAR;
3900
3901         /* Silence shouts if requested */
3902         if (appData.quietPlay &&
3903             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3904             SendToICS(ics_prefix);
3905             SendToICS("set shout 0\n");
3906         }
3907     }
3908     
3909     /* Deal with midgame name changes */
3910     if (!newGame) {
3911         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3912             if (gameInfo.white) free(gameInfo.white);
3913             gameInfo.white = StrSave(white);
3914         }
3915         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3916             if (gameInfo.black) free(gameInfo.black);
3917             gameInfo.black = StrSave(black);
3918         }
3919     }
3920     
3921     /* Throw away game result if anything actually changes in examine mode */
3922     if (gameMode == IcsExamining && !newGame) {
3923         gameInfo.result = GameUnfinished;
3924         if (gameInfo.resultDetails != NULL) {
3925             free(gameInfo.resultDetails);
3926             gameInfo.resultDetails = NULL;
3927         }
3928     }
3929     
3930     /* In pausing && IcsExamining mode, we ignore boards coming
3931        in if they are in a different variation than we are. */
3932     if (pauseExamInvalid) return;
3933     if (pausing && gameMode == IcsExamining) {
3934         if (moveNum <= pauseExamForwardMostMove) {
3935             pauseExamInvalid = TRUE;
3936             forwardMostMove = pauseExamForwardMostMove;
3937             return;
3938         }
3939     }
3940     
3941   if (appData.debugMode) {
3942     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3943   }
3944     /* Parse the board */
3945     for (k = 0; k < ranks; k++) {
3946       for (j = 0; j < files; j++)
3947         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3948       if(gameInfo.holdingsWidth > 1) {
3949            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3950            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3951       }
3952     }
3953     CopyBoard(boards[moveNum], board);
3954     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3955     if (moveNum == 0) {
3956         startedFromSetupPosition =
3957           !CompareBoards(board, initialPosition);
3958         if(startedFromSetupPosition)
3959             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3960     }
3961
3962     /* [HGM] Set castling rights. Take the outermost Rooks,
3963        to make it also work for FRC opening positions. Note that board12
3964        is really defective for later FRC positions, as it has no way to
3965        indicate which Rook can castle if they are on the same side of King.
3966        For the initial position we grant rights to the outermost Rooks,
3967        and remember thos rights, and we then copy them on positions
3968        later in an FRC game. This means WB might not recognize castlings with
3969        Rooks that have moved back to their original position as illegal,
3970        but in ICS mode that is not its job anyway.
3971     */
3972     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3973     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3974
3975         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3976             if(board[0][i] == WhiteRook) j = i;
3977         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3978         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3979             if(board[0][i] == WhiteRook) j = i;
3980         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3981         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3982             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3983         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3984         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3985             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3986         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3987
3988         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3989         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3990             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3991         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3992             if(board[BOARD_HEIGHT-1][k] == bKing)
3993                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3994         if(gameInfo.variant == VariantTwoKings) {
3995             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3996             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3997             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3998         }
3999     } else { int r;
4000         r = boards[moveNum][CASTLING][0] = initialRights[0];
4001         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4002         r = boards[moveNum][CASTLING][1] = initialRights[1];
4003         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4004         r = boards[moveNum][CASTLING][3] = initialRights[3];
4005         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4006         r = boards[moveNum][CASTLING][4] = initialRights[4];
4007         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4008         /* wildcastle kludge: always assume King has rights */
4009         r = boards[moveNum][CASTLING][2] = initialRights[2];
4010         r = boards[moveNum][CASTLING][5] = initialRights[5];
4011     }
4012     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4013     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4014
4015     
4016     if (ics_getting_history == H_GOT_REQ_HEADER ||
4017         ics_getting_history == H_GOT_UNREQ_HEADER) {
4018         /* This was an initial position from a move list, not
4019            the current position */
4020         return;
4021     }
4022     
4023     /* Update currentMove and known move number limits */
4024     newMove = newGame || moveNum > forwardMostMove;
4025
4026     if (newGame) {
4027         forwardMostMove = backwardMostMove = currentMove = moveNum;
4028         if (gameMode == IcsExamining && moveNum == 0) {
4029           /* Workaround for ICS limitation: we are not told the wild
4030              type when starting to examine a game.  But if we ask for
4031              the move list, the move list header will tell us */
4032             ics_getting_history = H_REQUESTED;
4033             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4034             SendToICS(str);
4035         }
4036     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4037                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4038 #if ZIPPY
4039         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4040         /* [HGM] applied this also to an engine that is silently watching        */
4041         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4042             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4043             gameInfo.variant == currentlyInitializedVariant) {
4044           takeback = forwardMostMove - moveNum;
4045           for (i = 0; i < takeback; i++) {
4046             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4047             SendToProgram("undo\n", &first);
4048           }
4049         }
4050 #endif
4051
4052         forwardMostMove = moveNum;
4053         if (!pausing || currentMove > forwardMostMove)
4054           currentMove = forwardMostMove;
4055     } else {
4056         /* New part of history that is not contiguous with old part */ 
4057         if (pausing && gameMode == IcsExamining) {
4058             pauseExamInvalid = TRUE;
4059             forwardMostMove = pauseExamForwardMostMove;
4060             return;
4061         }
4062         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4063 #if ZIPPY
4064             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4065                 // [HGM] when we will receive the move list we now request, it will be
4066                 // fed to the engine from the first move on. So if the engine is not
4067                 // in the initial position now, bring it there.
4068                 InitChessProgram(&first, 0);
4069             }
4070 #endif
4071             ics_getting_history = H_REQUESTED;
4072             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4073             SendToICS(str);
4074         }
4075         forwardMostMove = backwardMostMove = currentMove = moveNum;
4076     }
4077     
4078     /* Update the clocks */
4079     if (strchr(elapsed_time, '.')) {
4080       /* Time is in ms */
4081       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4082       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4083     } else {
4084       /* Time is in seconds */
4085       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4086       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4087     }
4088       
4089
4090 #if ZIPPY
4091     if (appData.zippyPlay && newGame &&
4092         gameMode != IcsObserving && gameMode != IcsIdle &&
4093         gameMode != IcsExamining)
4094       ZippyFirstBoard(moveNum, basetime, increment);
4095 #endif
4096     
4097     /* Put the move on the move list, first converting
4098        to canonical algebraic form. */
4099     if (moveNum > 0) {
4100   if (appData.debugMode) {
4101     if (appData.debugMode) { int f = forwardMostMove;
4102         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4103                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4104                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4105     }
4106     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4107     fprintf(debugFP, "moveNum = %d\n", moveNum);
4108     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4109     setbuf(debugFP, NULL);
4110   }
4111         if (moveNum <= backwardMostMove) {
4112             /* We don't know what the board looked like before
4113                this move.  Punt. */
4114             strcpy(parseList[moveNum - 1], move_str);
4115             strcat(parseList[moveNum - 1], " ");
4116             strcat(parseList[moveNum - 1], elapsed_time);
4117             moveList[moveNum - 1][0] = NULLCHAR;
4118         } else if (strcmp(move_str, "none") == 0) {
4119             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4120             /* Again, we don't know what the board looked like;
4121                this is really the start of the game. */
4122             parseList[moveNum - 1][0] = NULLCHAR;
4123             moveList[moveNum - 1][0] = NULLCHAR;
4124             backwardMostMove = moveNum;
4125             startedFromSetupPosition = TRUE;
4126             fromX = fromY = toX = toY = -1;
4127         } else {
4128           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4129           //                 So we parse the long-algebraic move string in stead of the SAN move
4130           int valid; char buf[MSG_SIZ], *prom;
4131
4132           // str looks something like "Q/a1-a2"; kill the slash
4133           if(str[1] == '/') 
4134                 sprintf(buf, "%c%s", str[0], str+2);
4135           else  strcpy(buf, str); // might be castling
4136           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4137                 strcat(buf, prom); // long move lacks promo specification!
4138           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4139                 if(appData.debugMode) 
4140                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4141                 strcpy(move_str, buf);
4142           }
4143           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4144                                 &fromX, &fromY, &toX, &toY, &promoChar)
4145                || ParseOneMove(buf, moveNum - 1, &moveType,
4146                                 &fromX, &fromY, &toX, &toY, &promoChar);
4147           // end of long SAN patch
4148           if (valid) {
4149             (void) CoordsToAlgebraic(boards[moveNum - 1],
4150                                      PosFlags(moveNum - 1),
4151                                      fromY, fromX, toY, toX, promoChar,
4152                                      parseList[moveNum-1]);
4153             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4154               case MT_NONE:
4155               case MT_STALEMATE:
4156               default:
4157                 break;
4158               case MT_CHECK:
4159                 if(gameInfo.variant != VariantShogi)
4160                     strcat(parseList[moveNum - 1], "+");
4161                 break;
4162               case MT_CHECKMATE:
4163               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4164                 strcat(parseList[moveNum - 1], "#");
4165                 break;
4166             }
4167             strcat(parseList[moveNum - 1], " ");
4168             strcat(parseList[moveNum - 1], elapsed_time);
4169             /* currentMoveString is set as a side-effect of ParseOneMove */
4170             strcpy(moveList[moveNum - 1], currentMoveString);
4171             strcat(moveList[moveNum - 1], "\n");
4172           } else {
4173             /* Move from ICS was illegal!?  Punt. */
4174   if (appData.debugMode) {
4175     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4176     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4177   }
4178             strcpy(parseList[moveNum - 1], move_str);
4179             strcat(parseList[moveNum - 1], " ");
4180             strcat(parseList[moveNum - 1], elapsed_time);
4181             moveList[moveNum - 1][0] = NULLCHAR;
4182             fromX = fromY = toX = toY = -1;
4183           }
4184         }
4185   if (appData.debugMode) {
4186     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4187     setbuf(debugFP, NULL);
4188   }
4189
4190 #if ZIPPY
4191         /* Send move to chess program (BEFORE animating it). */
4192         if (appData.zippyPlay && !newGame && newMove && 
4193            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4194
4195             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4196                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4197                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4198                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4199                             move_str);
4200                     DisplayError(str, 0);
4201                 } else {
4202                     if (first.sendTime) {
4203                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4204                     }
4205                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4206                     if (firstMove && !bookHit) {
4207                         firstMove = FALSE;
4208                         if (first.useColors) {
4209                           SendToProgram(gameMode == IcsPlayingWhite ?
4210                                         "white\ngo\n" :
4211                                         "black\ngo\n", &first);
4212                         } else {
4213                           SendToProgram("go\n", &first);
4214                         }
4215                         first.maybeThinking = TRUE;
4216                     }
4217                 }
4218             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4219               if (moveList[moveNum - 1][0] == NULLCHAR) {
4220                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4221                 DisplayError(str, 0);
4222               } else {
4223                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4224                 SendMoveToProgram(moveNum - 1, &first);
4225               }
4226             }
4227         }
4228 #endif
4229     }
4230
4231     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4232         /* If move comes from a remote source, animate it.  If it
4233            isn't remote, it will have already been animated. */
4234         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4235             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4236         }
4237         if (!pausing && appData.highlightLastMove) {
4238             SetHighlights(fromX, fromY, toX, toY);
4239         }
4240     }
4241     
4242     /* Start the clocks */
4243     whiteFlag = blackFlag = FALSE;
4244     appData.clockMode = !(basetime == 0 && increment == 0);
4245     if (ticking == 0) {
4246       ics_clock_paused = TRUE;
4247       StopClocks();
4248     } else if (ticking == 1) {
4249       ics_clock_paused = FALSE;
4250     }
4251     if (gameMode == IcsIdle ||
4252         relation == RELATION_OBSERVING_STATIC ||
4253         relation == RELATION_EXAMINING ||
4254         ics_clock_paused)
4255       DisplayBothClocks();
4256     else
4257       StartClocks();
4258     
4259     /* Display opponents and material strengths */
4260     if (gameInfo.variant != VariantBughouse &&
4261         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4262         if (tinyLayout || smallLayout) {
4263             if(gameInfo.variant == VariantNormal)
4264                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4265                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4266                     basetime, increment);
4267             else
4268                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4269                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4270                     basetime, increment, (int) gameInfo.variant);
4271         } else {
4272             if(gameInfo.variant == VariantNormal)
4273                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4274                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4275                     basetime, increment);
4276             else
4277                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4278                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4279                     basetime, increment, VariantName(gameInfo.variant));
4280         }
4281         DisplayTitle(str);
4282   if (appData.debugMode) {
4283     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4284   }
4285     }
4286
4287
4288     /* Display the board */
4289     if (!pausing && !appData.noGUI) {
4290       
4291       if (appData.premove)
4292           if (!gotPremove || 
4293              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4294              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4295               ClearPremoveHighlights();
4296
4297       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4298       DrawPosition(j, boards[currentMove]);
4299
4300       DisplayMove(moveNum - 1);
4301       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4302             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4303               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4304         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4305       }
4306     }
4307
4308     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4309 #if ZIPPY
4310     if(bookHit) { // [HGM] book: simulate book reply
4311         static char bookMove[MSG_SIZ]; // a bit generous?
4312
4313         programStats.nodes = programStats.depth = programStats.time = 
4314         programStats.score = programStats.got_only_move = 0;
4315         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4316
4317         strcpy(bookMove, "move ");
4318         strcat(bookMove, bookHit);
4319         HandleMachineMove(bookMove, &first);
4320     }
4321 #endif
4322 }
4323
4324 void
4325 GetMoveListEvent()
4326 {
4327     char buf[MSG_SIZ];
4328     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4329         ics_getting_history = H_REQUESTED;
4330         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4331         SendToICS(buf);
4332     }
4333 }
4334
4335 void
4336 AnalysisPeriodicEvent(force)
4337      int force;
4338 {
4339     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4340          && !force) || !appData.periodicUpdates)
4341       return;
4342
4343     /* Send . command to Crafty to collect stats */
4344     SendToProgram(".\n", &first);
4345
4346     /* Don't send another until we get a response (this makes
4347        us stop sending to old Crafty's which don't understand
4348        the "." command (sending illegal cmds resets node count & time,
4349        which looks bad)) */
4350     programStats.ok_to_send = 0;
4351 }
4352
4353 void ics_update_width(new_width)
4354         int new_width;
4355 {
4356         ics_printf("set width %d\n", new_width);
4357 }
4358
4359 void
4360 SendMoveToProgram(moveNum, cps)
4361      int moveNum;
4362      ChessProgramState *cps;
4363 {
4364     char buf[MSG_SIZ];
4365
4366     if (cps->useUsermove) {
4367       SendToProgram("usermove ", cps);
4368     }
4369     if (cps->useSAN) {
4370       char *space;
4371       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4372         int len = space - parseList[moveNum];
4373         memcpy(buf, parseList[moveNum], len);
4374         buf[len++] = '\n';
4375         buf[len] = NULLCHAR;
4376       } else {
4377         sprintf(buf, "%s\n", parseList[moveNum]);
4378       }
4379       SendToProgram(buf, cps);
4380     } else {
4381       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4382         AlphaRank(moveList[moveNum], 4);
4383         SendToProgram(moveList[moveNum], cps);
4384         AlphaRank(moveList[moveNum], 4); // and back
4385       } else
4386       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4387        * the engine. It would be nice to have a better way to identify castle 
4388        * moves here. */
4389       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4390                                                                          && cps->useOOCastle) {
4391         int fromX = moveList[moveNum][0] - AAA; 
4392         int fromY = moveList[moveNum][1] - ONE;
4393         int toX = moveList[moveNum][2] - AAA; 
4394         int toY = moveList[moveNum][3] - ONE;
4395         if((boards[moveNum][fromY][fromX] == WhiteKing 
4396             && boards[moveNum][toY][toX] == WhiteRook)
4397            || (boards[moveNum][fromY][fromX] == BlackKing 
4398                && boards[moveNum][toY][toX] == BlackRook)) {
4399           if(toX > fromX) SendToProgram("O-O\n", cps);
4400           else SendToProgram("O-O-O\n", cps);
4401         }
4402         else SendToProgram(moveList[moveNum], cps);
4403       }
4404       else SendToProgram(moveList[moveNum], cps);
4405       /* End of additions by Tord */
4406     }
4407
4408     /* [HGM] setting up the opening has brought engine in force mode! */
4409     /*       Send 'go' if we are in a mode where machine should play. */
4410     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4411         (gameMode == TwoMachinesPlay   ||
4412 #ifdef ZIPPY
4413          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4414 #endif
4415          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4416         SendToProgram("go\n", cps);
4417   if (appData.debugMode) {
4418     fprintf(debugFP, "(extra)\n");
4419   }
4420     }
4421     setboardSpoiledMachineBlack = 0;
4422 }
4423
4424 void
4425 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4426      ChessMove moveType;
4427      int fromX, fromY, toX, toY;
4428 {
4429     char user_move[MSG_SIZ];
4430
4431     switch (moveType) {
4432       default:
4433         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4434                 (int)moveType, fromX, fromY, toX, toY);
4435         DisplayError(user_move + strlen("say "), 0);
4436         break;
4437       case WhiteKingSideCastle:
4438       case BlackKingSideCastle:
4439       case WhiteQueenSideCastleWild:
4440       case BlackQueenSideCastleWild:
4441       /* PUSH Fabien */
4442       case WhiteHSideCastleFR:
4443       case BlackHSideCastleFR:
4444       /* POP Fabien */
4445         sprintf(user_move, "o-o\n");
4446         break;
4447       case WhiteQueenSideCastle:
4448       case BlackQueenSideCastle:
4449       case WhiteKingSideCastleWild:
4450       case BlackKingSideCastleWild:
4451       /* PUSH Fabien */
4452       case WhiteASideCastleFR:
4453       case BlackASideCastleFR:
4454       /* POP Fabien */
4455         sprintf(user_move, "o-o-o\n");
4456         break;
4457       case WhitePromotionQueen:
4458       case BlackPromotionQueen:
4459       case WhitePromotionRook:
4460       case BlackPromotionRook:
4461       case WhitePromotionBishop:
4462       case BlackPromotionBishop:
4463       case WhitePromotionKnight:
4464       case BlackPromotionKnight:
4465       case WhitePromotionKing:
4466       case BlackPromotionKing:
4467       case WhitePromotionChancellor:
4468       case BlackPromotionChancellor:
4469       case WhitePromotionArchbishop:
4470       case BlackPromotionArchbishop:
4471         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4472             sprintf(user_move, "%c%c%c%c=%c\n",
4473                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4474                 PieceToChar(WhiteFerz));
4475         else if(gameInfo.variant == VariantGreat)
4476             sprintf(user_move, "%c%c%c%c=%c\n",
4477                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4478                 PieceToChar(WhiteMan));
4479         else
4480             sprintf(user_move, "%c%c%c%c=%c\n",
4481                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4482                 PieceToChar(PromoPiece(moveType)));
4483         break;
4484       case WhiteDrop:
4485       case BlackDrop:
4486         sprintf(user_move, "%c@%c%c\n",
4487                 ToUpper(PieceToChar((ChessSquare) fromX)),
4488                 AAA + toX, ONE + toY);
4489         break;
4490       case NormalMove:
4491       case WhiteCapturesEnPassant:
4492       case BlackCapturesEnPassant:
4493       case IllegalMove:  /* could be a variant we don't quite understand */
4494         sprintf(user_move, "%c%c%c%c\n",
4495                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4496         break;
4497     }
4498     SendToICS(user_move);
4499     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4500         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4501 }
4502
4503 void
4504 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4505      int rf, ff, rt, ft;
4506      char promoChar;
4507      char move[7];
4508 {
4509     if (rf == DROP_RANK) {
4510         sprintf(move, "%c@%c%c\n",
4511                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4512     } else {
4513         if (promoChar == 'x' || promoChar == NULLCHAR) {
4514             sprintf(move, "%c%c%c%c\n",
4515                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4516         } else {
4517             sprintf(move, "%c%c%c%c%c\n",
4518                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4519         }
4520     }
4521 }
4522
4523 void
4524 ProcessICSInitScript(f)
4525      FILE *f;
4526 {
4527     char buf[MSG_SIZ];
4528
4529     while (fgets(buf, MSG_SIZ, f)) {
4530         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4531     }
4532
4533     fclose(f);
4534 }
4535
4536
4537 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4538 void
4539 AlphaRank(char *move, int n)
4540 {
4541 //    char *p = move, c; int x, y;
4542
4543     if (appData.debugMode) {
4544         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4545     }
4546
4547     if(move[1]=='*' && 
4548        move[2]>='0' && move[2]<='9' &&
4549        move[3]>='a' && move[3]<='x'    ) {
4550         move[1] = '@';
4551         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4552         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4553     } else
4554     if(move[0]>='0' && move[0]<='9' &&
4555        move[1]>='a' && move[1]<='x' &&
4556        move[2]>='0' && move[2]<='9' &&
4557        move[3]>='a' && move[3]<='x'    ) {
4558         /* input move, Shogi -> normal */
4559         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4560         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4561         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4562         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4563     } else
4564     if(move[1]=='@' &&
4565        move[3]>='0' && move[3]<='9' &&
4566        move[2]>='a' && move[2]<='x'    ) {
4567         move[1] = '*';
4568         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4569         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4570     } else
4571     if(
4572        move[0]>='a' && move[0]<='x' &&
4573        move[3]>='0' && move[3]<='9' &&
4574        move[2]>='a' && move[2]<='x'    ) {
4575          /* output move, normal -> Shogi */
4576         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4577         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4578         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4579         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4580         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4581     }
4582     if (appData.debugMode) {
4583         fprintf(debugFP, "   out = '%s'\n", move);
4584     }
4585 }
4586
4587 /* Parser for moves from gnuchess, ICS, or user typein box */
4588 Boolean
4589 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4590      char *move;
4591      int moveNum;
4592      ChessMove *moveType;
4593      int *fromX, *fromY, *toX, *toY;
4594      char *promoChar;
4595 {       
4596     if (appData.debugMode) {
4597         fprintf(debugFP, "move to parse: %s\n", move);
4598     }
4599     *moveType = yylexstr(moveNum, move);
4600
4601     switch (*moveType) {
4602       case WhitePromotionChancellor:
4603       case BlackPromotionChancellor:
4604       case WhitePromotionArchbishop:
4605       case BlackPromotionArchbishop:
4606       case WhitePromotionQueen:
4607       case BlackPromotionQueen:
4608       case WhitePromotionRook:
4609       case BlackPromotionRook:
4610       case WhitePromotionBishop:
4611       case BlackPromotionBishop:
4612       case WhitePromotionKnight:
4613       case BlackPromotionKnight:
4614       case WhitePromotionKing:
4615       case BlackPromotionKing:
4616       case NormalMove:
4617       case WhiteCapturesEnPassant:
4618       case BlackCapturesEnPassant:
4619       case WhiteKingSideCastle:
4620       case WhiteQueenSideCastle:
4621       case BlackKingSideCastle:
4622       case BlackQueenSideCastle:
4623       case WhiteKingSideCastleWild:
4624       case WhiteQueenSideCastleWild:
4625       case BlackKingSideCastleWild:
4626       case BlackQueenSideCastleWild:
4627       /* Code added by Tord: */
4628       case WhiteHSideCastleFR:
4629       case WhiteASideCastleFR:
4630       case BlackHSideCastleFR:
4631       case BlackASideCastleFR:
4632       /* End of code added by Tord */
4633       case IllegalMove:         /* bug or odd chess variant */
4634         *fromX = currentMoveString[0] - AAA;
4635         *fromY = currentMoveString[1] - ONE;
4636         *toX = currentMoveString[2] - AAA;
4637         *toY = currentMoveString[3] - ONE;
4638         *promoChar = currentMoveString[4];
4639         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4640             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4641     if (appData.debugMode) {
4642         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4643     }
4644             *fromX = *fromY = *toX = *toY = 0;
4645             return FALSE;
4646         }
4647         if (appData.testLegality) {
4648           return (*moveType != IllegalMove);
4649         } else {
4650           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4651                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4652         }
4653
4654       case WhiteDrop:
4655       case BlackDrop:
4656         *fromX = *moveType == WhiteDrop ?
4657           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4658           (int) CharToPiece(ToLower(currentMoveString[0]));
4659         *fromY = DROP_RANK;
4660         *toX = currentMoveString[2] - AAA;
4661         *toY = currentMoveString[3] - ONE;
4662         *promoChar = NULLCHAR;
4663         return TRUE;
4664
4665       case AmbiguousMove:
4666       case ImpossibleMove:
4667       case (ChessMove) 0:       /* end of file */
4668       case ElapsedTime:
4669       case Comment:
4670       case PGNTag:
4671       case NAG:
4672       case WhiteWins:
4673       case BlackWins:
4674       case GameIsDrawn:
4675       default:
4676     if (appData.debugMode) {
4677         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4678     }
4679         /* bug? */
4680         *fromX = *fromY = *toX = *toY = 0;
4681         *promoChar = NULLCHAR;
4682         return FALSE;
4683     }
4684 }
4685
4686
4687 void
4688 ParsePV(char *pv)
4689 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4690   int fromX, fromY, toX, toY; char promoChar;
4691   ChessMove moveType;
4692   Boolean valid;
4693   int nr = 0;
4694
4695   endPV = forwardMostMove;
4696   do {
4697     while(*pv == ' ') pv++;
4698     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4699     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4700 if(appData.debugMode){
4701 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4702 }
4703     if(!valid && nr == 0 &&
4704        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4705         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4706     }
4707     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4708     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4709     nr++;
4710     if(endPV+1 > framePtr) break; // no space, truncate
4711     if(!valid) break;
4712     endPV++;
4713     CopyBoard(boards[endPV], boards[endPV-1]);
4714     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4715     moveList[endPV-1][0] = fromX + AAA;
4716     moveList[endPV-1][1] = fromY + ONE;
4717     moveList[endPV-1][2] = toX + AAA;
4718     moveList[endPV-1][3] = toY + ONE;
4719     parseList[endPV-1][0] = NULLCHAR;
4720   } while(valid);
4721   currentMove = endPV;
4722   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4723   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4724                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4725   DrawPosition(TRUE, boards[currentMove]);
4726 }
4727
4728 static int lastX, lastY;
4729
4730 Boolean
4731 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4732 {
4733         int startPV;
4734
4735         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4736         lastX = x; lastY = y;
4737         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4738         startPV = index;
4739       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4740       index = startPV;
4741         while(buf[index] && buf[index] != '\n') index++;
4742         buf[index] = 0;
4743         ParsePV(buf+startPV);
4744         *start = startPV; *end = index-1;
4745         return TRUE;
4746 }
4747
4748 Boolean
4749 LoadPV(int x, int y)
4750 { // called on right mouse click to load PV
4751   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4752   lastX = x; lastY = y;
4753   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4754   return TRUE;
4755 }
4756
4757 void
4758 UnLoadPV()
4759 {
4760   if(endPV < 0) return;
4761   endPV = -1;
4762   currentMove = forwardMostMove;
4763   ClearPremoveHighlights();
4764   DrawPosition(TRUE, boards[currentMove]);
4765 }
4766
4767 void
4768 MovePV(int x, int y, int h)
4769 { // step through PV based on mouse coordinates (called on mouse move)
4770   int margin = h>>3, step = 0;
4771
4772   if(endPV < 0) return;
4773   // we must somehow check if right button is still down (might be released off board!)
4774   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4775   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4776   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4777   if(!step) return;
4778   lastX = x; lastY = y;
4779   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4780   currentMove += step;
4781   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4782   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4783                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4784   DrawPosition(FALSE, boards[currentMove]);
4785 }
4786
4787
4788 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4789 // All positions will have equal probability, but the current method will not provide a unique
4790 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4791 #define DARK 1
4792 #define LITE 2
4793 #define ANY 3
4794
4795 int squaresLeft[4];
4796 int piecesLeft[(int)BlackPawn];
4797 int seed, nrOfShuffles;
4798
4799 void GetPositionNumber()
4800 {       // sets global variable seed
4801         int i;
4802
4803         seed = appData.defaultFrcPosition;
4804         if(seed < 0) { // randomize based on time for negative FRC position numbers
4805                 for(i=0; i<50; i++) seed += random();
4806                 seed = random() ^ random() >> 8 ^ random() << 8;
4807                 if(seed<0) seed = -seed;
4808         }
4809 }
4810
4811 int put(Board board, int pieceType, int rank, int n, int shade)
4812 // put the piece on the (n-1)-th empty squares of the given shade
4813 {
4814         int i;
4815
4816         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4817                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4818                         board[rank][i] = (ChessSquare) pieceType;
4819                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4820                         squaresLeft[ANY]--;
4821                         piecesLeft[pieceType]--; 
4822                         return i;
4823                 }
4824         }
4825         return -1;
4826 }
4827
4828
4829 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4830 // calculate where the next piece goes, (any empty square), and put it there
4831 {
4832         int i;
4833
4834         i = seed % squaresLeft[shade];
4835         nrOfShuffles *= squaresLeft[shade];
4836         seed /= squaresLeft[shade];
4837         put(board, pieceType, rank, i, shade);
4838 }
4839
4840 void AddTwoPieces(Board board, int pieceType, int rank)
4841 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4842 {
4843         int i, n=squaresLeft[ANY], j=n-1, k;
4844
4845         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4846         i = seed % k;  // pick one
4847         nrOfShuffles *= k;
4848         seed /= k;
4849         while(i >= j) i -= j--;
4850         j = n - 1 - j; i += j;
4851         put(board, pieceType, rank, j, ANY);
4852         put(board, pieceType, rank, i, ANY);
4853 }
4854
4855 void SetUpShuffle(Board board, int number)
4856 {
4857         int i, p, first=1;
4858
4859         GetPositionNumber(); nrOfShuffles = 1;
4860
4861         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4862         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4863         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4864
4865         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4866
4867         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4868             p = (int) board[0][i];
4869             if(p < (int) BlackPawn) piecesLeft[p] ++;
4870             board[0][i] = EmptySquare;
4871         }
4872
4873         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4874             // shuffles restricted to allow normal castling put KRR first
4875             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4876                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4877             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4878                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4879             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4880                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4881             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4882                 put(board, WhiteRook, 0, 0, ANY);
4883             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4884         }
4885
4886         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4887             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4888             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4889                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4890                 while(piecesLeft[p] >= 2) {
4891                     AddOnePiece(board, p, 0, LITE);
4892                     AddOnePiece(board, p, 0, DARK);
4893                 }
4894                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4895             }
4896
4897         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4898             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4899             // but we leave King and Rooks for last, to possibly obey FRC restriction
4900             if(p == (int)WhiteRook) continue;
4901             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4902             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4903         }
4904
4905         // now everything is placed, except perhaps King (Unicorn) and Rooks
4906
4907         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4908             // Last King gets castling rights
4909             while(piecesLeft[(int)WhiteUnicorn]) {
4910                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4911                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4912             }
4913
4914             while(piecesLeft[(int)WhiteKing]) {
4915                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4916                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4917             }
4918
4919
4920         } else {
4921             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4922             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4923         }
4924
4925         // Only Rooks can be left; simply place them all
4926         while(piecesLeft[(int)WhiteRook]) {
4927                 i = put(board, WhiteRook, 0, 0, ANY);
4928                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4929                         if(first) {
4930                                 first=0;
4931                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4932                         }
4933                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4934                 }
4935         }
4936         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4937             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4938         }
4939
4940         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4941 }
4942
4943 int SetCharTable( char *table, const char * map )
4944 /* [HGM] moved here from winboard.c because of its general usefulness */
4945 /*       Basically a safe strcpy that uses the last character as King */
4946 {
4947     int result = FALSE; int NrPieces;
4948
4949     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4950                     && NrPieces >= 12 && !(NrPieces&1)) {
4951         int i; /* [HGM] Accept even length from 12 to 34 */
4952
4953         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4954         for( i=0; i<NrPieces/2-1; i++ ) {
4955             table[i] = map[i];
4956             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4957         }
4958         table[(int) WhiteKing]  = map[NrPieces/2-1];
4959         table[(int) BlackKing]  = map[NrPieces-1];
4960
4961         result = TRUE;
4962     }
4963
4964     return result;
4965 }
4966
4967 void Prelude(Board board)
4968 {       // [HGM] superchess: random selection of exo-pieces
4969         int i, j, k; ChessSquare p; 
4970         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4971
4972         GetPositionNumber(); // use FRC position number
4973
4974         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4975             SetCharTable(pieceToChar, appData.pieceToCharTable);
4976             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4977                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4978         }
4979
4980         j = seed%4;                 seed /= 4; 
4981         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4982         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4983         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4984         j = seed%3 + (seed%3 >= j); seed /= 3; 
4985         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4986         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4987         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4988         j = seed%3;                 seed /= 3; 
4989         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4990         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4991         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4992         j = seed%2 + (seed%2 >= j); seed /= 2; 
4993         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4994         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4995         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4996         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4997         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4998         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4999         put(board, exoPieces[0],    0, 0, ANY);
5000         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5001 }
5002
5003 void
5004 InitPosition(redraw)
5005      int redraw;
5006 {
5007     ChessSquare (* pieces)[BOARD_FILES];
5008     int i, j, pawnRow, overrule,
5009     oldx = gameInfo.boardWidth,
5010     oldy = gameInfo.boardHeight,
5011     oldh = gameInfo.holdingsWidth,
5012     oldv = gameInfo.variant;
5013
5014     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5015
5016     /* [AS] Initialize pv info list [HGM] and game status */
5017     {
5018         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5019             pvInfoList[i].depth = 0;
5020             boards[i][EP_STATUS] = EP_NONE;
5021             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5022         }
5023
5024         initialRulePlies = 0; /* 50-move counter start */
5025
5026         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5027         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5028     }
5029
5030     
5031     /* [HGM] logic here is completely changed. In stead of full positions */
5032     /* the initialized data only consist of the two backranks. The switch */
5033     /* selects which one we will use, which is than copied to the Board   */
5034     /* initialPosition, which for the rest is initialized by Pawns and    */
5035     /* empty squares. This initial position is then copied to boards[0],  */
5036     /* possibly after shuffling, so that it remains available.            */
5037
5038     gameInfo.holdingsWidth = 0; /* default board sizes */
5039     gameInfo.boardWidth    = 8;
5040     gameInfo.boardHeight   = 8;
5041     gameInfo.holdingsSize  = 0;
5042     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5043     for(i=0; i<BOARD_FILES-2; i++)
5044       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5045     initialPosition[EP_STATUS] = EP_NONE;
5046     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5047
5048     switch (gameInfo.variant) {
5049     case VariantFischeRandom:
5050       shuffleOpenings = TRUE;
5051     default:
5052       pieces = FIDEArray;
5053       break;
5054     case VariantShatranj:
5055       pieces = ShatranjArray;
5056       nrCastlingRights = 0;
5057       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5058       break;
5059     case VariantMakruk:
5060       pieces = makrukArray;
5061       nrCastlingRights = 0;
5062       startedFromSetupPosition = TRUE;
5063       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5064       break;
5065     case VariantTwoKings:
5066       pieces = twoKingsArray;
5067       break;
5068     case VariantCapaRandom:
5069       shuffleOpenings = TRUE;
5070     case VariantCapablanca:
5071       pieces = CapablancaArray;
5072       gameInfo.boardWidth = 10;
5073       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5074       break;
5075     case VariantGothic:
5076       pieces = GothicArray;
5077       gameInfo.boardWidth = 10;
5078       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5079       break;
5080     case VariantJanus:
5081       pieces = JanusArray;
5082       gameInfo.boardWidth = 10;
5083       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5084       nrCastlingRights = 6;
5085         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5086         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5087         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5088         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5089         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5090         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5091       break;
5092     case VariantFalcon:
5093       pieces = FalconArray;
5094       gameInfo.boardWidth = 10;
5095       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5096       break;
5097     case VariantXiangqi:
5098       pieces = XiangqiArray;
5099       gameInfo.boardWidth  = 9;
5100       gameInfo.boardHeight = 10;
5101       nrCastlingRights = 0;
5102       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5103       break;
5104     case VariantShogi:
5105       pieces = ShogiArray;
5106       gameInfo.boardWidth  = 9;
5107       gameInfo.boardHeight = 9;
5108       gameInfo.holdingsSize = 7;
5109       nrCastlingRights = 0;
5110       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5111       break;
5112     case VariantCourier:
5113       pieces = CourierArray;
5114       gameInfo.boardWidth  = 12;
5115       nrCastlingRights = 0;
5116       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5117       break;
5118     case VariantKnightmate:
5119       pieces = KnightmateArray;
5120       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5121       break;
5122     case VariantFairy:
5123       pieces = fairyArray;
5124       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5125       break;
5126     case VariantGreat:
5127       pieces = GreatArray;
5128       gameInfo.boardWidth = 10;
5129       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5130       gameInfo.holdingsSize = 8;
5131       break;
5132     case VariantSuper:
5133       pieces = FIDEArray;
5134       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5135       gameInfo.holdingsSize = 8;
5136       startedFromSetupPosition = TRUE;
5137       break;
5138     case VariantCrazyhouse:
5139     case VariantBughouse:
5140       pieces = FIDEArray;
5141       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5142       gameInfo.holdingsSize = 5;
5143       break;
5144     case VariantWildCastle:
5145       pieces = FIDEArray;
5146       /* !!?shuffle with kings guaranteed to be on d or e file */
5147       shuffleOpenings = 1;
5148       break;
5149     case VariantNoCastle:
5150       pieces = FIDEArray;
5151       nrCastlingRights = 0;
5152       /* !!?unconstrained back-rank shuffle */
5153       shuffleOpenings = 1;
5154       break;
5155     }
5156
5157     overrule = 0;
5158     if(appData.NrFiles >= 0) {
5159         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5160         gameInfo.boardWidth = appData.NrFiles;
5161     }
5162     if(appData.NrRanks >= 0) {
5163         gameInfo.boardHeight = appData.NrRanks;
5164     }
5165     if(appData.holdingsSize >= 0) {
5166         i = appData.holdingsSize;
5167         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5168         gameInfo.holdingsSize = i;
5169     }
5170     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5171     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5172         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5173
5174     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5175     if(pawnRow < 1) pawnRow = 1;
5176     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5177
5178     /* User pieceToChar list overrules defaults */
5179     if(appData.pieceToCharTable != NULL)
5180         SetCharTable(pieceToChar, appData.pieceToCharTable);
5181
5182     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5183
5184         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5185             s = (ChessSquare) 0; /* account holding counts in guard band */
5186         for( i=0; i<BOARD_HEIGHT; i++ )
5187             initialPosition[i][j] = s;
5188
5189         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5190         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5191         initialPosition[pawnRow][j] = WhitePawn;
5192         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5193         if(gameInfo.variant == VariantXiangqi) {
5194             if(j&1) {
5195                 initialPosition[pawnRow][j] = 
5196                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5197                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5198                    initialPosition[2][j] = WhiteCannon;
5199                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5200                 }
5201             }
5202         }
5203         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5204     }
5205     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5206
5207             j=BOARD_LEFT+1;
5208             initialPosition[1][j] = WhiteBishop;
5209             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5210             j=BOARD_RGHT-2;
5211             initialPosition[1][j] = WhiteRook;
5212             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5213     }
5214
5215     if( nrCastlingRights == -1) {
5216         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5217         /*       This sets default castling rights from none to normal corners   */
5218         /* Variants with other castling rights must set them themselves above    */
5219         nrCastlingRights = 6;
5220        
5221         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5222         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5223         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5224         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5225         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5226         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5227      }
5228
5229      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5230      if(gameInfo.variant == VariantGreat) { // promotion commoners
5231         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5232         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5233         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5234         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5235      }
5236   if (appData.debugMode) {
5237     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5238   }
5239     if(shuffleOpenings) {
5240         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5241         startedFromSetupPosition = TRUE;
5242     }
5243     if(startedFromPositionFile) {
5244       /* [HGM] loadPos: use PositionFile for every new game */
5245       CopyBoard(initialPosition, filePosition);
5246       for(i=0; i<nrCastlingRights; i++)
5247           initialRights[i] = filePosition[CASTLING][i];
5248       startedFromSetupPosition = TRUE;
5249     }
5250
5251     CopyBoard(boards[0], initialPosition);
5252
5253     if(oldx != gameInfo.boardWidth ||
5254        oldy != gameInfo.boardHeight ||
5255        oldh != gameInfo.holdingsWidth
5256 #ifdef GOTHIC
5257        || oldv == VariantGothic ||        // For licensing popups
5258        gameInfo.variant == VariantGothic
5259 #endif
5260 #ifdef FALCON
5261        || oldv == VariantFalcon ||
5262        gameInfo.variant == VariantFalcon
5263 #endif
5264                                          )
5265             InitDrawingSizes(-2 ,0);
5266
5267     if (redraw)
5268       DrawPosition(TRUE, boards[currentMove]);
5269 }
5270
5271 void
5272 SendBoard(cps, moveNum)
5273      ChessProgramState *cps;
5274      int moveNum;
5275 {
5276     char message[MSG_SIZ];
5277     
5278     if (cps->useSetboard) {
5279       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5280       sprintf(message, "setboard %s\n", fen);
5281       SendToProgram(message, cps);
5282       free(fen);
5283
5284     } else {
5285       ChessSquare *bp;
5286       int i, j;
5287       /* Kludge to set black to move, avoiding the troublesome and now
5288        * deprecated "black" command.
5289        */
5290       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5291
5292       SendToProgram("edit\n", cps);
5293       SendToProgram("#\n", cps);
5294       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5295         bp = &boards[moveNum][i][BOARD_LEFT];
5296         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5297           if ((int) *bp < (int) BlackPawn) {
5298             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5299                     AAA + j, ONE + i);
5300             if(message[0] == '+' || message[0] == '~') {
5301                 sprintf(message, "%c%c%c+\n",
5302                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5303                         AAA + j, ONE + i);
5304             }
5305             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5306                 message[1] = BOARD_RGHT   - 1 - j + '1';
5307                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5308             }
5309             SendToProgram(message, cps);
5310           }
5311         }
5312       }
5313     
5314       SendToProgram("c\n", cps);
5315       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5316         bp = &boards[moveNum][i][BOARD_LEFT];
5317         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5318           if (((int) *bp != (int) EmptySquare)
5319               && ((int) *bp >= (int) BlackPawn)) {
5320             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5321                     AAA + j, ONE + i);
5322             if(message[0] == '+' || message[0] == '~') {
5323                 sprintf(message, "%c%c%c+\n",
5324                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5325                         AAA + j, ONE + i);
5326             }
5327             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5328                 message[1] = BOARD_RGHT   - 1 - j + '1';
5329                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5330             }
5331             SendToProgram(message, cps);
5332           }
5333         }
5334       }
5335     
5336       SendToProgram(".\n", cps);
5337     }
5338     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5339 }
5340
5341 static int autoQueen; // [HGM] oneclick
5342
5343 int
5344 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5345 {
5346     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5347     /* [HGM] add Shogi promotions */
5348     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5349     ChessSquare piece;
5350     ChessMove moveType;
5351     Boolean premove;
5352
5353     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5354     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5355
5356     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5357       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5358         return FALSE;
5359
5360     piece = boards[currentMove][fromY][fromX];
5361     if(gameInfo.variant == VariantShogi) {
5362         promotionZoneSize = 3;
5363         highestPromotingPiece = (int)WhiteFerz;
5364     } else if(gameInfo.variant == VariantMakruk) {
5365         promotionZoneSize = 3;
5366     }
5367
5368     // next weed out all moves that do not touch the promotion zone at all
5369     if((int)piece >= BlackPawn) {
5370         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5371              return FALSE;
5372         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5373     } else {
5374         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5375            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5376     }
5377
5378     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5379
5380     // weed out mandatory Shogi promotions
5381     if(gameInfo.variant == VariantShogi) {
5382         if(piece >= BlackPawn) {
5383             if(toY == 0 && piece == BlackPawn ||
5384                toY == 0 && piece == BlackQueen ||
5385                toY <= 1 && piece == BlackKnight) {
5386                 *promoChoice = '+';
5387                 return FALSE;
5388             }
5389         } else {
5390             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5391                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5392                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5393                 *promoChoice = '+';
5394                 return FALSE;
5395             }
5396         }
5397     }
5398
5399     // weed out obviously illegal Pawn moves
5400     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5401         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5402         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5403         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5404         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5405         // note we are not allowed to test for valid (non-)capture, due to premove
5406     }
5407
5408     // we either have a choice what to promote to, or (in Shogi) whether to promote
5409     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5410         *promoChoice = PieceToChar(BlackFerz);  // no choice
5411         return FALSE;
5412     }
5413     if(autoQueen) { // predetermined
5414         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5415              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5416         else *promoChoice = PieceToChar(BlackQueen);
5417         return FALSE;
5418     }
5419
5420     // suppress promotion popup on illegal moves that are not premoves
5421     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5422               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5423     if(appData.testLegality && !premove) {
5424         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5425                         fromY, fromX, toY, toX, NULLCHAR);
5426         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5427            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5428             return FALSE;
5429     }
5430
5431     return TRUE;
5432 }
5433
5434 int
5435 InPalace(row, column)
5436      int row, column;
5437 {   /* [HGM] for Xiangqi */
5438     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5439          column < (BOARD_WIDTH + 4)/2 &&
5440          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5441     return FALSE;
5442 }
5443
5444 int
5445 PieceForSquare (x, y)
5446      int x;
5447      int y;
5448 {
5449   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5450      return -1;
5451   else
5452      return boards[currentMove][y][x];
5453 }
5454
5455 int
5456 OKToStartUserMove(x, y)
5457      int x, y;
5458 {
5459     ChessSquare from_piece;
5460     int white_piece;
5461
5462     if (matchMode) return FALSE;
5463     if (gameMode == EditPosition) return TRUE;
5464
5465     if (x >= 0 && y >= 0)
5466       from_piece = boards[currentMove][y][x];
5467     else
5468       from_piece = EmptySquare;
5469
5470     if (from_piece == EmptySquare) return FALSE;
5471
5472     white_piece = (int)from_piece >= (int)WhitePawn &&
5473       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5474
5475     switch (gameMode) {
5476       case PlayFromGameFile:
5477       case AnalyzeFile:
5478       case TwoMachinesPlay:
5479       case EndOfGame:
5480         return FALSE;
5481
5482       case IcsObserving:
5483       case IcsIdle:
5484         return FALSE;
5485
5486       case MachinePlaysWhite:
5487       case IcsPlayingBlack:
5488         if (appData.zippyPlay) return FALSE;
5489         if (white_piece) {
5490             DisplayMoveError(_("You are playing Black"));
5491             return FALSE;
5492         }
5493         break;
5494
5495       case MachinePlaysBlack:
5496       case IcsPlayingWhite:
5497         if (appData.zippyPlay) return FALSE;
5498         if (!white_piece) {
5499             DisplayMoveError(_("You are playing White"));
5500             return FALSE;
5501         }
5502         break;
5503
5504       case EditGame:
5505         if (!white_piece && WhiteOnMove(currentMove)) {
5506             DisplayMoveError(_("It is White's turn"));
5507             return FALSE;
5508         }           
5509         if (white_piece && !WhiteOnMove(currentMove)) {
5510             DisplayMoveError(_("It is Black's turn"));
5511             return FALSE;
5512         }           
5513         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5514             /* Editing correspondence game history */
5515             /* Could disallow this or prompt for confirmation */
5516             cmailOldMove = -1;
5517         }
5518         break;
5519
5520       case BeginningOfGame:
5521         if (appData.icsActive) return FALSE;
5522         if (!appData.noChessProgram) {
5523             if (!white_piece) {
5524                 DisplayMoveError(_("You are playing White"));
5525                 return FALSE;
5526             }
5527         }
5528         break;
5529         
5530       case Training:
5531         if (!white_piece && WhiteOnMove(currentMove)) {
5532             DisplayMoveError(_("It is White's turn"));
5533             return FALSE;
5534         }           
5535         if (white_piece && !WhiteOnMove(currentMove)) {
5536             DisplayMoveError(_("It is Black's turn"));
5537             return FALSE;
5538         }           
5539         break;
5540
5541       default:
5542       case IcsExamining:
5543         break;
5544     }
5545     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5546         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5547         && gameMode != AnalyzeFile && gameMode != Training) {
5548         DisplayMoveError(_("Displayed position is not current"));
5549         return FALSE;
5550     }
5551     return TRUE;
5552 }
5553
5554 Boolean
5555 OnlyMove(int *x, int *y, Boolean captures) {
5556     DisambiguateClosure cl;
5557     if (appData.zippyPlay) return FALSE;
5558     switch(gameMode) {
5559       case MachinePlaysBlack:
5560       case IcsPlayingWhite:
5561       case BeginningOfGame:
5562         if(!WhiteOnMove(currentMove)) return FALSE;
5563         break;
5564       case MachinePlaysWhite:
5565       case IcsPlayingBlack:
5566         if(WhiteOnMove(currentMove)) return FALSE;
5567         break;
5568       default:
5569         return FALSE;
5570     }
5571     cl.pieceIn = EmptySquare; 
5572     cl.rfIn = *y;
5573     cl.ffIn = *x;
5574     cl.rtIn = -1;
5575     cl.ftIn = -1;
5576     cl.promoCharIn = NULLCHAR;
5577     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5578     if( cl.kind == NormalMove ||
5579         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5580         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5581         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5582         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5583       fromX = cl.ff;
5584       fromY = cl.rf;
5585       *x = cl.ft;
5586       *y = cl.rt;
5587       return TRUE;
5588     }
5589     if(cl.kind != ImpossibleMove) return FALSE;
5590     cl.pieceIn = EmptySquare;
5591     cl.rfIn = -1;
5592     cl.ffIn = -1;
5593     cl.rtIn = *y;
5594     cl.ftIn = *x;
5595     cl.promoCharIn = NULLCHAR;
5596     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5597     if( cl.kind == NormalMove ||
5598         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5599         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5600         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5601         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5602       fromX = cl.ff;
5603       fromY = cl.rf;
5604       *x = cl.ft;
5605       *y = cl.rt;
5606       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5607       return TRUE;
5608     }
5609     return FALSE;
5610 }
5611
5612 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5613 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5614 int lastLoadGameUseList = FALSE;
5615 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5616 ChessMove lastLoadGameStart = (ChessMove) 0;
5617
5618 ChessMove
5619 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5620      int fromX, fromY, toX, toY;
5621      int promoChar;
5622      Boolean captureOwn;
5623 {
5624     ChessMove moveType;
5625     ChessSquare pdown, pup;
5626
5627     /* Check if the user is playing in turn.  This is complicated because we
5628        let the user "pick up" a piece before it is his turn.  So the piece he
5629        tried to pick up may have been captured by the time he puts it down!
5630        Therefore we use the color the user is supposed to be playing in this
5631        test, not the color of the piece that is currently on the starting
5632        square---except in EditGame mode, where the user is playing both
5633        sides; fortunately there the capture race can't happen.  (It can
5634        now happen in IcsExamining mode, but that's just too bad.  The user
5635        will get a somewhat confusing message in that case.)
5636        */
5637
5638     switch (gameMode) {
5639       case PlayFromGameFile:
5640       case AnalyzeFile:
5641       case TwoMachinesPlay:
5642       case EndOfGame:
5643       case IcsObserving:
5644       case IcsIdle:
5645         /* We switched into a game mode where moves are not accepted,
5646            perhaps while the mouse button was down. */
5647         return ImpossibleMove;
5648
5649       case MachinePlaysWhite:
5650         /* User is moving for Black */
5651         if (WhiteOnMove(currentMove)) {
5652             DisplayMoveError(_("It is White's turn"));
5653             return ImpossibleMove;
5654         }
5655         break;
5656
5657       case MachinePlaysBlack:
5658         /* User is moving for White */
5659         if (!WhiteOnMove(currentMove)) {
5660             DisplayMoveError(_("It is Black's turn"));
5661             return ImpossibleMove;
5662         }
5663         break;
5664
5665       case EditGame:
5666       case IcsExamining:
5667       case BeginningOfGame:
5668       case AnalyzeMode:
5669       case Training:
5670         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5671             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5672             /* User is moving for Black */
5673             if (WhiteOnMove(currentMove)) {
5674                 DisplayMoveError(_("It is White's turn"));
5675                 return ImpossibleMove;
5676             }
5677         } else {
5678             /* User is moving for White */
5679             if (!WhiteOnMove(currentMove)) {
5680                 DisplayMoveError(_("It is Black's turn"));
5681                 return ImpossibleMove;
5682             }
5683         }
5684         break;
5685
5686       case IcsPlayingBlack:
5687         /* User is moving for Black */
5688         if (WhiteOnMove(currentMove)) {
5689             if (!appData.premove) {
5690                 DisplayMoveError(_("It is White's turn"));
5691             } else if (toX >= 0 && toY >= 0) {
5692                 premoveToX = toX;
5693                 premoveToY = toY;
5694                 premoveFromX = fromX;
5695                 premoveFromY = fromY;
5696                 premovePromoChar = promoChar;
5697                 gotPremove = 1;
5698                 if (appData.debugMode) 
5699                     fprintf(debugFP, "Got premove: fromX %d,"
5700                             "fromY %d, toX %d, toY %d\n",
5701                             fromX, fromY, toX, toY);
5702             }
5703             return ImpossibleMove;
5704         }
5705         break;
5706
5707       case IcsPlayingWhite:
5708         /* User is moving for White */
5709         if (!WhiteOnMove(currentMove)) {
5710             if (!appData.premove) {
5711                 DisplayMoveError(_("It is Black's turn"));
5712             } else if (toX >= 0 && toY >= 0) {
5713                 premoveToX = toX;
5714                 premoveToY = toY;
5715                 premoveFromX = fromX;
5716                 premoveFromY = fromY;
5717                 premovePromoChar = promoChar;
5718                 gotPremove = 1;
5719                 if (appData.debugMode) 
5720                     fprintf(debugFP, "Got premove: fromX %d,"
5721                             "fromY %d, toX %d, toY %d\n",
5722                             fromX, fromY, toX, toY);
5723             }
5724             return ImpossibleMove;
5725         }
5726         break;
5727
5728       default:
5729         break;
5730
5731       case EditPosition:
5732         /* EditPosition, empty square, or different color piece;
5733            click-click move is possible */
5734         if (toX == -2 || toY == -2) {
5735             boards[0][fromY][fromX] = EmptySquare;
5736             return AmbiguousMove;
5737         } else if (toX >= 0 && toY >= 0) {
5738             boards[0][toY][toX] = boards[0][fromY][fromX];
5739             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5740                 if(boards[0][fromY][0] != EmptySquare) {
5741                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5742                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5743                 }
5744             } else
5745             if(fromX == BOARD_RGHT+1) {
5746                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5747                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5748                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5749                 }
5750             } else
5751             boards[0][fromY][fromX] = EmptySquare;
5752             return AmbiguousMove;
5753         }
5754         return ImpossibleMove;
5755     }
5756
5757     if(toX < 0 || toY < 0) return ImpossibleMove;
5758     pdown = boards[currentMove][fromY][fromX];
5759     pup = boards[currentMove][toY][toX];
5760
5761     /* [HGM] If move started in holdings, it means a drop */
5762     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5763          if( pup != EmptySquare ) return ImpossibleMove;
5764          if(appData.testLegality) {
5765              /* it would be more logical if LegalityTest() also figured out
5766               * which drops are legal. For now we forbid pawns on back rank.
5767               * Shogi is on its own here...
5768               */
5769              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5770                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5771                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5772          }
5773          return WhiteDrop; /* Not needed to specify white or black yet */
5774     }
5775
5776     /* [HGM] always test for legality, to get promotion info */
5777     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5778                                          fromY, fromX, toY, toX, promoChar);
5779     /* [HGM] but possibly ignore an IllegalMove result */
5780     if (appData.testLegality) {
5781         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5782             DisplayMoveError(_("Illegal move"));
5783             return ImpossibleMove;
5784         }
5785     }
5786
5787     return moveType;
5788     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5789        function is made into one that returns an OK move type if FinishMove
5790        should be called. This to give the calling driver routine the
5791        opportunity to finish the userMove input with a promotion popup,
5792        without bothering the user with this for invalid or illegal moves */
5793
5794 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5795 }
5796
5797 /* Common tail of UserMoveEvent and DropMenuEvent */
5798 int
5799 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5800      ChessMove moveType;
5801      int fromX, fromY, toX, toY;
5802      /*char*/int promoChar;
5803 {
5804     char *bookHit = 0;
5805
5806     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5807         // [HGM] superchess: suppress promotions to non-available piece
5808         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5809         if(WhiteOnMove(currentMove)) {
5810             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5811         } else {
5812             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5813         }
5814     }
5815
5816     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5817        move type in caller when we know the move is a legal promotion */
5818     if(moveType == NormalMove && promoChar)
5819         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5820
5821     /* [HGM] convert drag-and-drop piece drops to standard form */
5822     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5823          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5824            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5825                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5826            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5827            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5828            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5829            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5830          fromY = DROP_RANK;
5831     }
5832
5833     /* [HGM] <popupFix> The following if has been moved here from
5834        UserMoveEvent(). Because it seemed to belong here (why not allow
5835        piece drops in training games?), and because it can only be
5836        performed after it is known to what we promote. */
5837     if (gameMode == Training) {
5838       /* compare the move played on the board to the next move in the
5839        * game. If they match, display the move and the opponent's response. 
5840        * If they don't match, display an error message.
5841        */
5842       int saveAnimate;
5843       Board testBoard;
5844       CopyBoard(testBoard, boards[currentMove]);
5845       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5846
5847       if (CompareBoards(testBoard, boards[currentMove+1])) {
5848         ForwardInner(currentMove+1);
5849
5850         /* Autoplay the opponent's response.
5851          * if appData.animate was TRUE when Training mode was entered,
5852          * the response will be animated.
5853          */
5854         saveAnimate = appData.animate;
5855         appData.animate = animateTraining;
5856         ForwardInner(currentMove+1);
5857         appData.animate = saveAnimate;
5858
5859         /* check for the end of the game */
5860         if (currentMove >= forwardMostMove) {
5861           gameMode = PlayFromGameFile;
5862           ModeHighlight();
5863           SetTrainingModeOff();
5864           DisplayInformation(_("End of game"));
5865         }
5866       } else {
5867         DisplayError(_("Incorrect move"), 0);
5868       }
5869       return 1;
5870     }
5871
5872   /* Ok, now we know that the move is good, so we can kill
5873      the previous line in Analysis Mode */
5874   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5875                                 && currentMove < forwardMostMove) {
5876     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5877   }
5878
5879   /* If we need the chess program but it's dead, restart it */
5880   ResurrectChessProgram();
5881
5882   /* A user move restarts a paused game*/
5883   if (pausing)
5884     PauseEvent();
5885
5886   thinkOutput[0] = NULLCHAR;
5887
5888   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5889
5890   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5891
5892   if (gameMode == BeginningOfGame) {
5893     if (appData.noChessProgram) {
5894       gameMode = EditGame;
5895       SetGameInfo();
5896     } else {
5897       char buf[MSG_SIZ];
5898       gameMode = MachinePlaysBlack;
5899       StartClocks();
5900       SetGameInfo();
5901       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5902       DisplayTitle(buf);
5903       if (first.sendName) {
5904         sprintf(buf, "name %s\n", gameInfo.white);
5905         SendToProgram(buf, &first);
5906       }
5907       StartClocks();
5908     }
5909     ModeHighlight();
5910   }
5911
5912   /* Relay move to ICS or chess engine */
5913   if (appData.icsActive) {
5914     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5915         gameMode == IcsExamining) {
5916       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5917         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5918         SendToICS("draw ");
5919         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5920       }
5921       // also send plain move, in case ICS does not understand atomic claims
5922       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5923       ics_user_moved = 1;
5924     }
5925   } else {
5926     if (first.sendTime && (gameMode == BeginningOfGame ||
5927                            gameMode == MachinePlaysWhite ||
5928                            gameMode == MachinePlaysBlack)) {
5929       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5930     }
5931     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5932          // [HGM] book: if program might be playing, let it use book
5933         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5934         first.maybeThinking = TRUE;
5935     } else SendMoveToProgram(forwardMostMove-1, &first);
5936     if (currentMove == cmailOldMove + 1) {
5937       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5938     }
5939   }
5940
5941   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5942
5943   switch (gameMode) {
5944   case EditGame:
5945     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5946     case MT_NONE:
5947     case MT_CHECK:
5948       break;
5949     case MT_CHECKMATE:
5950     case MT_STAINMATE:
5951       if (WhiteOnMove(currentMove)) {
5952         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5953       } else {
5954         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5955       }
5956       break;
5957     case MT_STALEMATE:
5958       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5959       break;
5960     }
5961     break;
5962     
5963   case MachinePlaysBlack:
5964   case MachinePlaysWhite:
5965     /* disable certain menu options while machine is thinking */
5966     SetMachineThinkingEnables();
5967     break;
5968
5969   default:
5970     break;
5971   }
5972
5973   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5974         
5975   if(bookHit) { // [HGM] book: simulate book reply
5976         static char bookMove[MSG_SIZ]; // a bit generous?
5977
5978         programStats.nodes = programStats.depth = programStats.time = 
5979         programStats.score = programStats.got_only_move = 0;
5980         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5981
5982         strcpy(bookMove, "move ");
5983         strcat(bookMove, bookHit);
5984         HandleMachineMove(bookMove, &first);
5985   }
5986   return 1;
5987 }
5988
5989 void
5990 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5991      int fromX, fromY, toX, toY;
5992      int promoChar;
5993 {
5994     /* [HGM] This routine was added to allow calling of its two logical
5995        parts from other modules in the old way. Before, UserMoveEvent()
5996        automatically called FinishMove() if the move was OK, and returned
5997        otherwise. I separated the two, in order to make it possible to
5998        slip a promotion popup in between. But that it always needs two
5999        calls, to the first part, (now called UserMoveTest() ), and to
6000        FinishMove if the first part succeeded. Calls that do not need
6001        to do anything in between, can call this routine the old way. 
6002     */
6003     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6004 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6005     if(moveType == AmbiguousMove)
6006         DrawPosition(FALSE, boards[currentMove]);
6007     else if(moveType != ImpossibleMove && moveType != Comment)
6008         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6009 }
6010
6011 void
6012 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6013      Board board;
6014      int flags;
6015      ChessMove kind;
6016      int rf, ff, rt, ft;
6017      VOIDSTAR closure;
6018 {
6019     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6020     Markers *m = (Markers *) closure;
6021     if(rf == fromY && ff == fromX)
6022         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6023                          || kind == WhiteCapturesEnPassant
6024                          || kind == BlackCapturesEnPassant);
6025     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6026 }
6027
6028 void
6029 MarkTargetSquares(int clear)
6030 {
6031   int x, y;
6032   if(!appData.markers || !appData.highlightDragging || 
6033      !appData.testLegality || gameMode == EditPosition) return;
6034   if(clear) {
6035     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6036   } else {
6037     int capt = 0;
6038     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6039     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6040       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6041       if(capt)
6042       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6043     }
6044   }
6045   DrawPosition(TRUE, NULL);
6046 }
6047
6048 void LeftClick(ClickType clickType, int xPix, int yPix)
6049 {
6050     int x, y;
6051     Boolean saveAnimate;
6052     static int second = 0, promotionChoice = 0;
6053     char promoChoice = NULLCHAR;
6054
6055     if(appData.seekGraph && appData.icsActive && loggedOn &&
6056         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6057         SeekGraphClick(clickType, xPix, yPix, 0);
6058         return;
6059     }
6060
6061     if (clickType == Press) ErrorPopDown();
6062     MarkTargetSquares(1);
6063
6064     x = EventToSquare(xPix, BOARD_WIDTH);
6065     y = EventToSquare(yPix, BOARD_HEIGHT);
6066     if (!flipView && y >= 0) {
6067         y = BOARD_HEIGHT - 1 - y;
6068     }
6069     if (flipView && x >= 0) {
6070         x = BOARD_WIDTH - 1 - x;
6071     }
6072
6073     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6074         if(clickType == Release) return; // ignore upclick of click-click destination
6075         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6076         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6077         if(gameInfo.holdingsWidth && 
6078                 (WhiteOnMove(currentMove) 
6079                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6080                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6081             // click in right holdings, for determining promotion piece
6082             ChessSquare p = boards[currentMove][y][x];
6083             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6084             if(p != EmptySquare) {
6085                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6086                 fromX = fromY = -1;
6087                 return;
6088             }
6089         }
6090         DrawPosition(FALSE, boards[currentMove]);
6091         return;
6092     }
6093
6094     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6095     if(clickType == Press
6096             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6097               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6098               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6099         return;
6100
6101     autoQueen = appData.alwaysPromoteToQueen;
6102
6103     if (fromX == -1) {
6104       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6105         if (clickType == Press) {
6106             /* First square */
6107             if (OKToStartUserMove(x, y)) {
6108                 fromX = x;
6109                 fromY = y;
6110                 second = 0;
6111                 MarkTargetSquares(0);
6112                 DragPieceBegin(xPix, yPix);
6113                 if (appData.highlightDragging) {
6114                     SetHighlights(x, y, -1, -1);
6115                 }
6116             }
6117         }
6118         return;
6119       }
6120     }
6121
6122     /* fromX != -1 */
6123     if (clickType == Press && gameMode != EditPosition) {
6124         ChessSquare fromP;
6125         ChessSquare toP;
6126         int frc;
6127
6128         // ignore off-board to clicks
6129         if(y < 0 || x < 0) return;
6130
6131         /* Check if clicking again on the same color piece */
6132         fromP = boards[currentMove][fromY][fromX];
6133         toP = boards[currentMove][y][x];
6134         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6135         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6136              WhitePawn <= toP && toP <= WhiteKing &&
6137              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6138              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6139             (BlackPawn <= fromP && fromP <= BlackKing && 
6140              BlackPawn <= toP && toP <= BlackKing &&
6141              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6142              !(fromP == BlackKing && toP == BlackRook && frc))) {
6143             /* Clicked again on same color piece -- changed his mind */
6144             second = (x == fromX && y == fromY);
6145            if(!second || !OnlyMove(&x, &y, TRUE)) {
6146             if (appData.highlightDragging) {
6147                 SetHighlights(x, y, -1, -1);
6148             } else {
6149                 ClearHighlights();
6150             }
6151             if (OKToStartUserMove(x, y)) {
6152                 fromX = x;
6153                 fromY = y;
6154                 MarkTargetSquares(0);
6155                 DragPieceBegin(xPix, yPix);
6156             }
6157             return;
6158            }
6159         }
6160         // ignore clicks on holdings
6161         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6162     }
6163
6164     if (clickType == Release && x == fromX && y == fromY) {
6165         DragPieceEnd(xPix, yPix);
6166         if (appData.animateDragging) {
6167             /* Undo animation damage if any */
6168             DrawPosition(FALSE, NULL);
6169         }
6170         if (second) {
6171             /* Second up/down in same square; just abort move */
6172             second = 0;
6173             fromX = fromY = -1;
6174             ClearHighlights();
6175             gotPremove = 0;
6176             ClearPremoveHighlights();
6177         } else {
6178             /* First upclick in same square; start click-click mode */
6179             SetHighlights(x, y, -1, -1);
6180         }
6181         return;
6182     }
6183
6184     /* we now have a different from- and (possibly off-board) to-square */
6185     /* Completed move */
6186     toX = x;
6187     toY = y;
6188     saveAnimate = appData.animate;
6189     if (clickType == Press) {
6190         /* Finish clickclick move */
6191         if (appData.animate || appData.highlightLastMove) {
6192             SetHighlights(fromX, fromY, toX, toY);
6193         } else {
6194             ClearHighlights();
6195         }
6196     } else {
6197         /* Finish drag move */
6198         if (appData.highlightLastMove) {
6199             SetHighlights(fromX, fromY, toX, toY);
6200         } else {
6201             ClearHighlights();
6202         }
6203         DragPieceEnd(xPix, yPix);
6204         /* Don't animate move and drag both */
6205         appData.animate = FALSE;
6206     }
6207
6208     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6209     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6210         ChessSquare piece = boards[currentMove][fromY][fromX];
6211         if(gameMode == EditPosition && piece != EmptySquare &&
6212            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6213             int n;
6214              
6215             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6216                 n = PieceToNumber(piece - (int)BlackPawn);
6217                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6218                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6219                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6220             } else
6221             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6222                 n = PieceToNumber(piece);
6223                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6224                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6225                 boards[currentMove][n][BOARD_WIDTH-2]++;
6226             }
6227             boards[currentMove][fromY][fromX] = EmptySquare;
6228         }
6229         ClearHighlights();
6230         fromX = fromY = -1;
6231         DrawPosition(TRUE, boards[currentMove]);
6232         return;
6233     }
6234
6235     // off-board moves should not be highlighted
6236     if(x < 0 || x < 0) ClearHighlights();
6237
6238     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6239         SetHighlights(fromX, fromY, toX, toY);
6240         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6241             // [HGM] super: promotion to captured piece selected from holdings
6242             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6243             promotionChoice = TRUE;
6244             // kludge follows to temporarily execute move on display, without promoting yet
6245             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6246             boards[currentMove][toY][toX] = p;
6247             DrawPosition(FALSE, boards[currentMove]);
6248             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6249             boards[currentMove][toY][toX] = q;
6250             DisplayMessage("Click in holdings to choose piece", "");
6251             return;
6252         }
6253         PromotionPopUp();
6254     } else {
6255         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6256         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6257         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6258         fromX = fromY = -1;
6259     }
6260     appData.animate = saveAnimate;
6261     if (appData.animate || appData.animateDragging) {
6262         /* Undo animation damage if needed */
6263         DrawPosition(FALSE, NULL);
6264     }
6265 }
6266
6267 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6268 {   // front-end-free part taken out of PieceMenuPopup
6269     int whichMenu; int xSqr, ySqr;
6270
6271     if(seekGraphUp) { // [HGM] seekgraph
6272         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6273         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6274         return -2;
6275     }
6276
6277     xSqr = EventToSquare(x, BOARD_WIDTH);
6278     ySqr = EventToSquare(y, BOARD_HEIGHT);
6279     if (action == Release) UnLoadPV(); // [HGM] pv
6280     if (action != Press) return -2; // return code to be ignored
6281     switch (gameMode) {
6282       case IcsExamining:
6283         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6284       case EditPosition:
6285         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6286         if (xSqr < 0 || ySqr < 0) return -1;\r
6287         whichMenu = 0; // edit-position menu
6288         break;
6289       case IcsObserving:
6290         if(!appData.icsEngineAnalyze) return -1;
6291       case IcsPlayingWhite:
6292       case IcsPlayingBlack:
6293         if(!appData.zippyPlay) goto noZip;
6294       case AnalyzeMode:
6295       case AnalyzeFile:
6296       case MachinePlaysWhite:
6297       case MachinePlaysBlack:
6298       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6299         if (!appData.dropMenu) {
6300           LoadPV(x, y);
6301           return 2; // flag front-end to grab mouse events
6302         }
6303         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6304            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6305       case EditGame:
6306       noZip:
6307         if (xSqr < 0 || ySqr < 0) return -1;
6308         if (!appData.dropMenu || appData.testLegality &&
6309             gameInfo.variant != VariantBughouse &&
6310             gameInfo.variant != VariantCrazyhouse) return -1;
6311         whichMenu = 1; // drop menu
6312         break;
6313       default:
6314         return -1;
6315     }
6316
6317     if (((*fromX = xSqr) < 0) ||
6318         ((*fromY = ySqr) < 0)) {
6319         *fromX = *fromY = -1;
6320         return -1;
6321     }
6322     if (flipView)
6323       *fromX = BOARD_WIDTH - 1 - *fromX;
6324     else
6325       *fromY = BOARD_HEIGHT - 1 - *fromY;
6326
6327     return whichMenu;
6328 }
6329
6330 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6331 {
6332 //    char * hint = lastHint;
6333     FrontEndProgramStats stats;
6334
6335     stats.which = cps == &first ? 0 : 1;
6336     stats.depth = cpstats->depth;
6337     stats.nodes = cpstats->nodes;
6338     stats.score = cpstats->score;
6339     stats.time = cpstats->time;
6340     stats.pv = cpstats->movelist;
6341     stats.hint = lastHint;
6342     stats.an_move_index = 0;
6343     stats.an_move_count = 0;
6344
6345     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6346         stats.hint = cpstats->move_name;
6347         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6348         stats.an_move_count = cpstats->nr_moves;
6349     }
6350
6351     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6352
6353     SetProgramStats( &stats );
6354 }
6355
6356 int
6357 Adjudicate(ChessProgramState *cps)
6358 {       // [HGM] some adjudications useful with buggy engines
6359         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6360         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6361         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6362         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6363         int k, count = 0; static int bare = 1;
6364         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6365         Boolean canAdjudicate = !appData.icsActive;
6366
6367         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6368         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6369             if( appData.testLegality )
6370             {   /* [HGM] Some more adjudications for obstinate engines */
6371                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6372                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6373                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6374                 static int moveCount = 6;
6375                 ChessMove result;
6376                 char *reason = NULL;
6377
6378                 /* Count what is on board. */
6379                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6380                 {   ChessSquare p = boards[forwardMostMove][i][j];
6381                     int m=i;
6382
6383                     switch((int) p)
6384                     {   /* count B,N,R and other of each side */
6385                         case WhiteKing:
6386                         case BlackKing:
6387                              NrK++; break; // [HGM] atomic: count Kings
6388                         case WhiteKnight:
6389                              NrWN++; break;
6390                         case WhiteBishop:
6391                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6392                              bishopsColor |= 1 << ((i^j)&1);
6393                              NrWB++; break;
6394                         case BlackKnight:
6395                              NrBN++; break;
6396                         case BlackBishop:
6397                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6398                              bishopsColor |= 1 << ((i^j)&1);
6399                              NrBB++; break;
6400                         case WhiteRook:
6401                              NrWR++; break;
6402                         case BlackRook:
6403                              NrBR++; break;
6404                         case WhiteQueen:
6405                              NrWQ++; break;
6406                         case BlackQueen:
6407                              NrBQ++; break;
6408                         case EmptySquare: 
6409                              break;
6410                         case BlackPawn:
6411                              m = 7-i;
6412                         case WhitePawn:
6413                              PawnAdvance += m; NrPawns++;
6414                     }
6415                     NrPieces += (p != EmptySquare);
6416                     NrW += ((int)p < (int)BlackPawn);
6417                     if(gameInfo.variant == VariantXiangqi && 
6418                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6419                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6420                         NrW -= ((int)p < (int)BlackPawn);
6421                     }
6422                 }
6423
6424                 /* Some material-based adjudications that have to be made before stalemate test */
6425                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6426                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6427                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6428                      if(canAdjudicate && appData.checkMates) {
6429                          if(engineOpponent)
6430                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6431                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6432                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6433                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6434                          return 1;
6435                      }
6436                 }
6437
6438                 /* Bare King in Shatranj (loses) or Losers (wins) */
6439                 if( NrW == 1 || NrPieces - NrW == 1) {
6440                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6441                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6442                      if(canAdjudicate && appData.checkMates) {
6443                          if(engineOpponent)
6444                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6445                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6446                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6447                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6448                          return 1;
6449                      }
6450                   } else
6451                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6452                   {    /* bare King */
6453                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6454                         if(canAdjudicate && appData.checkMates) {
6455                             /* but only adjudicate if adjudication enabled */
6456                             if(engineOpponent)
6457                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6458                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6459                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6460                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6461                             return 1;
6462                         }
6463                   }
6464                 } else bare = 1;
6465
6466
6467             // don't wait for engine to announce game end if we can judge ourselves
6468             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6469               case MT_CHECK:
6470                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6471                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6472                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6473                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6474                             checkCnt++;
6475                         if(checkCnt >= 2) {
6476                             reason = "Xboard adjudication: 3rd check";
6477                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6478                             break;
6479                         }
6480                     }
6481                 }
6482               case MT_NONE:
6483               default:
6484                 break;
6485               case MT_STALEMATE:
6486               case MT_STAINMATE:
6487                 reason = "Xboard adjudication: Stalemate";
6488                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6489                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6490                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6491                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6492                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6493                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6494                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6495                                                                         EP_CHECKMATE : EP_WINS);
6496                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6497                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6498                 }
6499                 break;
6500               case MT_CHECKMATE:
6501                 reason = "Xboard adjudication: Checkmate";
6502                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6503                 break;
6504             }
6505
6506                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6507                     case EP_STALEMATE:
6508                         result = GameIsDrawn; break;
6509                     case EP_CHECKMATE:
6510                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6511                     case EP_WINS:
6512                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6513                     default:
6514                         result = (ChessMove) 0;
6515                 }
6516                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6517                     if(engineOpponent)
6518                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6519                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6520                     GameEnds( result, reason, GE_XBOARD );
6521                     return 1;
6522                 }
6523
6524                 /* Next absolutely insufficient mating material. */
6525                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6526                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6527                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6528                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6529                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6530
6531                      /* always flag draws, for judging claims */
6532                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6533
6534                      if(canAdjudicate && appData.materialDraws) {
6535                          /* but only adjudicate them if adjudication enabled */
6536                          if(engineOpponent) {
6537                            SendToProgram("force\n", engineOpponent); // suppress reply
6538                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6539                          }
6540                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6541                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6542                          return 1;
6543                      }
6544                 }
6545
6546                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6547                 if(NrPieces == 4 && 
6548                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6549                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6550                    || NrWN==2 || NrBN==2     /* KNNK */
6551                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6552                   ) ) {
6553                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6554                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6555                           if(engineOpponent) {
6556                             SendToProgram("force\n", engineOpponent); // suppress reply
6557                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6558                           }
6559                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6560                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6561                           return 1;
6562                      }
6563                 } else moveCount = 6;
6564             }
6565         }
6566           
6567         if (appData.debugMode) { int i;
6568             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6569                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6570                     appData.drawRepeats);
6571             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6572               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6573             
6574         }
6575
6576         // Repetition draws and 50-move rule can be applied independently of legality testing
6577
6578                 /* Check for rep-draws */
6579                 count = 0;
6580                 for(k = forwardMostMove-2;
6581                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6582                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6583                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6584                     k-=2)
6585                 {   int rights=0;
6586                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6587                         /* compare castling rights */
6588                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6589                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6590                                 rights++; /* King lost rights, while rook still had them */
6591                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6592                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6593                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6594                                    rights++; /* but at least one rook lost them */
6595                         }
6596                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6597                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6598                                 rights++; 
6599                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6600                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6601                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6602                                    rights++;
6603                         }
6604                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6605                             && appData.drawRepeats > 1) {
6606                              /* adjudicate after user-specified nr of repeats */
6607                              if(engineOpponent) {
6608                                SendToProgram("force\n", engineOpponent); // suppress reply
6609                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6610                              }
6611                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6612                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6613                                 // [HGM] xiangqi: check for forbidden perpetuals
6614                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6615                                 for(m=forwardMostMove; m>k; m-=2) {
6616                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6617                                         ourPerpetual = 0; // the current mover did not always check
6618                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6619                                         hisPerpetual = 0; // the opponent did not always check
6620                                 }
6621                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6622                                                                         ourPerpetual, hisPerpetual);
6623                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6624                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6625                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6626                                     return 1;
6627                                 }
6628                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6629                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6630                                 // Now check for perpetual chases
6631                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6632                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6633                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6634                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6635                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6636                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6637                                         return 1;
6638                                     }
6639                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6640                                         break; // Abort repetition-checking loop.
6641                                 }
6642                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6643                              }
6644                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6645                              return 1;
6646                         }
6647                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6648                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6649                     }
6650                 }
6651
6652                 /* Now we test for 50-move draws. Determine ply count */
6653                 count = forwardMostMove;
6654                 /* look for last irreversble move */
6655                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6656                     count--;
6657                 /* if we hit starting position, add initial plies */
6658                 if( count == backwardMostMove )
6659                     count -= initialRulePlies;
6660                 count = forwardMostMove - count; 
6661                 if( count >= 100)
6662                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6663                          /* this is used to judge if draw claims are legal */
6664                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6665                          if(engineOpponent) {
6666                            SendToProgram("force\n", engineOpponent); // suppress reply
6667                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6668                          }
6669                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6670                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6671                          return 1;
6672                 }
6673
6674                 /* if draw offer is pending, treat it as a draw claim
6675                  * when draw condition present, to allow engines a way to
6676                  * claim draws before making their move to avoid a race
6677                  * condition occurring after their move
6678                  */
6679                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6680                          char *p = NULL;
6681                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6682                              p = "Draw claim: 50-move rule";
6683                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6684                              p = "Draw claim: 3-fold repetition";
6685                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6686                              p = "Draw claim: insufficient mating material";
6687                          if( p != NULL && canAdjudicate) {
6688                              if(engineOpponent) {
6689                                SendToProgram("force\n", engineOpponent); // suppress reply
6690                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6691                              }
6692                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6693                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6694                              return 1;
6695                          }
6696                 }
6697
6698                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6699                     if(engineOpponent) {
6700                       SendToProgram("force\n", engineOpponent); // suppress reply
6701                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6702                     }
6703                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6704                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6705                     return 1;
6706                 }
6707         return 0;
6708 }
6709
6710 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6711 {   // [HGM] book: this routine intercepts moves to simulate book replies
6712     char *bookHit = NULL;
6713
6714     //first determine if the incoming move brings opponent into his book
6715     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6716         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6717     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6718     if(bookHit != NULL && !cps->bookSuspend) {
6719         // make sure opponent is not going to reply after receiving move to book position
6720         SendToProgram("force\n", cps);
6721         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6722     }
6723     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6724     // now arrange restart after book miss
6725     if(bookHit) {
6726         // after a book hit we never send 'go', and the code after the call to this routine
6727         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6728         char buf[MSG_SIZ];
6729         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6730         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6731         SendToProgram(buf, cps);
6732         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6733     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6734         SendToProgram("go\n", cps);
6735         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6736     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6737         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6738             SendToProgram("go\n", cps); 
6739         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6740     }
6741     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6742 }
6743
6744 char *savedMessage;
6745 ChessProgramState *savedState;
6746 void DeferredBookMove(void)
6747 {
6748         if(savedState->lastPing != savedState->lastPong)
6749                     ScheduleDelayedEvent(DeferredBookMove, 10);
6750         else
6751         HandleMachineMove(savedMessage, savedState);
6752 }
6753
6754 void
6755 HandleMachineMove(message, cps)
6756      char *message;
6757      ChessProgramState *cps;
6758 {
6759     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6760     char realname[MSG_SIZ];
6761     int fromX, fromY, toX, toY;
6762     ChessMove moveType;
6763     char promoChar;
6764     char *p;
6765     int machineWhite;
6766     char *bookHit;
6767
6768     cps->userError = 0;
6769
6770 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6771     /*
6772      * Kludge to ignore BEL characters
6773      */
6774     while (*message == '\007') message++;
6775
6776     /*
6777      * [HGM] engine debug message: ignore lines starting with '#' character
6778      */
6779     if(cps->debug && *message == '#') return;
6780
6781     /*
6782      * Look for book output
6783      */
6784     if (cps == &first && bookRequested) {
6785         if (message[0] == '\t' || message[0] == ' ') {
6786             /* Part of the book output is here; append it */
6787             strcat(bookOutput, message);
6788             strcat(bookOutput, "  \n");
6789             return;
6790         } else if (bookOutput[0] != NULLCHAR) {
6791             /* All of book output has arrived; display it */
6792             char *p = bookOutput;
6793             while (*p != NULLCHAR) {
6794                 if (*p == '\t') *p = ' ';
6795                 p++;
6796             }
6797             DisplayInformation(bookOutput);
6798             bookRequested = FALSE;
6799             /* Fall through to parse the current output */
6800         }
6801     }
6802
6803     /*
6804      * Look for machine move.
6805      */
6806     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6807         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6808     {
6809         /* This method is only useful on engines that support ping */
6810         if (cps->lastPing != cps->lastPong) {
6811           if (gameMode == BeginningOfGame) {
6812             /* Extra move from before last new; ignore */
6813             if (appData.debugMode) {
6814                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6815             }
6816           } else {
6817             if (appData.debugMode) {
6818                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6819                         cps->which, gameMode);
6820             }
6821
6822             SendToProgram("undo\n", cps);
6823           }
6824           return;
6825         }
6826
6827         switch (gameMode) {
6828           case BeginningOfGame:
6829             /* Extra move from before last reset; ignore */
6830             if (appData.debugMode) {
6831                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6832             }
6833             return;
6834
6835           case EndOfGame:
6836           case IcsIdle:
6837           default:
6838             /* Extra move after we tried to stop.  The mode test is
6839                not a reliable way of detecting this problem, but it's
6840                the best we can do on engines that don't support ping.
6841             */
6842             if (appData.debugMode) {
6843                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6844                         cps->which, gameMode);
6845             }
6846             SendToProgram("undo\n", cps);
6847             return;
6848
6849           case MachinePlaysWhite:
6850           case IcsPlayingWhite:
6851             machineWhite = TRUE;
6852             break;
6853
6854           case MachinePlaysBlack:
6855           case IcsPlayingBlack:
6856             machineWhite = FALSE;
6857             break;
6858
6859           case TwoMachinesPlay:
6860             machineWhite = (cps->twoMachinesColor[0] == 'w');
6861             break;
6862         }
6863         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6864             if (appData.debugMode) {
6865                 fprintf(debugFP,
6866                         "Ignoring move out of turn by %s, gameMode %d"
6867                         ", forwardMost %d\n",
6868                         cps->which, gameMode, forwardMostMove);
6869             }
6870             return;
6871         }
6872
6873     if (appData.debugMode) { int f = forwardMostMove;
6874         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6875                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6876                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6877     }
6878         if(cps->alphaRank) AlphaRank(machineMove, 4);
6879         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6880                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6881             /* Machine move could not be parsed; ignore it. */
6882             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6883                     machineMove, cps->which);
6884             DisplayError(buf1, 0);
6885             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6886                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6887             if (gameMode == TwoMachinesPlay) {
6888               GameEnds(machineWhite ? BlackWins : WhiteWins,
6889                        buf1, GE_XBOARD);
6890             }
6891             return;
6892         }
6893
6894         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6895         /* So we have to redo legality test with true e.p. status here,  */
6896         /* to make sure an illegal e.p. capture does not slip through,   */
6897         /* to cause a forfeit on a justified illegal-move complaint      */
6898         /* of the opponent.                                              */
6899         if( gameMode==TwoMachinesPlay && appData.testLegality
6900             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6901                                                               ) {
6902            ChessMove moveType;
6903            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6904                              fromY, fromX, toY, toX, promoChar);
6905             if (appData.debugMode) {
6906                 int i;
6907                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6908                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6909                 fprintf(debugFP, "castling rights\n");
6910             }
6911             if(moveType == IllegalMove) {
6912                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6913                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6914                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6915                            buf1, GE_XBOARD);
6916                 return;
6917            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6918            /* [HGM] Kludge to handle engines that send FRC-style castling
6919               when they shouldn't (like TSCP-Gothic) */
6920            switch(moveType) {
6921              case WhiteASideCastleFR:
6922              case BlackASideCastleFR:
6923                toX+=2;
6924                currentMoveString[2]++;
6925                break;
6926              case WhiteHSideCastleFR:
6927              case BlackHSideCastleFR:
6928                toX--;
6929                currentMoveString[2]--;
6930                break;
6931              default: ; // nothing to do, but suppresses warning of pedantic compilers
6932            }
6933         }
6934         hintRequested = FALSE;
6935         lastHint[0] = NULLCHAR;
6936         bookRequested = FALSE;
6937         /* Program may be pondering now */
6938         cps->maybeThinking = TRUE;
6939         if (cps->sendTime == 2) cps->sendTime = 1;
6940         if (cps->offeredDraw) cps->offeredDraw--;
6941
6942         /* currentMoveString is set as a side-effect of ParseOneMove */
6943         strcpy(machineMove, currentMoveString);
6944         strcat(machineMove, "\n");
6945         strcpy(moveList[forwardMostMove], machineMove);
6946
6947         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6948
6949         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6950         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6951             int count = 0;
6952
6953             while( count < adjudicateLossPlies ) {
6954                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6955
6956                 if( count & 1 ) {
6957                     score = -score; /* Flip score for winning side */
6958                 }
6959
6960                 if( score > adjudicateLossThreshold ) {
6961                     break;
6962                 }
6963
6964                 count++;
6965             }
6966
6967             if( count >= adjudicateLossPlies ) {
6968                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6969
6970                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6971                     "Xboard adjudication", 
6972                     GE_XBOARD );
6973
6974                 return;
6975             }
6976         }
6977
6978         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6979
6980 #if ZIPPY
6981         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6982             first.initDone) {
6983           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6984                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6985                 SendToICS("draw ");
6986                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6987           }
6988           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6989           ics_user_moved = 1;
6990           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6991                 char buf[3*MSG_SIZ];
6992
6993                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6994                         programStats.score / 100.,
6995                         programStats.depth,
6996                         programStats.time / 100.,
6997                         (unsigned int)programStats.nodes,
6998                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6999                         programStats.movelist);
7000                 SendToICS(buf);
7001 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7002           }
7003         }
7004 #endif
7005
7006         /* [AS] Save move info and clear stats for next move */
7007         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7008         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7009         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7010         ClearProgramStats();
7011         thinkOutput[0] = NULLCHAR;
7012         hiddenThinkOutputState = 0;
7013
7014         bookHit = NULL;
7015         if (gameMode == TwoMachinesPlay) {
7016             /* [HGM] relaying draw offers moved to after reception of move */
7017             /* and interpreting offer as claim if it brings draw condition */
7018             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7019                 SendToProgram("draw\n", cps->other);
7020             }
7021             if (cps->other->sendTime) {
7022                 SendTimeRemaining(cps->other,
7023                                   cps->other->twoMachinesColor[0] == 'w');
7024             }
7025             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7026             if (firstMove && !bookHit) {
7027                 firstMove = FALSE;
7028                 if (cps->other->useColors) {
7029                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7030                 }
7031                 SendToProgram("go\n", cps->other);
7032             }
7033             cps->other->maybeThinking = TRUE;
7034         }
7035
7036         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7037         
7038         if (!pausing && appData.ringBellAfterMoves) {
7039             RingBell();
7040         }
7041
7042         /* 
7043          * Reenable menu items that were disabled while
7044          * machine was thinking
7045          */
7046         if (gameMode != TwoMachinesPlay)
7047             SetUserThinkingEnables();
7048
7049         // [HGM] book: after book hit opponent has received move and is now in force mode
7050         // force the book reply into it, and then fake that it outputted this move by jumping
7051         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7052         if(bookHit) {
7053                 static char bookMove[MSG_SIZ]; // a bit generous?
7054
7055                 strcpy(bookMove, "move ");
7056                 strcat(bookMove, bookHit);
7057                 message = bookMove;
7058                 cps = cps->other;
7059                 programStats.nodes = programStats.depth = programStats.time = 
7060                 programStats.score = programStats.got_only_move = 0;
7061                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7062
7063                 if(cps->lastPing != cps->lastPong) {
7064                     savedMessage = message; // args for deferred call
7065                     savedState = cps;
7066                     ScheduleDelayedEvent(DeferredBookMove, 10);
7067                     return;
7068                 }
7069                 goto FakeBookMove;
7070         }
7071
7072         return;
7073     }
7074
7075     /* Set special modes for chess engines.  Later something general
7076      *  could be added here; for now there is just one kludge feature,
7077      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7078      *  when "xboard" is given as an interactive command.
7079      */
7080     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7081         cps->useSigint = FALSE;
7082         cps->useSigterm = FALSE;
7083     }
7084     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7085       ParseFeatures(message+8, cps);
7086       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7087     }
7088
7089     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7090      * want this, I was asked to put it in, and obliged.
7091      */
7092     if (!strncmp(message, "setboard ", 9)) {
7093         Board initial_position;
7094
7095         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7096
7097         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7098             DisplayError(_("Bad FEN received from engine"), 0);
7099             return ;
7100         } else {
7101            Reset(TRUE, FALSE);
7102            CopyBoard(boards[0], initial_position);
7103            initialRulePlies = FENrulePlies;
7104            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7105            else gameMode = MachinePlaysBlack;                 
7106            DrawPosition(FALSE, boards[currentMove]);
7107         }
7108         return;
7109     }
7110
7111     /*
7112      * Look for communication commands
7113      */
7114     if (!strncmp(message, "telluser ", 9)) {
7115         DisplayNote(message + 9);
7116         return;
7117     }
7118     if (!strncmp(message, "tellusererror ", 14)) {
7119         cps->userError = 1;
7120         DisplayError(message + 14, 0);
7121         return;
7122     }
7123     if (!strncmp(message, "tellopponent ", 13)) {
7124       if (appData.icsActive) {
7125         if (loggedOn) {
7126           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7127           SendToICS(buf1);
7128         }
7129       } else {
7130         DisplayNote(message + 13);
7131       }
7132       return;
7133     }
7134     if (!strncmp(message, "tellothers ", 11)) {
7135       if (appData.icsActive) {
7136         if (loggedOn) {
7137           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7138           SendToICS(buf1);
7139         }
7140       }
7141       return;
7142     }
7143     if (!strncmp(message, "tellall ", 8)) {
7144       if (appData.icsActive) {
7145         if (loggedOn) {
7146           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7147           SendToICS(buf1);
7148         }
7149       } else {
7150         DisplayNote(message + 8);
7151       }
7152       return;
7153     }
7154     if (strncmp(message, "warning", 7) == 0) {
7155         /* Undocumented feature, use tellusererror in new code */
7156         DisplayError(message, 0);
7157         return;
7158     }
7159     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7160         strcpy(realname, cps->tidy);
7161         strcat(realname, " query");
7162         AskQuestion(realname, buf2, buf1, cps->pr);
7163         return;
7164     }
7165     /* Commands from the engine directly to ICS.  We don't allow these to be 
7166      *  sent until we are logged on. Crafty kibitzes have been known to 
7167      *  interfere with the login process.
7168      */
7169     if (loggedOn) {
7170         if (!strncmp(message, "tellics ", 8)) {
7171             SendToICS(message + 8);
7172             SendToICS("\n");
7173             return;
7174         }
7175         if (!strncmp(message, "tellicsnoalias ", 15)) {
7176             SendToICS(ics_prefix);
7177             SendToICS(message + 15);
7178             SendToICS("\n");
7179             return;
7180         }
7181         /* The following are for backward compatibility only */
7182         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7183             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7184             SendToICS(ics_prefix);
7185             SendToICS(message);
7186             SendToICS("\n");
7187             return;
7188         }
7189     }
7190     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7191         return;
7192     }
7193     /*
7194      * If the move is illegal, cancel it and redraw the board.
7195      * Also deal with other error cases.  Matching is rather loose
7196      * here to accommodate engines written before the spec.
7197      */
7198     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7199         strncmp(message, "Error", 5) == 0) {
7200         if (StrStr(message, "name") || 
7201             StrStr(message, "rating") || StrStr(message, "?") ||
7202             StrStr(message, "result") || StrStr(message, "board") ||
7203             StrStr(message, "bk") || StrStr(message, "computer") ||
7204             StrStr(message, "variant") || StrStr(message, "hint") ||
7205             StrStr(message, "random") || StrStr(message, "depth") ||
7206             StrStr(message, "accepted")) {
7207             return;
7208         }
7209         if (StrStr(message, "protover")) {
7210           /* Program is responding to input, so it's apparently done
7211              initializing, and this error message indicates it is
7212              protocol version 1.  So we don't need to wait any longer
7213              for it to initialize and send feature commands. */
7214           FeatureDone(cps, 1);
7215           cps->protocolVersion = 1;
7216           return;
7217         }
7218         cps->maybeThinking = FALSE;
7219
7220         if (StrStr(message, "draw")) {
7221             /* Program doesn't have "draw" command */
7222             cps->sendDrawOffers = 0;
7223             return;
7224         }
7225         if (cps->sendTime != 1 &&
7226             (StrStr(message, "time") || StrStr(message, "otim"))) {
7227           /* Program apparently doesn't have "time" or "otim" command */
7228           cps->sendTime = 0;
7229           return;
7230         }
7231         if (StrStr(message, "analyze")) {
7232             cps->analysisSupport = FALSE;
7233             cps->analyzing = FALSE;
7234             Reset(FALSE, TRUE);
7235             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7236             DisplayError(buf2, 0);
7237             return;
7238         }
7239         if (StrStr(message, "(no matching move)st")) {
7240           /* Special kludge for GNU Chess 4 only */
7241           cps->stKludge = TRUE;
7242           SendTimeControl(cps, movesPerSession, timeControl,
7243                           timeIncrement, appData.searchDepth,
7244                           searchTime);
7245           return;
7246         }
7247         if (StrStr(message, "(no matching move)sd")) {
7248           /* Special kludge for GNU Chess 4 only */
7249           cps->sdKludge = TRUE;
7250           SendTimeControl(cps, movesPerSession, timeControl,
7251                           timeIncrement, appData.searchDepth,
7252                           searchTime);
7253           return;
7254         }
7255         if (!StrStr(message, "llegal")) {
7256             return;
7257         }
7258         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7259             gameMode == IcsIdle) return;
7260         if (forwardMostMove <= backwardMostMove) return;
7261         if (pausing) PauseEvent();
7262       if(appData.forceIllegal) {
7263             // [HGM] illegal: machine refused move; force position after move into it
7264           SendToProgram("force\n", cps);
7265           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7266                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7267                 // when black is to move, while there might be nothing on a2 or black
7268                 // might already have the move. So send the board as if white has the move.
7269                 // But first we must change the stm of the engine, as it refused the last move
7270                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7271                 if(WhiteOnMove(forwardMostMove)) {
7272                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7273                     SendBoard(cps, forwardMostMove); // kludgeless board
7274                 } else {
7275                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7276                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7277                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7278                 }
7279           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7280             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7281                  gameMode == TwoMachinesPlay)
7282               SendToProgram("go\n", cps);
7283             return;
7284       } else
7285         if (gameMode == PlayFromGameFile) {
7286             /* Stop reading this game file */
7287             gameMode = EditGame;
7288             ModeHighlight();
7289         }
7290         currentMove = --forwardMostMove;
7291         DisplayMove(currentMove-1); /* before DisplayMoveError */
7292         SwitchClocks();
7293         DisplayBothClocks();
7294         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7295                 parseList[currentMove], cps->which);
7296         DisplayMoveError(buf1);
7297         DrawPosition(FALSE, boards[currentMove]);
7298
7299         /* [HGM] illegal-move claim should forfeit game when Xboard */
7300         /* only passes fully legal moves                            */
7301         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7302             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7303                                 "False illegal-move claim", GE_XBOARD );
7304         }
7305         return;
7306     }
7307     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7308         /* Program has a broken "time" command that
7309            outputs a string not ending in newline.
7310            Don't use it. */
7311         cps->sendTime = 0;
7312     }
7313     
7314     /*
7315      * If chess program startup fails, exit with an error message.
7316      * Attempts to recover here are futile.
7317      */
7318     if ((StrStr(message, "unknown host") != NULL)
7319         || (StrStr(message, "No remote directory") != NULL)
7320         || (StrStr(message, "not found") != NULL)
7321         || (StrStr(message, "No such file") != NULL)
7322         || (StrStr(message, "can't alloc") != NULL)
7323         || (StrStr(message, "Permission denied") != NULL)) {
7324
7325         cps->maybeThinking = FALSE;
7326         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7327                 cps->which, cps->program, cps->host, message);
7328         RemoveInputSource(cps->isr);
7329         DisplayFatalError(buf1, 0, 1);
7330         return;
7331     }
7332     
7333     /* 
7334      * Look for hint output
7335      */
7336     if (sscanf(message, "Hint: %s", buf1) == 1) {
7337         if (cps == &first && hintRequested) {
7338             hintRequested = FALSE;
7339             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7340                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7341                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7342                                     PosFlags(forwardMostMove),
7343                                     fromY, fromX, toY, toX, promoChar, buf1);
7344                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7345                 DisplayInformation(buf2);
7346             } else {
7347                 /* Hint move could not be parsed!? */
7348               snprintf(buf2, sizeof(buf2),
7349                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7350                         buf1, cps->which);
7351                 DisplayError(buf2, 0);
7352             }
7353         } else {
7354             strcpy(lastHint, buf1);
7355         }
7356         return;
7357     }
7358
7359     /*
7360      * Ignore other messages if game is not in progress
7361      */
7362     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7363         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7364
7365     /*
7366      * look for win, lose, draw, or draw offer
7367      */
7368     if (strncmp(message, "1-0", 3) == 0) {
7369         char *p, *q, *r = "";
7370         p = strchr(message, '{');
7371         if (p) {
7372             q = strchr(p, '}');
7373             if (q) {
7374                 *q = NULLCHAR;
7375                 r = p + 1;
7376             }
7377         }
7378         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7379         return;
7380     } else if (strncmp(message, "0-1", 3) == 0) {
7381         char *p, *q, *r = "";
7382         p = strchr(message, '{');
7383         if (p) {
7384             q = strchr(p, '}');
7385             if (q) {
7386                 *q = NULLCHAR;
7387                 r = p + 1;
7388             }
7389         }
7390         /* Kludge for Arasan 4.1 bug */
7391         if (strcmp(r, "Black resigns") == 0) {
7392             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7393             return;
7394         }
7395         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7396         return;
7397     } else if (strncmp(message, "1/2", 3) == 0) {
7398         char *p, *q, *r = "";
7399         p = strchr(message, '{');
7400         if (p) {
7401             q = strchr(p, '}');
7402             if (q) {
7403                 *q = NULLCHAR;
7404                 r = p + 1;
7405             }
7406         }
7407             
7408         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7409         return;
7410
7411     } else if (strncmp(message, "White resign", 12) == 0) {
7412         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7413         return;
7414     } else if (strncmp(message, "Black resign", 12) == 0) {
7415         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7416         return;
7417     } else if (strncmp(message, "White matches", 13) == 0 ||
7418                strncmp(message, "Black matches", 13) == 0   ) {
7419         /* [HGM] ignore GNUShogi noises */
7420         return;
7421     } else if (strncmp(message, "White", 5) == 0 &&
7422                message[5] != '(' &&
7423                StrStr(message, "Black") == NULL) {
7424         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7425         return;
7426     } else if (strncmp(message, "Black", 5) == 0 &&
7427                message[5] != '(') {
7428         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7429         return;
7430     } else if (strcmp(message, "resign") == 0 ||
7431                strcmp(message, "computer resigns") == 0) {
7432         switch (gameMode) {
7433           case MachinePlaysBlack:
7434           case IcsPlayingBlack:
7435             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7436             break;
7437           case MachinePlaysWhite:
7438           case IcsPlayingWhite:
7439             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7440             break;
7441           case TwoMachinesPlay:
7442             if (cps->twoMachinesColor[0] == 'w')
7443               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7444             else
7445               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7446             break;
7447           default:
7448             /* can't happen */
7449             break;
7450         }
7451         return;
7452     } else if (strncmp(message, "opponent mates", 14) == 0) {
7453         switch (gameMode) {
7454           case MachinePlaysBlack:
7455           case IcsPlayingBlack:
7456             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7457             break;
7458           case MachinePlaysWhite:
7459           case IcsPlayingWhite:
7460             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7461             break;
7462           case TwoMachinesPlay:
7463             if (cps->twoMachinesColor[0] == 'w')
7464               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7465             else
7466               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7467             break;
7468           default:
7469             /* can't happen */
7470             break;
7471         }
7472         return;
7473     } else if (strncmp(message, "computer mates", 14) == 0) {
7474         switch (gameMode) {
7475           case MachinePlaysBlack:
7476           case IcsPlayingBlack:
7477             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7478             break;
7479           case MachinePlaysWhite:
7480           case IcsPlayingWhite:
7481             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7482             break;
7483           case TwoMachinesPlay:
7484             if (cps->twoMachinesColor[0] == 'w')
7485               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7486             else
7487               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7488             break;
7489           default:
7490             /* can't happen */
7491             break;
7492         }
7493         return;
7494     } else if (strncmp(message, "checkmate", 9) == 0) {
7495         if (WhiteOnMove(forwardMostMove)) {
7496             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7497         } else {
7498             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7499         }
7500         return;
7501     } else if (strstr(message, "Draw") != NULL ||
7502                strstr(message, "game is a draw") != NULL) {
7503         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7504         return;
7505     } else if (strstr(message, "offer") != NULL &&
7506                strstr(message, "draw") != NULL) {
7507 #if ZIPPY
7508         if (appData.zippyPlay && first.initDone) {
7509             /* Relay offer to ICS */
7510             SendToICS(ics_prefix);
7511             SendToICS("draw\n");
7512         }
7513 #endif
7514         cps->offeredDraw = 2; /* valid until this engine moves twice */
7515         if (gameMode == TwoMachinesPlay) {
7516             if (cps->other->offeredDraw) {
7517                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7518             /* [HGM] in two-machine mode we delay relaying draw offer      */
7519             /* until after we also have move, to see if it is really claim */
7520             }
7521         } else if (gameMode == MachinePlaysWhite ||
7522                    gameMode == MachinePlaysBlack) {
7523           if (userOfferedDraw) {
7524             DisplayInformation(_("Machine accepts your draw offer"));
7525             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7526           } else {
7527             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7528           }
7529         }
7530     }
7531
7532     
7533     /*
7534      * Look for thinking output
7535      */
7536     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7537           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7538                                 ) {
7539         int plylev, mvleft, mvtot, curscore, time;
7540         char mvname[MOVE_LEN];
7541         u64 nodes; // [DM]
7542         char plyext;
7543         int ignore = FALSE;
7544         int prefixHint = FALSE;
7545         mvname[0] = NULLCHAR;
7546
7547         switch (gameMode) {
7548           case MachinePlaysBlack:
7549           case IcsPlayingBlack:
7550             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7551             break;
7552           case MachinePlaysWhite:
7553           case IcsPlayingWhite:
7554             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7555             break;
7556           case AnalyzeMode:
7557           case AnalyzeFile:
7558             break;
7559           case IcsObserving: /* [DM] icsEngineAnalyze */
7560             if (!appData.icsEngineAnalyze) ignore = TRUE;
7561             break;
7562           case TwoMachinesPlay:
7563             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7564                 ignore = TRUE;
7565             }
7566             break;
7567           default:
7568             ignore = TRUE;
7569             break;
7570         }
7571
7572         if (!ignore) {
7573             buf1[0] = NULLCHAR;
7574             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7575                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7576
7577                 if (plyext != ' ' && plyext != '\t') {
7578                     time *= 100;
7579                 }
7580
7581                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7582                 if( cps->scoreIsAbsolute && 
7583                     ( gameMode == MachinePlaysBlack ||
7584                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7585                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7586                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7587                      !WhiteOnMove(currentMove)
7588                     ) )
7589                 {
7590                     curscore = -curscore;
7591                 }
7592
7593
7594                 programStats.depth = plylev;
7595                 programStats.nodes = nodes;
7596                 programStats.time = time;
7597                 programStats.score = curscore;
7598                 programStats.got_only_move = 0;
7599
7600                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7601                         int ticklen;
7602
7603                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7604                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7605                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7606                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7607                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7608                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7609                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7610                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7611                 }
7612
7613                 /* Buffer overflow protection */
7614                 if (buf1[0] != NULLCHAR) {
7615                     if (strlen(buf1) >= sizeof(programStats.movelist)
7616                         && appData.debugMode) {
7617                         fprintf(debugFP,
7618                                 "PV is too long; using the first %u bytes.\n",
7619                                 (unsigned) sizeof(programStats.movelist) - 1);
7620                     }
7621
7622                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7623                 } else {
7624                     sprintf(programStats.movelist, " no PV\n");
7625                 }
7626
7627                 if (programStats.seen_stat) {
7628                     programStats.ok_to_send = 1;
7629                 }
7630
7631                 if (strchr(programStats.movelist, '(') != NULL) {
7632                     programStats.line_is_book = 1;
7633                     programStats.nr_moves = 0;
7634                     programStats.moves_left = 0;
7635                 } else {
7636                     programStats.line_is_book = 0;
7637                 }
7638
7639                 SendProgramStatsToFrontend( cps, &programStats );
7640
7641                 /* 
7642                     [AS] Protect the thinkOutput buffer from overflow... this
7643                     is only useful if buf1 hasn't overflowed first!
7644                 */
7645                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7646                         plylev, 
7647                         (gameMode == TwoMachinesPlay ?
7648                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7649                         ((double) curscore) / 100.0,
7650                         prefixHint ? lastHint : "",
7651                         prefixHint ? " " : "" );
7652
7653                 if( buf1[0] != NULLCHAR ) {
7654                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7655
7656                     if( strlen(buf1) > max_len ) {
7657                         if( appData.debugMode) {
7658                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7659                         }
7660                         buf1[max_len+1] = '\0';
7661                     }
7662
7663                     strcat( thinkOutput, buf1 );
7664                 }
7665
7666                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7667                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7668                     DisplayMove(currentMove - 1);
7669                 }
7670                 return;
7671
7672             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7673                 /* crafty (9.25+) says "(only move) <move>"
7674                  * if there is only 1 legal move
7675                  */
7676                 sscanf(p, "(only move) %s", buf1);
7677                 sprintf(thinkOutput, "%s (only move)", buf1);
7678                 sprintf(programStats.movelist, "%s (only move)", buf1);
7679                 programStats.depth = 1;
7680                 programStats.nr_moves = 1;
7681                 programStats.moves_left = 1;
7682                 programStats.nodes = 1;
7683                 programStats.time = 1;
7684                 programStats.got_only_move = 1;
7685
7686                 /* Not really, but we also use this member to
7687                    mean "line isn't going to change" (Crafty
7688                    isn't searching, so stats won't change) */
7689                 programStats.line_is_book = 1;
7690
7691                 SendProgramStatsToFrontend( cps, &programStats );
7692                 
7693                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7694                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7695                     DisplayMove(currentMove - 1);
7696                 }
7697                 return;
7698             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7699                               &time, &nodes, &plylev, &mvleft,
7700                               &mvtot, mvname) >= 5) {
7701                 /* The stat01: line is from Crafty (9.29+) in response
7702                    to the "." command */
7703                 programStats.seen_stat = 1;
7704                 cps->maybeThinking = TRUE;
7705
7706                 if (programStats.got_only_move || !appData.periodicUpdates)
7707                   return;
7708
7709                 programStats.depth = plylev;
7710                 programStats.time = time;
7711                 programStats.nodes = nodes;
7712                 programStats.moves_left = mvleft;
7713                 programStats.nr_moves = mvtot;
7714                 strcpy(programStats.move_name, mvname);
7715                 programStats.ok_to_send = 1;
7716                 programStats.movelist[0] = '\0';
7717
7718                 SendProgramStatsToFrontend( cps, &programStats );
7719
7720                 return;
7721
7722             } else if (strncmp(message,"++",2) == 0) {
7723                 /* Crafty 9.29+ outputs this */
7724                 programStats.got_fail = 2;
7725                 return;
7726
7727             } else if (strncmp(message,"--",2) == 0) {
7728                 /* Crafty 9.29+ outputs this */
7729                 programStats.got_fail = 1;
7730                 return;
7731
7732             } else if (thinkOutput[0] != NULLCHAR &&
7733                        strncmp(message, "    ", 4) == 0) {
7734                 unsigned message_len;
7735
7736                 p = message;
7737                 while (*p && *p == ' ') p++;
7738
7739                 message_len = strlen( p );
7740
7741                 /* [AS] Avoid buffer overflow */
7742                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7743                     strcat(thinkOutput, " ");
7744                     strcat(thinkOutput, p);
7745                 }
7746
7747                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7748                     strcat(programStats.movelist, " ");
7749                     strcat(programStats.movelist, p);
7750                 }
7751
7752                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7753                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7754                     DisplayMove(currentMove - 1);
7755                 }
7756                 return;
7757             }
7758         }
7759         else {
7760             buf1[0] = NULLCHAR;
7761
7762             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7763                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7764             {
7765                 ChessProgramStats cpstats;
7766
7767                 if (plyext != ' ' && plyext != '\t') {
7768                     time *= 100;
7769                 }
7770
7771                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7772                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7773                     curscore = -curscore;
7774                 }
7775
7776                 cpstats.depth = plylev;
7777                 cpstats.nodes = nodes;
7778                 cpstats.time = time;
7779                 cpstats.score = curscore;
7780                 cpstats.got_only_move = 0;
7781                 cpstats.movelist[0] = '\0';
7782
7783                 if (buf1[0] != NULLCHAR) {
7784                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7785                 }
7786
7787                 cpstats.ok_to_send = 0;
7788                 cpstats.line_is_book = 0;
7789                 cpstats.nr_moves = 0;
7790                 cpstats.moves_left = 0;
7791
7792                 SendProgramStatsToFrontend( cps, &cpstats );
7793             }
7794         }
7795     }
7796 }
7797
7798
7799 /* Parse a game score from the character string "game", and
7800    record it as the history of the current game.  The game
7801    score is NOT assumed to start from the standard position. 
7802    The display is not updated in any way.
7803    */
7804 void
7805 ParseGameHistory(game)
7806      char *game;
7807 {
7808     ChessMove moveType;
7809     int fromX, fromY, toX, toY, boardIndex;
7810     char promoChar;
7811     char *p, *q;
7812     char buf[MSG_SIZ];
7813
7814     if (appData.debugMode)
7815       fprintf(debugFP, "Parsing game history: %s\n", game);
7816
7817     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7818     gameInfo.site = StrSave(appData.icsHost);
7819     gameInfo.date = PGNDate();
7820     gameInfo.round = StrSave("-");
7821
7822     /* Parse out names of players */
7823     while (*game == ' ') game++;
7824     p = buf;
7825     while (*game != ' ') *p++ = *game++;
7826     *p = NULLCHAR;
7827     gameInfo.white = StrSave(buf);
7828     while (*game == ' ') game++;
7829     p = buf;
7830     while (*game != ' ' && *game != '\n') *p++ = *game++;
7831     *p = NULLCHAR;
7832     gameInfo.black = StrSave(buf);
7833
7834     /* Parse moves */
7835     boardIndex = blackPlaysFirst ? 1 : 0;
7836     yynewstr(game);
7837     for (;;) {
7838         yyboardindex = boardIndex;
7839         moveType = (ChessMove) yylex();
7840         switch (moveType) {
7841           case IllegalMove:             /* maybe suicide chess, etc. */
7842   if (appData.debugMode) {
7843     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7844     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7845     setbuf(debugFP, NULL);
7846   }
7847           case WhitePromotionChancellor:
7848           case BlackPromotionChancellor:
7849           case WhitePromotionArchbishop:
7850           case BlackPromotionArchbishop:
7851           case WhitePromotionQueen:
7852           case BlackPromotionQueen:
7853           case WhitePromotionRook:
7854           case BlackPromotionRook:
7855           case WhitePromotionBishop:
7856           case BlackPromotionBishop:
7857           case WhitePromotionKnight:
7858           case BlackPromotionKnight:
7859           case WhitePromotionKing:
7860           case BlackPromotionKing:
7861           case NormalMove:
7862           case WhiteCapturesEnPassant:
7863           case BlackCapturesEnPassant:
7864           case WhiteKingSideCastle:
7865           case WhiteQueenSideCastle:
7866           case BlackKingSideCastle:
7867           case BlackQueenSideCastle:
7868           case WhiteKingSideCastleWild:
7869           case WhiteQueenSideCastleWild:
7870           case BlackKingSideCastleWild:
7871           case BlackQueenSideCastleWild:
7872           /* PUSH Fabien */
7873           case WhiteHSideCastleFR:
7874           case WhiteASideCastleFR:
7875           case BlackHSideCastleFR:
7876           case BlackASideCastleFR:
7877           /* POP Fabien */
7878             fromX = currentMoveString[0] - AAA;
7879             fromY = currentMoveString[1] - ONE;
7880             toX = currentMoveString[2] - AAA;
7881             toY = currentMoveString[3] - ONE;
7882             promoChar = currentMoveString[4];
7883             break;
7884           case WhiteDrop:
7885           case BlackDrop:
7886             fromX = moveType == WhiteDrop ?
7887               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7888             (int) CharToPiece(ToLower(currentMoveString[0]));
7889             fromY = DROP_RANK;
7890             toX = currentMoveString[2] - AAA;
7891             toY = currentMoveString[3] - ONE;
7892             promoChar = NULLCHAR;
7893             break;
7894           case AmbiguousMove:
7895             /* bug? */
7896             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7897   if (appData.debugMode) {
7898     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7899     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7900     setbuf(debugFP, NULL);
7901   }
7902             DisplayError(buf, 0);
7903             return;
7904           case ImpossibleMove:
7905             /* bug? */
7906             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7907   if (appData.debugMode) {
7908     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7909     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7910     setbuf(debugFP, NULL);
7911   }
7912             DisplayError(buf, 0);
7913             return;
7914           case (ChessMove) 0:   /* end of file */
7915             if (boardIndex < backwardMostMove) {
7916                 /* Oops, gap.  How did that happen? */
7917                 DisplayError(_("Gap in move list"), 0);
7918                 return;
7919             }
7920             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7921             if (boardIndex > forwardMostMove) {
7922                 forwardMostMove = boardIndex;
7923             }
7924             return;
7925           case ElapsedTime:
7926             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7927                 strcat(parseList[boardIndex-1], " ");
7928                 strcat(parseList[boardIndex-1], yy_text);
7929             }
7930             continue;
7931           case Comment:
7932           case PGNTag:
7933           case NAG:
7934           default:
7935             /* ignore */
7936             continue;
7937           case WhiteWins:
7938           case BlackWins:
7939           case GameIsDrawn:
7940           case GameUnfinished:
7941             if (gameMode == IcsExamining) {
7942                 if (boardIndex < backwardMostMove) {
7943                     /* Oops, gap.  How did that happen? */
7944                     return;
7945                 }
7946                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7947                 return;
7948             }
7949             gameInfo.result = moveType;
7950             p = strchr(yy_text, '{');
7951             if (p == NULL) p = strchr(yy_text, '(');
7952             if (p == NULL) {
7953                 p = yy_text;
7954                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7955             } else {
7956                 q = strchr(p, *p == '{' ? '}' : ')');
7957                 if (q != NULL) *q = NULLCHAR;
7958                 p++;
7959             }
7960             gameInfo.resultDetails = StrSave(p);
7961             continue;
7962         }
7963         if (boardIndex >= forwardMostMove &&
7964             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7965             backwardMostMove = blackPlaysFirst ? 1 : 0;
7966             return;
7967         }
7968         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7969                                  fromY, fromX, toY, toX, promoChar,
7970                                  parseList[boardIndex]);
7971         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7972         /* currentMoveString is set as a side-effect of yylex */
7973         strcpy(moveList[boardIndex], currentMoveString);
7974         strcat(moveList[boardIndex], "\n");
7975         boardIndex++;
7976         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7977         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7978           case MT_NONE:
7979           case MT_STALEMATE:
7980           default:
7981             break;
7982           case MT_CHECK:
7983             if(gameInfo.variant != VariantShogi)
7984                 strcat(parseList[boardIndex - 1], "+");
7985             break;
7986           case MT_CHECKMATE:
7987           case MT_STAINMATE:
7988             strcat(parseList[boardIndex - 1], "#");
7989             break;
7990         }
7991     }
7992 }
7993
7994
7995 /* Apply a move to the given board  */
7996 void
7997 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7998      int fromX, fromY, toX, toY;
7999      int promoChar;
8000      Board board;
8001 {
8002   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8003   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8004
8005     /* [HGM] compute & store e.p. status and castling rights for new position */
8006     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8007     { int i;
8008
8009       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8010       oldEP = (signed char)board[EP_STATUS];
8011       board[EP_STATUS] = EP_NONE;
8012
8013       if( board[toY][toX] != EmptySquare ) 
8014            board[EP_STATUS] = EP_CAPTURE;  
8015
8016       if( board[fromY][fromX] == WhitePawn ) {
8017            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8018                board[EP_STATUS] = EP_PAWN_MOVE;
8019            if( toY-fromY==2) {
8020                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8021                         gameInfo.variant != VariantBerolina || toX < fromX)
8022                       board[EP_STATUS] = toX | berolina;
8023                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8024                         gameInfo.variant != VariantBerolina || toX > fromX) 
8025                       board[EP_STATUS] = toX;
8026            }
8027       } else 
8028       if( board[fromY][fromX] == BlackPawn ) {
8029            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8030                board[EP_STATUS] = EP_PAWN_MOVE; 
8031            if( toY-fromY== -2) {
8032                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8033                         gameInfo.variant != VariantBerolina || toX < fromX)
8034                       board[EP_STATUS] = toX | berolina;
8035                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8036                         gameInfo.variant != VariantBerolina || toX > fromX) 
8037                       board[EP_STATUS] = toX;
8038            }
8039        }
8040
8041        for(i=0; i<nrCastlingRights; i++) {
8042            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8043               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8044              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8045        }
8046
8047     }
8048
8049   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8050   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8051        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8052          
8053   if (fromX == toX && fromY == toY) return;
8054
8055   if (fromY == DROP_RANK) {
8056         /* must be first */
8057         piece = board[toY][toX] = (ChessSquare) fromX;
8058   } else {
8059      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8060      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8061      if(gameInfo.variant == VariantKnightmate)
8062          king += (int) WhiteUnicorn - (int) WhiteKing;
8063
8064     /* Code added by Tord: */
8065     /* FRC castling assumed when king captures friendly rook. */
8066     if (board[fromY][fromX] == WhiteKing &&
8067              board[toY][toX] == WhiteRook) {
8068       board[fromY][fromX] = EmptySquare;
8069       board[toY][toX] = EmptySquare;
8070       if(toX > fromX) {
8071         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8072       } else {
8073         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8074       }
8075     } else if (board[fromY][fromX] == BlackKing &&
8076                board[toY][toX] == BlackRook) {
8077       board[fromY][fromX] = EmptySquare;
8078       board[toY][toX] = EmptySquare;
8079       if(toX > fromX) {
8080         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8081       } else {
8082         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8083       }
8084     /* End of code added by Tord */
8085
8086     } else if (board[fromY][fromX] == king
8087         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8088         && toY == fromY && toX > fromX+1) {
8089         board[fromY][fromX] = EmptySquare;
8090         board[toY][toX] = king;
8091         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8092         board[fromY][BOARD_RGHT-1] = EmptySquare;
8093     } else if (board[fromY][fromX] == king
8094         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8095                && toY == fromY && toX < fromX-1) {
8096         board[fromY][fromX] = EmptySquare;
8097         board[toY][toX] = king;
8098         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8099         board[fromY][BOARD_LEFT] = EmptySquare;
8100     } else if (board[fromY][fromX] == WhitePawn
8101                && toY >= BOARD_HEIGHT-promoRank
8102                && gameInfo.variant != VariantXiangqi
8103                ) {
8104         /* white pawn promotion */
8105         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8106         if (board[toY][toX] == EmptySquare) {
8107             board[toY][toX] = WhiteQueen;
8108         }
8109         if(gameInfo.variant==VariantBughouse ||
8110            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8111             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8112         board[fromY][fromX] = EmptySquare;
8113     } else if ((fromY == BOARD_HEIGHT-4)
8114                && (toX != fromX)
8115                && gameInfo.variant != VariantXiangqi
8116                && gameInfo.variant != VariantBerolina
8117                && (board[fromY][fromX] == WhitePawn)
8118                && (board[toY][toX] == EmptySquare)) {
8119         board[fromY][fromX] = EmptySquare;
8120         board[toY][toX] = WhitePawn;
8121         captured = board[toY - 1][toX];
8122         board[toY - 1][toX] = EmptySquare;
8123     } else if ((fromY == BOARD_HEIGHT-4)
8124                && (toX == fromX)
8125                && gameInfo.variant == VariantBerolina
8126                && (board[fromY][fromX] == WhitePawn)
8127                && (board[toY][toX] == EmptySquare)) {
8128         board[fromY][fromX] = EmptySquare;
8129         board[toY][toX] = WhitePawn;
8130         if(oldEP & EP_BEROLIN_A) {
8131                 captured = board[fromY][fromX-1];
8132                 board[fromY][fromX-1] = EmptySquare;
8133         }else{  captured = board[fromY][fromX+1];
8134                 board[fromY][fromX+1] = EmptySquare;
8135         }
8136     } else if (board[fromY][fromX] == king
8137         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8138                && toY == fromY && toX > fromX+1) {
8139         board[fromY][fromX] = EmptySquare;
8140         board[toY][toX] = king;
8141         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8142         board[fromY][BOARD_RGHT-1] = EmptySquare;
8143     } else if (board[fromY][fromX] == king
8144         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8145                && toY == fromY && toX < fromX-1) {
8146         board[fromY][fromX] = EmptySquare;
8147         board[toY][toX] = king;
8148         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8149         board[fromY][BOARD_LEFT] = EmptySquare;
8150     } else if (fromY == 7 && fromX == 3
8151                && board[fromY][fromX] == BlackKing
8152                && toY == 7 && toX == 5) {
8153         board[fromY][fromX] = EmptySquare;
8154         board[toY][toX] = BlackKing;
8155         board[fromY][7] = EmptySquare;
8156         board[toY][4] = BlackRook;
8157     } else if (fromY == 7 && fromX == 3
8158                && board[fromY][fromX] == BlackKing
8159                && toY == 7 && toX == 1) {
8160         board[fromY][fromX] = EmptySquare;
8161         board[toY][toX] = BlackKing;
8162         board[fromY][0] = EmptySquare;
8163         board[toY][2] = BlackRook;
8164     } else if (board[fromY][fromX] == BlackPawn
8165                && toY < promoRank
8166                && gameInfo.variant != VariantXiangqi
8167                ) {
8168         /* black pawn promotion */
8169         board[toY][toX] = CharToPiece(ToLower(promoChar));
8170         if (board[toY][toX] == EmptySquare) {
8171             board[toY][toX] = BlackQueen;
8172         }
8173         if(gameInfo.variant==VariantBughouse ||
8174            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8175             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8176         board[fromY][fromX] = EmptySquare;
8177     } else if ((fromY == 3)
8178                && (toX != fromX)
8179                && gameInfo.variant != VariantXiangqi
8180                && gameInfo.variant != VariantBerolina
8181                && (board[fromY][fromX] == BlackPawn)
8182                && (board[toY][toX] == EmptySquare)) {
8183         board[fromY][fromX] = EmptySquare;
8184         board[toY][toX] = BlackPawn;
8185         captured = board[toY + 1][toX];
8186         board[toY + 1][toX] = EmptySquare;
8187     } else if ((fromY == 3)
8188                && (toX == fromX)
8189                && gameInfo.variant == VariantBerolina
8190                && (board[fromY][fromX] == BlackPawn)
8191                && (board[toY][toX] == EmptySquare)) {
8192         board[fromY][fromX] = EmptySquare;
8193         board[toY][toX] = BlackPawn;
8194         if(oldEP & EP_BEROLIN_A) {
8195                 captured = board[fromY][fromX-1];
8196                 board[fromY][fromX-1] = EmptySquare;
8197         }else{  captured = board[fromY][fromX+1];
8198                 board[fromY][fromX+1] = EmptySquare;
8199         }
8200     } else {
8201         board[toY][toX] = board[fromY][fromX];
8202         board[fromY][fromX] = EmptySquare;
8203     }
8204
8205     /* [HGM] now we promote for Shogi, if needed */
8206     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8207         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8208   }
8209
8210     if (gameInfo.holdingsWidth != 0) {
8211
8212       /* !!A lot more code needs to be written to support holdings  */
8213       /* [HGM] OK, so I have written it. Holdings are stored in the */
8214       /* penultimate board files, so they are automaticlly stored   */
8215       /* in the game history.                                       */
8216       if (fromY == DROP_RANK) {
8217         /* Delete from holdings, by decreasing count */
8218         /* and erasing image if necessary            */
8219         p = (int) fromX;
8220         if(p < (int) BlackPawn) { /* white drop */
8221              p -= (int)WhitePawn;
8222                  p = PieceToNumber((ChessSquare)p);
8223              if(p >= gameInfo.holdingsSize) p = 0;
8224              if(--board[p][BOARD_WIDTH-2] <= 0)
8225                   board[p][BOARD_WIDTH-1] = EmptySquare;
8226              if((int)board[p][BOARD_WIDTH-2] < 0)
8227                         board[p][BOARD_WIDTH-2] = 0;
8228         } else {                  /* black drop */
8229              p -= (int)BlackPawn;
8230                  p = PieceToNumber((ChessSquare)p);
8231              if(p >= gameInfo.holdingsSize) p = 0;
8232              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8233                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8234              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8235                         board[BOARD_HEIGHT-1-p][1] = 0;
8236         }
8237       }
8238       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8239           && gameInfo.variant != VariantBughouse        ) {
8240         /* [HGM] holdings: Add to holdings, if holdings exist */
8241         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8242                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8243                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8244         }
8245         p = (int) captured;
8246         if (p >= (int) BlackPawn) {
8247           p -= (int)BlackPawn;
8248           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8249                   /* in Shogi restore piece to its original  first */
8250                   captured = (ChessSquare) (DEMOTED captured);
8251                   p = DEMOTED p;
8252           }
8253           p = PieceToNumber((ChessSquare)p);
8254           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8255           board[p][BOARD_WIDTH-2]++;
8256           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8257         } else {
8258           p -= (int)WhitePawn;
8259           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8260                   captured = (ChessSquare) (DEMOTED captured);
8261                   p = DEMOTED p;
8262           }
8263           p = PieceToNumber((ChessSquare)p);
8264           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8265           board[BOARD_HEIGHT-1-p][1]++;
8266           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8267         }
8268       }
8269     } else if (gameInfo.variant == VariantAtomic) {
8270       if (captured != EmptySquare) {
8271         int y, x;
8272         for (y = toY-1; y <= toY+1; y++) {
8273           for (x = toX-1; x <= toX+1; x++) {
8274             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8275                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8276               board[y][x] = EmptySquare;
8277             }
8278           }
8279         }
8280         board[toY][toX] = EmptySquare;
8281       }
8282     }
8283     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8284         /* [HGM] Shogi promotions */
8285         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8286     }
8287
8288     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8289                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8290         // [HGM] superchess: take promotion piece out of holdings
8291         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8292         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8293             if(!--board[k][BOARD_WIDTH-2])
8294                 board[k][BOARD_WIDTH-1] = EmptySquare;
8295         } else {
8296             if(!--board[BOARD_HEIGHT-1-k][1])
8297                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8298         }
8299     }
8300
8301 }
8302
8303 /* Updates forwardMostMove */
8304 void
8305 MakeMove(fromX, fromY, toX, toY, promoChar)
8306      int fromX, fromY, toX, toY;
8307      int promoChar;
8308 {
8309 //    forwardMostMove++; // [HGM] bare: moved downstream
8310
8311     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8312         int timeLeft; static int lastLoadFlag=0; int king, piece;
8313         piece = boards[forwardMostMove][fromY][fromX];
8314         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8315         if(gameInfo.variant == VariantKnightmate)
8316             king += (int) WhiteUnicorn - (int) WhiteKing;
8317         if(forwardMostMove == 0) {
8318             if(blackPlaysFirst) 
8319                 fprintf(serverMoves, "%s;", second.tidy);
8320             fprintf(serverMoves, "%s;", first.tidy);
8321             if(!blackPlaysFirst) 
8322                 fprintf(serverMoves, "%s;", second.tidy);
8323         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8324         lastLoadFlag = loadFlag;
8325         // print base move
8326         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8327         // print castling suffix
8328         if( toY == fromY && piece == king ) {
8329             if(toX-fromX > 1)
8330                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8331             if(fromX-toX >1)
8332                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8333         }
8334         // e.p. suffix
8335         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8336              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8337              boards[forwardMostMove][toY][toX] == EmptySquare
8338              && fromX != toX )
8339                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8340         // promotion suffix
8341         if(promoChar != NULLCHAR)
8342                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8343         if(!loadFlag) {
8344             fprintf(serverMoves, "/%d/%d",
8345                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8346             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8347             else                      timeLeft = blackTimeRemaining/1000;
8348             fprintf(serverMoves, "/%d", timeLeft);
8349         }
8350         fflush(serverMoves);
8351     }
8352
8353     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8354       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8355                         0, 1);
8356       return;
8357     }
8358     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8359     if (commentList[forwardMostMove+1] != NULL) {
8360         free(commentList[forwardMostMove+1]);
8361         commentList[forwardMostMove+1] = NULL;
8362     }
8363     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8364     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8365     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8366     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8367     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8368     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8369     gameInfo.result = GameUnfinished;
8370     if (gameInfo.resultDetails != NULL) {
8371         free(gameInfo.resultDetails);
8372         gameInfo.resultDetails = NULL;
8373     }
8374     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8375                               moveList[forwardMostMove - 1]);
8376     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8377                              PosFlags(forwardMostMove - 1),
8378                              fromY, fromX, toY, toX, promoChar,
8379                              parseList[forwardMostMove - 1]);
8380     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8381       case MT_NONE:
8382       case MT_STALEMATE:
8383       default:
8384         break;
8385       case MT_CHECK:
8386         if(gameInfo.variant != VariantShogi)
8387             strcat(parseList[forwardMostMove - 1], "+");
8388         break;
8389       case MT_CHECKMATE:
8390       case MT_STAINMATE:
8391         strcat(parseList[forwardMostMove - 1], "#");
8392         break;
8393     }
8394     if (appData.debugMode) {
8395         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8396     }
8397
8398 }
8399
8400 /* Updates currentMove if not pausing */
8401 void
8402 ShowMove(fromX, fromY, toX, toY)
8403 {
8404     int instant = (gameMode == PlayFromGameFile) ?
8405         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8406     if(appData.noGUI) return;
8407     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8408         if (!instant) {
8409             if (forwardMostMove == currentMove + 1) {
8410                 AnimateMove(boards[forwardMostMove - 1],
8411                             fromX, fromY, toX, toY);
8412             }
8413             if (appData.highlightLastMove) {
8414                 SetHighlights(fromX, fromY, toX, toY);
8415             }
8416         }
8417         currentMove = forwardMostMove;
8418     }
8419
8420     if (instant) return;
8421
8422     DisplayMove(currentMove - 1);
8423     DrawPosition(FALSE, boards[currentMove]);
8424     DisplayBothClocks();
8425     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8426 }
8427
8428 void SendEgtPath(ChessProgramState *cps)
8429 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8430         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8431
8432         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8433
8434         while(*p) {
8435             char c, *q = name+1, *r, *s;
8436
8437             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8438             while(*p && *p != ',') *q++ = *p++;
8439             *q++ = ':'; *q = 0;
8440             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8441                 strcmp(name, ",nalimov:") == 0 ) {
8442                 // take nalimov path from the menu-changeable option first, if it is defined
8443                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8444                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8445             } else
8446             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8447                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8448                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8449                 s = r = StrStr(s, ":") + 1; // beginning of path info
8450                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8451                 c = *r; *r = 0;             // temporarily null-terminate path info
8452                     *--q = 0;               // strip of trailig ':' from name
8453                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8454                 *r = c;
8455                 SendToProgram(buf,cps);     // send egtbpath command for this format
8456             }
8457             if(*p == ',') p++; // read away comma to position for next format name
8458         }
8459 }
8460
8461 void
8462 InitChessProgram(cps, setup)
8463      ChessProgramState *cps;
8464      int setup; /* [HGM] needed to setup FRC opening position */
8465 {
8466     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8467     if (appData.noChessProgram) return;
8468     hintRequested = FALSE;
8469     bookRequested = FALSE;
8470
8471     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8472     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8473     if(cps->memSize) { /* [HGM] memory */
8474         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8475         SendToProgram(buf, cps);
8476     }
8477     SendEgtPath(cps); /* [HGM] EGT */
8478     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8479         sprintf(buf, "cores %d\n", appData.smpCores);
8480         SendToProgram(buf, cps);
8481     }
8482
8483     SendToProgram(cps->initString, cps);
8484     if (gameInfo.variant != VariantNormal &&
8485         gameInfo.variant != VariantLoadable
8486         /* [HGM] also send variant if board size non-standard */
8487         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8488                                             ) {
8489       char *v = VariantName(gameInfo.variant);
8490       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8491         /* [HGM] in protocol 1 we have to assume all variants valid */
8492         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8493         DisplayFatalError(buf, 0, 1);
8494         return;
8495       }
8496
8497       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8498       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8499       if( gameInfo.variant == VariantXiangqi )
8500            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8501       if( gameInfo.variant == VariantShogi )
8502            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8503       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8504            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8505       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8506                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8507            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8508       if( gameInfo.variant == VariantCourier )
8509            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8510       if( gameInfo.variant == VariantSuper )
8511            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8512       if( gameInfo.variant == VariantGreat )
8513            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8514
8515       if(overruled) {
8516            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8517                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8518            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8519            if(StrStr(cps->variants, b) == NULL) { 
8520                // specific sized variant not known, check if general sizing allowed
8521                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8522                    if(StrStr(cps->variants, "boardsize") == NULL) {
8523                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8524                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8525                        DisplayFatalError(buf, 0, 1);
8526                        return;
8527                    }
8528                    /* [HGM] here we really should compare with the maximum supported board size */
8529                }
8530            }
8531       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8532       sprintf(buf, "variant %s\n", b);
8533       SendToProgram(buf, cps);
8534     }
8535     currentlyInitializedVariant = gameInfo.variant;
8536
8537     /* [HGM] send opening position in FRC to first engine */
8538     if(setup) {
8539           SendToProgram("force\n", cps);
8540           SendBoard(cps, 0);
8541           /* engine is now in force mode! Set flag to wake it up after first move. */
8542           setboardSpoiledMachineBlack = 1;
8543     }
8544
8545     if (cps->sendICS) {
8546       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8547       SendToProgram(buf, cps);
8548     }
8549     cps->maybeThinking = FALSE;
8550     cps->offeredDraw = 0;
8551     if (!appData.icsActive) {
8552         SendTimeControl(cps, movesPerSession, timeControl,
8553                         timeIncrement, appData.searchDepth,
8554                         searchTime);
8555     }
8556     if (appData.showThinking 
8557         // [HGM] thinking: four options require thinking output to be sent
8558         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8559                                 ) {
8560         SendToProgram("post\n", cps);
8561     }
8562     SendToProgram("hard\n", cps);
8563     if (!appData.ponderNextMove) {
8564         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8565            it without being sure what state we are in first.  "hard"
8566            is not a toggle, so that one is OK.
8567          */
8568         SendToProgram("easy\n", cps);
8569     }
8570     if (cps->usePing) {
8571       sprintf(buf, "ping %d\n", ++cps->lastPing);
8572       SendToProgram(buf, cps);
8573     }
8574     cps->initDone = TRUE;
8575 }   
8576
8577
8578 void
8579 StartChessProgram(cps)
8580      ChessProgramState *cps;
8581 {
8582     char buf[MSG_SIZ];
8583     int err;
8584
8585     if (appData.noChessProgram) return;
8586     cps->initDone = FALSE;
8587
8588     if (strcmp(cps->host, "localhost") == 0) {
8589         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8590     } else if (*appData.remoteShell == NULLCHAR) {
8591         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8592     } else {
8593         if (*appData.remoteUser == NULLCHAR) {
8594           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8595                     cps->program);
8596         } else {
8597           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8598                     cps->host, appData.remoteUser, cps->program);
8599         }
8600         err = StartChildProcess(buf, "", &cps->pr);
8601     }
8602     
8603     if (err != 0) {
8604         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8605         DisplayFatalError(buf, err, 1);
8606         cps->pr = NoProc;
8607         cps->isr = NULL;
8608         return;
8609     }
8610     
8611     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8612     if (cps->protocolVersion > 1) {
8613       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8614       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8615       cps->comboCnt = 0;  //                and values of combo boxes
8616       SendToProgram(buf, cps);
8617     } else {
8618       SendToProgram("xboard\n", cps);
8619     }
8620 }
8621
8622
8623 void
8624 TwoMachinesEventIfReady P((void))
8625 {
8626   if (first.lastPing != first.lastPong) {
8627     DisplayMessage("", _("Waiting for first chess program"));
8628     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8629     return;
8630   }
8631   if (second.lastPing != second.lastPong) {
8632     DisplayMessage("", _("Waiting for second chess program"));
8633     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8634     return;
8635   }
8636   ThawUI();
8637   TwoMachinesEvent();
8638 }
8639
8640 void
8641 NextMatchGame P((void))
8642 {
8643     int index; /* [HGM] autoinc: step load index during match */
8644     Reset(FALSE, TRUE);
8645     if (*appData.loadGameFile != NULLCHAR) {
8646         index = appData.loadGameIndex;
8647         if(index < 0) { // [HGM] autoinc
8648             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8649             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8650         } 
8651         LoadGameFromFile(appData.loadGameFile,
8652                          index,
8653                          appData.loadGameFile, FALSE);
8654     } else if (*appData.loadPositionFile != NULLCHAR) {
8655         index = appData.loadPositionIndex;
8656         if(index < 0) { // [HGM] autoinc
8657             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8658             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8659         } 
8660         LoadPositionFromFile(appData.loadPositionFile,
8661                              index,
8662                              appData.loadPositionFile);
8663     }
8664     TwoMachinesEventIfReady();
8665 }
8666
8667 void UserAdjudicationEvent( int result )
8668 {
8669     ChessMove gameResult = GameIsDrawn;
8670
8671     if( result > 0 ) {
8672         gameResult = WhiteWins;
8673     }
8674     else if( result < 0 ) {
8675         gameResult = BlackWins;
8676     }
8677
8678     if( gameMode == TwoMachinesPlay ) {
8679         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8680     }
8681 }
8682
8683
8684 // [HGM] save: calculate checksum of game to make games easily identifiable
8685 int StringCheckSum(char *s)
8686 {
8687         int i = 0;
8688         if(s==NULL) return 0;
8689         while(*s) i = i*259 + *s++;
8690         return i;
8691 }
8692
8693 int GameCheckSum()
8694 {
8695         int i, sum=0;
8696         for(i=backwardMostMove; i<forwardMostMove; i++) {
8697                 sum += pvInfoList[i].depth;
8698                 sum += StringCheckSum(parseList[i]);
8699                 sum += StringCheckSum(commentList[i]);
8700                 sum *= 261;
8701         }
8702         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8703         return sum + StringCheckSum(commentList[i]);
8704 } // end of save patch
8705
8706 void
8707 GameEnds(result, resultDetails, whosays)
8708      ChessMove result;
8709      char *resultDetails;
8710      int whosays;
8711 {
8712     GameMode nextGameMode;
8713     int isIcsGame;
8714     char buf[MSG_SIZ];
8715
8716     if(endingGame) return; /* [HGM] crash: forbid recursion */
8717     endingGame = 1;
8718
8719     if (appData.debugMode) {
8720       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8721               result, resultDetails ? resultDetails : "(null)", whosays);
8722     }
8723
8724     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8725
8726     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8727         /* If we are playing on ICS, the server decides when the
8728            game is over, but the engine can offer to draw, claim 
8729            a draw, or resign. 
8730          */
8731 #if ZIPPY
8732         if (appData.zippyPlay && first.initDone) {
8733             if (result == GameIsDrawn) {
8734                 /* In case draw still needs to be claimed */
8735                 SendToICS(ics_prefix);
8736                 SendToICS("draw\n");
8737             } else if (StrCaseStr(resultDetails, "resign")) {
8738                 SendToICS(ics_prefix);
8739                 SendToICS("resign\n");
8740             }
8741         }
8742 #endif
8743         endingGame = 0; /* [HGM] crash */
8744         return;
8745     }
8746
8747     /* If we're loading the game from a file, stop */
8748     if (whosays == GE_FILE) {
8749       (void) StopLoadGameTimer();
8750       gameFileFP = NULL;
8751     }
8752
8753     /* Cancel draw offers */
8754     first.offeredDraw = second.offeredDraw = 0;
8755
8756     /* If this is an ICS game, only ICS can really say it's done;
8757        if not, anyone can. */
8758     isIcsGame = (gameMode == IcsPlayingWhite || 
8759                  gameMode == IcsPlayingBlack || 
8760                  gameMode == IcsObserving    || 
8761                  gameMode == IcsExamining);
8762
8763     if (!isIcsGame || whosays == GE_ICS) {
8764         /* OK -- not an ICS game, or ICS said it was done */
8765         StopClocks();
8766         if (!isIcsGame && !appData.noChessProgram) 
8767           SetUserThinkingEnables();
8768     
8769         /* [HGM] if a machine claims the game end we verify this claim */
8770         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8771             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8772                 char claimer;
8773                 ChessMove trueResult = (ChessMove) -1;
8774
8775                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8776                                             first.twoMachinesColor[0] :
8777                                             second.twoMachinesColor[0] ;
8778
8779                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8780                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8781                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8782                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8783                 } else
8784                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8785                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8786                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8787                 } else
8788                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8789                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8790                 }
8791
8792                 // now verify win claims, but not in drop games, as we don't understand those yet
8793                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8794                                                  || gameInfo.variant == VariantGreat) &&
8795                     (result == WhiteWins && claimer == 'w' ||
8796                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8797                       if (appData.debugMode) {
8798                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8799                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8800                       }
8801                       if(result != trueResult) {
8802                               sprintf(buf, "False win claim: '%s'", resultDetails);
8803                               result = claimer == 'w' ? BlackWins : WhiteWins;
8804                               resultDetails = buf;
8805                       }
8806                 } else
8807                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8808                     && (forwardMostMove <= backwardMostMove ||
8809                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8810                         (claimer=='b')==(forwardMostMove&1))
8811                                                                                   ) {
8812                       /* [HGM] verify: draws that were not flagged are false claims */
8813                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8814                       result = claimer == 'w' ? BlackWins : WhiteWins;
8815                       resultDetails = buf;
8816                 }
8817                 /* (Claiming a loss is accepted no questions asked!) */
8818             }
8819             /* [HGM] bare: don't allow bare King to win */
8820             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8821                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8822                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8823                && result != GameIsDrawn)
8824             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8825                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8826                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8827                         if(p >= 0 && p <= (int)WhiteKing) k++;
8828                 }
8829                 if (appData.debugMode) {
8830                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8831                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8832                 }
8833                 if(k <= 1) {
8834                         result = GameIsDrawn;
8835                         sprintf(buf, "%s but bare king", resultDetails);
8836                         resultDetails = buf;
8837                 }
8838             }
8839         }
8840
8841
8842         if(serverMoves != NULL && !loadFlag) { char c = '=';
8843             if(result==WhiteWins) c = '+';
8844             if(result==BlackWins) c = '-';
8845             if(resultDetails != NULL)
8846                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8847         }
8848         if (resultDetails != NULL) {
8849             gameInfo.result = result;
8850             gameInfo.resultDetails = StrSave(resultDetails);
8851
8852             /* display last move only if game was not loaded from file */
8853             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8854                 DisplayMove(currentMove - 1);
8855     
8856             if (forwardMostMove != 0) {
8857                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8858                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8859                                                                 ) {
8860                     if (*appData.saveGameFile != NULLCHAR) {
8861                         SaveGameToFile(appData.saveGameFile, TRUE);
8862                     } else if (appData.autoSaveGames) {
8863                         AutoSaveGame();
8864                     }
8865                     if (*appData.savePositionFile != NULLCHAR) {
8866                         SavePositionToFile(appData.savePositionFile);
8867                     }
8868                 }
8869             }
8870
8871             /* Tell program how game ended in case it is learning */
8872             /* [HGM] Moved this to after saving the PGN, just in case */
8873             /* engine died and we got here through time loss. In that */
8874             /* case we will get a fatal error writing the pipe, which */
8875             /* would otherwise lose us the PGN.                       */
8876             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8877             /* output during GameEnds should never be fatal anymore   */
8878             if (gameMode == MachinePlaysWhite ||
8879                 gameMode == MachinePlaysBlack ||
8880                 gameMode == TwoMachinesPlay ||
8881                 gameMode == IcsPlayingWhite ||
8882                 gameMode == IcsPlayingBlack ||
8883                 gameMode == BeginningOfGame) {
8884                 char buf[MSG_SIZ];
8885                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8886                         resultDetails);
8887                 if (first.pr != NoProc) {
8888                     SendToProgram(buf, &first);
8889                 }
8890                 if (second.pr != NoProc &&
8891                     gameMode == TwoMachinesPlay) {
8892                     SendToProgram(buf, &second);
8893                 }
8894             }
8895         }
8896
8897         if (appData.icsActive) {
8898             if (appData.quietPlay &&
8899                 (gameMode == IcsPlayingWhite ||
8900                  gameMode == IcsPlayingBlack)) {
8901                 SendToICS(ics_prefix);
8902                 SendToICS("set shout 1\n");
8903             }
8904             nextGameMode = IcsIdle;
8905             ics_user_moved = FALSE;
8906             /* clean up premove.  It's ugly when the game has ended and the
8907              * premove highlights are still on the board.
8908              */
8909             if (gotPremove) {
8910               gotPremove = FALSE;
8911               ClearPremoveHighlights();
8912               DrawPosition(FALSE, boards[currentMove]);
8913             }
8914             if (whosays == GE_ICS) {
8915                 switch (result) {
8916                 case WhiteWins:
8917                     if (gameMode == IcsPlayingWhite)
8918                         PlayIcsWinSound();
8919                     else if(gameMode == IcsPlayingBlack)
8920                         PlayIcsLossSound();
8921                     break;
8922                 case BlackWins:
8923                     if (gameMode == IcsPlayingBlack)
8924                         PlayIcsWinSound();
8925                     else if(gameMode == IcsPlayingWhite)
8926                         PlayIcsLossSound();
8927                     break;
8928                 case GameIsDrawn:
8929                     PlayIcsDrawSound();
8930                     break;
8931                 default:
8932                     PlayIcsUnfinishedSound();
8933                 }
8934             }
8935         } else if (gameMode == EditGame ||
8936                    gameMode == PlayFromGameFile || 
8937                    gameMode == AnalyzeMode || 
8938                    gameMode == AnalyzeFile) {
8939             nextGameMode = gameMode;
8940         } else {
8941             nextGameMode = EndOfGame;
8942         }
8943         pausing = FALSE;
8944         ModeHighlight();
8945     } else {
8946         nextGameMode = gameMode;
8947     }
8948
8949     if (appData.noChessProgram) {
8950         gameMode = nextGameMode;
8951         ModeHighlight();
8952         endingGame = 0; /* [HGM] crash */
8953         return;
8954     }
8955
8956     if (first.reuse) {
8957         /* Put first chess program into idle state */
8958         if (first.pr != NoProc &&
8959             (gameMode == MachinePlaysWhite ||
8960              gameMode == MachinePlaysBlack ||
8961              gameMode == TwoMachinesPlay ||
8962              gameMode == IcsPlayingWhite ||
8963              gameMode == IcsPlayingBlack ||
8964              gameMode == BeginningOfGame)) {
8965             SendToProgram("force\n", &first);
8966             if (first.usePing) {
8967               char buf[MSG_SIZ];
8968               sprintf(buf, "ping %d\n", ++first.lastPing);
8969               SendToProgram(buf, &first);
8970             }
8971         }
8972     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8973         /* Kill off first chess program */
8974         if (first.isr != NULL)
8975           RemoveInputSource(first.isr);
8976         first.isr = NULL;
8977     
8978         if (first.pr != NoProc) {
8979             ExitAnalyzeMode();
8980             DoSleep( appData.delayBeforeQuit );
8981             SendToProgram("quit\n", &first);
8982             DoSleep( appData.delayAfterQuit );
8983             DestroyChildProcess(first.pr, first.useSigterm);
8984         }
8985         first.pr = NoProc;
8986     }
8987     if (second.reuse) {
8988         /* Put second chess program into idle state */
8989         if (second.pr != NoProc &&
8990             gameMode == TwoMachinesPlay) {
8991             SendToProgram("force\n", &second);
8992             if (second.usePing) {
8993               char buf[MSG_SIZ];
8994               sprintf(buf, "ping %d\n", ++second.lastPing);
8995               SendToProgram(buf, &second);
8996             }
8997         }
8998     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8999         /* Kill off second chess program */
9000         if (second.isr != NULL)
9001           RemoveInputSource(second.isr);
9002         second.isr = NULL;
9003     
9004         if (second.pr != NoProc) {
9005             DoSleep( appData.delayBeforeQuit );
9006             SendToProgram("quit\n", &second);
9007             DoSleep( appData.delayAfterQuit );
9008             DestroyChildProcess(second.pr, second.useSigterm);
9009         }
9010         second.pr = NoProc;
9011     }
9012
9013     if (matchMode && gameMode == TwoMachinesPlay) {
9014         switch (result) {
9015         case WhiteWins:
9016           if (first.twoMachinesColor[0] == 'w') {
9017             first.matchWins++;
9018           } else {
9019             second.matchWins++;
9020           }
9021           break;
9022         case BlackWins:
9023           if (first.twoMachinesColor[0] == 'b') {
9024             first.matchWins++;
9025           } else {
9026             second.matchWins++;
9027           }
9028           break;
9029         default:
9030           break;
9031         }
9032         if (matchGame < appData.matchGames) {
9033             char *tmp;
9034             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9035                 tmp = first.twoMachinesColor;
9036                 first.twoMachinesColor = second.twoMachinesColor;
9037                 second.twoMachinesColor = tmp;
9038             }
9039             gameMode = nextGameMode;
9040             matchGame++;
9041             if(appData.matchPause>10000 || appData.matchPause<10)
9042                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9043             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9044             endingGame = 0; /* [HGM] crash */
9045             return;
9046         } else {
9047             char buf[MSG_SIZ];
9048             gameMode = nextGameMode;
9049             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9050                     first.tidy, second.tidy,
9051                     first.matchWins, second.matchWins,
9052                     appData.matchGames - (first.matchWins + second.matchWins));
9053             DisplayFatalError(buf, 0, 0);
9054         }
9055     }
9056     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9057         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9058       ExitAnalyzeMode();
9059     gameMode = nextGameMode;
9060     ModeHighlight();
9061     endingGame = 0;  /* [HGM] crash */
9062 }
9063
9064 /* Assumes program was just initialized (initString sent).
9065    Leaves program in force mode. */
9066 void
9067 FeedMovesToProgram(cps, upto) 
9068      ChessProgramState *cps;
9069      int upto;
9070 {
9071     int i;
9072     
9073     if (appData.debugMode)
9074       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9075               startedFromSetupPosition ? "position and " : "",
9076               backwardMostMove, upto, cps->which);
9077     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9078         // [HGM] variantswitch: make engine aware of new variant
9079         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9080                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9081         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9082         SendToProgram(buf, cps);
9083         currentlyInitializedVariant = gameInfo.variant;
9084     }
9085     SendToProgram("force\n", cps);
9086     if (startedFromSetupPosition) {
9087         SendBoard(cps, backwardMostMove);
9088     if (appData.debugMode) {
9089         fprintf(debugFP, "feedMoves\n");
9090     }
9091     }
9092     for (i = backwardMostMove; i < upto; i++) {
9093         SendMoveToProgram(i, cps);
9094     }
9095 }
9096
9097
9098 void
9099 ResurrectChessProgram()
9100 {
9101      /* The chess program may have exited.
9102         If so, restart it and feed it all the moves made so far. */
9103
9104     if (appData.noChessProgram || first.pr != NoProc) return;
9105     
9106     StartChessProgram(&first);
9107     InitChessProgram(&first, FALSE);
9108     FeedMovesToProgram(&first, currentMove);
9109
9110     if (!first.sendTime) {
9111         /* can't tell gnuchess what its clock should read,
9112            so we bow to its notion. */
9113         ResetClocks();
9114         timeRemaining[0][currentMove] = whiteTimeRemaining;
9115         timeRemaining[1][currentMove] = blackTimeRemaining;
9116     }
9117
9118     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9119                 appData.icsEngineAnalyze) && first.analysisSupport) {
9120       SendToProgram("analyze\n", &first);
9121       first.analyzing = TRUE;
9122     }
9123 }
9124
9125 /*
9126  * Button procedures
9127  */
9128 void
9129 Reset(redraw, init)
9130      int redraw, init;
9131 {
9132     int i;
9133
9134     if (appData.debugMode) {
9135         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9136                 redraw, init, gameMode);
9137     }
9138     CleanupTail(); // [HGM] vari: delete any stored variations
9139     pausing = pauseExamInvalid = FALSE;
9140     startedFromSetupPosition = blackPlaysFirst = FALSE;
9141     firstMove = TRUE;
9142     whiteFlag = blackFlag = FALSE;
9143     userOfferedDraw = FALSE;
9144     hintRequested = bookRequested = FALSE;
9145     first.maybeThinking = FALSE;
9146     second.maybeThinking = FALSE;
9147     first.bookSuspend = FALSE; // [HGM] book
9148     second.bookSuspend = FALSE;
9149     thinkOutput[0] = NULLCHAR;
9150     lastHint[0] = NULLCHAR;
9151     ClearGameInfo(&gameInfo);
9152     gameInfo.variant = StringToVariant(appData.variant);
9153     ics_user_moved = ics_clock_paused = FALSE;
9154     ics_getting_history = H_FALSE;
9155     ics_gamenum = -1;
9156     white_holding[0] = black_holding[0] = NULLCHAR;
9157     ClearProgramStats();
9158     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9159     
9160     ResetFrontEnd();
9161     ClearHighlights();
9162     flipView = appData.flipView;
9163     ClearPremoveHighlights();
9164     gotPremove = FALSE;
9165     alarmSounded = FALSE;
9166
9167     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9168     if(appData.serverMovesName != NULL) {
9169         /* [HGM] prepare to make moves file for broadcasting */
9170         clock_t t = clock();
9171         if(serverMoves != NULL) fclose(serverMoves);
9172         serverMoves = fopen(appData.serverMovesName, "r");
9173         if(serverMoves != NULL) {
9174             fclose(serverMoves);
9175             /* delay 15 sec before overwriting, so all clients can see end */
9176             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9177         }
9178         serverMoves = fopen(appData.serverMovesName, "w");
9179     }
9180
9181     ExitAnalyzeMode();
9182     gameMode = BeginningOfGame;
9183     ModeHighlight();
9184     if(appData.icsActive) gameInfo.variant = VariantNormal;
9185     currentMove = forwardMostMove = backwardMostMove = 0;
9186     InitPosition(redraw);
9187     for (i = 0; i < MAX_MOVES; i++) {
9188         if (commentList[i] != NULL) {
9189             free(commentList[i]);
9190             commentList[i] = NULL;
9191         }
9192     }
9193     ResetClocks();
9194     timeRemaining[0][0] = whiteTimeRemaining;
9195     timeRemaining[1][0] = blackTimeRemaining;
9196     if (first.pr == NULL) {
9197         StartChessProgram(&first);
9198     }
9199     if (init) {
9200             InitChessProgram(&first, startedFromSetupPosition);
9201     }
9202     DisplayTitle("");
9203     DisplayMessage("", "");
9204     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9205     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9206 }
9207
9208 void
9209 AutoPlayGameLoop()
9210 {
9211     for (;;) {
9212         if (!AutoPlayOneMove())
9213           return;
9214         if (matchMode || appData.timeDelay == 0)
9215           continue;
9216         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9217           return;
9218         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9219         break;
9220     }
9221 }
9222
9223
9224 int
9225 AutoPlayOneMove()
9226 {
9227     int fromX, fromY, toX, toY;
9228
9229     if (appData.debugMode) {
9230       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9231     }
9232
9233     if (gameMode != PlayFromGameFile)
9234       return FALSE;
9235
9236     if (currentMove >= forwardMostMove) {
9237       gameMode = EditGame;
9238       ModeHighlight();
9239
9240       /* [AS] Clear current move marker at the end of a game */
9241       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9242
9243       return FALSE;
9244     }
9245     
9246     toX = moveList[currentMove][2] - AAA;
9247     toY = moveList[currentMove][3] - ONE;
9248
9249     if (moveList[currentMove][1] == '@') {
9250         if (appData.highlightLastMove) {
9251             SetHighlights(-1, -1, toX, toY);
9252         }
9253     } else {
9254         fromX = moveList[currentMove][0] - AAA;
9255         fromY = moveList[currentMove][1] - ONE;
9256
9257         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9258
9259         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9260
9261         if (appData.highlightLastMove) {
9262             SetHighlights(fromX, fromY, toX, toY);
9263         }
9264     }
9265     DisplayMove(currentMove);
9266     SendMoveToProgram(currentMove++, &first);
9267     DisplayBothClocks();
9268     DrawPosition(FALSE, boards[currentMove]);
9269     // [HGM] PV info: always display, routine tests if empty
9270     DisplayComment(currentMove - 1, commentList[currentMove]);
9271     return TRUE;
9272 }
9273
9274
9275 int
9276 LoadGameOneMove(readAhead)
9277      ChessMove readAhead;
9278 {
9279     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9280     char promoChar = NULLCHAR;
9281     ChessMove moveType;
9282     char move[MSG_SIZ];
9283     char *p, *q;
9284     
9285     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9286         gameMode != AnalyzeMode && gameMode != Training) {
9287         gameFileFP = NULL;
9288         return FALSE;
9289     }
9290     
9291     yyboardindex = forwardMostMove;
9292     if (readAhead != (ChessMove)0) {
9293       moveType = readAhead;
9294     } else {
9295       if (gameFileFP == NULL)
9296           return FALSE;
9297       moveType = (ChessMove) yylex();
9298     }
9299     
9300     done = FALSE;
9301     switch (moveType) {
9302       case Comment:
9303         if (appData.debugMode) 
9304           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9305         p = yy_text;
9306
9307         /* append the comment but don't display it */
9308         AppendComment(currentMove, p, FALSE);
9309         return TRUE;
9310
9311       case WhiteCapturesEnPassant:
9312       case BlackCapturesEnPassant:
9313       case WhitePromotionChancellor:
9314       case BlackPromotionChancellor:
9315       case WhitePromotionArchbishop:
9316       case BlackPromotionArchbishop:
9317       case WhitePromotionCentaur:
9318       case BlackPromotionCentaur:
9319       case WhitePromotionQueen:
9320       case BlackPromotionQueen:
9321       case WhitePromotionRook:
9322       case BlackPromotionRook:
9323       case WhitePromotionBishop:
9324       case BlackPromotionBishop:
9325       case WhitePromotionKnight:
9326       case BlackPromotionKnight:
9327       case WhitePromotionKing:
9328       case BlackPromotionKing:
9329       case NormalMove:
9330       case WhiteKingSideCastle:
9331       case WhiteQueenSideCastle:
9332       case BlackKingSideCastle:
9333       case BlackQueenSideCastle:
9334       case WhiteKingSideCastleWild:
9335       case WhiteQueenSideCastleWild:
9336       case BlackKingSideCastleWild:
9337       case BlackQueenSideCastleWild:
9338       /* PUSH Fabien */
9339       case WhiteHSideCastleFR:
9340       case WhiteASideCastleFR:
9341       case BlackHSideCastleFR:
9342       case BlackASideCastleFR:
9343       /* POP Fabien */
9344         if (appData.debugMode)
9345           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9346         fromX = currentMoveString[0] - AAA;
9347         fromY = currentMoveString[1] - ONE;
9348         toX = currentMoveString[2] - AAA;
9349         toY = currentMoveString[3] - ONE;
9350         promoChar = currentMoveString[4];
9351         break;
9352
9353       case WhiteDrop:
9354       case BlackDrop:
9355         if (appData.debugMode)
9356           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9357         fromX = moveType == WhiteDrop ?
9358           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9359         (int) CharToPiece(ToLower(currentMoveString[0]));
9360         fromY = DROP_RANK;
9361         toX = currentMoveString[2] - AAA;
9362         toY = currentMoveString[3] - ONE;
9363         break;
9364
9365       case WhiteWins:
9366       case BlackWins:
9367       case GameIsDrawn:
9368       case GameUnfinished:
9369         if (appData.debugMode)
9370           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9371         p = strchr(yy_text, '{');
9372         if (p == NULL) p = strchr(yy_text, '(');
9373         if (p == NULL) {
9374             p = yy_text;
9375             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9376         } else {
9377             q = strchr(p, *p == '{' ? '}' : ')');
9378             if (q != NULL) *q = NULLCHAR;
9379             p++;
9380         }
9381         GameEnds(moveType, p, GE_FILE);
9382         done = TRUE;
9383         if (cmailMsgLoaded) {
9384             ClearHighlights();
9385             flipView = WhiteOnMove(currentMove);
9386             if (moveType == GameUnfinished) flipView = !flipView;
9387             if (appData.debugMode)
9388               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9389         }
9390         break;
9391
9392       case (ChessMove) 0:       /* end of file */
9393         if (appData.debugMode)
9394           fprintf(debugFP, "Parser hit end of file\n");
9395         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9396           case MT_NONE:
9397           case MT_CHECK:
9398             break;
9399           case MT_CHECKMATE:
9400           case MT_STAINMATE:
9401             if (WhiteOnMove(currentMove)) {
9402                 GameEnds(BlackWins, "Black mates", GE_FILE);
9403             } else {
9404                 GameEnds(WhiteWins, "White mates", GE_FILE);
9405             }
9406             break;
9407           case MT_STALEMATE:
9408             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9409             break;
9410         }
9411         done = TRUE;
9412         break;
9413
9414       case MoveNumberOne:
9415         if (lastLoadGameStart == GNUChessGame) {
9416             /* GNUChessGames have numbers, but they aren't move numbers */
9417             if (appData.debugMode)
9418               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9419                       yy_text, (int) moveType);
9420             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9421         }
9422         /* else fall thru */
9423
9424       case XBoardGame:
9425       case GNUChessGame:
9426       case PGNTag:
9427         /* Reached start of next game in file */
9428         if (appData.debugMode)
9429           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9430         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9431           case MT_NONE:
9432           case MT_CHECK:
9433             break;
9434           case MT_CHECKMATE:
9435           case MT_STAINMATE:
9436             if (WhiteOnMove(currentMove)) {
9437                 GameEnds(BlackWins, "Black mates", GE_FILE);
9438             } else {
9439                 GameEnds(WhiteWins, "White mates", GE_FILE);
9440             }
9441             break;
9442           case MT_STALEMATE:
9443             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9444             break;
9445         }
9446         done = TRUE;
9447         break;
9448
9449       case PositionDiagram:     /* should not happen; ignore */
9450       case ElapsedTime:         /* ignore */
9451       case NAG:                 /* ignore */
9452         if (appData.debugMode)
9453           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9454                   yy_text, (int) moveType);
9455         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9456
9457       case IllegalMove:
9458         if (appData.testLegality) {
9459             if (appData.debugMode)
9460               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9461             sprintf(move, _("Illegal move: %d.%s%s"),
9462                     (forwardMostMove / 2) + 1,
9463                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9464             DisplayError(move, 0);
9465             done = TRUE;
9466         } else {
9467             if (appData.debugMode)
9468               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9469                       yy_text, currentMoveString);
9470             fromX = currentMoveString[0] - AAA;
9471             fromY = currentMoveString[1] - ONE;
9472             toX = currentMoveString[2] - AAA;
9473             toY = currentMoveString[3] - ONE;
9474             promoChar = currentMoveString[4];
9475         }
9476         break;
9477
9478       case AmbiguousMove:
9479         if (appData.debugMode)
9480           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9481         sprintf(move, _("Ambiguous move: %d.%s%s"),
9482                 (forwardMostMove / 2) + 1,
9483                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9484         DisplayError(move, 0);
9485         done = TRUE;
9486         break;
9487
9488       default:
9489       case ImpossibleMove:
9490         if (appData.debugMode)
9491           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9492         sprintf(move, _("Illegal move: %d.%s%s"),
9493                 (forwardMostMove / 2) + 1,
9494                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9495         DisplayError(move, 0);
9496         done = TRUE;
9497         break;
9498     }
9499
9500     if (done) {
9501         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9502             DrawPosition(FALSE, boards[currentMove]);
9503             DisplayBothClocks();
9504             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9505               DisplayComment(currentMove - 1, commentList[currentMove]);
9506         }
9507         (void) StopLoadGameTimer();
9508         gameFileFP = NULL;
9509         cmailOldMove = forwardMostMove;
9510         return FALSE;
9511     } else {
9512         /* currentMoveString is set as a side-effect of yylex */
9513         strcat(currentMoveString, "\n");
9514         strcpy(moveList[forwardMostMove], currentMoveString);
9515         
9516         thinkOutput[0] = NULLCHAR;
9517         MakeMove(fromX, fromY, toX, toY, promoChar);
9518         currentMove = forwardMostMove;
9519         return TRUE;
9520     }
9521 }
9522
9523 /* Load the nth game from the given file */
9524 int
9525 LoadGameFromFile(filename, n, title, useList)
9526      char *filename;
9527      int n;
9528      char *title;
9529      /*Boolean*/ int useList;
9530 {
9531     FILE *f;
9532     char buf[MSG_SIZ];
9533
9534     if (strcmp(filename, "-") == 0) {
9535         f = stdin;
9536         title = "stdin";
9537     } else {
9538         f = fopen(filename, "rb");
9539         if (f == NULL) {
9540           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9541             DisplayError(buf, errno);
9542             return FALSE;
9543         }
9544     }
9545     if (fseek(f, 0, 0) == -1) {
9546         /* f is not seekable; probably a pipe */
9547         useList = FALSE;
9548     }
9549     if (useList && n == 0) {
9550         int error = GameListBuild(f);
9551         if (error) {
9552             DisplayError(_("Cannot build game list"), error);
9553         } else if (!ListEmpty(&gameList) &&
9554                    ((ListGame *) gameList.tailPred)->number > 1) {
9555             GameListPopUp(f, title);
9556             return TRUE;
9557         }
9558         GameListDestroy();
9559         n = 1;
9560     }
9561     if (n == 0) n = 1;
9562     return LoadGame(f, n, title, FALSE);
9563 }
9564
9565
9566 void
9567 MakeRegisteredMove()
9568 {
9569     int fromX, fromY, toX, toY;
9570     char promoChar;
9571     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9572         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9573           case CMAIL_MOVE:
9574           case CMAIL_DRAW:
9575             if (appData.debugMode)
9576               fprintf(debugFP, "Restoring %s for game %d\n",
9577                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9578     
9579             thinkOutput[0] = NULLCHAR;
9580             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9581             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9582             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9583             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9584             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9585             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9586             MakeMove(fromX, fromY, toX, toY, promoChar);
9587             ShowMove(fromX, fromY, toX, toY);
9588               
9589             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9590               case MT_NONE:
9591               case MT_CHECK:
9592                 break;
9593                 
9594               case MT_CHECKMATE:
9595               case MT_STAINMATE:
9596                 if (WhiteOnMove(currentMove)) {
9597                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9598                 } else {
9599                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9600                 }
9601                 break;
9602                 
9603               case MT_STALEMATE:
9604                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9605                 break;
9606             }
9607
9608             break;
9609             
9610           case CMAIL_RESIGN:
9611             if (WhiteOnMove(currentMove)) {
9612                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9613             } else {
9614                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9615             }
9616             break;
9617             
9618           case CMAIL_ACCEPT:
9619             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9620             break;
9621               
9622           default:
9623             break;
9624         }
9625     }
9626
9627     return;
9628 }
9629
9630 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9631 int
9632 CmailLoadGame(f, gameNumber, title, useList)
9633      FILE *f;
9634      int gameNumber;
9635      char *title;
9636      int useList;
9637 {
9638     int retVal;
9639
9640     if (gameNumber > nCmailGames) {
9641         DisplayError(_("No more games in this message"), 0);
9642         return FALSE;
9643     }
9644     if (f == lastLoadGameFP) {
9645         int offset = gameNumber - lastLoadGameNumber;
9646         if (offset == 0) {
9647             cmailMsg[0] = NULLCHAR;
9648             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9649                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9650                 nCmailMovesRegistered--;
9651             }
9652             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9653             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9654                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9655             }
9656         } else {
9657             if (! RegisterMove()) return FALSE;
9658         }
9659     }
9660
9661     retVal = LoadGame(f, gameNumber, title, useList);
9662
9663     /* Make move registered during previous look at this game, if any */
9664     MakeRegisteredMove();
9665
9666     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9667         commentList[currentMove]
9668           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9669         DisplayComment(currentMove - 1, commentList[currentMove]);
9670     }
9671
9672     return retVal;
9673 }
9674
9675 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9676 int
9677 ReloadGame(offset)
9678      int offset;
9679 {
9680     int gameNumber = lastLoadGameNumber + offset;
9681     if (lastLoadGameFP == NULL) {
9682         DisplayError(_("No game has been loaded yet"), 0);
9683         return FALSE;
9684     }
9685     if (gameNumber <= 0) {
9686         DisplayError(_("Can't back up any further"), 0);
9687         return FALSE;
9688     }
9689     if (cmailMsgLoaded) {
9690         return CmailLoadGame(lastLoadGameFP, gameNumber,
9691                              lastLoadGameTitle, lastLoadGameUseList);
9692     } else {
9693         return LoadGame(lastLoadGameFP, gameNumber,
9694                         lastLoadGameTitle, lastLoadGameUseList);
9695     }
9696 }
9697
9698
9699
9700 /* Load the nth game from open file f */
9701 int
9702 LoadGame(f, gameNumber, title, useList)
9703      FILE *f;
9704      int gameNumber;
9705      char *title;
9706      int useList;
9707 {
9708     ChessMove cm;
9709     char buf[MSG_SIZ];
9710     int gn = gameNumber;
9711     ListGame *lg = NULL;
9712     int numPGNTags = 0;
9713     int err;
9714     GameMode oldGameMode;
9715     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9716
9717     if (appData.debugMode) 
9718         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9719
9720     if (gameMode == Training )
9721         SetTrainingModeOff();
9722
9723     oldGameMode = gameMode;
9724     if (gameMode != BeginningOfGame) {
9725       Reset(FALSE, TRUE);
9726     }
9727
9728     gameFileFP = f;
9729     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9730         fclose(lastLoadGameFP);
9731     }
9732
9733     if (useList) {
9734         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9735         
9736         if (lg) {
9737             fseek(f, lg->offset, 0);
9738             GameListHighlight(gameNumber);
9739             gn = 1;
9740         }
9741         else {
9742             DisplayError(_("Game number out of range"), 0);
9743             return FALSE;
9744         }
9745     } else {
9746         GameListDestroy();
9747         if (fseek(f, 0, 0) == -1) {
9748             if (f == lastLoadGameFP ?
9749                 gameNumber == lastLoadGameNumber + 1 :
9750                 gameNumber == 1) {
9751                 gn = 1;
9752             } else {
9753                 DisplayError(_("Can't seek on game file"), 0);
9754                 return FALSE;
9755             }
9756         }
9757     }
9758     lastLoadGameFP = f;
9759     lastLoadGameNumber = gameNumber;
9760     strcpy(lastLoadGameTitle, title);
9761     lastLoadGameUseList = useList;
9762
9763     yynewfile(f);
9764
9765     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9766       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9767                 lg->gameInfo.black);
9768             DisplayTitle(buf);
9769     } else if (*title != NULLCHAR) {
9770         if (gameNumber > 1) {
9771             sprintf(buf, "%s %d", title, gameNumber);
9772             DisplayTitle(buf);
9773         } else {
9774             DisplayTitle(title);
9775         }
9776     }
9777
9778     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9779         gameMode = PlayFromGameFile;
9780         ModeHighlight();
9781     }
9782
9783     currentMove = forwardMostMove = backwardMostMove = 0;
9784     CopyBoard(boards[0], initialPosition);
9785     StopClocks();
9786
9787     /*
9788      * Skip the first gn-1 games in the file.
9789      * Also skip over anything that precedes an identifiable 
9790      * start of game marker, to avoid being confused by 
9791      * garbage at the start of the file.  Currently 
9792      * recognized start of game markers are the move number "1",
9793      * the pattern "gnuchess .* game", the pattern
9794      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9795      * A game that starts with one of the latter two patterns
9796      * will also have a move number 1, possibly
9797      * following a position diagram.
9798      * 5-4-02: Let's try being more lenient and allowing a game to
9799      * start with an unnumbered move.  Does that break anything?
9800      */
9801     cm = lastLoadGameStart = (ChessMove) 0;
9802     while (gn > 0) {
9803         yyboardindex = forwardMostMove;
9804         cm = (ChessMove) yylex();
9805         switch (cm) {
9806           case (ChessMove) 0:
9807             if (cmailMsgLoaded) {
9808                 nCmailGames = CMAIL_MAX_GAMES - gn;
9809             } else {
9810                 Reset(TRUE, TRUE);
9811                 DisplayError(_("Game not found in file"), 0);
9812             }
9813             return FALSE;
9814
9815           case GNUChessGame:
9816           case XBoardGame:
9817             gn--;
9818             lastLoadGameStart = cm;
9819             break;
9820             
9821           case MoveNumberOne:
9822             switch (lastLoadGameStart) {
9823               case GNUChessGame:
9824               case XBoardGame:
9825               case PGNTag:
9826                 break;
9827               case MoveNumberOne:
9828               case (ChessMove) 0:
9829                 gn--;           /* count this game */
9830                 lastLoadGameStart = cm;
9831                 break;
9832               default:
9833                 /* impossible */
9834                 break;
9835             }
9836             break;
9837
9838           case PGNTag:
9839             switch (lastLoadGameStart) {
9840               case GNUChessGame:
9841               case PGNTag:
9842               case MoveNumberOne:
9843               case (ChessMove) 0:
9844                 gn--;           /* count this game */
9845                 lastLoadGameStart = cm;
9846                 break;
9847               case XBoardGame:
9848                 lastLoadGameStart = cm; /* game counted already */
9849                 break;
9850               default:
9851                 /* impossible */
9852                 break;
9853             }
9854             if (gn > 0) {
9855                 do {
9856                     yyboardindex = forwardMostMove;
9857                     cm = (ChessMove) yylex();
9858                 } while (cm == PGNTag || cm == Comment);
9859             }
9860             break;
9861
9862           case WhiteWins:
9863           case BlackWins:
9864           case GameIsDrawn:
9865             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9866                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9867                     != CMAIL_OLD_RESULT) {
9868                     nCmailResults ++ ;
9869                     cmailResult[  CMAIL_MAX_GAMES
9870                                 - gn - 1] = CMAIL_OLD_RESULT;
9871                 }
9872             }
9873             break;
9874
9875           case NormalMove:
9876             /* Only a NormalMove can be at the start of a game
9877              * without a position diagram. */
9878             if (lastLoadGameStart == (ChessMove) 0) {
9879               gn--;
9880               lastLoadGameStart = MoveNumberOne;
9881             }
9882             break;
9883
9884           default:
9885             break;
9886         }
9887     }
9888     
9889     if (appData.debugMode)
9890       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9891
9892     if (cm == XBoardGame) {
9893         /* Skip any header junk before position diagram and/or move 1 */
9894         for (;;) {
9895             yyboardindex = forwardMostMove;
9896             cm = (ChessMove) yylex();
9897
9898             if (cm == (ChessMove) 0 ||
9899                 cm == GNUChessGame || cm == XBoardGame) {
9900                 /* Empty game; pretend end-of-file and handle later */
9901                 cm = (ChessMove) 0;
9902                 break;
9903             }
9904
9905             if (cm == MoveNumberOne || cm == PositionDiagram ||
9906                 cm == PGNTag || cm == Comment)
9907               break;
9908         }
9909     } else if (cm == GNUChessGame) {
9910         if (gameInfo.event != NULL) {
9911             free(gameInfo.event);
9912         }
9913         gameInfo.event = StrSave(yy_text);
9914     }   
9915
9916     startedFromSetupPosition = FALSE;
9917     while (cm == PGNTag) {
9918         if (appData.debugMode) 
9919           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9920         err = ParsePGNTag(yy_text, &gameInfo);
9921         if (!err) numPGNTags++;
9922
9923         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9924         if(gameInfo.variant != oldVariant) {
9925             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9926             InitPosition(TRUE);
9927             oldVariant = gameInfo.variant;
9928             if (appData.debugMode) 
9929               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9930         }
9931
9932
9933         if (gameInfo.fen != NULL) {
9934           Board initial_position;
9935           startedFromSetupPosition = TRUE;
9936           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9937             Reset(TRUE, TRUE);
9938             DisplayError(_("Bad FEN position in file"), 0);
9939             return FALSE;
9940           }
9941           CopyBoard(boards[0], initial_position);
9942           if (blackPlaysFirst) {
9943             currentMove = forwardMostMove = backwardMostMove = 1;
9944             CopyBoard(boards[1], initial_position);
9945             strcpy(moveList[0], "");
9946             strcpy(parseList[0], "");
9947             timeRemaining[0][1] = whiteTimeRemaining;
9948             timeRemaining[1][1] = blackTimeRemaining;
9949             if (commentList[0] != NULL) {
9950               commentList[1] = commentList[0];
9951               commentList[0] = NULL;
9952             }
9953           } else {
9954             currentMove = forwardMostMove = backwardMostMove = 0;
9955           }
9956           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9957           {   int i;
9958               initialRulePlies = FENrulePlies;
9959               for( i=0; i< nrCastlingRights; i++ )
9960                   initialRights[i] = initial_position[CASTLING][i];
9961           }
9962           yyboardindex = forwardMostMove;
9963           free(gameInfo.fen);
9964           gameInfo.fen = NULL;
9965         }
9966
9967         yyboardindex = forwardMostMove;
9968         cm = (ChessMove) yylex();
9969
9970         /* Handle comments interspersed among the tags */
9971         while (cm == Comment) {
9972             char *p;
9973             if (appData.debugMode) 
9974               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9975             p = yy_text;
9976             AppendComment(currentMove, p, FALSE);
9977             yyboardindex = forwardMostMove;
9978             cm = (ChessMove) yylex();
9979         }
9980     }
9981
9982     /* don't rely on existence of Event tag since if game was
9983      * pasted from clipboard the Event tag may not exist
9984      */
9985     if (numPGNTags > 0){
9986         char *tags;
9987         if (gameInfo.variant == VariantNormal) {
9988           gameInfo.variant = StringToVariant(gameInfo.event);
9989         }
9990         if (!matchMode) {
9991           if( appData.autoDisplayTags ) {
9992             tags = PGNTags(&gameInfo);
9993             TagsPopUp(tags, CmailMsg());
9994             free(tags);
9995           }
9996         }
9997     } else {
9998         /* Make something up, but don't display it now */
9999         SetGameInfo();
10000         TagsPopDown();
10001     }
10002
10003     if (cm == PositionDiagram) {
10004         int i, j;
10005         char *p;
10006         Board initial_position;
10007
10008         if (appData.debugMode)
10009           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10010
10011         if (!startedFromSetupPosition) {
10012             p = yy_text;
10013             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10014               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10015                 switch (*p) {
10016                   case '[':
10017                   case '-':
10018                   case ' ':
10019                   case '\t':
10020                   case '\n':
10021                   case '\r':
10022                     break;
10023                   default:
10024                     initial_position[i][j++] = CharToPiece(*p);
10025                     break;
10026                 }
10027             while (*p == ' ' || *p == '\t' ||
10028                    *p == '\n' || *p == '\r') p++;
10029         
10030             if (strncmp(p, "black", strlen("black"))==0)
10031               blackPlaysFirst = TRUE;
10032             else
10033               blackPlaysFirst = FALSE;
10034             startedFromSetupPosition = TRUE;
10035         
10036             CopyBoard(boards[0], initial_position);
10037             if (blackPlaysFirst) {
10038                 currentMove = forwardMostMove = backwardMostMove = 1;
10039                 CopyBoard(boards[1], initial_position);
10040                 strcpy(moveList[0], "");
10041                 strcpy(parseList[0], "");
10042                 timeRemaining[0][1] = whiteTimeRemaining;
10043                 timeRemaining[1][1] = blackTimeRemaining;
10044                 if (commentList[0] != NULL) {
10045                     commentList[1] = commentList[0];
10046                     commentList[0] = NULL;
10047                 }
10048             } else {
10049                 currentMove = forwardMostMove = backwardMostMove = 0;
10050             }
10051         }
10052         yyboardindex = forwardMostMove;
10053         cm = (ChessMove) yylex();
10054     }
10055
10056     if (first.pr == NoProc) {
10057         StartChessProgram(&first);
10058     }
10059     InitChessProgram(&first, FALSE);
10060     SendToProgram("force\n", &first);
10061     if (startedFromSetupPosition) {
10062         SendBoard(&first, forwardMostMove);
10063     if (appData.debugMode) {
10064         fprintf(debugFP, "Load Game\n");
10065     }
10066         DisplayBothClocks();
10067     }      
10068
10069     /* [HGM] server: flag to write setup moves in broadcast file as one */
10070     loadFlag = appData.suppressLoadMoves;
10071
10072     while (cm == Comment) {
10073         char *p;
10074         if (appData.debugMode) 
10075           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10076         p = yy_text;
10077         AppendComment(currentMove, p, FALSE);
10078         yyboardindex = forwardMostMove;
10079         cm = (ChessMove) yylex();
10080     }
10081
10082     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10083         cm == WhiteWins || cm == BlackWins ||
10084         cm == GameIsDrawn || cm == GameUnfinished) {
10085         DisplayMessage("", _("No moves in game"));
10086         if (cmailMsgLoaded) {
10087             if (appData.debugMode)
10088               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10089             ClearHighlights();
10090             flipView = FALSE;
10091         }
10092         DrawPosition(FALSE, boards[currentMove]);
10093         DisplayBothClocks();
10094         gameMode = EditGame;
10095         ModeHighlight();
10096         gameFileFP = NULL;
10097         cmailOldMove = 0;
10098         return TRUE;
10099     }
10100
10101     // [HGM] PV info: routine tests if comment empty
10102     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10103         DisplayComment(currentMove - 1, commentList[currentMove]);
10104     }
10105     if (!matchMode && appData.timeDelay != 0) 
10106       DrawPosition(FALSE, boards[currentMove]);
10107
10108     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10109       programStats.ok_to_send = 1;
10110     }
10111
10112     /* if the first token after the PGN tags is a move
10113      * and not move number 1, retrieve it from the parser 
10114      */
10115     if (cm != MoveNumberOne)
10116         LoadGameOneMove(cm);
10117
10118     /* load the remaining moves from the file */
10119     while (LoadGameOneMove((ChessMove)0)) {
10120       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10121       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10122     }
10123
10124     /* rewind to the start of the game */
10125     currentMove = backwardMostMove;
10126
10127     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10128
10129     if (oldGameMode == AnalyzeFile ||
10130         oldGameMode == AnalyzeMode) {
10131       AnalyzeFileEvent();
10132     }
10133
10134     if (matchMode || appData.timeDelay == 0) {
10135       ToEndEvent();
10136       gameMode = EditGame;
10137       ModeHighlight();
10138     } else if (appData.timeDelay > 0) {
10139       AutoPlayGameLoop();
10140     }
10141
10142     if (appData.debugMode) 
10143         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10144
10145     loadFlag = 0; /* [HGM] true game starts */
10146     return TRUE;
10147 }
10148
10149 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10150 int
10151 ReloadPosition(offset)
10152      int offset;
10153 {
10154     int positionNumber = lastLoadPositionNumber + offset;
10155     if (lastLoadPositionFP == NULL) {
10156         DisplayError(_("No position has been loaded yet"), 0);
10157         return FALSE;
10158     }
10159     if (positionNumber <= 0) {
10160         DisplayError(_("Can't back up any further"), 0);
10161         return FALSE;
10162     }
10163     return LoadPosition(lastLoadPositionFP, positionNumber,
10164                         lastLoadPositionTitle);
10165 }
10166
10167 /* Load the nth position from the given file */
10168 int
10169 LoadPositionFromFile(filename, n, title)
10170      char *filename;
10171      int n;
10172      char *title;
10173 {
10174     FILE *f;
10175     char buf[MSG_SIZ];
10176
10177     if (strcmp(filename, "-") == 0) {
10178         return LoadPosition(stdin, n, "stdin");
10179     } else {
10180         f = fopen(filename, "rb");
10181         if (f == NULL) {
10182             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10183             DisplayError(buf, errno);
10184             return FALSE;
10185         } else {
10186             return LoadPosition(f, n, title);
10187         }
10188     }
10189 }
10190
10191 /* Load the nth position from the given open file, and close it */
10192 int
10193 LoadPosition(f, positionNumber, title)
10194      FILE *f;
10195      int positionNumber;
10196      char *title;
10197 {
10198     char *p, line[MSG_SIZ];
10199     Board initial_position;
10200     int i, j, fenMode, pn;
10201     
10202     if (gameMode == Training )
10203         SetTrainingModeOff();
10204
10205     if (gameMode != BeginningOfGame) {
10206         Reset(FALSE, TRUE);
10207     }
10208     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10209         fclose(lastLoadPositionFP);
10210     }
10211     if (positionNumber == 0) positionNumber = 1;
10212     lastLoadPositionFP = f;
10213     lastLoadPositionNumber = positionNumber;
10214     strcpy(lastLoadPositionTitle, title);
10215     if (first.pr == NoProc) {
10216       StartChessProgram(&first);
10217       InitChessProgram(&first, FALSE);
10218     }    
10219     pn = positionNumber;
10220     if (positionNumber < 0) {
10221         /* Negative position number means to seek to that byte offset */
10222         if (fseek(f, -positionNumber, 0) == -1) {
10223             DisplayError(_("Can't seek on position file"), 0);
10224             return FALSE;
10225         };
10226         pn = 1;
10227     } else {
10228         if (fseek(f, 0, 0) == -1) {
10229             if (f == lastLoadPositionFP ?
10230                 positionNumber == lastLoadPositionNumber + 1 :
10231                 positionNumber == 1) {
10232                 pn = 1;
10233             } else {
10234                 DisplayError(_("Can't seek on position file"), 0);
10235                 return FALSE;
10236             }
10237         }
10238     }
10239     /* See if this file is FEN or old-style xboard */
10240     if (fgets(line, MSG_SIZ, f) == NULL) {
10241         DisplayError(_("Position not found in file"), 0);
10242         return FALSE;
10243     }
10244     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10245     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10246
10247     if (pn >= 2) {
10248         if (fenMode || line[0] == '#') pn--;
10249         while (pn > 0) {
10250             /* skip positions before number pn */
10251             if (fgets(line, MSG_SIZ, f) == NULL) {
10252                 Reset(TRUE, TRUE);
10253                 DisplayError(_("Position not found in file"), 0);
10254                 return FALSE;
10255             }
10256             if (fenMode || line[0] == '#') pn--;
10257         }
10258     }
10259
10260     if (fenMode) {
10261         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10262             DisplayError(_("Bad FEN position in file"), 0);
10263             return FALSE;
10264         }
10265     } else {
10266         (void) fgets(line, MSG_SIZ, f);
10267         (void) fgets(line, MSG_SIZ, f);
10268     
10269         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10270             (void) fgets(line, MSG_SIZ, f);
10271             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10272                 if (*p == ' ')
10273                   continue;
10274                 initial_position[i][j++] = CharToPiece(*p);
10275             }
10276         }
10277     
10278         blackPlaysFirst = FALSE;
10279         if (!feof(f)) {
10280             (void) fgets(line, MSG_SIZ, f);
10281             if (strncmp(line, "black", strlen("black"))==0)
10282               blackPlaysFirst = TRUE;
10283         }
10284     }
10285     startedFromSetupPosition = TRUE;
10286     
10287     SendToProgram("force\n", &first);
10288     CopyBoard(boards[0], initial_position);
10289     if (blackPlaysFirst) {
10290         currentMove = forwardMostMove = backwardMostMove = 1;
10291         strcpy(moveList[0], "");
10292         strcpy(parseList[0], "");
10293         CopyBoard(boards[1], initial_position);
10294         DisplayMessage("", _("Black to play"));
10295     } else {
10296         currentMove = forwardMostMove = backwardMostMove = 0;
10297         DisplayMessage("", _("White to play"));
10298     }
10299     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10300     SendBoard(&first, forwardMostMove);
10301     if (appData.debugMode) {
10302 int i, j;
10303   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10304   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10305         fprintf(debugFP, "Load Position\n");
10306     }
10307
10308     if (positionNumber > 1) {
10309         sprintf(line, "%s %d", title, positionNumber);
10310         DisplayTitle(line);
10311     } else {
10312         DisplayTitle(title);
10313     }
10314     gameMode = EditGame;
10315     ModeHighlight();
10316     ResetClocks();
10317     timeRemaining[0][1] = whiteTimeRemaining;
10318     timeRemaining[1][1] = blackTimeRemaining;
10319     DrawPosition(FALSE, boards[currentMove]);
10320    
10321     return TRUE;
10322 }
10323
10324
10325 void
10326 CopyPlayerNameIntoFileName(dest, src)
10327      char **dest, *src;
10328 {
10329     while (*src != NULLCHAR && *src != ',') {
10330         if (*src == ' ') {
10331             *(*dest)++ = '_';
10332             src++;
10333         } else {
10334             *(*dest)++ = *src++;
10335         }
10336     }
10337 }
10338
10339 char *DefaultFileName(ext)
10340      char *ext;
10341 {
10342     static char def[MSG_SIZ];
10343     char *p;
10344
10345     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10346         p = def;
10347         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10348         *p++ = '-';
10349         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10350         *p++ = '.';
10351         strcpy(p, ext);
10352     } else {
10353         def[0] = NULLCHAR;
10354     }
10355     return def;
10356 }
10357
10358 /* Save the current game to the given file */
10359 int
10360 SaveGameToFile(filename, append)
10361      char *filename;
10362      int append;
10363 {
10364     FILE *f;
10365     char buf[MSG_SIZ];
10366
10367     if (strcmp(filename, "-") == 0) {
10368         return SaveGame(stdout, 0, NULL);
10369     } else {
10370         f = fopen(filename, append ? "a" : "w");
10371         if (f == NULL) {
10372             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10373             DisplayError(buf, errno);
10374             return FALSE;
10375         } else {
10376             return SaveGame(f, 0, NULL);
10377         }
10378     }
10379 }
10380
10381 char *
10382 SavePart(str)
10383      char *str;
10384 {
10385     static char buf[MSG_SIZ];
10386     char *p;
10387     
10388     p = strchr(str, ' ');
10389     if (p == NULL) return str;
10390     strncpy(buf, str, p - str);
10391     buf[p - str] = NULLCHAR;
10392     return buf;
10393 }
10394
10395 #define PGN_MAX_LINE 75
10396
10397 #define PGN_SIDE_WHITE  0
10398 #define PGN_SIDE_BLACK  1
10399
10400 /* [AS] */
10401 static int FindFirstMoveOutOfBook( int side )
10402 {
10403     int result = -1;
10404
10405     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10406         int index = backwardMostMove;
10407         int has_book_hit = 0;
10408
10409         if( (index % 2) != side ) {
10410             index++;
10411         }
10412
10413         while( index < forwardMostMove ) {
10414             /* Check to see if engine is in book */
10415             int depth = pvInfoList[index].depth;
10416             int score = pvInfoList[index].score;
10417             int in_book = 0;
10418
10419             if( depth <= 2 ) {
10420                 in_book = 1;
10421             }
10422             else if( score == 0 && depth == 63 ) {
10423                 in_book = 1; /* Zappa */
10424             }
10425             else if( score == 2 && depth == 99 ) {
10426                 in_book = 1; /* Abrok */
10427             }
10428
10429             has_book_hit += in_book;
10430
10431             if( ! in_book ) {
10432                 result = index;
10433
10434                 break;
10435             }
10436
10437             index += 2;
10438         }
10439     }
10440
10441     return result;
10442 }
10443
10444 /* [AS] */
10445 void GetOutOfBookInfo( char * buf )
10446 {
10447     int oob[2];
10448     int i;
10449     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10450
10451     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10452     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10453
10454     *buf = '\0';
10455
10456     if( oob[0] >= 0 || oob[1] >= 0 ) {
10457         for( i=0; i<2; i++ ) {
10458             int idx = oob[i];
10459
10460             if( idx >= 0 ) {
10461                 if( i > 0 && oob[0] >= 0 ) {
10462                     strcat( buf, "   " );
10463                 }
10464
10465                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10466                 sprintf( buf+strlen(buf), "%s%.2f", 
10467                     pvInfoList[idx].score >= 0 ? "+" : "",
10468                     pvInfoList[idx].score / 100.0 );
10469             }
10470         }
10471     }
10472 }
10473
10474 /* Save game in PGN style and close the file */
10475 int
10476 SaveGamePGN(f)
10477      FILE *f;
10478 {
10479     int i, offset, linelen, newblock;
10480     time_t tm;
10481 //    char *movetext;
10482     char numtext[32];
10483     int movelen, numlen, blank;
10484     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10485
10486     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10487     
10488     tm = time((time_t *) NULL);
10489     
10490     PrintPGNTags(f, &gameInfo);
10491     
10492     if (backwardMostMove > 0 || startedFromSetupPosition) {
10493         char *fen = PositionToFEN(backwardMostMove, NULL);
10494         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10495         fprintf(f, "\n{--------------\n");
10496         PrintPosition(f, backwardMostMove);
10497         fprintf(f, "--------------}\n");
10498         free(fen);
10499     }
10500     else {
10501         /* [AS] Out of book annotation */
10502         if( appData.saveOutOfBookInfo ) {
10503             char buf[64];
10504
10505             GetOutOfBookInfo( buf );
10506
10507             if( buf[0] != '\0' ) {
10508                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10509             }
10510         }
10511
10512         fprintf(f, "\n");
10513     }
10514
10515     i = backwardMostMove;
10516     linelen = 0;
10517     newblock = TRUE;
10518
10519     while (i < forwardMostMove) {
10520         /* Print comments preceding this move */
10521         if (commentList[i] != NULL) {
10522             if (linelen > 0) fprintf(f, "\n");
10523             fprintf(f, "%s", commentList[i]);
10524             linelen = 0;
10525             newblock = TRUE;
10526         }
10527
10528         /* Format move number */
10529         if ((i % 2) == 0) {
10530             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10531         } else {
10532             if (newblock) {
10533                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10534             } else {
10535                 numtext[0] = NULLCHAR;
10536             }
10537         }
10538         numlen = strlen(numtext);
10539         newblock = FALSE;
10540
10541         /* Print move number */
10542         blank = linelen > 0 && numlen > 0;
10543         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10544             fprintf(f, "\n");
10545             linelen = 0;
10546             blank = 0;
10547         }
10548         if (blank) {
10549             fprintf(f, " ");
10550             linelen++;
10551         }
10552         fprintf(f, "%s", numtext);
10553         linelen += numlen;
10554
10555         /* Get move */
10556         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10557         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10558
10559         /* Print move */
10560         blank = linelen > 0 && movelen > 0;
10561         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10562             fprintf(f, "\n");
10563             linelen = 0;
10564             blank = 0;
10565         }
10566         if (blank) {
10567             fprintf(f, " ");
10568             linelen++;
10569         }
10570         fprintf(f, "%s", move_buffer);
10571         linelen += movelen;
10572
10573         /* [AS] Add PV info if present */
10574         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10575             /* [HGM] add time */
10576             char buf[MSG_SIZ]; int seconds;
10577
10578             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10579
10580             if( seconds <= 0) buf[0] = 0; else
10581             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10582                 seconds = (seconds + 4)/10; // round to full seconds
10583                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10584                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10585             }
10586
10587             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10588                 pvInfoList[i].score >= 0 ? "+" : "",
10589                 pvInfoList[i].score / 100.0,
10590                 pvInfoList[i].depth,
10591                 buf );
10592
10593             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10594
10595             /* Print score/depth */
10596             blank = linelen > 0 && movelen > 0;
10597             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10598                 fprintf(f, "\n");
10599                 linelen = 0;
10600                 blank = 0;
10601             }
10602             if (blank) {
10603                 fprintf(f, " ");
10604                 linelen++;
10605             }
10606             fprintf(f, "%s", move_buffer);
10607             linelen += movelen;
10608         }
10609
10610         i++;
10611     }
10612     
10613     /* Start a new line */
10614     if (linelen > 0) fprintf(f, "\n");
10615
10616     /* Print comments after last move */
10617     if (commentList[i] != NULL) {
10618         fprintf(f, "%s\n", commentList[i]);
10619     }
10620
10621     /* Print result */
10622     if (gameInfo.resultDetails != NULL &&
10623         gameInfo.resultDetails[0] != NULLCHAR) {
10624         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10625                 PGNResult(gameInfo.result));
10626     } else {
10627         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10628     }
10629
10630     fclose(f);
10631     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10632     return TRUE;
10633 }
10634
10635 /* Save game in old style and close the file */
10636 int
10637 SaveGameOldStyle(f)
10638      FILE *f;
10639 {
10640     int i, offset;
10641     time_t tm;
10642     
10643     tm = time((time_t *) NULL);
10644     
10645     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10646     PrintOpponents(f);
10647     
10648     if (backwardMostMove > 0 || startedFromSetupPosition) {
10649         fprintf(f, "\n[--------------\n");
10650         PrintPosition(f, backwardMostMove);
10651         fprintf(f, "--------------]\n");
10652     } else {
10653         fprintf(f, "\n");
10654     }
10655
10656     i = backwardMostMove;
10657     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10658
10659     while (i < forwardMostMove) {
10660         if (commentList[i] != NULL) {
10661             fprintf(f, "[%s]\n", commentList[i]);
10662         }
10663
10664         if ((i % 2) == 1) {
10665             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10666             i++;
10667         } else {
10668             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10669             i++;
10670             if (commentList[i] != NULL) {
10671                 fprintf(f, "\n");
10672                 continue;
10673             }
10674             if (i >= forwardMostMove) {
10675                 fprintf(f, "\n");
10676                 break;
10677             }
10678             fprintf(f, "%s\n", parseList[i]);
10679             i++;
10680         }
10681     }
10682     
10683     if (commentList[i] != NULL) {
10684         fprintf(f, "[%s]\n", commentList[i]);
10685     }
10686
10687     /* This isn't really the old style, but it's close enough */
10688     if (gameInfo.resultDetails != NULL &&
10689         gameInfo.resultDetails[0] != NULLCHAR) {
10690         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10691                 gameInfo.resultDetails);
10692     } else {
10693         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10694     }
10695
10696     fclose(f);
10697     return TRUE;
10698 }
10699
10700 /* Save the current game to open file f and close the file */
10701 int
10702 SaveGame(f, dummy, dummy2)
10703      FILE *f;
10704      int dummy;
10705      char *dummy2;
10706 {
10707     if (gameMode == EditPosition) EditPositionDone(TRUE);
10708     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10709     if (appData.oldSaveStyle)
10710       return SaveGameOldStyle(f);
10711     else
10712       return SaveGamePGN(f);
10713 }
10714
10715 /* Save the current position to the given file */
10716 int
10717 SavePositionToFile(filename)
10718      char *filename;
10719 {
10720     FILE *f;
10721     char buf[MSG_SIZ];
10722
10723     if (strcmp(filename, "-") == 0) {
10724         return SavePosition(stdout, 0, NULL);
10725     } else {
10726         f = fopen(filename, "a");
10727         if (f == NULL) {
10728             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10729             DisplayError(buf, errno);
10730             return FALSE;
10731         } else {
10732             SavePosition(f, 0, NULL);
10733             return TRUE;
10734         }
10735     }
10736 }
10737
10738 /* Save the current position to the given open file and close the file */
10739 int
10740 SavePosition(f, dummy, dummy2)
10741      FILE *f;
10742      int dummy;
10743      char *dummy2;
10744 {
10745     time_t tm;
10746     char *fen;
10747     
10748     if (gameMode == EditPosition) EditPositionDone(TRUE);
10749     if (appData.oldSaveStyle) {
10750         tm = time((time_t *) NULL);
10751     
10752         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10753         PrintOpponents(f);
10754         fprintf(f, "[--------------\n");
10755         PrintPosition(f, currentMove);
10756         fprintf(f, "--------------]\n");
10757     } else {
10758         fen = PositionToFEN(currentMove, NULL);
10759         fprintf(f, "%s\n", fen);
10760         free(fen);
10761     }
10762     fclose(f);
10763     return TRUE;
10764 }
10765
10766 void
10767 ReloadCmailMsgEvent(unregister)
10768      int unregister;
10769 {
10770 #if !WIN32
10771     static char *inFilename = NULL;
10772     static char *outFilename;
10773     int i;
10774     struct stat inbuf, outbuf;
10775     int status;
10776     
10777     /* Any registered moves are unregistered if unregister is set, */
10778     /* i.e. invoked by the signal handler */
10779     if (unregister) {
10780         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10781             cmailMoveRegistered[i] = FALSE;
10782             if (cmailCommentList[i] != NULL) {
10783                 free(cmailCommentList[i]);
10784                 cmailCommentList[i] = NULL;
10785             }
10786         }
10787         nCmailMovesRegistered = 0;
10788     }
10789
10790     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10791         cmailResult[i] = CMAIL_NOT_RESULT;
10792     }
10793     nCmailResults = 0;
10794
10795     if (inFilename == NULL) {
10796         /* Because the filenames are static they only get malloced once  */
10797         /* and they never get freed                                      */
10798         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10799         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10800
10801         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10802         sprintf(outFilename, "%s.out", appData.cmailGameName);
10803     }
10804     
10805     status = stat(outFilename, &outbuf);
10806     if (status < 0) {
10807         cmailMailedMove = FALSE;
10808     } else {
10809         status = stat(inFilename, &inbuf);
10810         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10811     }
10812     
10813     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10814        counts the games, notes how each one terminated, etc.
10815        
10816        It would be nice to remove this kludge and instead gather all
10817        the information while building the game list.  (And to keep it
10818        in the game list nodes instead of having a bunch of fixed-size
10819        parallel arrays.)  Note this will require getting each game's
10820        termination from the PGN tags, as the game list builder does
10821        not process the game moves.  --mann
10822        */
10823     cmailMsgLoaded = TRUE;
10824     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10825     
10826     /* Load first game in the file or popup game menu */
10827     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10828
10829 #endif /* !WIN32 */
10830     return;
10831 }
10832
10833 int
10834 RegisterMove()
10835 {
10836     FILE *f;
10837     char string[MSG_SIZ];
10838
10839     if (   cmailMailedMove
10840         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10841         return TRUE;            /* Allow free viewing  */
10842     }
10843
10844     /* Unregister move to ensure that we don't leave RegisterMove        */
10845     /* with the move registered when the conditions for registering no   */
10846     /* longer hold                                                       */
10847     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10848         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10849         nCmailMovesRegistered --;
10850
10851         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10852           {
10853               free(cmailCommentList[lastLoadGameNumber - 1]);
10854               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10855           }
10856     }
10857
10858     if (cmailOldMove == -1) {
10859         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10860         return FALSE;
10861     }
10862
10863     if (currentMove > cmailOldMove + 1) {
10864         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10865         return FALSE;
10866     }
10867
10868     if (currentMove < cmailOldMove) {
10869         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10870         return FALSE;
10871     }
10872
10873     if (forwardMostMove > currentMove) {
10874         /* Silently truncate extra moves */
10875         TruncateGame();
10876     }
10877
10878     if (   (currentMove == cmailOldMove + 1)
10879         || (   (currentMove == cmailOldMove)
10880             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10881                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10882         if (gameInfo.result != GameUnfinished) {
10883             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10884         }
10885
10886         if (commentList[currentMove] != NULL) {
10887             cmailCommentList[lastLoadGameNumber - 1]
10888               = StrSave(commentList[currentMove]);
10889         }
10890         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10891
10892         if (appData.debugMode)
10893           fprintf(debugFP, "Saving %s for game %d\n",
10894                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10895
10896         sprintf(string,
10897                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10898         
10899         f = fopen(string, "w");
10900         if (appData.oldSaveStyle) {
10901             SaveGameOldStyle(f); /* also closes the file */
10902             
10903             sprintf(string, "%s.pos.out", appData.cmailGameName);
10904             f = fopen(string, "w");
10905             SavePosition(f, 0, NULL); /* also closes the file */
10906         } else {
10907             fprintf(f, "{--------------\n");
10908             PrintPosition(f, currentMove);
10909             fprintf(f, "--------------}\n\n");
10910             
10911             SaveGame(f, 0, NULL); /* also closes the file*/
10912         }
10913         
10914         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10915         nCmailMovesRegistered ++;
10916     } else if (nCmailGames == 1) {
10917         DisplayError(_("You have not made a move yet"), 0);
10918         return FALSE;
10919     }
10920
10921     return TRUE;
10922 }
10923
10924 void
10925 MailMoveEvent()
10926 {
10927 #if !WIN32
10928     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10929     FILE *commandOutput;
10930     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10931     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10932     int nBuffers;
10933     int i;
10934     int archived;
10935     char *arcDir;
10936
10937     if (! cmailMsgLoaded) {
10938         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10939         return;
10940     }
10941
10942     if (nCmailGames == nCmailResults) {
10943         DisplayError(_("No unfinished games"), 0);
10944         return;
10945     }
10946
10947 #if CMAIL_PROHIBIT_REMAIL
10948     if (cmailMailedMove) {
10949         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);
10950         DisplayError(msg, 0);
10951         return;
10952     }
10953 #endif
10954
10955     if (! (cmailMailedMove || RegisterMove())) return;
10956     
10957     if (   cmailMailedMove
10958         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10959         sprintf(string, partCommandString,
10960                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10961         commandOutput = popen(string, "r");
10962
10963         if (commandOutput == NULL) {
10964             DisplayError(_("Failed to invoke cmail"), 0);
10965         } else {
10966             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10967                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10968             }
10969             if (nBuffers > 1) {
10970                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10971                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10972                 nBytes = MSG_SIZ - 1;
10973             } else {
10974                 (void) memcpy(msg, buffer, nBytes);
10975             }
10976             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10977
10978             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10979                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10980
10981                 archived = TRUE;
10982                 for (i = 0; i < nCmailGames; i ++) {
10983                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10984                         archived = FALSE;
10985                     }
10986                 }
10987                 if (   archived
10988                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10989                         != NULL)) {
10990                     sprintf(buffer, "%s/%s.%s.archive",
10991                             arcDir,
10992                             appData.cmailGameName,
10993                             gameInfo.date);
10994                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10995                     cmailMsgLoaded = FALSE;
10996                 }
10997             }
10998
10999             DisplayInformation(msg);
11000             pclose(commandOutput);
11001         }
11002     } else {
11003         if ((*cmailMsg) != '\0') {
11004             DisplayInformation(cmailMsg);
11005         }
11006     }
11007
11008     return;
11009 #endif /* !WIN32 */
11010 }
11011
11012 char *
11013 CmailMsg()
11014 {
11015 #if WIN32
11016     return NULL;
11017 #else
11018     int  prependComma = 0;
11019     char number[5];
11020     char string[MSG_SIZ];       /* Space for game-list */
11021     int  i;
11022     
11023     if (!cmailMsgLoaded) return "";
11024
11025     if (cmailMailedMove) {
11026         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11027     } else {
11028         /* Create a list of games left */
11029         sprintf(string, "[");
11030         for (i = 0; i < nCmailGames; i ++) {
11031             if (! (   cmailMoveRegistered[i]
11032                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11033                 if (prependComma) {
11034                     sprintf(number, ",%d", i + 1);
11035                 } else {
11036                     sprintf(number, "%d", i + 1);
11037                     prependComma = 1;
11038                 }
11039                 
11040                 strcat(string, number);
11041             }
11042         }
11043         strcat(string, "]");
11044
11045         if (nCmailMovesRegistered + nCmailResults == 0) {
11046             switch (nCmailGames) {
11047               case 1:
11048                 sprintf(cmailMsg,
11049                         _("Still need to make move for game\n"));
11050                 break;
11051                 
11052               case 2:
11053                 sprintf(cmailMsg,
11054                         _("Still need to make moves for both games\n"));
11055                 break;
11056                 
11057               default:
11058                 sprintf(cmailMsg,
11059                         _("Still need to make moves for all %d games\n"),
11060                         nCmailGames);
11061                 break;
11062             }
11063         } else {
11064             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11065               case 1:
11066                 sprintf(cmailMsg,
11067                         _("Still need to make a move for game %s\n"),
11068                         string);
11069                 break;
11070                 
11071               case 0:
11072                 if (nCmailResults == nCmailGames) {
11073                     sprintf(cmailMsg, _("No unfinished games\n"));
11074                 } else {
11075                     sprintf(cmailMsg, _("Ready to send mail\n"));
11076                 }
11077                 break;
11078                 
11079               default:
11080                 sprintf(cmailMsg,
11081                         _("Still need to make moves for games %s\n"),
11082                         string);
11083             }
11084         }
11085     }
11086     return cmailMsg;
11087 #endif /* WIN32 */
11088 }
11089
11090 void
11091 ResetGameEvent()
11092 {
11093     if (gameMode == Training)
11094       SetTrainingModeOff();
11095
11096     Reset(TRUE, TRUE);
11097     cmailMsgLoaded = FALSE;
11098     if (appData.icsActive) {
11099       SendToICS(ics_prefix);
11100       SendToICS("refresh\n");
11101     }
11102 }
11103
11104 void
11105 ExitEvent(status)
11106      int status;
11107 {
11108     exiting++;
11109     if (exiting > 2) {
11110       /* Give up on clean exit */
11111       exit(status);
11112     }
11113     if (exiting > 1) {
11114       /* Keep trying for clean exit */
11115       return;
11116     }
11117
11118     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11119
11120     if (telnetISR != NULL) {
11121       RemoveInputSource(telnetISR);
11122     }
11123     if (icsPR != NoProc) {
11124       DestroyChildProcess(icsPR, TRUE);
11125     }
11126
11127     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11128     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11129
11130     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11131     /* make sure this other one finishes before killing it!                  */
11132     if(endingGame) { int count = 0;
11133         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11134         while(endingGame && count++ < 10) DoSleep(1);
11135         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11136     }
11137
11138     /* Kill off chess programs */
11139     if (first.pr != NoProc) {
11140         ExitAnalyzeMode();
11141         
11142         DoSleep( appData.delayBeforeQuit );
11143         SendToProgram("quit\n", &first);
11144         DoSleep( appData.delayAfterQuit );
11145         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11146     }
11147     if (second.pr != NoProc) {
11148         DoSleep( appData.delayBeforeQuit );
11149         SendToProgram("quit\n", &second);
11150         DoSleep( appData.delayAfterQuit );
11151         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11152     }
11153     if (first.isr != NULL) {
11154         RemoveInputSource(first.isr);
11155     }
11156     if (second.isr != NULL) {
11157         RemoveInputSource(second.isr);
11158     }
11159
11160     ShutDownFrontEnd();
11161     exit(status);
11162 }
11163
11164 void
11165 PauseEvent()
11166 {
11167     if (appData.debugMode)
11168         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11169     if (pausing) {
11170         pausing = FALSE;
11171         ModeHighlight();
11172         if (gameMode == MachinePlaysWhite ||
11173             gameMode == MachinePlaysBlack) {
11174             StartClocks();
11175         } else {
11176             DisplayBothClocks();
11177         }
11178         if (gameMode == PlayFromGameFile) {
11179             if (appData.timeDelay >= 0) 
11180                 AutoPlayGameLoop();
11181         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11182             Reset(FALSE, TRUE);
11183             SendToICS(ics_prefix);
11184             SendToICS("refresh\n");
11185         } else if (currentMove < forwardMostMove) {
11186             ForwardInner(forwardMostMove);
11187         }
11188         pauseExamInvalid = FALSE;
11189     } else {
11190         switch (gameMode) {
11191           default:
11192             return;
11193           case IcsExamining:
11194             pauseExamForwardMostMove = forwardMostMove;
11195             pauseExamInvalid = FALSE;
11196             /* fall through */
11197           case IcsObserving:
11198           case IcsPlayingWhite:
11199           case IcsPlayingBlack:
11200             pausing = TRUE;
11201             ModeHighlight();
11202             return;
11203           case PlayFromGameFile:
11204             (void) StopLoadGameTimer();
11205             pausing = TRUE;
11206             ModeHighlight();
11207             break;
11208           case BeginningOfGame:
11209             if (appData.icsActive) return;
11210             /* else fall through */
11211           case MachinePlaysWhite:
11212           case MachinePlaysBlack:
11213           case TwoMachinesPlay:
11214             if (forwardMostMove == 0)
11215               return;           /* don't pause if no one has moved */
11216             if ((gameMode == MachinePlaysWhite &&
11217                  !WhiteOnMove(forwardMostMove)) ||
11218                 (gameMode == MachinePlaysBlack &&
11219                  WhiteOnMove(forwardMostMove))) {
11220                 StopClocks();
11221             }
11222             pausing = TRUE;
11223             ModeHighlight();
11224             break;
11225         }
11226     }
11227 }
11228
11229 void
11230 EditCommentEvent()
11231 {
11232     char title[MSG_SIZ];
11233
11234     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11235         strcpy(title, _("Edit comment"));
11236     } else {
11237         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11238                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11239                 parseList[currentMove - 1]);
11240     }
11241
11242     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11243 }
11244
11245
11246 void
11247 EditTagsEvent()
11248 {
11249     char *tags = PGNTags(&gameInfo);
11250     EditTagsPopUp(tags);
11251     free(tags);
11252 }
11253
11254 void
11255 AnalyzeModeEvent()
11256 {
11257     if (appData.noChessProgram || gameMode == AnalyzeMode)
11258       return;
11259
11260     if (gameMode != AnalyzeFile) {
11261         if (!appData.icsEngineAnalyze) {
11262                EditGameEvent();
11263                if (gameMode != EditGame) return;
11264         }
11265         ResurrectChessProgram();
11266         SendToProgram("analyze\n", &first);
11267         first.analyzing = TRUE;
11268         /*first.maybeThinking = TRUE;*/
11269         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11270         EngineOutputPopUp();
11271     }
11272     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11273     pausing = FALSE;
11274     ModeHighlight();
11275     SetGameInfo();
11276
11277     StartAnalysisClock();
11278     GetTimeMark(&lastNodeCountTime);
11279     lastNodeCount = 0;
11280 }
11281
11282 void
11283 AnalyzeFileEvent()
11284 {
11285     if (appData.noChessProgram || gameMode == AnalyzeFile)
11286       return;
11287
11288     if (gameMode != AnalyzeMode) {
11289         EditGameEvent();
11290         if (gameMode != EditGame) return;
11291         ResurrectChessProgram();
11292         SendToProgram("analyze\n", &first);
11293         first.analyzing = TRUE;
11294         /*first.maybeThinking = TRUE;*/
11295         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11296         EngineOutputPopUp();
11297     }
11298     gameMode = AnalyzeFile;
11299     pausing = FALSE;
11300     ModeHighlight();
11301     SetGameInfo();
11302
11303     StartAnalysisClock();
11304     GetTimeMark(&lastNodeCountTime);
11305     lastNodeCount = 0;
11306 }
11307
11308 void
11309 MachineWhiteEvent()
11310 {
11311     char buf[MSG_SIZ];
11312     char *bookHit = NULL;
11313
11314     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11315       return;
11316
11317
11318     if (gameMode == PlayFromGameFile || 
11319         gameMode == TwoMachinesPlay  || 
11320         gameMode == Training         || 
11321         gameMode == AnalyzeMode      || 
11322         gameMode == EndOfGame)
11323         EditGameEvent();
11324
11325     if (gameMode == EditPosition) 
11326         EditPositionDone(TRUE);
11327
11328     if (!WhiteOnMove(currentMove)) {
11329         DisplayError(_("It is not White's turn"), 0);
11330         return;
11331     }
11332   
11333     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11334       ExitAnalyzeMode();
11335
11336     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11337         gameMode == AnalyzeFile)
11338         TruncateGame();
11339
11340     ResurrectChessProgram();    /* in case it isn't running */
11341     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11342         gameMode = MachinePlaysWhite;
11343         ResetClocks();
11344     } else
11345     gameMode = MachinePlaysWhite;
11346     pausing = FALSE;
11347     ModeHighlight();
11348     SetGameInfo();
11349     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11350     DisplayTitle(buf);
11351     if (first.sendName) {
11352       sprintf(buf, "name %s\n", gameInfo.black);
11353       SendToProgram(buf, &first);
11354     }
11355     if (first.sendTime) {
11356       if (first.useColors) {
11357         SendToProgram("black\n", &first); /*gnu kludge*/
11358       }
11359       SendTimeRemaining(&first, TRUE);
11360     }
11361     if (first.useColors) {
11362       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11363     }
11364     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11365     SetMachineThinkingEnables();
11366     first.maybeThinking = TRUE;
11367     StartClocks();
11368     firstMove = FALSE;
11369
11370     if (appData.autoFlipView && !flipView) {
11371       flipView = !flipView;
11372       DrawPosition(FALSE, NULL);
11373       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11374     }
11375
11376     if(bookHit) { // [HGM] book: simulate book reply
11377         static char bookMove[MSG_SIZ]; // a bit generous?
11378
11379         programStats.nodes = programStats.depth = programStats.time = 
11380         programStats.score = programStats.got_only_move = 0;
11381         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11382
11383         strcpy(bookMove, "move ");
11384         strcat(bookMove, bookHit);
11385         HandleMachineMove(bookMove, &first);
11386     }
11387 }
11388
11389 void
11390 MachineBlackEvent()
11391 {
11392     char buf[MSG_SIZ];
11393    char *bookHit = NULL;
11394
11395     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11396         return;
11397
11398
11399     if (gameMode == PlayFromGameFile || 
11400         gameMode == TwoMachinesPlay  || 
11401         gameMode == Training         || 
11402         gameMode == AnalyzeMode      || 
11403         gameMode == EndOfGame)
11404         EditGameEvent();
11405
11406     if (gameMode == EditPosition) 
11407         EditPositionDone(TRUE);
11408
11409     if (WhiteOnMove(currentMove)) {
11410         DisplayError(_("It is not Black's turn"), 0);
11411         return;
11412     }
11413     
11414     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11415       ExitAnalyzeMode();
11416
11417     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11418         gameMode == AnalyzeFile)
11419         TruncateGame();
11420
11421     ResurrectChessProgram();    /* in case it isn't running */
11422     gameMode = MachinePlaysBlack;
11423     pausing = FALSE;
11424     ModeHighlight();
11425     SetGameInfo();
11426     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11427     DisplayTitle(buf);
11428     if (first.sendName) {
11429       sprintf(buf, "name %s\n", gameInfo.white);
11430       SendToProgram(buf, &first);
11431     }
11432     if (first.sendTime) {
11433       if (first.useColors) {
11434         SendToProgram("white\n", &first); /*gnu kludge*/
11435       }
11436       SendTimeRemaining(&first, FALSE);
11437     }
11438     if (first.useColors) {
11439       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11440     }
11441     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11442     SetMachineThinkingEnables();
11443     first.maybeThinking = TRUE;
11444     StartClocks();
11445
11446     if (appData.autoFlipView && flipView) {
11447       flipView = !flipView;
11448       DrawPosition(FALSE, NULL);
11449       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11450     }
11451     if(bookHit) { // [HGM] book: simulate book reply
11452         static char bookMove[MSG_SIZ]; // a bit generous?
11453
11454         programStats.nodes = programStats.depth = programStats.time = 
11455         programStats.score = programStats.got_only_move = 0;
11456         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11457
11458         strcpy(bookMove, "move ");
11459         strcat(bookMove, bookHit);
11460         HandleMachineMove(bookMove, &first);
11461     }
11462 }
11463
11464
11465 void
11466 DisplayTwoMachinesTitle()
11467 {
11468     char buf[MSG_SIZ];
11469     if (appData.matchGames > 0) {
11470         if (first.twoMachinesColor[0] == 'w') {
11471             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11472                     gameInfo.white, gameInfo.black,
11473                     first.matchWins, second.matchWins,
11474                     matchGame - 1 - (first.matchWins + second.matchWins));
11475         } else {
11476             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11477                     gameInfo.white, gameInfo.black,
11478                     second.matchWins, first.matchWins,
11479                     matchGame - 1 - (first.matchWins + second.matchWins));
11480         }
11481     } else {
11482         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11483     }
11484     DisplayTitle(buf);
11485 }
11486
11487 void
11488 TwoMachinesEvent P((void))
11489 {
11490     int i;
11491     char buf[MSG_SIZ];
11492     ChessProgramState *onmove;
11493     char *bookHit = NULL;
11494     
11495     if (appData.noChessProgram) return;
11496
11497     switch (gameMode) {
11498       case TwoMachinesPlay:
11499         return;
11500       case MachinePlaysWhite:
11501       case MachinePlaysBlack:
11502         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11503             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11504             return;
11505         }
11506         /* fall through */
11507       case BeginningOfGame:
11508       case PlayFromGameFile:
11509       case EndOfGame:
11510         EditGameEvent();
11511         if (gameMode != EditGame) return;
11512         break;
11513       case EditPosition:
11514         EditPositionDone(TRUE);
11515         break;
11516       case AnalyzeMode:
11517       case AnalyzeFile:
11518         ExitAnalyzeMode();
11519         break;
11520       case EditGame:
11521       default:
11522         break;
11523     }
11524
11525 //    forwardMostMove = currentMove;
11526     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11527     ResurrectChessProgram();    /* in case first program isn't running */
11528
11529     if (second.pr == NULL) {
11530         StartChessProgram(&second);
11531         if (second.protocolVersion == 1) {
11532           TwoMachinesEventIfReady();
11533         } else {
11534           /* kludge: allow timeout for initial "feature" command */
11535           FreezeUI();
11536           DisplayMessage("", _("Starting second chess program"));
11537           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11538         }
11539         return;
11540     }
11541     DisplayMessage("", "");
11542     InitChessProgram(&second, FALSE);
11543     SendToProgram("force\n", &second);
11544     if (startedFromSetupPosition) {
11545         SendBoard(&second, backwardMostMove);
11546     if (appData.debugMode) {
11547         fprintf(debugFP, "Two Machines\n");
11548     }
11549     }
11550     for (i = backwardMostMove; i < forwardMostMove; i++) {
11551         SendMoveToProgram(i, &second);
11552     }
11553
11554     gameMode = TwoMachinesPlay;
11555     pausing = FALSE;
11556     ModeHighlight();
11557     SetGameInfo();
11558     DisplayTwoMachinesTitle();
11559     firstMove = TRUE;
11560     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11561         onmove = &first;
11562     } else {
11563         onmove = &second;
11564     }
11565
11566     SendToProgram(first.computerString, &first);
11567     if (first.sendName) {
11568       sprintf(buf, "name %s\n", second.tidy);
11569       SendToProgram(buf, &first);
11570     }
11571     SendToProgram(second.computerString, &second);
11572     if (second.sendName) {
11573       sprintf(buf, "name %s\n", first.tidy);
11574       SendToProgram(buf, &second);
11575     }
11576
11577     ResetClocks();
11578     if (!first.sendTime || !second.sendTime) {
11579         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11580         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11581     }
11582     if (onmove->sendTime) {
11583       if (onmove->useColors) {
11584         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11585       }
11586       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11587     }
11588     if (onmove->useColors) {
11589       SendToProgram(onmove->twoMachinesColor, onmove);
11590     }
11591     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11592 //    SendToProgram("go\n", onmove);
11593     onmove->maybeThinking = TRUE;
11594     SetMachineThinkingEnables();
11595
11596     StartClocks();
11597
11598     if(bookHit) { // [HGM] book: simulate book reply
11599         static char bookMove[MSG_SIZ]; // a bit generous?
11600
11601         programStats.nodes = programStats.depth = programStats.time = 
11602         programStats.score = programStats.got_only_move = 0;
11603         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11604
11605         strcpy(bookMove, "move ");
11606         strcat(bookMove, bookHit);
11607         savedMessage = bookMove; // args for deferred call
11608         savedState = onmove;
11609         ScheduleDelayedEvent(DeferredBookMove, 1);
11610     }
11611 }
11612
11613 void
11614 TrainingEvent()
11615 {
11616     if (gameMode == Training) {
11617       SetTrainingModeOff();
11618       gameMode = PlayFromGameFile;
11619       DisplayMessage("", _("Training mode off"));
11620     } else {
11621       gameMode = Training;
11622       animateTraining = appData.animate;
11623
11624       /* make sure we are not already at the end of the game */
11625       if (currentMove < forwardMostMove) {
11626         SetTrainingModeOn();
11627         DisplayMessage("", _("Training mode on"));
11628       } else {
11629         gameMode = PlayFromGameFile;
11630         DisplayError(_("Already at end of game"), 0);
11631       }
11632     }
11633     ModeHighlight();
11634 }
11635
11636 void
11637 IcsClientEvent()
11638 {
11639     if (!appData.icsActive) return;
11640     switch (gameMode) {
11641       case IcsPlayingWhite:
11642       case IcsPlayingBlack:
11643       case IcsObserving:
11644       case IcsIdle:
11645       case BeginningOfGame:
11646       case IcsExamining:
11647         return;
11648
11649       case EditGame:
11650         break;
11651
11652       case EditPosition:
11653         EditPositionDone(TRUE);
11654         break;
11655
11656       case AnalyzeMode:
11657       case AnalyzeFile:
11658         ExitAnalyzeMode();
11659         break;
11660         
11661       default:
11662         EditGameEvent();
11663         break;
11664     }
11665
11666     gameMode = IcsIdle;
11667     ModeHighlight();
11668     return;
11669 }
11670
11671
11672 void
11673 EditGameEvent()
11674 {
11675     int i;
11676
11677     switch (gameMode) {
11678       case Training:
11679         SetTrainingModeOff();
11680         break;
11681       case MachinePlaysWhite:
11682       case MachinePlaysBlack:
11683       case BeginningOfGame:
11684         SendToProgram("force\n", &first);
11685         SetUserThinkingEnables();
11686         break;
11687       case PlayFromGameFile:
11688         (void) StopLoadGameTimer();
11689         if (gameFileFP != NULL) {
11690             gameFileFP = NULL;
11691         }
11692         break;
11693       case EditPosition:
11694         EditPositionDone(TRUE);
11695         break;
11696       case AnalyzeMode:
11697       case AnalyzeFile:
11698         ExitAnalyzeMode();
11699         SendToProgram("force\n", &first);
11700         break;
11701       case TwoMachinesPlay:
11702         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11703         ResurrectChessProgram();
11704         SetUserThinkingEnables();
11705         break;
11706       case EndOfGame:
11707         ResurrectChessProgram();
11708         break;
11709       case IcsPlayingBlack:
11710       case IcsPlayingWhite:
11711         DisplayError(_("Warning: You are still playing a game"), 0);
11712         break;
11713       case IcsObserving:
11714         DisplayError(_("Warning: You are still observing a game"), 0);
11715         break;
11716       case IcsExamining:
11717         DisplayError(_("Warning: You are still examining a game"), 0);
11718         break;
11719       case IcsIdle:
11720         break;
11721       case EditGame:
11722       default:
11723         return;
11724     }
11725     
11726     pausing = FALSE;
11727     StopClocks();
11728     first.offeredDraw = second.offeredDraw = 0;
11729
11730     if (gameMode == PlayFromGameFile) {
11731         whiteTimeRemaining = timeRemaining[0][currentMove];
11732         blackTimeRemaining = timeRemaining[1][currentMove];
11733         DisplayTitle("");
11734     }
11735
11736     if (gameMode == MachinePlaysWhite ||
11737         gameMode == MachinePlaysBlack ||
11738         gameMode == TwoMachinesPlay ||
11739         gameMode == EndOfGame) {
11740         i = forwardMostMove;
11741         while (i > currentMove) {
11742             SendToProgram("undo\n", &first);
11743             i--;
11744         }
11745         whiteTimeRemaining = timeRemaining[0][currentMove];
11746         blackTimeRemaining = timeRemaining[1][currentMove];
11747         DisplayBothClocks();
11748         if (whiteFlag || blackFlag) {
11749             whiteFlag = blackFlag = 0;
11750         }
11751         DisplayTitle("");
11752     }           
11753     
11754     gameMode = EditGame;
11755     ModeHighlight();
11756     SetGameInfo();
11757 }
11758
11759
11760 void
11761 EditPositionEvent()
11762 {
11763     if (gameMode == EditPosition) {
11764         EditGameEvent();
11765         return;
11766     }
11767     
11768     EditGameEvent();
11769     if (gameMode != EditGame) return;
11770     
11771     gameMode = EditPosition;
11772     ModeHighlight();
11773     SetGameInfo();
11774     if (currentMove > 0)
11775       CopyBoard(boards[0], boards[currentMove]);
11776     
11777     blackPlaysFirst = !WhiteOnMove(currentMove);
11778     ResetClocks();
11779     currentMove = forwardMostMove = backwardMostMove = 0;
11780     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11781     DisplayMove(-1);
11782 }
11783
11784 void
11785 ExitAnalyzeMode()
11786 {
11787     /* [DM] icsEngineAnalyze - possible call from other functions */
11788     if (appData.icsEngineAnalyze) {
11789         appData.icsEngineAnalyze = FALSE;
11790
11791         DisplayMessage("",_("Close ICS engine analyze..."));
11792     }
11793     if (first.analysisSupport && first.analyzing) {
11794       SendToProgram("exit\n", &first);
11795       first.analyzing = FALSE;
11796     }
11797     thinkOutput[0] = NULLCHAR;
11798 }
11799
11800 void
11801 EditPositionDone(Boolean fakeRights)
11802 {
11803     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11804
11805     startedFromSetupPosition = TRUE;
11806     InitChessProgram(&first, FALSE);
11807     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11808       boards[0][EP_STATUS] = EP_NONE;
11809       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11810     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11811         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11812         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11813       } else boards[0][CASTLING][2] = NoRights;
11814     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11815         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11816         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11817       } else boards[0][CASTLING][5] = NoRights;
11818     }
11819     SendToProgram("force\n", &first);
11820     if (blackPlaysFirst) {
11821         strcpy(moveList[0], "");
11822         strcpy(parseList[0], "");
11823         currentMove = forwardMostMove = backwardMostMove = 1;
11824         CopyBoard(boards[1], boards[0]);
11825     } else {
11826         currentMove = forwardMostMove = backwardMostMove = 0;
11827     }
11828     SendBoard(&first, forwardMostMove);
11829     if (appData.debugMode) {
11830         fprintf(debugFP, "EditPosDone\n");
11831     }
11832     DisplayTitle("");
11833     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11834     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11835     gameMode = EditGame;
11836     ModeHighlight();
11837     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11838     ClearHighlights(); /* [AS] */
11839 }
11840
11841 /* Pause for `ms' milliseconds */
11842 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11843 void
11844 TimeDelay(ms)
11845      long ms;
11846 {
11847     TimeMark m1, m2;
11848
11849     GetTimeMark(&m1);
11850     do {
11851         GetTimeMark(&m2);
11852     } while (SubtractTimeMarks(&m2, &m1) < ms);
11853 }
11854
11855 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11856 void
11857 SendMultiLineToICS(buf)
11858      char *buf;
11859 {
11860     char temp[MSG_SIZ+1], *p;
11861     int len;
11862
11863     len = strlen(buf);
11864     if (len > MSG_SIZ)
11865       len = MSG_SIZ;
11866   
11867     strncpy(temp, buf, len);
11868     temp[len] = 0;
11869
11870     p = temp;
11871     while (*p) {
11872         if (*p == '\n' || *p == '\r')
11873           *p = ' ';
11874         ++p;
11875     }
11876
11877     strcat(temp, "\n");
11878     SendToICS(temp);
11879     SendToPlayer(temp, strlen(temp));
11880 }
11881
11882 void
11883 SetWhiteToPlayEvent()
11884 {
11885     if (gameMode == EditPosition) {
11886         blackPlaysFirst = FALSE;
11887         DisplayBothClocks();    /* works because currentMove is 0 */
11888     } else if (gameMode == IcsExamining) {
11889         SendToICS(ics_prefix);
11890         SendToICS("tomove white\n");
11891     }
11892 }
11893
11894 void
11895 SetBlackToPlayEvent()
11896 {
11897     if (gameMode == EditPosition) {
11898         blackPlaysFirst = TRUE;
11899         currentMove = 1;        /* kludge */
11900         DisplayBothClocks();
11901         currentMove = 0;
11902     } else if (gameMode == IcsExamining) {
11903         SendToICS(ics_prefix);
11904         SendToICS("tomove black\n");
11905     }
11906 }
11907
11908 void
11909 EditPositionMenuEvent(selection, x, y)
11910      ChessSquare selection;
11911      int x, y;
11912 {
11913     char buf[MSG_SIZ];
11914     ChessSquare piece = boards[0][y][x];
11915
11916     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11917
11918     switch (selection) {
11919       case ClearBoard:
11920         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11921             SendToICS(ics_prefix);
11922             SendToICS("bsetup clear\n");
11923         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11924             SendToICS(ics_prefix);
11925             SendToICS("clearboard\n");
11926         } else {
11927             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11928                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11929                 for (y = 0; y < BOARD_HEIGHT; y++) {
11930                     if (gameMode == IcsExamining) {
11931                         if (boards[currentMove][y][x] != EmptySquare) {
11932                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11933                                     AAA + x, ONE + y);
11934                             SendToICS(buf);
11935                         }
11936                     } else {
11937                         boards[0][y][x] = p;
11938                     }
11939                 }
11940             }
11941         }
11942         if (gameMode == EditPosition) {
11943             DrawPosition(FALSE, boards[0]);
11944         }
11945         break;
11946
11947       case WhitePlay:
11948         SetWhiteToPlayEvent();
11949         break;
11950
11951       case BlackPlay:
11952         SetBlackToPlayEvent();
11953         break;
11954
11955       case EmptySquare:
11956         if (gameMode == IcsExamining) {
11957             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11958             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11959             SendToICS(buf);
11960         } else {
11961             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11962                 if(x == BOARD_LEFT-2) {
11963                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11964                     boards[0][y][1] = 0;
11965                 } else
11966                 if(x == BOARD_RGHT+1) {
11967                     if(y >= gameInfo.holdingsSize) break;
11968                     boards[0][y][BOARD_WIDTH-2] = 0;
11969                 } else break;
11970             }
11971             boards[0][y][x] = EmptySquare;
11972             DrawPosition(FALSE, boards[0]);
11973         }
11974         break;
11975
11976       case PromotePiece:
11977         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11978            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11979             selection = (ChessSquare) (PROMOTED piece);
11980         } else if(piece == EmptySquare) selection = WhiteSilver;
11981         else selection = (ChessSquare)((int)piece - 1);
11982         goto defaultlabel;
11983
11984       case DemotePiece:
11985         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11986            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11987             selection = (ChessSquare) (DEMOTED piece);
11988         } else if(piece == EmptySquare) selection = BlackSilver;
11989         else selection = (ChessSquare)((int)piece + 1);       
11990         goto defaultlabel;
11991
11992       case WhiteQueen:
11993       case BlackQueen:
11994         if(gameInfo.variant == VariantShatranj ||
11995            gameInfo.variant == VariantXiangqi  ||
11996            gameInfo.variant == VariantCourier  ||
11997            gameInfo.variant == VariantMakruk     )
11998             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11999         goto defaultlabel;
12000
12001       case WhiteKing:
12002       case BlackKing:
12003         if(gameInfo.variant == VariantXiangqi)
12004             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12005         if(gameInfo.variant == VariantKnightmate)
12006             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12007       default:
12008         defaultlabel:
12009         if (gameMode == IcsExamining) {
12010             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12011             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12012                     PieceToChar(selection), AAA + x, ONE + y);
12013             SendToICS(buf);
12014         } else {
12015             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12016                 int n;
12017                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12018                     n = PieceToNumber(selection - BlackPawn);
12019                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12020                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12021                     boards[0][BOARD_HEIGHT-1-n][1]++;
12022                 } else
12023                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12024                     n = PieceToNumber(selection);
12025                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12026                     boards[0][n][BOARD_WIDTH-1] = selection;
12027                     boards[0][n][BOARD_WIDTH-2]++;
12028                 }
12029             } else
12030             boards[0][y][x] = selection;
12031             DrawPosition(TRUE, boards[0]);
12032         }
12033         break;
12034     }
12035 }
12036
12037
12038 void
12039 DropMenuEvent(selection, x, y)
12040      ChessSquare selection;
12041      int x, y;
12042 {
12043     ChessMove moveType;
12044
12045     switch (gameMode) {
12046       case IcsPlayingWhite:
12047       case MachinePlaysBlack:
12048         if (!WhiteOnMove(currentMove)) {
12049             DisplayMoveError(_("It is Black's turn"));
12050             return;
12051         }
12052         moveType = WhiteDrop;
12053         break;
12054       case IcsPlayingBlack:
12055       case MachinePlaysWhite:
12056         if (WhiteOnMove(currentMove)) {
12057             DisplayMoveError(_("It is White's turn"));
12058             return;
12059         }
12060         moveType = BlackDrop;
12061         break;
12062       case EditGame:
12063         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12064         break;
12065       default:
12066         return;
12067     }
12068
12069     if (moveType == BlackDrop && selection < BlackPawn) {
12070       selection = (ChessSquare) ((int) selection
12071                                  + (int) BlackPawn - (int) WhitePawn);
12072     }
12073     if (boards[currentMove][y][x] != EmptySquare) {
12074         DisplayMoveError(_("That square is occupied"));
12075         return;
12076     }
12077
12078     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12079 }
12080
12081 void
12082 AcceptEvent()
12083 {
12084     /* Accept a pending offer of any kind from opponent */
12085     
12086     if (appData.icsActive) {
12087         SendToICS(ics_prefix);
12088         SendToICS("accept\n");
12089     } else if (cmailMsgLoaded) {
12090         if (currentMove == cmailOldMove &&
12091             commentList[cmailOldMove] != NULL &&
12092             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12093                    "Black offers a draw" : "White offers a draw")) {
12094             TruncateGame();
12095             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12096             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12097         } else {
12098             DisplayError(_("There is no pending offer on this move"), 0);
12099             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12100         }
12101     } else {
12102         /* Not used for offers from chess program */
12103     }
12104 }
12105
12106 void
12107 DeclineEvent()
12108 {
12109     /* Decline a pending offer of any kind from opponent */
12110     
12111     if (appData.icsActive) {
12112         SendToICS(ics_prefix);
12113         SendToICS("decline\n");
12114     } else if (cmailMsgLoaded) {
12115         if (currentMove == cmailOldMove &&
12116             commentList[cmailOldMove] != NULL &&
12117             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12118                    "Black offers a draw" : "White offers a draw")) {
12119 #ifdef NOTDEF
12120             AppendComment(cmailOldMove, "Draw declined", TRUE);
12121             DisplayComment(cmailOldMove - 1, "Draw declined");
12122 #endif /*NOTDEF*/
12123         } else {
12124             DisplayError(_("There is no pending offer on this move"), 0);
12125         }
12126     } else {
12127         /* Not used for offers from chess program */
12128     }
12129 }
12130
12131 void
12132 RematchEvent()
12133 {
12134     /* Issue ICS rematch command */
12135     if (appData.icsActive) {
12136         SendToICS(ics_prefix);
12137         SendToICS("rematch\n");
12138     }
12139 }
12140
12141 void
12142 CallFlagEvent()
12143 {
12144     /* Call your opponent's flag (claim a win on time) */
12145     if (appData.icsActive) {
12146         SendToICS(ics_prefix);
12147         SendToICS("flag\n");
12148     } else {
12149         switch (gameMode) {
12150           default:
12151             return;
12152           case MachinePlaysWhite:
12153             if (whiteFlag) {
12154                 if (blackFlag)
12155                   GameEnds(GameIsDrawn, "Both players ran out of time",
12156                            GE_PLAYER);
12157                 else
12158                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12159             } else {
12160                 DisplayError(_("Your opponent is not out of time"), 0);
12161             }
12162             break;
12163           case MachinePlaysBlack:
12164             if (blackFlag) {
12165                 if (whiteFlag)
12166                   GameEnds(GameIsDrawn, "Both players ran out of time",
12167                            GE_PLAYER);
12168                 else
12169                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12170             } else {
12171                 DisplayError(_("Your opponent is not out of time"), 0);
12172             }
12173             break;
12174         }
12175     }
12176 }
12177
12178 void
12179 DrawEvent()
12180 {
12181     /* Offer draw or accept pending draw offer from opponent */
12182     
12183     if (appData.icsActive) {
12184         /* Note: tournament rules require draw offers to be
12185            made after you make your move but before you punch
12186            your clock.  Currently ICS doesn't let you do that;
12187            instead, you immediately punch your clock after making
12188            a move, but you can offer a draw at any time. */
12189         
12190         SendToICS(ics_prefix);
12191         SendToICS("draw\n");
12192         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12193     } else if (cmailMsgLoaded) {
12194         if (currentMove == cmailOldMove &&
12195             commentList[cmailOldMove] != NULL &&
12196             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12197                    "Black offers a draw" : "White offers a draw")) {
12198             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12199             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12200         } else if (currentMove == cmailOldMove + 1) {
12201             char *offer = WhiteOnMove(cmailOldMove) ?
12202               "White offers a draw" : "Black offers a draw";
12203             AppendComment(currentMove, offer, TRUE);
12204             DisplayComment(currentMove - 1, offer);
12205             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12206         } else {
12207             DisplayError(_("You must make your move before offering a draw"), 0);
12208             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12209         }
12210     } else if (first.offeredDraw) {
12211         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12212     } else {
12213         if (first.sendDrawOffers) {
12214             SendToProgram("draw\n", &first);
12215             userOfferedDraw = TRUE;
12216         }
12217     }
12218 }
12219
12220 void
12221 AdjournEvent()
12222 {
12223     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12224     
12225     if (appData.icsActive) {
12226         SendToICS(ics_prefix);
12227         SendToICS("adjourn\n");
12228     } else {
12229         /* Currently GNU Chess doesn't offer or accept Adjourns */
12230     }
12231 }
12232
12233
12234 void
12235 AbortEvent()
12236 {
12237     /* Offer Abort or accept pending Abort offer from opponent */
12238     
12239     if (appData.icsActive) {
12240         SendToICS(ics_prefix);
12241         SendToICS("abort\n");
12242     } else {
12243         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12244     }
12245 }
12246
12247 void
12248 ResignEvent()
12249 {
12250     /* Resign.  You can do this even if it's not your turn. */
12251     
12252     if (appData.icsActive) {
12253         SendToICS(ics_prefix);
12254         SendToICS("resign\n");
12255     } else {
12256         switch (gameMode) {
12257           case MachinePlaysWhite:
12258             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12259             break;
12260           case MachinePlaysBlack:
12261             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12262             break;
12263           case EditGame:
12264             if (cmailMsgLoaded) {
12265                 TruncateGame();
12266                 if (WhiteOnMove(cmailOldMove)) {
12267                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12268                 } else {
12269                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12270                 }
12271                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12272             }
12273             break;
12274           default:
12275             break;
12276         }
12277     }
12278 }
12279
12280
12281 void
12282 StopObservingEvent()
12283 {
12284     /* Stop observing current games */
12285     SendToICS(ics_prefix);
12286     SendToICS("unobserve\n");
12287 }
12288
12289 void
12290 StopExaminingEvent()
12291 {
12292     /* Stop observing current game */
12293     SendToICS(ics_prefix);
12294     SendToICS("unexamine\n");
12295 }
12296
12297 void
12298 ForwardInner(target)
12299      int target;
12300 {
12301     int limit;
12302
12303     if (appData.debugMode)
12304         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12305                 target, currentMove, forwardMostMove);
12306
12307     if (gameMode == EditPosition)
12308       return;
12309
12310     if (gameMode == PlayFromGameFile && !pausing)
12311       PauseEvent();
12312     
12313     if (gameMode == IcsExamining && pausing)
12314       limit = pauseExamForwardMostMove;
12315     else
12316       limit = forwardMostMove;
12317     
12318     if (target > limit) target = limit;
12319
12320     if (target > 0 && moveList[target - 1][0]) {
12321         int fromX, fromY, toX, toY;
12322         toX = moveList[target - 1][2] - AAA;
12323         toY = moveList[target - 1][3] - ONE;
12324         if (moveList[target - 1][1] == '@') {
12325             if (appData.highlightLastMove) {
12326                 SetHighlights(-1, -1, toX, toY);
12327             }
12328         } else {
12329             fromX = moveList[target - 1][0] - AAA;
12330             fromY = moveList[target - 1][1] - ONE;
12331             if (target == currentMove + 1) {
12332                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12333             }
12334             if (appData.highlightLastMove) {
12335                 SetHighlights(fromX, fromY, toX, toY);
12336             }
12337         }
12338     }
12339     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12340         gameMode == Training || gameMode == PlayFromGameFile || 
12341         gameMode == AnalyzeFile) {
12342         while (currentMove < target) {
12343             SendMoveToProgram(currentMove++, &first);
12344         }
12345     } else {
12346         currentMove = target;
12347     }
12348     
12349     if (gameMode == EditGame || gameMode == EndOfGame) {
12350         whiteTimeRemaining = timeRemaining[0][currentMove];
12351         blackTimeRemaining = timeRemaining[1][currentMove];
12352     }
12353     DisplayBothClocks();
12354     DisplayMove(currentMove - 1);
12355     DrawPosition(FALSE, boards[currentMove]);
12356     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12357     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12358         DisplayComment(currentMove - 1, commentList[currentMove]);
12359     }
12360 }
12361
12362
12363 void
12364 ForwardEvent()
12365 {
12366     if (gameMode == IcsExamining && !pausing) {
12367         SendToICS(ics_prefix);
12368         SendToICS("forward\n");
12369     } else {
12370         ForwardInner(currentMove + 1);
12371     }
12372 }
12373
12374 void
12375 ToEndEvent()
12376 {
12377     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12378         /* to optimze, we temporarily turn off analysis mode while we feed
12379          * the remaining moves to the engine. Otherwise we get analysis output
12380          * after each move.
12381          */ 
12382         if (first.analysisSupport) {
12383           SendToProgram("exit\nforce\n", &first);
12384           first.analyzing = FALSE;
12385         }
12386     }
12387         
12388     if (gameMode == IcsExamining && !pausing) {
12389         SendToICS(ics_prefix);
12390         SendToICS("forward 999999\n");
12391     } else {
12392         ForwardInner(forwardMostMove);
12393     }
12394
12395     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12396         /* we have fed all the moves, so reactivate analysis mode */
12397         SendToProgram("analyze\n", &first);
12398         first.analyzing = TRUE;
12399         /*first.maybeThinking = TRUE;*/
12400         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12401     }
12402 }
12403
12404 void
12405 BackwardInner(target)
12406      int target;
12407 {
12408     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12409
12410     if (appData.debugMode)
12411         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12412                 target, currentMove, forwardMostMove);
12413
12414     if (gameMode == EditPosition) return;
12415     if (currentMove <= backwardMostMove) {
12416         ClearHighlights();
12417         DrawPosition(full_redraw, boards[currentMove]);
12418         return;
12419     }
12420     if (gameMode == PlayFromGameFile && !pausing)
12421       PauseEvent();
12422     
12423     if (moveList[target][0]) {
12424         int fromX, fromY, toX, toY;
12425         toX = moveList[target][2] - AAA;
12426         toY = moveList[target][3] - ONE;
12427         if (moveList[target][1] == '@') {
12428             if (appData.highlightLastMove) {
12429                 SetHighlights(-1, -1, toX, toY);
12430             }
12431         } else {
12432             fromX = moveList[target][0] - AAA;
12433             fromY = moveList[target][1] - ONE;
12434             if (target == currentMove - 1) {
12435                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12436             }
12437             if (appData.highlightLastMove) {
12438                 SetHighlights(fromX, fromY, toX, toY);
12439             }
12440         }
12441     }
12442     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12443         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12444         while (currentMove > target) {
12445             SendToProgram("undo\n", &first);
12446             currentMove--;
12447         }
12448     } else {
12449         currentMove = target;
12450     }
12451     
12452     if (gameMode == EditGame || gameMode == EndOfGame) {
12453         whiteTimeRemaining = timeRemaining[0][currentMove];
12454         blackTimeRemaining = timeRemaining[1][currentMove];
12455     }
12456     DisplayBothClocks();
12457     DisplayMove(currentMove - 1);
12458     DrawPosition(full_redraw, boards[currentMove]);
12459     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12460     // [HGM] PV info: routine tests if comment empty
12461     DisplayComment(currentMove - 1, commentList[currentMove]);
12462 }
12463
12464 void
12465 BackwardEvent()
12466 {
12467     if (gameMode == IcsExamining && !pausing) {
12468         SendToICS(ics_prefix);
12469         SendToICS("backward\n");
12470     } else {
12471         BackwardInner(currentMove - 1);
12472     }
12473 }
12474
12475 void
12476 ToStartEvent()
12477 {
12478     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12479         /* to optimize, we temporarily turn off analysis mode while we undo
12480          * all the moves. Otherwise we get analysis output after each undo.
12481          */ 
12482         if (first.analysisSupport) {
12483           SendToProgram("exit\nforce\n", &first);
12484           first.analyzing = FALSE;
12485         }
12486     }
12487
12488     if (gameMode == IcsExamining && !pausing) {
12489         SendToICS(ics_prefix);
12490         SendToICS("backward 999999\n");
12491     } else {
12492         BackwardInner(backwardMostMove);
12493     }
12494
12495     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12496         /* we have fed all the moves, so reactivate analysis mode */
12497         SendToProgram("analyze\n", &first);
12498         first.analyzing = TRUE;
12499         /*first.maybeThinking = TRUE;*/
12500         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12501     }
12502 }
12503
12504 void
12505 ToNrEvent(int to)
12506 {
12507   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12508   if (to >= forwardMostMove) to = forwardMostMove;
12509   if (to <= backwardMostMove) to = backwardMostMove;
12510   if (to < currentMove) {
12511     BackwardInner(to);
12512   } else {
12513     ForwardInner(to);
12514   }
12515 }
12516
12517 void
12518 RevertEvent()
12519 {
12520     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12521         return;
12522     }
12523     if (gameMode != IcsExamining) {
12524         DisplayError(_("You are not examining a game"), 0);
12525         return;
12526     }
12527     if (pausing) {
12528         DisplayError(_("You can't revert while pausing"), 0);
12529         return;
12530     }
12531     SendToICS(ics_prefix);
12532     SendToICS("revert\n");
12533 }
12534
12535 void
12536 RetractMoveEvent()
12537 {
12538     switch (gameMode) {
12539       case MachinePlaysWhite:
12540       case MachinePlaysBlack:
12541         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12542             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12543             return;
12544         }
12545         if (forwardMostMove < 2) return;
12546         currentMove = forwardMostMove = forwardMostMove - 2;
12547         whiteTimeRemaining = timeRemaining[0][currentMove];
12548         blackTimeRemaining = timeRemaining[1][currentMove];
12549         DisplayBothClocks();
12550         DisplayMove(currentMove - 1);
12551         ClearHighlights();/*!! could figure this out*/
12552         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12553         SendToProgram("remove\n", &first);
12554         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12555         break;
12556
12557       case BeginningOfGame:
12558       default:
12559         break;
12560
12561       case IcsPlayingWhite:
12562       case IcsPlayingBlack:
12563         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12564             SendToICS(ics_prefix);
12565             SendToICS("takeback 2\n");
12566         } else {
12567             SendToICS(ics_prefix);
12568             SendToICS("takeback 1\n");
12569         }
12570         break;
12571     }
12572 }
12573
12574 void
12575 MoveNowEvent()
12576 {
12577     ChessProgramState *cps;
12578
12579     switch (gameMode) {
12580       case MachinePlaysWhite:
12581         if (!WhiteOnMove(forwardMostMove)) {
12582             DisplayError(_("It is your turn"), 0);
12583             return;
12584         }
12585         cps = &first;
12586         break;
12587       case MachinePlaysBlack:
12588         if (WhiteOnMove(forwardMostMove)) {
12589             DisplayError(_("It is your turn"), 0);
12590             return;
12591         }
12592         cps = &first;
12593         break;
12594       case TwoMachinesPlay:
12595         if (WhiteOnMove(forwardMostMove) ==
12596             (first.twoMachinesColor[0] == 'w')) {
12597             cps = &first;
12598         } else {
12599             cps = &second;
12600         }
12601         break;
12602       case BeginningOfGame:
12603       default:
12604         return;
12605     }
12606     SendToProgram("?\n", cps);
12607 }
12608
12609 void
12610 TruncateGameEvent()
12611 {
12612     EditGameEvent();
12613     if (gameMode != EditGame) return;
12614     TruncateGame();
12615 }
12616
12617 void
12618 TruncateGame()
12619 {
12620     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12621     if (forwardMostMove > currentMove) {
12622         if (gameInfo.resultDetails != NULL) {
12623             free(gameInfo.resultDetails);
12624             gameInfo.resultDetails = NULL;
12625             gameInfo.result = GameUnfinished;
12626         }
12627         forwardMostMove = currentMove;
12628         HistorySet(parseList, backwardMostMove, forwardMostMove,
12629                    currentMove-1);
12630     }
12631 }
12632
12633 void
12634 HintEvent()
12635 {
12636     if (appData.noChessProgram) return;
12637     switch (gameMode) {
12638       case MachinePlaysWhite:
12639         if (WhiteOnMove(forwardMostMove)) {
12640             DisplayError(_("Wait until your turn"), 0);
12641             return;
12642         }
12643         break;
12644       case BeginningOfGame:
12645       case MachinePlaysBlack:
12646         if (!WhiteOnMove(forwardMostMove)) {
12647             DisplayError(_("Wait until your turn"), 0);
12648             return;
12649         }
12650         break;
12651       default:
12652         DisplayError(_("No hint available"), 0);
12653         return;
12654     }
12655     SendToProgram("hint\n", &first);
12656     hintRequested = TRUE;
12657 }
12658
12659 void
12660 BookEvent()
12661 {
12662     if (appData.noChessProgram) return;
12663     switch (gameMode) {
12664       case MachinePlaysWhite:
12665         if (WhiteOnMove(forwardMostMove)) {
12666             DisplayError(_("Wait until your turn"), 0);
12667             return;
12668         }
12669         break;
12670       case BeginningOfGame:
12671       case MachinePlaysBlack:
12672         if (!WhiteOnMove(forwardMostMove)) {
12673             DisplayError(_("Wait until your turn"), 0);
12674             return;
12675         }
12676         break;
12677       case EditPosition:
12678         EditPositionDone(TRUE);
12679         break;
12680       case TwoMachinesPlay:
12681         return;
12682       default:
12683         break;
12684     }
12685     SendToProgram("bk\n", &first);
12686     bookOutput[0] = NULLCHAR;
12687     bookRequested = TRUE;
12688 }
12689
12690 void
12691 AboutGameEvent()
12692 {
12693     char *tags = PGNTags(&gameInfo);
12694     TagsPopUp(tags, CmailMsg());
12695     free(tags);
12696 }
12697
12698 /* end button procedures */
12699
12700 void
12701 PrintPosition(fp, move)
12702      FILE *fp;
12703      int move;
12704 {
12705     int i, j;
12706     
12707     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12708         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12709             char c = PieceToChar(boards[move][i][j]);
12710             fputc(c == 'x' ? '.' : c, fp);
12711             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12712         }
12713     }
12714     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12715       fprintf(fp, "white to play\n");
12716     else
12717       fprintf(fp, "black to play\n");
12718 }
12719
12720 void
12721 PrintOpponents(fp)
12722      FILE *fp;
12723 {
12724     if (gameInfo.white != NULL) {
12725         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12726     } else {
12727         fprintf(fp, "\n");
12728     }
12729 }
12730
12731 /* Find last component of program's own name, using some heuristics */
12732 void
12733 TidyProgramName(prog, host, buf)
12734      char *prog, *host, buf[MSG_SIZ];
12735 {
12736     char *p, *q;
12737     int local = (strcmp(host, "localhost") == 0);
12738     while (!local && (p = strchr(prog, ';')) != NULL) {
12739         p++;
12740         while (*p == ' ') p++;
12741         prog = p;
12742     }
12743     if (*prog == '"' || *prog == '\'') {
12744         q = strchr(prog + 1, *prog);
12745     } else {
12746         q = strchr(prog, ' ');
12747     }
12748     if (q == NULL) q = prog + strlen(prog);
12749     p = q;
12750     while (p >= prog && *p != '/' && *p != '\\') p--;
12751     p++;
12752     if(p == prog && *p == '"') p++;
12753     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12754     memcpy(buf, p, q - p);
12755     buf[q - p] = NULLCHAR;
12756     if (!local) {
12757         strcat(buf, "@");
12758         strcat(buf, host);
12759     }
12760 }
12761
12762 char *
12763 TimeControlTagValue()
12764 {
12765     char buf[MSG_SIZ];
12766     if (!appData.clockMode) {
12767         strcpy(buf, "-");
12768     } else if (movesPerSession > 0) {
12769         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12770     } else if (timeIncrement == 0) {
12771         sprintf(buf, "%ld", timeControl/1000);
12772     } else {
12773         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12774     }
12775     return StrSave(buf);
12776 }
12777
12778 void
12779 SetGameInfo()
12780 {
12781     /* This routine is used only for certain modes */
12782     VariantClass v = gameInfo.variant;
12783     ChessMove r = GameUnfinished;
12784     char *p = NULL;
12785
12786     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12787         r = gameInfo.result; 
12788         p = gameInfo.resultDetails; 
12789         gameInfo.resultDetails = NULL;
12790     }
12791     ClearGameInfo(&gameInfo);
12792     gameInfo.variant = v;
12793
12794     switch (gameMode) {
12795       case MachinePlaysWhite:
12796         gameInfo.event = StrSave( appData.pgnEventHeader );
12797         gameInfo.site = StrSave(HostName());
12798         gameInfo.date = PGNDate();
12799         gameInfo.round = StrSave("-");
12800         gameInfo.white = StrSave(first.tidy);
12801         gameInfo.black = StrSave(UserName());
12802         gameInfo.timeControl = TimeControlTagValue();
12803         break;
12804
12805       case MachinePlaysBlack:
12806         gameInfo.event = StrSave( appData.pgnEventHeader );
12807         gameInfo.site = StrSave(HostName());
12808         gameInfo.date = PGNDate();
12809         gameInfo.round = StrSave("-");
12810         gameInfo.white = StrSave(UserName());
12811         gameInfo.black = StrSave(first.tidy);
12812         gameInfo.timeControl = TimeControlTagValue();
12813         break;
12814
12815       case TwoMachinesPlay:
12816         gameInfo.event = StrSave( appData.pgnEventHeader );
12817         gameInfo.site = StrSave(HostName());
12818         gameInfo.date = PGNDate();
12819         if (matchGame > 0) {
12820             char buf[MSG_SIZ];
12821             sprintf(buf, "%d", matchGame);
12822             gameInfo.round = StrSave(buf);
12823         } else {
12824             gameInfo.round = StrSave("-");
12825         }
12826         if (first.twoMachinesColor[0] == 'w') {
12827             gameInfo.white = StrSave(first.tidy);
12828             gameInfo.black = StrSave(second.tidy);
12829         } else {
12830             gameInfo.white = StrSave(second.tidy);
12831             gameInfo.black = StrSave(first.tidy);
12832         }
12833         gameInfo.timeControl = TimeControlTagValue();
12834         break;
12835
12836       case EditGame:
12837         gameInfo.event = StrSave("Edited game");
12838         gameInfo.site = StrSave(HostName());
12839         gameInfo.date = PGNDate();
12840         gameInfo.round = StrSave("-");
12841         gameInfo.white = StrSave("-");
12842         gameInfo.black = StrSave("-");
12843         gameInfo.result = r;
12844         gameInfo.resultDetails = p;
12845         break;
12846
12847       case EditPosition:
12848         gameInfo.event = StrSave("Edited position");
12849         gameInfo.site = StrSave(HostName());
12850         gameInfo.date = PGNDate();
12851         gameInfo.round = StrSave("-");
12852         gameInfo.white = StrSave("-");
12853         gameInfo.black = StrSave("-");
12854         break;
12855
12856       case IcsPlayingWhite:
12857       case IcsPlayingBlack:
12858       case IcsObserving:
12859       case IcsExamining:
12860         break;
12861
12862       case PlayFromGameFile:
12863         gameInfo.event = StrSave("Game from non-PGN file");
12864         gameInfo.site = StrSave(HostName());
12865         gameInfo.date = PGNDate();
12866         gameInfo.round = StrSave("-");
12867         gameInfo.white = StrSave("?");
12868         gameInfo.black = StrSave("?");
12869         break;
12870
12871       default:
12872         break;
12873     }
12874 }
12875
12876 void
12877 ReplaceComment(index, text)
12878      int index;
12879      char *text;
12880 {
12881     int len;
12882
12883     while (*text == '\n') text++;
12884     len = strlen(text);
12885     while (len > 0 && text[len - 1] == '\n') len--;
12886
12887     if (commentList[index] != NULL)
12888       free(commentList[index]);
12889
12890     if (len == 0) {
12891         commentList[index] = NULL;
12892         return;
12893     }
12894   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12895       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12896       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12897     commentList[index] = (char *) malloc(len + 2);
12898     strncpy(commentList[index], text, len);
12899     commentList[index][len] = '\n';
12900     commentList[index][len + 1] = NULLCHAR;
12901   } else { 
12902     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12903     char *p;
12904     commentList[index] = (char *) malloc(len + 6);
12905     strcpy(commentList[index], "{\n");
12906     strncpy(commentList[index]+2, text, len);
12907     commentList[index][len+2] = NULLCHAR;
12908     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12909     strcat(commentList[index], "\n}\n");
12910   }
12911 }
12912
12913 void
12914 CrushCRs(text)
12915      char *text;
12916 {
12917   char *p = text;
12918   char *q = text;
12919   char ch;
12920
12921   do {
12922     ch = *p++;
12923     if (ch == '\r') continue;
12924     *q++ = ch;
12925   } while (ch != '\0');
12926 }
12927
12928 void
12929 AppendComment(index, text, addBraces)
12930      int index;
12931      char *text;
12932      Boolean addBraces; // [HGM] braces: tells if we should add {}
12933 {
12934     int oldlen, len;
12935     char *old;
12936
12937 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12938     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12939
12940     CrushCRs(text);
12941     while (*text == '\n') text++;
12942     len = strlen(text);
12943     while (len > 0 && text[len - 1] == '\n') len--;
12944
12945     if (len == 0) return;
12946
12947     if (commentList[index] != NULL) {
12948         old = commentList[index];
12949         oldlen = strlen(old);
12950         while(commentList[index][oldlen-1] ==  '\n')
12951           commentList[index][--oldlen] = NULLCHAR;
12952         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12953         strcpy(commentList[index], old);
12954         free(old);
12955         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12956         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12957           if(addBraces) addBraces = FALSE; else { text++; len--; }
12958           while (*text == '\n') { text++; len--; }
12959           commentList[index][--oldlen] = NULLCHAR;
12960       }
12961         if(addBraces) strcat(commentList[index], "\n{\n");
12962         else          strcat(commentList[index], "\n");
12963         strcat(commentList[index], text);
12964         if(addBraces) strcat(commentList[index], "\n}\n");
12965         else          strcat(commentList[index], "\n");
12966     } else {
12967         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12968         if(addBraces)
12969              strcpy(commentList[index], "{\n");
12970         else commentList[index][0] = NULLCHAR;
12971         strcat(commentList[index], text);
12972         strcat(commentList[index], "\n");
12973         if(addBraces) strcat(commentList[index], "}\n");
12974     }
12975 }
12976
12977 static char * FindStr( char * text, char * sub_text )
12978 {
12979     char * result = strstr( text, sub_text );
12980
12981     if( result != NULL ) {
12982         result += strlen( sub_text );
12983     }
12984
12985     return result;
12986 }
12987
12988 /* [AS] Try to extract PV info from PGN comment */
12989 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12990 char *GetInfoFromComment( int index, char * text )
12991 {
12992     char * sep = text;
12993
12994     if( text != NULL && index > 0 ) {
12995         int score = 0;
12996         int depth = 0;
12997         int time = -1, sec = 0, deci;
12998         char * s_eval = FindStr( text, "[%eval " );
12999         char * s_emt = FindStr( text, "[%emt " );
13000
13001         if( s_eval != NULL || s_emt != NULL ) {
13002             /* New style */
13003             char delim;
13004
13005             if( s_eval != NULL ) {
13006                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13007                     return text;
13008                 }
13009
13010                 if( delim != ']' ) {
13011                     return text;
13012                 }
13013             }
13014
13015             if( s_emt != NULL ) {
13016             }
13017                 return text;
13018         }
13019         else {
13020             /* We expect something like: [+|-]nnn.nn/dd */
13021             int score_lo = 0;
13022
13023             if(*text != '{') return text; // [HGM] braces: must be normal comment
13024
13025             sep = strchr( text, '/' );
13026             if( sep == NULL || sep < (text+4) ) {
13027                 return text;
13028             }
13029
13030             time = -1; sec = -1; deci = -1;
13031             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13032                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13033                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13034                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13035                 return text;
13036             }
13037
13038             if( score_lo < 0 || score_lo >= 100 ) {
13039                 return text;
13040             }
13041
13042             if(sec >= 0) time = 600*time + 10*sec; else
13043             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13044
13045             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13046
13047             /* [HGM] PV time: now locate end of PV info */
13048             while( *++sep >= '0' && *sep <= '9'); // strip depth
13049             if(time >= 0)
13050             while( *++sep >= '0' && *sep <= '9'); // strip time
13051             if(sec >= 0)
13052             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13053             if(deci >= 0)
13054             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13055             while(*sep == ' ') sep++;
13056         }
13057
13058         if( depth <= 0 ) {
13059             return text;
13060         }
13061
13062         if( time < 0 ) {
13063             time = -1;
13064         }
13065
13066         pvInfoList[index-1].depth = depth;
13067         pvInfoList[index-1].score = score;
13068         pvInfoList[index-1].time  = 10*time; // centi-sec
13069         if(*sep == '}') *sep = 0; else *--sep = '{';
13070     }
13071     return sep;
13072 }
13073
13074 void
13075 SendToProgram(message, cps)
13076      char *message;
13077      ChessProgramState *cps;
13078 {
13079     int count, outCount, error;
13080     char buf[MSG_SIZ];
13081
13082     if (cps->pr == NULL) return;
13083     Attention(cps);
13084     
13085     if (appData.debugMode) {
13086         TimeMark now;
13087         GetTimeMark(&now);
13088         fprintf(debugFP, "%ld >%-6s: %s", 
13089                 SubtractTimeMarks(&now, &programStartTime),
13090                 cps->which, message);
13091     }
13092     
13093     count = strlen(message);
13094     outCount = OutputToProcess(cps->pr, message, count, &error);
13095     if (outCount < count && !exiting 
13096                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13097         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13098         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13099             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13100                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13101                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13102             } else {
13103                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13104             }
13105             gameInfo.resultDetails = StrSave(buf);
13106         }
13107         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13108     }
13109 }
13110
13111 void
13112 ReceiveFromProgram(isr, closure, message, count, error)
13113      InputSourceRef isr;
13114      VOIDSTAR closure;
13115      char *message;
13116      int count;
13117      int error;
13118 {
13119     char *end_str;
13120     char buf[MSG_SIZ];
13121     ChessProgramState *cps = (ChessProgramState *)closure;
13122
13123     if (isr != cps->isr) return; /* Killed intentionally */
13124     if (count <= 0) {
13125         if (count == 0) {
13126             sprintf(buf,
13127                     _("Error: %s chess program (%s) exited unexpectedly"),
13128                     cps->which, cps->program);
13129         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13130                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13131                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13132                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13133                 } else {
13134                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13135                 }
13136                 gameInfo.resultDetails = StrSave(buf);
13137             }
13138             RemoveInputSource(cps->isr);
13139             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13140         } else {
13141             sprintf(buf,
13142                     _("Error reading from %s chess program (%s)"),
13143                     cps->which, cps->program);
13144             RemoveInputSource(cps->isr);
13145
13146             /* [AS] Program is misbehaving badly... kill it */
13147             if( count == -2 ) {
13148                 DestroyChildProcess( cps->pr, 9 );
13149                 cps->pr = NoProc;
13150             }
13151
13152             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13153         }
13154         return;
13155     }
13156     
13157     if ((end_str = strchr(message, '\r')) != NULL)
13158       *end_str = NULLCHAR;
13159     if ((end_str = strchr(message, '\n')) != NULL)
13160       *end_str = NULLCHAR;
13161     
13162     if (appData.debugMode) {
13163         TimeMark now; int print = 1;
13164         char *quote = ""; char c; int i;
13165
13166         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13167                 char start = message[0];
13168                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13169                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13170                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13171                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13172                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13173                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13174                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13175                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13176                         { quote = "# "; print = (appData.engineComments == 2); }
13177                 message[0] = start; // restore original message
13178         }
13179         if(print) {
13180                 GetTimeMark(&now);
13181                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13182                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13183                         quote,
13184                         message);
13185         }
13186     }
13187
13188     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13189     if (appData.icsEngineAnalyze) {
13190         if (strstr(message, "whisper") != NULL ||
13191              strstr(message, "kibitz") != NULL || 
13192             strstr(message, "tellics") != NULL) return;
13193     }
13194
13195     HandleMachineMove(message, cps);
13196 }
13197
13198
13199 void
13200 SendTimeControl(cps, mps, tc, inc, sd, st)
13201      ChessProgramState *cps;
13202      int mps, inc, sd, st;
13203      long tc;
13204 {
13205     char buf[MSG_SIZ];
13206     int seconds;
13207
13208     if( timeControl_2 > 0 ) {
13209         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13210             tc = timeControl_2;
13211         }
13212     }
13213     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13214     inc /= cps->timeOdds;
13215     st  /= cps->timeOdds;
13216
13217     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13218
13219     if (st > 0) {
13220       /* Set exact time per move, normally using st command */
13221       if (cps->stKludge) {
13222         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13223         seconds = st % 60;
13224         if (seconds == 0) {
13225           sprintf(buf, "level 1 %d\n", st/60);
13226         } else {
13227           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13228         }
13229       } else {
13230         sprintf(buf, "st %d\n", st);
13231       }
13232     } else {
13233       /* Set conventional or incremental time control, using level command */
13234       if (seconds == 0) {
13235         /* Note old gnuchess bug -- minutes:seconds used to not work.
13236            Fixed in later versions, but still avoid :seconds
13237            when seconds is 0. */
13238         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13239       } else {
13240         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13241                 seconds, inc/1000);
13242       }
13243     }
13244     SendToProgram(buf, cps);
13245
13246     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13247     /* Orthogonally, limit search to given depth */
13248     if (sd > 0) {
13249       if (cps->sdKludge) {
13250         sprintf(buf, "depth\n%d\n", sd);
13251       } else {
13252         sprintf(buf, "sd %d\n", sd);
13253       }
13254       SendToProgram(buf, cps);
13255     }
13256
13257     if(cps->nps > 0) { /* [HGM] nps */
13258         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13259         else {
13260                 sprintf(buf, "nps %d\n", cps->nps);
13261               SendToProgram(buf, cps);
13262         }
13263     }
13264 }
13265
13266 ChessProgramState *WhitePlayer()
13267 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13268 {
13269     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13270        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13271         return &second;
13272     return &first;
13273 }
13274
13275 void
13276 SendTimeRemaining(cps, machineWhite)
13277      ChessProgramState *cps;
13278      int /*boolean*/ machineWhite;
13279 {
13280     char message[MSG_SIZ];
13281     long time, otime;
13282
13283     /* Note: this routine must be called when the clocks are stopped
13284        or when they have *just* been set or switched; otherwise
13285        it will be off by the time since the current tick started.
13286     */
13287     if (machineWhite) {
13288         time = whiteTimeRemaining / 10;
13289         otime = blackTimeRemaining / 10;
13290     } else {
13291         time = blackTimeRemaining / 10;
13292         otime = whiteTimeRemaining / 10;
13293     }
13294     /* [HGM] translate opponent's time by time-odds factor */
13295     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13296     if (appData.debugMode) {
13297         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13298     }
13299
13300     if (time <= 0) time = 1;
13301     if (otime <= 0) otime = 1;
13302     
13303     sprintf(message, "time %ld\n", time);
13304     SendToProgram(message, cps);
13305
13306     sprintf(message, "otim %ld\n", otime);
13307     SendToProgram(message, cps);
13308 }
13309
13310 int
13311 BoolFeature(p, name, loc, cps)
13312      char **p;
13313      char *name;
13314      int *loc;
13315      ChessProgramState *cps;
13316 {
13317   char buf[MSG_SIZ];
13318   int len = strlen(name);
13319   int val;
13320   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13321     (*p) += len + 1;
13322     sscanf(*p, "%d", &val);
13323     *loc = (val != 0);
13324     while (**p && **p != ' ') (*p)++;
13325     sprintf(buf, "accepted %s\n", name);
13326     SendToProgram(buf, cps);
13327     return TRUE;
13328   }
13329   return FALSE;
13330 }
13331
13332 int
13333 IntFeature(p, name, loc, cps)
13334      char **p;
13335      char *name;
13336      int *loc;
13337      ChessProgramState *cps;
13338 {
13339   char buf[MSG_SIZ];
13340   int len = strlen(name);
13341   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13342     (*p) += len + 1;
13343     sscanf(*p, "%d", loc);
13344     while (**p && **p != ' ') (*p)++;
13345     sprintf(buf, "accepted %s\n", name);
13346     SendToProgram(buf, cps);
13347     return TRUE;
13348   }
13349   return FALSE;
13350 }
13351
13352 int
13353 StringFeature(p, name, loc, cps)
13354      char **p;
13355      char *name;
13356      char loc[];
13357      ChessProgramState *cps;
13358 {
13359   char buf[MSG_SIZ];
13360   int len = strlen(name);
13361   if (strncmp((*p), name, len) == 0
13362       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13363     (*p) += len + 2;
13364     sscanf(*p, "%[^\"]", loc);
13365     while (**p && **p != '\"') (*p)++;
13366     if (**p == '\"') (*p)++;
13367     sprintf(buf, "accepted %s\n", name);
13368     SendToProgram(buf, cps);
13369     return TRUE;
13370   }
13371   return FALSE;
13372 }
13373
13374 int 
13375 ParseOption(Option *opt, ChessProgramState *cps)
13376 // [HGM] options: process the string that defines an engine option, and determine
13377 // name, type, default value, and allowed value range
13378 {
13379         char *p, *q, buf[MSG_SIZ];
13380         int n, min = (-1)<<31, max = 1<<31, def;
13381
13382         if(p = strstr(opt->name, " -spin ")) {
13383             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13384             if(max < min) max = min; // enforce consistency
13385             if(def < min) def = min;
13386             if(def > max) def = max;
13387             opt->value = def;
13388             opt->min = min;
13389             opt->max = max;
13390             opt->type = Spin;
13391         } else if((p = strstr(opt->name, " -slider "))) {
13392             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13393             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13394             if(max < min) max = min; // enforce consistency
13395             if(def < min) def = min;
13396             if(def > max) def = max;
13397             opt->value = def;
13398             opt->min = min;
13399             opt->max = max;
13400             opt->type = Spin; // Slider;
13401         } else if((p = strstr(opt->name, " -string "))) {
13402             opt->textValue = p+9;
13403             opt->type = TextBox;
13404         } else if((p = strstr(opt->name, " -file "))) {
13405             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13406             opt->textValue = p+7;
13407             opt->type = TextBox; // FileName;
13408         } else if((p = strstr(opt->name, " -path "))) {
13409             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13410             opt->textValue = p+7;
13411             opt->type = TextBox; // PathName;
13412         } else if(p = strstr(opt->name, " -check ")) {
13413             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13414             opt->value = (def != 0);
13415             opt->type = CheckBox;
13416         } else if(p = strstr(opt->name, " -combo ")) {
13417             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13418             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13419             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13420             opt->value = n = 0;
13421             while(q = StrStr(q, " /// ")) {
13422                 n++; *q = 0;    // count choices, and null-terminate each of them
13423                 q += 5;
13424                 if(*q == '*') { // remember default, which is marked with * prefix
13425                     q++;
13426                     opt->value = n;
13427                 }
13428                 cps->comboList[cps->comboCnt++] = q;
13429             }
13430             cps->comboList[cps->comboCnt++] = NULL;
13431             opt->max = n + 1;
13432             opt->type = ComboBox;
13433         } else if(p = strstr(opt->name, " -button")) {
13434             opt->type = Button;
13435         } else if(p = strstr(opt->name, " -save")) {
13436             opt->type = SaveButton;
13437         } else return FALSE;
13438         *p = 0; // terminate option name
13439         // now look if the command-line options define a setting for this engine option.
13440         if(cps->optionSettings && cps->optionSettings[0])
13441             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13442         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13443                 sprintf(buf, "option %s", p);
13444                 if(p = strstr(buf, ",")) *p = 0;
13445                 strcat(buf, "\n");
13446                 SendToProgram(buf, cps);
13447         }
13448         return TRUE;
13449 }
13450
13451 void
13452 FeatureDone(cps, val)
13453      ChessProgramState* cps;
13454      int val;
13455 {
13456   DelayedEventCallback cb = GetDelayedEvent();
13457   if ((cb == InitBackEnd3 && cps == &first) ||
13458       (cb == TwoMachinesEventIfReady && cps == &second)) {
13459     CancelDelayedEvent();
13460     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13461   }
13462   cps->initDone = val;
13463 }
13464
13465 /* Parse feature command from engine */
13466 void
13467 ParseFeatures(args, cps)
13468      char* args;
13469      ChessProgramState *cps;  
13470 {
13471   char *p = args;
13472   char *q;
13473   int val;
13474   char buf[MSG_SIZ];
13475
13476   for (;;) {
13477     while (*p == ' ') p++;
13478     if (*p == NULLCHAR) return;
13479
13480     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13481     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13482     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13483     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13484     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13485     if (BoolFeature(&p, "reuse", &val, cps)) {
13486       /* Engine can disable reuse, but can't enable it if user said no */
13487       if (!val) cps->reuse = FALSE;
13488       continue;
13489     }
13490     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13491     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13492       if (gameMode == TwoMachinesPlay) {
13493         DisplayTwoMachinesTitle();
13494       } else {
13495         DisplayTitle("");
13496       }
13497       continue;
13498     }
13499     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13500     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13501     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13502     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13503     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13504     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13505     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13506     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13507     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13508     if (IntFeature(&p, "done", &val, cps)) {
13509       FeatureDone(cps, val);
13510       continue;
13511     }
13512     /* Added by Tord: */
13513     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13514     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13515     /* End of additions by Tord */
13516
13517     /* [HGM] added features: */
13518     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13519     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13520     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13521     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13522     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13523     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13524     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13525         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13526             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13527             SendToProgram(buf, cps);
13528             continue;
13529         }
13530         if(cps->nrOptions >= MAX_OPTIONS) {
13531             cps->nrOptions--;
13532             sprintf(buf, "%s engine has too many options\n", cps->which);
13533             DisplayError(buf, 0);
13534         }
13535         continue;
13536     }
13537     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13538     /* End of additions by HGM */
13539
13540     /* unknown feature: complain and skip */
13541     q = p;
13542     while (*q && *q != '=') q++;
13543     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13544     SendToProgram(buf, cps);
13545     p = q;
13546     if (*p == '=') {
13547       p++;
13548       if (*p == '\"') {
13549         p++;
13550         while (*p && *p != '\"') p++;
13551         if (*p == '\"') p++;
13552       } else {
13553         while (*p && *p != ' ') p++;
13554       }
13555     }
13556   }
13557
13558 }
13559
13560 void
13561 PeriodicUpdatesEvent(newState)
13562      int newState;
13563 {
13564     if (newState == appData.periodicUpdates)
13565       return;
13566
13567     appData.periodicUpdates=newState;
13568
13569     /* Display type changes, so update it now */
13570 //    DisplayAnalysis();
13571
13572     /* Get the ball rolling again... */
13573     if (newState) {
13574         AnalysisPeriodicEvent(1);
13575         StartAnalysisClock();
13576     }
13577 }
13578
13579 void
13580 PonderNextMoveEvent(newState)
13581      int newState;
13582 {
13583     if (newState == appData.ponderNextMove) return;
13584     if (gameMode == EditPosition) EditPositionDone(TRUE);
13585     if (newState) {
13586         SendToProgram("hard\n", &first);
13587         if (gameMode == TwoMachinesPlay) {
13588             SendToProgram("hard\n", &second);
13589         }
13590     } else {
13591         SendToProgram("easy\n", &first);
13592         thinkOutput[0] = NULLCHAR;
13593         if (gameMode == TwoMachinesPlay) {
13594             SendToProgram("easy\n", &second);
13595         }
13596     }
13597     appData.ponderNextMove = newState;
13598 }
13599
13600 void
13601 NewSettingEvent(option, command, value)
13602      char *command;
13603      int option, value;
13604 {
13605     char buf[MSG_SIZ];
13606
13607     if (gameMode == EditPosition) EditPositionDone(TRUE);
13608     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13609     SendToProgram(buf, &first);
13610     if (gameMode == TwoMachinesPlay) {
13611         SendToProgram(buf, &second);
13612     }
13613 }
13614
13615 void
13616 ShowThinkingEvent()
13617 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13618 {
13619     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13620     int newState = appData.showThinking
13621         // [HGM] thinking: other features now need thinking output as well
13622         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13623     
13624     if (oldState == newState) return;
13625     oldState = newState;
13626     if (gameMode == EditPosition) EditPositionDone(TRUE);
13627     if (oldState) {
13628         SendToProgram("post\n", &first);
13629         if (gameMode == TwoMachinesPlay) {
13630             SendToProgram("post\n", &second);
13631         }
13632     } else {
13633         SendToProgram("nopost\n", &first);
13634         thinkOutput[0] = NULLCHAR;
13635         if (gameMode == TwoMachinesPlay) {
13636             SendToProgram("nopost\n", &second);
13637         }
13638     }
13639 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13640 }
13641
13642 void
13643 AskQuestionEvent(title, question, replyPrefix, which)
13644      char *title; char *question; char *replyPrefix; char *which;
13645 {
13646   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13647   if (pr == NoProc) return;
13648   AskQuestion(title, question, replyPrefix, pr);
13649 }
13650
13651 void
13652 DisplayMove(moveNumber)
13653      int moveNumber;
13654 {
13655     char message[MSG_SIZ];
13656     char res[MSG_SIZ];
13657     char cpThinkOutput[MSG_SIZ];
13658
13659     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13660     
13661     if (moveNumber == forwardMostMove - 1 || 
13662         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13663
13664         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13665
13666         if (strchr(cpThinkOutput, '\n')) {
13667             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13668         }
13669     } else {
13670         *cpThinkOutput = NULLCHAR;
13671     }
13672
13673     /* [AS] Hide thinking from human user */
13674     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13675         *cpThinkOutput = NULLCHAR;
13676         if( thinkOutput[0] != NULLCHAR ) {
13677             int i;
13678
13679             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13680                 cpThinkOutput[i] = '.';
13681             }
13682             cpThinkOutput[i] = NULLCHAR;
13683             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13684         }
13685     }
13686
13687     if (moveNumber == forwardMostMove - 1 &&
13688         gameInfo.resultDetails != NULL) {
13689         if (gameInfo.resultDetails[0] == NULLCHAR) {
13690             sprintf(res, " %s", PGNResult(gameInfo.result));
13691         } else {
13692             sprintf(res, " {%s} %s",
13693                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13694         }
13695     } else {
13696         res[0] = NULLCHAR;
13697     }
13698
13699     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13700         DisplayMessage(res, cpThinkOutput);
13701     } else {
13702         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13703                 WhiteOnMove(moveNumber) ? " " : ".. ",
13704                 parseList[moveNumber], res);
13705         DisplayMessage(message, cpThinkOutput);
13706     }
13707 }
13708
13709 void
13710 DisplayComment(moveNumber, text)
13711      int moveNumber;
13712      char *text;
13713 {
13714     char title[MSG_SIZ];
13715     char buf[8000]; // comment can be long!
13716     int score, depth;
13717     
13718     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13719       strcpy(title, "Comment");
13720     } else {
13721       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13722               WhiteOnMove(moveNumber) ? " " : ".. ",
13723               parseList[moveNumber]);
13724     }
13725     // [HGM] PV info: display PV info together with (or as) comment
13726     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13727       if(text == NULL) text = "";                                           
13728       score = pvInfoList[moveNumber].score;
13729       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13730               depth, (pvInfoList[moveNumber].time+50)/100, text);
13731       text = buf;
13732     }
13733     if (text != NULL && (appData.autoDisplayComment || commentUp))
13734         CommentPopUp(title, text);
13735 }
13736
13737 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13738  * might be busy thinking or pondering.  It can be omitted if your
13739  * gnuchess is configured to stop thinking immediately on any user
13740  * input.  However, that gnuchess feature depends on the FIONREAD
13741  * ioctl, which does not work properly on some flavors of Unix.
13742  */
13743 void
13744 Attention(cps)
13745      ChessProgramState *cps;
13746 {
13747 #if ATTENTION
13748     if (!cps->useSigint) return;
13749     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13750     switch (gameMode) {
13751       case MachinePlaysWhite:
13752       case MachinePlaysBlack:
13753       case TwoMachinesPlay:
13754       case IcsPlayingWhite:
13755       case IcsPlayingBlack:
13756       case AnalyzeMode:
13757       case AnalyzeFile:
13758         /* Skip if we know it isn't thinking */
13759         if (!cps->maybeThinking) return;
13760         if (appData.debugMode)
13761           fprintf(debugFP, "Interrupting %s\n", cps->which);
13762         InterruptChildProcess(cps->pr);
13763         cps->maybeThinking = FALSE;
13764         break;
13765       default:
13766         break;
13767     }
13768 #endif /*ATTENTION*/
13769 }
13770
13771 int
13772 CheckFlags()
13773 {
13774     if (whiteTimeRemaining <= 0) {
13775         if (!whiteFlag) {
13776             whiteFlag = TRUE;
13777             if (appData.icsActive) {
13778                 if (appData.autoCallFlag &&
13779                     gameMode == IcsPlayingBlack && !blackFlag) {
13780                   SendToICS(ics_prefix);
13781                   SendToICS("flag\n");
13782                 }
13783             } else {
13784                 if (blackFlag) {
13785                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13786                 } else {
13787                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13788                     if (appData.autoCallFlag) {
13789                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13790                         return TRUE;
13791                     }
13792                 }
13793             }
13794         }
13795     }
13796     if (blackTimeRemaining <= 0) {
13797         if (!blackFlag) {
13798             blackFlag = TRUE;
13799             if (appData.icsActive) {
13800                 if (appData.autoCallFlag &&
13801                     gameMode == IcsPlayingWhite && !whiteFlag) {
13802                   SendToICS(ics_prefix);
13803                   SendToICS("flag\n");
13804                 }
13805             } else {
13806                 if (whiteFlag) {
13807                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13808                 } else {
13809                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13810                     if (appData.autoCallFlag) {
13811                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13812                         return TRUE;
13813                     }
13814                 }
13815             }
13816         }
13817     }
13818     return FALSE;
13819 }
13820
13821 void
13822 CheckTimeControl()
13823 {
13824     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13825         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13826
13827     /*
13828      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13829      */
13830     if ( !WhiteOnMove(forwardMostMove) )
13831         /* White made time control */
13832         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13833         /* [HGM] time odds: correct new time quota for time odds! */
13834                                             / WhitePlayer()->timeOdds;
13835       else
13836         /* Black made time control */
13837         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13838                                             / WhitePlayer()->other->timeOdds;
13839 }
13840
13841 void
13842 DisplayBothClocks()
13843 {
13844     int wom = gameMode == EditPosition ?
13845       !blackPlaysFirst : WhiteOnMove(currentMove);
13846     DisplayWhiteClock(whiteTimeRemaining, wom);
13847     DisplayBlackClock(blackTimeRemaining, !wom);
13848 }
13849
13850
13851 /* Timekeeping seems to be a portability nightmare.  I think everyone
13852    has ftime(), but I'm really not sure, so I'm including some ifdefs
13853    to use other calls if you don't.  Clocks will be less accurate if
13854    you have neither ftime nor gettimeofday.
13855 */
13856
13857 /* VS 2008 requires the #include outside of the function */
13858 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13859 #include <sys/timeb.h>
13860 #endif
13861
13862 /* Get the current time as a TimeMark */
13863 void
13864 GetTimeMark(tm)
13865      TimeMark *tm;
13866 {
13867 #if HAVE_GETTIMEOFDAY
13868
13869     struct timeval timeVal;
13870     struct timezone timeZone;
13871
13872     gettimeofday(&timeVal, &timeZone);
13873     tm->sec = (long) timeVal.tv_sec; 
13874     tm->ms = (int) (timeVal.tv_usec / 1000L);
13875
13876 #else /*!HAVE_GETTIMEOFDAY*/
13877 #if HAVE_FTIME
13878
13879 // include <sys/timeb.h> / moved to just above start of function
13880     struct timeb timeB;
13881
13882     ftime(&timeB);
13883     tm->sec = (long) timeB.time;
13884     tm->ms = (int) timeB.millitm;
13885
13886 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13887     tm->sec = (long) time(NULL);
13888     tm->ms = 0;
13889 #endif
13890 #endif
13891 }
13892
13893 /* Return the difference in milliseconds between two
13894    time marks.  We assume the difference will fit in a long!
13895 */
13896 long
13897 SubtractTimeMarks(tm2, tm1)
13898      TimeMark *tm2, *tm1;
13899 {
13900     return 1000L*(tm2->sec - tm1->sec) +
13901            (long) (tm2->ms - tm1->ms);
13902 }
13903
13904
13905 /*
13906  * Code to manage the game clocks.
13907  *
13908  * In tournament play, black starts the clock and then white makes a move.
13909  * We give the human user a slight advantage if he is playing white---the
13910  * clocks don't run until he makes his first move, so it takes zero time.
13911  * Also, we don't account for network lag, so we could get out of sync
13912  * with GNU Chess's clock -- but then, referees are always right.  
13913  */
13914
13915 static TimeMark tickStartTM;
13916 static long intendedTickLength;
13917
13918 long
13919 NextTickLength(timeRemaining)
13920      long timeRemaining;
13921 {
13922     long nominalTickLength, nextTickLength;
13923
13924     if (timeRemaining > 0L && timeRemaining <= 10000L)
13925       nominalTickLength = 100L;
13926     else
13927       nominalTickLength = 1000L;
13928     nextTickLength = timeRemaining % nominalTickLength;
13929     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13930
13931     return nextTickLength;
13932 }
13933
13934 /* Adjust clock one minute up or down */
13935 void
13936 AdjustClock(Boolean which, int dir)
13937 {
13938     if(which) blackTimeRemaining += 60000*dir;
13939     else      whiteTimeRemaining += 60000*dir;
13940     DisplayBothClocks();
13941 }
13942
13943 /* Stop clocks and reset to a fresh time control */
13944 void
13945 ResetClocks() 
13946 {
13947     (void) StopClockTimer();
13948     if (appData.icsActive) {
13949         whiteTimeRemaining = blackTimeRemaining = 0;
13950     } else if (searchTime) {
13951         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13952         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13953     } else { /* [HGM] correct new time quote for time odds */
13954         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13955         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13956     }
13957     if (whiteFlag || blackFlag) {
13958         DisplayTitle("");
13959         whiteFlag = blackFlag = FALSE;
13960     }
13961     DisplayBothClocks();
13962 }
13963
13964 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13965
13966 /* Decrement running clock by amount of time that has passed */
13967 void
13968 DecrementClocks()
13969 {
13970     long timeRemaining;
13971     long lastTickLength, fudge;
13972     TimeMark now;
13973
13974     if (!appData.clockMode) return;
13975     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13976         
13977     GetTimeMark(&now);
13978
13979     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13980
13981     /* Fudge if we woke up a little too soon */
13982     fudge = intendedTickLength - lastTickLength;
13983     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13984
13985     if (WhiteOnMove(forwardMostMove)) {
13986         if(whiteNPS >= 0) lastTickLength = 0;
13987         timeRemaining = whiteTimeRemaining -= lastTickLength;
13988         DisplayWhiteClock(whiteTimeRemaining - fudge,
13989                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13990     } else {
13991         if(blackNPS >= 0) lastTickLength = 0;
13992         timeRemaining = blackTimeRemaining -= lastTickLength;
13993         DisplayBlackClock(blackTimeRemaining - fudge,
13994                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13995     }
13996
13997     if (CheckFlags()) return;
13998         
13999     tickStartTM = now;
14000     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14001     StartClockTimer(intendedTickLength);
14002
14003     /* if the time remaining has fallen below the alarm threshold, sound the
14004      * alarm. if the alarm has sounded and (due to a takeback or time control
14005      * with increment) the time remaining has increased to a level above the
14006      * threshold, reset the alarm so it can sound again. 
14007      */
14008     
14009     if (appData.icsActive && appData.icsAlarm) {
14010
14011         /* make sure we are dealing with the user's clock */
14012         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14013                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14014            )) return;
14015
14016         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14017             alarmSounded = FALSE;
14018         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14019             PlayAlarmSound();
14020             alarmSounded = TRUE;
14021         }
14022     }
14023 }
14024
14025
14026 /* A player has just moved, so stop the previously running
14027    clock and (if in clock mode) start the other one.
14028    We redisplay both clocks in case we're in ICS mode, because
14029    ICS gives us an update to both clocks after every move.
14030    Note that this routine is called *after* forwardMostMove
14031    is updated, so the last fractional tick must be subtracted
14032    from the color that is *not* on move now.
14033 */
14034 void
14035 SwitchClocks()
14036 {
14037     long lastTickLength;
14038     TimeMark now;
14039     int flagged = FALSE;
14040
14041     GetTimeMark(&now);
14042
14043     if (StopClockTimer() && appData.clockMode) {
14044         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14045         if (WhiteOnMove(forwardMostMove)) {
14046             if(blackNPS >= 0) lastTickLength = 0;
14047             blackTimeRemaining -= lastTickLength;
14048            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14049 //         if(pvInfoList[forwardMostMove-1].time == -1)
14050                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14051                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14052         } else {
14053            if(whiteNPS >= 0) lastTickLength = 0;
14054            whiteTimeRemaining -= lastTickLength;
14055            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14056 //         if(pvInfoList[forwardMostMove-1].time == -1)
14057                  pvInfoList[forwardMostMove-1].time = 
14058                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14059         }
14060         flagged = CheckFlags();
14061     }
14062     CheckTimeControl();
14063
14064     if (flagged || !appData.clockMode) return;
14065
14066     switch (gameMode) {
14067       case MachinePlaysBlack:
14068       case MachinePlaysWhite:
14069       case BeginningOfGame:
14070         if (pausing) return;
14071         break;
14072
14073       case EditGame:
14074       case PlayFromGameFile:
14075       case IcsExamining:
14076         return;
14077
14078       default:
14079         break;
14080     }
14081
14082     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14083         if(WhiteOnMove(forwardMostMove))
14084              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14085         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14086     }
14087
14088     tickStartTM = now;
14089     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14090       whiteTimeRemaining : blackTimeRemaining);
14091     StartClockTimer(intendedTickLength);
14092 }
14093         
14094
14095 /* Stop both clocks */
14096 void
14097 StopClocks()
14098 {       
14099     long lastTickLength;
14100     TimeMark now;
14101
14102     if (!StopClockTimer()) return;
14103     if (!appData.clockMode) return;
14104
14105     GetTimeMark(&now);
14106
14107     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14108     if (WhiteOnMove(forwardMostMove)) {
14109         if(whiteNPS >= 0) lastTickLength = 0;
14110         whiteTimeRemaining -= lastTickLength;
14111         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14112     } else {
14113         if(blackNPS >= 0) lastTickLength = 0;
14114         blackTimeRemaining -= lastTickLength;
14115         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14116     }
14117     CheckFlags();
14118 }
14119         
14120 /* Start clock of player on move.  Time may have been reset, so
14121    if clock is already running, stop and restart it. */
14122 void
14123 StartClocks()
14124 {
14125     (void) StopClockTimer(); /* in case it was running already */
14126     DisplayBothClocks();
14127     if (CheckFlags()) return;
14128
14129     if (!appData.clockMode) return;
14130     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14131
14132     GetTimeMark(&tickStartTM);
14133     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14134       whiteTimeRemaining : blackTimeRemaining);
14135
14136    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14137     whiteNPS = blackNPS = -1; 
14138     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14139        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14140         whiteNPS = first.nps;
14141     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14142        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14143         blackNPS = first.nps;
14144     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14145         whiteNPS = second.nps;
14146     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14147         blackNPS = second.nps;
14148     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14149
14150     StartClockTimer(intendedTickLength);
14151 }
14152
14153 char *
14154 TimeString(ms)
14155      long ms;
14156 {
14157     long second, minute, hour, day;
14158     char *sign = "";
14159     static char buf[32];
14160     
14161     if (ms > 0 && ms <= 9900) {
14162       /* convert milliseconds to tenths, rounding up */
14163       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14164
14165       sprintf(buf, " %03.1f ", tenths/10.0);
14166       return buf;
14167     }
14168
14169     /* convert milliseconds to seconds, rounding up */
14170     /* use floating point to avoid strangeness of integer division
14171        with negative dividends on many machines */
14172     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14173
14174     if (second < 0) {
14175         sign = "-";
14176         second = -second;
14177     }
14178     
14179     day = second / (60 * 60 * 24);
14180     second = second % (60 * 60 * 24);
14181     hour = second / (60 * 60);
14182     second = second % (60 * 60);
14183     minute = second / 60;
14184     second = second % 60;
14185     
14186     if (day > 0)
14187       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14188               sign, day, hour, minute, second);
14189     else if (hour > 0)
14190       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14191     else
14192       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14193     
14194     return buf;
14195 }
14196
14197
14198 /*
14199  * This is necessary because some C libraries aren't ANSI C compliant yet.
14200  */
14201 char *
14202 StrStr(string, match)
14203      char *string, *match;
14204 {
14205     int i, length;
14206     
14207     length = strlen(match);
14208     
14209     for (i = strlen(string) - length; i >= 0; i--, string++)
14210       if (!strncmp(match, string, length))
14211         return string;
14212     
14213     return NULL;
14214 }
14215
14216 char *
14217 StrCaseStr(string, match)
14218      char *string, *match;
14219 {
14220     int i, j, length;
14221     
14222     length = strlen(match);
14223     
14224     for (i = strlen(string) - length; i >= 0; i--, string++) {
14225         for (j = 0; j < length; j++) {
14226             if (ToLower(match[j]) != ToLower(string[j]))
14227               break;
14228         }
14229         if (j == length) return string;
14230     }
14231
14232     return NULL;
14233 }
14234
14235 #ifndef _amigados
14236 int
14237 StrCaseCmp(s1, s2)
14238      char *s1, *s2;
14239 {
14240     char c1, c2;
14241     
14242     for (;;) {
14243         c1 = ToLower(*s1++);
14244         c2 = ToLower(*s2++);
14245         if (c1 > c2) return 1;
14246         if (c1 < c2) return -1;
14247         if (c1 == NULLCHAR) return 0;
14248     }
14249 }
14250
14251
14252 int
14253 ToLower(c)
14254      int c;
14255 {
14256     return isupper(c) ? tolower(c) : c;
14257 }
14258
14259
14260 int
14261 ToUpper(c)
14262      int c;
14263 {
14264     return islower(c) ? toupper(c) : c;
14265 }
14266 #endif /* !_amigados    */
14267
14268 char *
14269 StrSave(s)
14270      char *s;
14271 {
14272     char *ret;
14273
14274     if ((ret = (char *) malloc(strlen(s) + 1))) {
14275         strcpy(ret, s);
14276     }
14277     return ret;
14278 }
14279
14280 char *
14281 StrSavePtr(s, savePtr)
14282      char *s, **savePtr;
14283 {
14284     if (*savePtr) {
14285         free(*savePtr);
14286     }
14287     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14288         strcpy(*savePtr, s);
14289     }
14290     return(*savePtr);
14291 }
14292
14293 char *
14294 PGNDate()
14295 {
14296     time_t clock;
14297     struct tm *tm;
14298     char buf[MSG_SIZ];
14299
14300     clock = time((time_t *)NULL);
14301     tm = localtime(&clock);
14302     sprintf(buf, "%04d.%02d.%02d",
14303             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14304     return StrSave(buf);
14305 }
14306
14307
14308 char *
14309 PositionToFEN(move, overrideCastling)
14310      int move;
14311      char *overrideCastling;
14312 {
14313     int i, j, fromX, fromY, toX, toY;
14314     int whiteToPlay;
14315     char buf[128];
14316     char *p, *q;
14317     int emptycount;
14318     ChessSquare piece;
14319
14320     whiteToPlay = (gameMode == EditPosition) ?
14321       !blackPlaysFirst : (move % 2 == 0);
14322     p = buf;
14323
14324     /* Piece placement data */
14325     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14326         emptycount = 0;
14327         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14328             if (boards[move][i][j] == EmptySquare) {
14329                 emptycount++;
14330             } else { ChessSquare piece = boards[move][i][j];
14331                 if (emptycount > 0) {
14332                     if(emptycount<10) /* [HGM] can be >= 10 */
14333                         *p++ = '0' + emptycount;
14334                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14335                     emptycount = 0;
14336                 }
14337                 if(PieceToChar(piece) == '+') {
14338                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14339                     *p++ = '+';
14340                     piece = (ChessSquare)(DEMOTED piece);
14341                 } 
14342                 *p++ = PieceToChar(piece);
14343                 if(p[-1] == '~') {
14344                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14345                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14346                     *p++ = '~';
14347                 }
14348             }
14349         }
14350         if (emptycount > 0) {
14351             if(emptycount<10) /* [HGM] can be >= 10 */
14352                 *p++ = '0' + emptycount;
14353             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14354             emptycount = 0;
14355         }
14356         *p++ = '/';
14357     }
14358     *(p - 1) = ' ';
14359
14360     /* [HGM] print Crazyhouse or Shogi holdings */
14361     if( gameInfo.holdingsWidth ) {
14362         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14363         q = p;
14364         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14365             piece = boards[move][i][BOARD_WIDTH-1];
14366             if( piece != EmptySquare )
14367               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14368                   *p++ = PieceToChar(piece);
14369         }
14370         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14371             piece = boards[move][BOARD_HEIGHT-i-1][0];
14372             if( piece != EmptySquare )
14373               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14374                   *p++ = PieceToChar(piece);
14375         }
14376
14377         if( q == p ) *p++ = '-';
14378         *p++ = ']';
14379         *p++ = ' ';
14380     }
14381
14382     /* Active color */
14383     *p++ = whiteToPlay ? 'w' : 'b';
14384     *p++ = ' ';
14385
14386   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14387     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14388   } else {
14389   if(nrCastlingRights) {
14390      q = p;
14391      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14392        /* [HGM] write directly from rights */
14393            if(boards[move][CASTLING][2] != NoRights &&
14394               boards[move][CASTLING][0] != NoRights   )
14395                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14396            if(boards[move][CASTLING][2] != NoRights &&
14397               boards[move][CASTLING][1] != NoRights   )
14398                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14399            if(boards[move][CASTLING][5] != NoRights &&
14400               boards[move][CASTLING][3] != NoRights   )
14401                 *p++ = boards[move][CASTLING][3] + AAA;
14402            if(boards[move][CASTLING][5] != NoRights &&
14403               boards[move][CASTLING][4] != NoRights   )
14404                 *p++ = boards[move][CASTLING][4] + AAA;
14405      } else {
14406
14407         /* [HGM] write true castling rights */
14408         if( nrCastlingRights == 6 ) {
14409             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14410                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14411             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14412                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14413             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14414                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14415             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14416                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14417         }
14418      }
14419      if (q == p) *p++ = '-'; /* No castling rights */
14420      *p++ = ' ';
14421   }
14422
14423   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14424      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14425     /* En passant target square */
14426     if (move > backwardMostMove) {
14427         fromX = moveList[move - 1][0] - AAA;
14428         fromY = moveList[move - 1][1] - ONE;
14429         toX = moveList[move - 1][2] - AAA;
14430         toY = moveList[move - 1][3] - ONE;
14431         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14432             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14433             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14434             fromX == toX) {
14435             /* 2-square pawn move just happened */
14436             *p++ = toX + AAA;
14437             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14438         } else {
14439             *p++ = '-';
14440         }
14441     } else if(move == backwardMostMove) {
14442         // [HGM] perhaps we should always do it like this, and forget the above?
14443         if((signed char)boards[move][EP_STATUS] >= 0) {
14444             *p++ = boards[move][EP_STATUS] + AAA;
14445             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14446         } else {
14447             *p++ = '-';
14448         }
14449     } else {
14450         *p++ = '-';
14451     }
14452     *p++ = ' ';
14453   }
14454   }
14455
14456     /* [HGM] find reversible plies */
14457     {   int i = 0, j=move;
14458
14459         if (appData.debugMode) { int k;
14460             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14461             for(k=backwardMostMove; k<=forwardMostMove; k++)
14462                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14463
14464         }
14465
14466         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14467         if( j == backwardMostMove ) i += initialRulePlies;
14468         sprintf(p, "%d ", i);
14469         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14470     }
14471     /* Fullmove number */
14472     sprintf(p, "%d", (move / 2) + 1);
14473     
14474     return StrSave(buf);
14475 }
14476
14477 Boolean
14478 ParseFEN(board, blackPlaysFirst, fen)
14479     Board board;
14480      int *blackPlaysFirst;
14481      char *fen;
14482 {
14483     int i, j;
14484     char *p;
14485     int emptycount;
14486     ChessSquare piece;
14487
14488     p = fen;
14489
14490     /* [HGM] by default clear Crazyhouse holdings, if present */
14491     if(gameInfo.holdingsWidth) {
14492        for(i=0; i<BOARD_HEIGHT; i++) {
14493            board[i][0]             = EmptySquare; /* black holdings */
14494            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14495            board[i][1]             = (ChessSquare) 0; /* black counts */
14496            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14497        }
14498     }
14499
14500     /* Piece placement data */
14501     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14502         j = 0;
14503         for (;;) {
14504             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14505                 if (*p == '/') p++;
14506                 emptycount = gameInfo.boardWidth - j;
14507                 while (emptycount--)
14508                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14509                 break;
14510 #if(BOARD_FILES >= 10)
14511             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14512                 p++; emptycount=10;
14513                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14514                 while (emptycount--)
14515                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14516 #endif
14517             } else if (isdigit(*p)) {
14518                 emptycount = *p++ - '0';
14519                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14520                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14521                 while (emptycount--)
14522                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14523             } else if (*p == '+' || isalpha(*p)) {
14524                 if (j >= gameInfo.boardWidth) return FALSE;
14525                 if(*p=='+') {
14526                     piece = CharToPiece(*++p);
14527                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14528                     piece = (ChessSquare) (PROMOTED piece ); p++;
14529                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14530                 } else piece = CharToPiece(*p++);
14531
14532                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14533                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14534                     piece = (ChessSquare) (PROMOTED piece);
14535                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14536                     p++;
14537                 }
14538                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14539             } else {
14540                 return FALSE;
14541             }
14542         }
14543     }
14544     while (*p == '/' || *p == ' ') p++;
14545
14546     /* [HGM] look for Crazyhouse holdings here */
14547     while(*p==' ') p++;
14548     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14549         if(*p == '[') p++;
14550         if(*p == '-' ) *p++; /* empty holdings */ else {
14551             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14552             /* if we would allow FEN reading to set board size, we would   */
14553             /* have to add holdings and shift the board read so far here   */
14554             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14555                 *p++;
14556                 if((int) piece >= (int) BlackPawn ) {
14557                     i = (int)piece - (int)BlackPawn;
14558                     i = PieceToNumber((ChessSquare)i);
14559                     if( i >= gameInfo.holdingsSize ) return FALSE;
14560                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14561                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14562                 } else {
14563                     i = (int)piece - (int)WhitePawn;
14564                     i = PieceToNumber((ChessSquare)i);
14565                     if( i >= gameInfo.holdingsSize ) return FALSE;
14566                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14567                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14568                 }
14569             }
14570         }
14571         if(*p == ']') *p++;
14572     }
14573
14574     while(*p == ' ') p++;
14575
14576     /* Active color */
14577     switch (*p++) {
14578       case 'w':
14579         *blackPlaysFirst = FALSE;
14580         break;
14581       case 'b': 
14582         *blackPlaysFirst = TRUE;
14583         break;
14584       default:
14585         return FALSE;
14586     }
14587
14588     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14589     /* return the extra info in global variiables             */
14590
14591     /* set defaults in case FEN is incomplete */
14592     board[EP_STATUS] = EP_UNKNOWN;
14593     for(i=0; i<nrCastlingRights; i++ ) {
14594         board[CASTLING][i] =
14595             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14596     }   /* assume possible unless obviously impossible */
14597     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14598     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14599     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14600                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14601     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14602     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14603     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14604                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14605     FENrulePlies = 0;
14606
14607     while(*p==' ') p++;
14608     if(nrCastlingRights) {
14609       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14610           /* castling indicator present, so default becomes no castlings */
14611           for(i=0; i<nrCastlingRights; i++ ) {
14612                  board[CASTLING][i] = NoRights;
14613           }
14614       }
14615       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14616              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14617              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14618              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14619         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14620
14621         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14622             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14623             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14624         }
14625         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14626             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14627         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14628                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14629         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14630                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14631         switch(c) {
14632           case'K':
14633               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14634               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14635               board[CASTLING][2] = whiteKingFile;
14636               break;
14637           case'Q':
14638               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14639               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14640               board[CASTLING][2] = whiteKingFile;
14641               break;
14642           case'k':
14643               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14644               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14645               board[CASTLING][5] = blackKingFile;
14646               break;
14647           case'q':
14648               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14649               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14650               board[CASTLING][5] = blackKingFile;
14651           case '-':
14652               break;
14653           default: /* FRC castlings */
14654               if(c >= 'a') { /* black rights */
14655                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14656                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14657                   if(i == BOARD_RGHT) break;
14658                   board[CASTLING][5] = i;
14659                   c -= AAA;
14660                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14661                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14662                   if(c > i)
14663                       board[CASTLING][3] = c;
14664                   else
14665                       board[CASTLING][4] = c;
14666               } else { /* white rights */
14667                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14668                     if(board[0][i] == WhiteKing) break;
14669                   if(i == BOARD_RGHT) break;
14670                   board[CASTLING][2] = i;
14671                   c -= AAA - 'a' + 'A';
14672                   if(board[0][c] >= WhiteKing) break;
14673                   if(c > i)
14674                       board[CASTLING][0] = c;
14675                   else
14676                       board[CASTLING][1] = c;
14677               }
14678         }
14679       }
14680       for(i=0; i<nrCastlingRights; i++)
14681         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14682     if (appData.debugMode) {
14683         fprintf(debugFP, "FEN castling rights:");
14684         for(i=0; i<nrCastlingRights; i++)
14685         fprintf(debugFP, " %d", board[CASTLING][i]);
14686         fprintf(debugFP, "\n");
14687     }
14688
14689       while(*p==' ') p++;
14690     }
14691
14692     /* read e.p. field in games that know e.p. capture */
14693     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14694        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14695       if(*p=='-') {
14696         p++; board[EP_STATUS] = EP_NONE;
14697       } else {
14698          char c = *p++ - AAA;
14699
14700          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14701          if(*p >= '0' && *p <='9') *p++;
14702          board[EP_STATUS] = c;
14703       }
14704     }
14705
14706
14707     if(sscanf(p, "%d", &i) == 1) {
14708         FENrulePlies = i; /* 50-move ply counter */
14709         /* (The move number is still ignored)    */
14710     }
14711
14712     return TRUE;
14713 }
14714       
14715 void
14716 EditPositionPasteFEN(char *fen)
14717 {
14718   if (fen != NULL) {
14719     Board initial_position;
14720
14721     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14722       DisplayError(_("Bad FEN position in clipboard"), 0);
14723       return ;
14724     } else {
14725       int savedBlackPlaysFirst = blackPlaysFirst;
14726       EditPositionEvent();
14727       blackPlaysFirst = savedBlackPlaysFirst;
14728       CopyBoard(boards[0], initial_position);
14729       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14730       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14731       DisplayBothClocks();
14732       DrawPosition(FALSE, boards[currentMove]);
14733     }
14734   }
14735 }
14736
14737 static char cseq[12] = "\\   ";
14738
14739 Boolean set_cont_sequence(char *new_seq)
14740 {
14741     int len;
14742     Boolean ret;
14743
14744     // handle bad attempts to set the sequence
14745         if (!new_seq)
14746                 return 0; // acceptable error - no debug
14747
14748     len = strlen(new_seq);
14749     ret = (len > 0) && (len < sizeof(cseq));
14750     if (ret)
14751         strcpy(cseq, new_seq);
14752     else if (appData.debugMode)
14753         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14754     return ret;
14755 }
14756
14757 /*
14758     reformat a source message so words don't cross the width boundary.  internal
14759     newlines are not removed.  returns the wrapped size (no null character unless
14760     included in source message).  If dest is NULL, only calculate the size required
14761     for the dest buffer.  lp argument indicats line position upon entry, and it's
14762     passed back upon exit.
14763 */
14764 int wrap(char *dest, char *src, int count, int width, int *lp)
14765 {
14766     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14767
14768     cseq_len = strlen(cseq);
14769     old_line = line = *lp;
14770     ansi = len = clen = 0;
14771
14772     for (i=0; i < count; i++)
14773     {
14774         if (src[i] == '\033')
14775             ansi = 1;
14776
14777         // if we hit the width, back up
14778         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14779         {
14780             // store i & len in case the word is too long
14781             old_i = i, old_len = len;
14782
14783             // find the end of the last word
14784             while (i && src[i] != ' ' && src[i] != '\n')
14785             {
14786                 i--;
14787                 len--;
14788             }
14789
14790             // word too long?  restore i & len before splitting it
14791             if ((old_i-i+clen) >= width)
14792             {
14793                 i = old_i;
14794                 len = old_len;
14795             }
14796
14797             // extra space?
14798             if (i && src[i-1] == ' ')
14799                 len--;
14800
14801             if (src[i] != ' ' && src[i] != '\n')
14802             {
14803                 i--;
14804                 if (len)
14805                     len--;
14806             }
14807
14808             // now append the newline and continuation sequence
14809             if (dest)
14810                 dest[len] = '\n';
14811             len++;
14812             if (dest)
14813                 strncpy(dest+len, cseq, cseq_len);
14814             len += cseq_len;
14815             line = cseq_len;
14816             clen = cseq_len;
14817             continue;
14818         }
14819
14820         if (dest)
14821             dest[len] = src[i];
14822         len++;
14823         if (!ansi)
14824             line++;
14825         if (src[i] == '\n')
14826             line = 0;
14827         if (src[i] == 'm')
14828             ansi = 0;
14829     }
14830     if (dest && appData.debugMode)
14831     {
14832         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14833             count, width, line, len, *lp);
14834         show_bytes(debugFP, src, count);
14835         fprintf(debugFP, "\ndest: ");
14836         show_bytes(debugFP, dest, len);
14837         fprintf(debugFP, "\n");
14838     }
14839     *lp = dest ? line : old_line;
14840
14841     return len;
14842 }
14843
14844 // [HGM] vari: routines for shelving variations
14845
14846 void 
14847 PushTail(int firstMove, int lastMove)
14848 {
14849         int i, j, nrMoves = lastMove - firstMove;
14850
14851         if(appData.icsActive) { // only in local mode
14852                 forwardMostMove = currentMove; // mimic old ICS behavior
14853                 return;
14854         }
14855         if(storedGames >= MAX_VARIATIONS-1) return;
14856
14857         // push current tail of game on stack
14858         savedResult[storedGames] = gameInfo.result;
14859         savedDetails[storedGames] = gameInfo.resultDetails;
14860         gameInfo.resultDetails = NULL;
14861         savedFirst[storedGames] = firstMove;
14862         savedLast [storedGames] = lastMove;
14863         savedFramePtr[storedGames] = framePtr;
14864         framePtr -= nrMoves; // reserve space for the boards
14865         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14866             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14867             for(j=0; j<MOVE_LEN; j++)
14868                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14869             for(j=0; j<2*MOVE_LEN; j++)
14870                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14871             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14872             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14873             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14874             pvInfoList[firstMove+i-1].depth = 0;
14875             commentList[framePtr+i] = commentList[firstMove+i];
14876             commentList[firstMove+i] = NULL;
14877         }
14878
14879         storedGames++;
14880         forwardMostMove = currentMove; // truncte game so we can start variation
14881         if(storedGames == 1) GreyRevert(FALSE);
14882 }
14883
14884 Boolean
14885 PopTail(Boolean annotate)
14886 {
14887         int i, j, nrMoves;
14888         char buf[8000], moveBuf[20];
14889
14890         if(appData.icsActive) return FALSE; // only in local mode
14891         if(!storedGames) return FALSE; // sanity
14892
14893         storedGames--;
14894         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14895         nrMoves = savedLast[storedGames] - currentMove;
14896         if(annotate) {
14897                 int cnt = 10;
14898                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14899                 else strcpy(buf, "(");
14900                 for(i=currentMove; i<forwardMostMove; i++) {
14901                         if(WhiteOnMove(i))
14902                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14903                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14904                         strcat(buf, moveBuf);
14905                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14906                 }
14907                 strcat(buf, ")");
14908         }
14909         for(i=1; i<nrMoves; i++) { // copy last variation back
14910             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14911             for(j=0; j<MOVE_LEN; j++)
14912                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14913             for(j=0; j<2*MOVE_LEN; j++)
14914                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14915             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14916             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14917             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14918             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14919             commentList[currentMove+i] = commentList[framePtr+i];
14920             commentList[framePtr+i] = NULL;
14921         }
14922         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14923         framePtr = savedFramePtr[storedGames];
14924         gameInfo.result = savedResult[storedGames];
14925         if(gameInfo.resultDetails != NULL) {
14926             free(gameInfo.resultDetails);
14927       }
14928         gameInfo.resultDetails = savedDetails[storedGames];
14929         forwardMostMove = currentMove + nrMoves;
14930         if(storedGames == 0) GreyRevert(TRUE);
14931         return TRUE;
14932 }
14933
14934 void 
14935 CleanupTail()
14936 {       // remove all shelved variations
14937         int i;
14938         for(i=0; i<storedGames; i++) {
14939             if(savedDetails[i])
14940                 free(savedDetails[i]);
14941             savedDetails[i] = NULL;
14942         }
14943         for(i=framePtr; i<MAX_MOVES; i++) {
14944                 if(commentList[i]) free(commentList[i]);
14945                 commentList[i] = NULL;
14946         }
14947         framePtr = MAX_MOVES-1;
14948         storedGames = 0;
14949 }