763b0fc3b634297eaa9feecd22e018103edcf71e
[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 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     {
1107       err = establish();
1108       if (err != 0) 
1109         {
1110           if (*appData.icsCommPort != NULLCHAR) 
1111             {
1112               sprintf(buf, _("Could not open comm port %s"),
1113                       appData.icsCommPort);
1114             }
1115           else 
1116             {
1117               snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1118                        appData.icsHost, appData.icsPort);
1119             }
1120           DisplayFatalError(buf, err, 1);
1121           return;
1122         }
1123
1124         SetICSMode();
1125         telnetISR =
1126           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1127         fromUserISR =
1128           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1131     }
1132   else if (appData.noChessProgram) 
1133     {
1134       SetNCPMode();
1135     } 
1136   else 
1137     {
1138       SetGNUMode();
1139     }
1140   
1141   if (*appData.cmailGameName != NULLCHAR) 
1142     {
1143       SetCmailMode();
1144       OpenLoopback(&cmailPR);
1145       cmailISR =
1146         AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1147     }
1148   
1149   ThawUI();
1150   DisplayMessage("", "");
1151   if (StrCaseCmp(appData.initialMode, "") == 0) 
1152     {
1153       initialMode = BeginningOfGame;
1154     } 
1155   else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) 
1156     {
1157       initialMode = TwoMachinesPlay;
1158     } 
1159   else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) 
1160     {
1161       initialMode = AnalyzeFile;
1162     } 
1163   else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) 
1164     {
1165       initialMode = AnalyzeMode;
1166     } 
1167   else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) 
1168     {
1169       initialMode = MachinePlaysWhite;
1170     } 
1171   else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) 
1172     {
1173       initialMode = MachinePlaysBlack;
1174     } 
1175   else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) 
1176     {
1177       initialMode = EditGame;
1178     } 
1179   else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) 
1180     {
1181       initialMode = EditPosition;
1182     } 
1183   else if (StrCaseCmp(appData.initialMode, "Training") == 0) 
1184     {
1185       initialMode = Training;
1186     } 
1187   else 
1188     {
1189       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1190       DisplayFatalError(buf, 0, 2);
1191       return;
1192     }
1193   
1194   if (appData.matchMode) 
1195     {
1196       /* Set up machine vs. machine match */
1197       if (appData.noChessProgram) 
1198         {
1199           DisplayFatalError(_("Can't have a match with no chess programs"),
1200                             0, 2);
1201           return;
1202         }
1203       matchMode = TRUE;
1204       matchGame = 1;
1205       if (*appData.loadGameFile != NULLCHAR) 
1206         {
1207           int index = appData.loadGameIndex; // [HGM] autoinc
1208           if(index<0) lastIndex = index = 1;
1209           if (!LoadGameFromFile(appData.loadGameFile,
1210                                 index,
1211                                 appData.loadGameFile, FALSE)) 
1212             {
1213               DisplayFatalError(_("Bad game file"), 0, 1);
1214               return;
1215             }
1216         } 
1217       else if (*appData.loadPositionFile != NULLCHAR) 
1218         {
1219           int index = appData.loadPositionIndex; // [HGM] autoinc
1220           if(index<0) lastIndex = index = 1;
1221           if (!LoadPositionFromFile(appData.loadPositionFile,
1222                                     index,
1223                                     appData.loadPositionFile)) 
1224             {
1225               DisplayFatalError(_("Bad position file"), 0, 1);
1226               return;
1227             }
1228         }
1229       TwoMachinesEvent();
1230     } 
1231   else if (*appData.cmailGameName != NULLCHAR) 
1232     {
1233       /* Set up cmail mode */
1234       ReloadCmailMsgEvent(TRUE);
1235     } 
1236   else 
1237     {
1238       /* Set up other modes */
1239       if (initialMode == AnalyzeFile) 
1240         {
1241           if (*appData.loadGameFile == NULLCHAR) 
1242             {
1243               DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1244               return;
1245             }
1246         }
1247       if (*appData.loadGameFile != NULLCHAR) 
1248         {
1249           (void) LoadGameFromFile(appData.loadGameFile,
1250                                   appData.loadGameIndex,
1251                                   appData.loadGameFile, TRUE);
1252         } 
1253       else if (*appData.loadPositionFile != NULLCHAR) 
1254         {
1255           (void) LoadPositionFromFile(appData.loadPositionFile,
1256                                       appData.loadPositionIndex,
1257                                       appData.loadPositionFile);
1258           /* [HGM] try to make self-starting even after FEN load */
1259           /* to allow automatic setup of fairy variants with wtm */
1260           if(initialMode == BeginningOfGame && !blackPlaysFirst) 
1261             {
1262               gameMode = BeginningOfGame;
1263               setboardSpoiledMachineBlack = 1;
1264             }
1265           /* [HGM] loadPos: make that every new game uses the setup */
1266           /* from file as long as we do not switch variant          */
1267           if(!blackPlaysFirst) 
1268             {
1269               startedFromPositionFile = TRUE;
1270               CopyBoard(filePosition, boards[0]);
1271             }
1272         }
1273       if (initialMode == AnalyzeMode) 
1274         {
1275           if (appData.noChessProgram) 
1276             {
1277               DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1278               return;
1279             }
1280           if (appData.icsActive) 
1281             {
1282               DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1283               return;
1284             }
1285           AnalyzeModeEvent();
1286         } 
1287       else if (initialMode == AnalyzeFile) 
1288         {
1289           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1290           ShowThinkingEvent();
1291           AnalyzeFileEvent();
1292           AnalysisPeriodicEvent(1);
1293         } 
1294       else if (initialMode == MachinePlaysWhite) 
1295         {
1296           if (appData.noChessProgram) 
1297             {
1298               DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1299                                 0, 2);
1300               return;
1301             }
1302           if (appData.icsActive) 
1303             {
1304               DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1305                                 0, 2);
1306               return;
1307             }
1308           MachineWhiteEvent();
1309         } 
1310       else if (initialMode == MachinePlaysBlack) 
1311         {
1312           if (appData.noChessProgram) 
1313             {
1314               DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1315                                 0, 2);
1316               return;
1317             }
1318           if (appData.icsActive) 
1319             {
1320               DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1321                                 0, 2);
1322               return;
1323             }
1324           MachineBlackEvent();
1325         } 
1326       else if (initialMode == TwoMachinesPlay) 
1327         {
1328           if (appData.noChessProgram) 
1329             {
1330               DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1331                                 0, 2);
1332               return;
1333             }
1334           if (appData.icsActive) 
1335             {
1336               DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1337                                 0, 2);
1338               return;
1339             }
1340           TwoMachinesEvent();
1341         } 
1342       else if (initialMode == EditGame) 
1343         {
1344           EditGameEvent();
1345         } 
1346       else if (initialMode == EditPosition) 
1347         {
1348           EditPositionEvent();
1349         } 
1350       else if (initialMode == Training) 
1351         {
1352           if (*appData.loadGameFile == NULLCHAR) 
1353             {
1354               DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1355               return;
1356             }
1357           TrainingEvent();
1358         }
1359     }
1360   
1361   return;
1362 }
1363
1364 /*
1365  * Establish will establish a contact to a remote host.port.
1366  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1367  *  used to talk to the host.
1368  * Returns 0 if okay, error code if not.
1369  */
1370 int
1371 establish()
1372 {
1373     char buf[MSG_SIZ];
1374
1375     if (*appData.icsCommPort != NULLCHAR) {
1376         /* Talk to the host through a serial comm port */
1377         return OpenCommPort(appData.icsCommPort, &icsPR);
1378
1379     } else if (*appData.gateway != NULLCHAR) {
1380         if (*appData.remoteShell == NULLCHAR) {
1381             /* Use the rcmd protocol to run telnet program on a gateway host */
1382             snprintf(buf, sizeof(buf), "%s %s %s",
1383                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1384             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1385
1386         } else {
1387             /* Use the rsh program to run telnet program on a gateway host */
1388             if (*appData.remoteUser == NULLCHAR) {
1389                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1390                         appData.gateway, appData.telnetProgram,
1391                         appData.icsHost, appData.icsPort);
1392             } else {
1393                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1394                         appData.remoteShell, appData.gateway,
1395                         appData.remoteUser, appData.telnetProgram,
1396                         appData.icsHost, appData.icsPort);
1397             }
1398             return StartChildProcess(buf, "", &icsPR);
1399
1400         }
1401     } else if (appData.useTelnet) {
1402         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1403
1404     } else {
1405         /* TCP socket interface differs somewhat between
1406            Unix and NT; handle details in the front end.
1407            */
1408         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1409     }
1410 }
1411
1412 void
1413 show_bytes(fp, buf, count)
1414      FILE *fp;
1415      char *buf;
1416      int count;
1417 {
1418     while (count--) {
1419         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1420             fprintf(fp, "\\%03o", *buf & 0xff);
1421         } else {
1422             putc(*buf, fp);
1423         }
1424         buf++;
1425     }
1426     fflush(fp);
1427 }
1428
1429 /* Returns an errno value */
1430 int
1431 OutputMaybeTelnet(pr, message, count, outError)
1432      ProcRef pr;
1433      char *message;
1434      int count;
1435      int *outError;
1436 {
1437     char buf[8192], *p, *q, *buflim;
1438     int left, newcount, outcount;
1439
1440     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1441         *appData.gateway != NULLCHAR) {
1442         if (appData.debugMode) {
1443             fprintf(debugFP, ">ICS: ");
1444             show_bytes(debugFP, message, count);
1445             fprintf(debugFP, "\n");
1446         }
1447         return OutputToProcess(pr, message, count, outError);
1448     }
1449
1450     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1451     p = message;
1452     q = buf;
1453     left = count;
1454     newcount = 0;
1455     while (left) {
1456         if (q >= buflim) {
1457             if (appData.debugMode) {
1458                 fprintf(debugFP, ">ICS: ");
1459                 show_bytes(debugFP, buf, newcount);
1460                 fprintf(debugFP, "\n");
1461             }
1462             outcount = OutputToProcess(pr, buf, newcount, outError);
1463             if (outcount < newcount) return -1; /* to be sure */
1464             q = buf;
1465             newcount = 0;
1466         }
1467         if (*p == '\n') {
1468             *q++ = '\r';
1469             newcount++;
1470         } else if (((unsigned char) *p) == TN_IAC) {
1471             *q++ = (char) TN_IAC;
1472             newcount ++;
1473         }
1474         *q++ = *p++;
1475         newcount++;
1476         left--;
1477     }
1478     if (appData.debugMode) {
1479         fprintf(debugFP, ">ICS: ");
1480         show_bytes(debugFP, buf, newcount);
1481         fprintf(debugFP, "\n");
1482     }
1483     outcount = OutputToProcess(pr, buf, newcount, outError);
1484     if (outcount < newcount) return -1; /* to be sure */
1485     return count;
1486 }
1487
1488 void
1489 read_from_player(isr, closure, message, count, error)
1490      InputSourceRef isr;
1491      VOIDSTAR closure;
1492      char *message;
1493      int count;
1494      int error;
1495 {
1496     int outError, outCount;
1497     static int gotEof = 0;
1498
1499     /* Pass data read from player on to ICS */
1500     if (count > 0) {
1501         gotEof = 0;
1502         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1503         if (outCount < count) {
1504             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1505         }
1506     } else if (count < 0) {
1507         RemoveInputSource(isr);
1508         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1509     } else if (gotEof++ > 0) {
1510         RemoveInputSource(isr);
1511         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1512     }
1513 }
1514
1515 void
1516 KeepAlive()
1517 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1518     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1519     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1520     SendToICS("date\n");
1521     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1522 }
1523
1524 /* added routine for printf style output to ics */
1525 void ics_printf(char *format, ...)
1526 {
1527     char buffer[MSG_SIZ];
1528     va_list args;
1529
1530     va_start(args, format);
1531     vsnprintf(buffer, sizeof(buffer), format, args);
1532     buffer[sizeof(buffer)-1] = '\0';
1533     SendToICS(buffer);
1534     va_end(args);
1535 }
1536
1537 void
1538 SendToICS(s)
1539      char *s;
1540 {
1541     int count, outCount, outError;
1542
1543     if (icsPR == NULL) return;
1544
1545     count = strlen(s);
1546     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1547     if (outCount < count) {
1548         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1549     }
1550 }
1551
1552 /* This is used for sending logon scripts to the ICS. Sending
1553    without a delay causes problems when using timestamp on ICC
1554    (at least on my machine). */
1555 void
1556 SendToICSDelayed(s,msdelay)
1557      char *s;
1558      long msdelay;
1559 {
1560     int count, outCount, outError;
1561
1562     if (icsPR == NULL) return;
1563
1564     count = strlen(s);
1565     if (appData.debugMode) {
1566         fprintf(debugFP, ">ICS: ");
1567         show_bytes(debugFP, s, count);
1568         fprintf(debugFP, "\n");
1569     }
1570     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1571                                       msdelay);
1572     if (outCount < count) {
1573         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1574     }
1575 }
1576
1577
1578 /* Remove all highlighting escape sequences in s
1579    Also deletes any suffix starting with '('
1580    */
1581 char *
1582 StripHighlightAndTitle(s)
1583      char *s;
1584 {
1585     static char retbuf[MSG_SIZ];
1586     char *p = retbuf;
1587
1588     while (*s != NULLCHAR) {
1589         while (*s == '\033') {
1590             while (*s != NULLCHAR && !isalpha(*s)) s++;
1591             if (*s != NULLCHAR) s++;
1592         }
1593         while (*s != NULLCHAR && *s != '\033') {
1594             if (*s == '(' || *s == '[') {
1595                 *p = NULLCHAR;
1596                 return retbuf;
1597             }
1598             *p++ = *s++;
1599         }
1600     }
1601     *p = NULLCHAR;
1602     return retbuf;
1603 }
1604
1605 /* Remove all highlighting escape sequences in s */
1606 char *
1607 StripHighlight(s)
1608      char *s;
1609 {
1610     static char retbuf[MSG_SIZ];
1611     char *p = retbuf;
1612
1613     while (*s != NULLCHAR) {
1614         while (*s == '\033') {
1615             while (*s != NULLCHAR && !isalpha(*s)) s++;
1616             if (*s != NULLCHAR) s++;
1617         }
1618         while (*s != NULLCHAR && *s != '\033') {
1619             *p++ = *s++;
1620         }
1621     }
1622     *p = NULLCHAR;
1623     return retbuf;
1624 }
1625
1626 char *variantNames[] = VARIANT_NAMES;
1627 char *
1628 VariantName(v)
1629      VariantClass v;
1630 {
1631     return variantNames[v];
1632 }
1633
1634
1635 /* Identify a variant from the strings the chess servers use or the
1636    PGN Variant tag names we use. */
1637 VariantClass
1638 StringToVariant(e)
1639      char *e;
1640 {
1641     char *p;
1642     int wnum = -1;
1643     VariantClass v = VariantNormal;
1644     int i, found = FALSE;
1645     char buf[MSG_SIZ];
1646
1647     if (!e) return v;
1648
1649     /* [HGM] skip over optional board-size prefixes */
1650     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1651         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1652         while( *e++ != '_');
1653     }
1654
1655     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1656         v = VariantNormal;
1657         found = TRUE;
1658     } else
1659     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1660       if (StrCaseStr(e, variantNames[i])) {
1661         v = (VariantClass) i;
1662         found = TRUE;
1663         break;
1664       }
1665     }
1666
1667     if (!found) {
1668       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1669           || StrCaseStr(e, "wild/fr")
1670           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1671         v = VariantFischeRandom;
1672       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1673                  (i = 1, p = StrCaseStr(e, "w"))) {
1674         p += i;
1675         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1676         if (isdigit(*p)) {
1677           wnum = atoi(p);
1678         } else {
1679           wnum = -1;
1680         }
1681         switch (wnum) {
1682         case 0: /* FICS only, actually */
1683         case 1:
1684           /* Castling legal even if K starts on d-file */
1685           v = VariantWildCastle;
1686           break;
1687         case 2:
1688         case 3:
1689         case 4:
1690           /* Castling illegal even if K & R happen to start in
1691              normal positions. */
1692           v = VariantNoCastle;
1693           break;
1694         case 5:
1695         case 7:
1696         case 8:
1697         case 10:
1698         case 11:
1699         case 12:
1700         case 13:
1701         case 14:
1702         case 15:
1703         case 18:
1704         case 19:
1705           /* Castling legal iff K & R start in normal positions */
1706           v = VariantNormal;
1707           break;
1708         case 6:
1709         case 20:
1710         case 21:
1711           /* Special wilds for position setup; unclear what to do here */
1712           v = VariantLoadable;
1713           break;
1714         case 9:
1715           /* Bizarre ICC game */
1716           v = VariantTwoKings;
1717           break;
1718         case 16:
1719           v = VariantKriegspiel;
1720           break;
1721         case 17:
1722           v = VariantLosers;
1723           break;
1724         case 22:
1725           v = VariantFischeRandom;
1726           break;
1727         case 23:
1728           v = VariantCrazyhouse;
1729           break;
1730         case 24:
1731           v = VariantBughouse;
1732           break;
1733         case 25:
1734           v = Variant3Check;
1735           break;
1736         case 26:
1737           /* Not quite the same as FICS suicide! */
1738           v = VariantGiveaway;
1739           break;
1740         case 27:
1741           v = VariantAtomic;
1742           break;
1743         case 28:
1744           v = VariantShatranj;
1745           break;
1746
1747         /* Temporary names for future ICC types.  The name *will* change in
1748            the next xboard/WinBoard release after ICC defines it. */
1749         case 29:
1750           v = Variant29;
1751           break;
1752         case 30:
1753           v = Variant30;
1754           break;
1755         case 31:
1756           v = Variant31;
1757           break;
1758         case 32:
1759           v = Variant32;
1760           break;
1761         case 33:
1762           v = Variant33;
1763           break;
1764         case 34:
1765           v = Variant34;
1766           break;
1767         case 35:
1768           v = Variant35;
1769           break;
1770         case 36:
1771           v = Variant36;
1772           break;
1773         case 37:
1774           v = VariantShogi;
1775           break;
1776         case 38:
1777           v = VariantXiangqi;
1778           break;
1779         case 39:
1780           v = VariantCourier;
1781           break;
1782         case 40:
1783           v = VariantGothic;
1784           break;
1785         case 41:
1786           v = VariantCapablanca;
1787           break;
1788         case 42:
1789           v = VariantKnightmate;
1790           break;
1791         case 43:
1792           v = VariantFairy;
1793           break;
1794         case 44:
1795           v = VariantCylinder;
1796           break;
1797         case 45:
1798           v = VariantFalcon;
1799           break;
1800         case 46:
1801           v = VariantCapaRandom;
1802           break;
1803         case 47:
1804           v = VariantBerolina;
1805           break;
1806         case 48:
1807           v = VariantJanus;
1808           break;
1809         case 49:
1810           v = VariantSuper;
1811           break;
1812         case 50:
1813           v = VariantGreat;
1814           break;
1815         case -1:
1816           /* Found "wild" or "w" in the string but no number;
1817              must assume it's normal chess. */
1818           v = VariantNormal;
1819           break;
1820         default:
1821           sprintf(buf, _("Unknown wild type %d"), wnum);
1822           DisplayError(buf, 0);
1823           v = VariantUnknown;
1824           break;
1825         }
1826       }
1827     }
1828     if (appData.debugMode) {
1829       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1830               e, wnum, VariantName(v));
1831     }
1832     return v;
1833 }
1834
1835 static int leftover_start = 0, leftover_len = 0;
1836 char star_match[STAR_MATCH_N][MSG_SIZ];
1837
1838 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1839    advance *index beyond it, and set leftover_start to the new value of
1840    *index; else return FALSE.  If pattern contains the character '*', it
1841    matches any sequence of characters not containing '\r', '\n', or the
1842    character following the '*' (if any), and the matched sequence(s) are
1843    copied into star_match.
1844    */
1845 int
1846 looking_at(buf, index, pattern)
1847      char *buf;
1848      int *index;
1849      char *pattern;
1850 {
1851     char *bufp = &buf[*index], *patternp = pattern;
1852     int star_count = 0;
1853     char *matchp = star_match[0];
1854
1855     for (;;) {
1856         if (*patternp == NULLCHAR) {
1857             *index = leftover_start = bufp - buf;
1858             *matchp = NULLCHAR;
1859             return TRUE;
1860         }
1861         if (*bufp == NULLCHAR) return FALSE;
1862         if (*patternp == '*') {
1863             if (*bufp == *(patternp + 1)) {
1864                 *matchp = NULLCHAR;
1865                 matchp = star_match[++star_count];
1866                 patternp += 2;
1867                 bufp++;
1868                 continue;
1869             } else if (*bufp == '\n' || *bufp == '\r') {
1870                 patternp++;
1871                 if (*patternp == NULLCHAR)
1872                   continue;
1873                 else
1874                   return FALSE;
1875             } else {
1876                 *matchp++ = *bufp++;
1877                 continue;
1878             }
1879         }
1880         if (*patternp != *bufp) return FALSE;
1881         patternp++;
1882         bufp++;
1883     }
1884 }
1885
1886 void
1887 SendToPlayer(data, length)
1888      char *data;
1889      int length;
1890 {
1891     int error, outCount;
1892     outCount = OutputToProcess(NoProc, data, length, &error);
1893     if (outCount < length) {
1894         DisplayFatalError(_("Error writing to display"), error, 1);
1895     }
1896 }
1897
1898 void
1899 PackHolding(packed, holding)
1900      char packed[];
1901      char *holding;
1902 {
1903     char *p = holding;
1904     char *q = packed;
1905     int runlength = 0;
1906     int curr = 9999;
1907     do {
1908         if (*p == curr) {
1909             runlength++;
1910         } else {
1911             switch (runlength) {
1912               case 0:
1913                 break;
1914               case 1:
1915                 *q++ = curr;
1916                 break;
1917               case 2:
1918                 *q++ = curr;
1919                 *q++ = curr;
1920                 break;
1921               default:
1922                 sprintf(q, "%d", runlength);
1923                 while (*q) q++;
1924                 *q++ = curr;
1925                 break;
1926             }
1927             runlength = 1;
1928             curr = *p;
1929         }
1930     } while (*p++);
1931     *q = NULLCHAR;
1932 }
1933
1934 /* Telnet protocol requests from the front end */
1935 void
1936 TelnetRequest(ddww, option)
1937      unsigned char ddww, option;
1938 {
1939     unsigned char msg[3];
1940     int outCount, outError;
1941
1942     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1943
1944     if (appData.debugMode) {
1945         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1946         switch (ddww) {
1947           case TN_DO:
1948             ddwwStr = "DO";
1949             break;
1950           case TN_DONT:
1951             ddwwStr = "DONT";
1952             break;
1953           case TN_WILL:
1954             ddwwStr = "WILL";
1955             break;
1956           case TN_WONT:
1957             ddwwStr = "WONT";
1958             break;
1959           default:
1960             ddwwStr = buf1;
1961             sprintf(buf1, "%d", ddww);
1962             break;
1963         }
1964         switch (option) {
1965           case TN_ECHO:
1966             optionStr = "ECHO";
1967             break;
1968           default:
1969             optionStr = buf2;
1970             sprintf(buf2, "%d", option);
1971             break;
1972         }
1973         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1974     }
1975     msg[0] = TN_IAC;
1976     msg[1] = ddww;
1977     msg[2] = option;
1978     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1979     if (outCount < 3) {
1980         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1981     }
1982 }
1983
1984 void
1985 DoEcho()
1986 {
1987     if (!appData.icsActive) return;
1988     TelnetRequest(TN_DO, TN_ECHO);
1989 }
1990
1991 void
1992 DontEcho()
1993 {
1994     if (!appData.icsActive) return;
1995     TelnetRequest(TN_DONT, TN_ECHO);
1996 }
1997
1998 void
1999 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2000 {
2001     /* put the holdings sent to us by the server on the board holdings area */
2002     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2003     char p;
2004     ChessSquare piece;
2005
2006     if(gameInfo.holdingsWidth < 2)  return;
2007     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2008         return; // prevent overwriting by pre-board holdings
2009
2010     if( (int)lowestPiece >= BlackPawn ) {
2011         holdingsColumn = 0;
2012         countsColumn = 1;
2013         holdingsStartRow = BOARD_HEIGHT-1;
2014         direction = -1;
2015     } else {
2016         holdingsColumn = BOARD_WIDTH-1;
2017         countsColumn = BOARD_WIDTH-2;
2018         holdingsStartRow = 0;
2019         direction = 1;
2020     }
2021
2022     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2023         board[i][holdingsColumn] = EmptySquare;
2024         board[i][countsColumn]   = (ChessSquare) 0;
2025     }
2026     while( (p=*holdings++) != NULLCHAR ) {
2027         piece = CharToPiece( ToUpper(p) );
2028         if(piece == EmptySquare) continue;
2029         /*j = (int) piece - (int) WhitePawn;*/
2030         j = PieceToNumber(piece);
2031         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2032         if(j < 0) continue;               /* should not happen */
2033         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2034         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2035         board[holdingsStartRow+j*direction][countsColumn]++;
2036     }
2037 }
2038
2039
2040 void
2041 VariantSwitch(Board board, VariantClass newVariant)
2042 {
2043    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2044    Board oldBoard;
2045
2046    startedFromPositionFile = FALSE;
2047    if(gameInfo.variant == newVariant) return;
2048
2049    /* [HGM] This routine is called each time an assignment is made to
2050     * gameInfo.variant during a game, to make sure the board sizes
2051     * are set to match the new variant. If that means adding or deleting
2052     * holdings, we shift the playing board accordingly
2053     * This kludge is needed because in ICS observe mode, we get boards
2054     * of an ongoing game without knowing the variant, and learn about the
2055     * latter only later. This can be because of the move list we requested,
2056     * in which case the game history is refilled from the beginning anyway,
2057     * but also when receiving holdings of a crazyhouse game. In the latter
2058     * case we want to add those holdings to the already received position.
2059     */
2060    
2061    if (appData.debugMode) {
2062      fprintf(debugFP, "Switch board from %s to %s\n",
2063              VariantName(gameInfo.variant), VariantName(newVariant));
2064      setbuf(debugFP, NULL);
2065    }
2066    shuffleOpenings = 0;       /* [HGM] shuffle */
2067    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2068    switch(newVariant) 
2069      {
2070      case VariantShogi:
2071        newWidth = 9;  newHeight = 9;
2072        gameInfo.holdingsSize = 7;
2073      case VariantBughouse:
2074      case VariantCrazyhouse:
2075        newHoldingsWidth = 2; break;
2076      case VariantGreat:
2077        newWidth = 10;
2078      case VariantSuper:
2079        newHoldingsWidth = 2;
2080        gameInfo.holdingsSize = 8;
2081        break;
2082      case VariantGothic:
2083      case VariantCapablanca:
2084      case VariantCapaRandom:
2085        newWidth = 10;
2086      default:
2087        newHoldingsWidth = gameInfo.holdingsSize = 0;
2088      };
2089    
2090    if(newWidth  != gameInfo.boardWidth  ||
2091       newHeight != gameInfo.boardHeight ||
2092       newHoldingsWidth != gameInfo.holdingsWidth ) {
2093      
2094      /* shift position to new playing area, if needed */
2095      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2096        for(i=0; i<BOARD_HEIGHT; i++) 
2097          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2098            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2099              board[i][j];
2100        for(i=0; i<newHeight; i++) {
2101          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2102          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2103        }
2104      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2105        for(i=0; i<BOARD_HEIGHT; i++)
2106          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2107            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2108              board[i][j];
2109      }
2110      gameInfo.boardWidth  = newWidth;
2111      gameInfo.boardHeight = newHeight;
2112      gameInfo.holdingsWidth = newHoldingsWidth;
2113      gameInfo.variant = newVariant;
2114      InitDrawingSizes(-2, 0);
2115    } else gameInfo.variant = newVariant;
2116    CopyBoard(oldBoard, board);   // remember correctly formatted board
2117      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2118    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2119 }
2120
2121 static int loggedOn = FALSE;
2122
2123 /*-- Game start info cache: --*/
2124 int gs_gamenum;
2125 char gs_kind[MSG_SIZ];
2126 static char player1Name[128] = "";
2127 static char player2Name[128] = "";
2128 static char cont_seq[] = "\n\\   ";
2129 static int player1Rating = -1;
2130 static int player2Rating = -1;
2131 /*----------------------------*/
2132
2133 ColorClass curColor = ColorNormal;
2134 int suppressKibitz = 0;
2135
2136 // [HGM] seekgraph
2137 Boolean soughtPending = FALSE;
2138 Boolean seekGraphUp;
2139 #define MAX_SEEK_ADS 200
2140 #define SQUARE 0x80
2141 char *seekAdList[MAX_SEEK_ADS];
2142 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2143 float tcList[MAX_SEEK_ADS];
2144 char colorList[MAX_SEEK_ADS];
2145 int nrOfSeekAds = 0;
2146 int minRating = 1010, maxRating = 2800;
2147 int hMargin = 10, vMargin = 20, h, w;
2148 extern int squareSize, lineGap;
2149
2150 void
2151 PlotSeekAd(int i)
2152 {
2153         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2154         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2155         if(r < minRating+100 && r >=0 ) r = minRating+100;
2156         if(r > maxRating) r = maxRating;
2157         if(tc < 1.) tc = 1.;
2158         if(tc > 95.) tc = 95.;
2159         x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2160         y = ((double)r - minRating)/(maxRating - minRating)
2161             * (h-vMargin-squareSize/8-1) + vMargin;
2162         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2163         if(strstr(seekAdList[i], " u ")) color = 1;
2164         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2165            !strstr(seekAdList[i], "bullet") &&
2166            !strstr(seekAdList[i], "blitz") &&
2167            !strstr(seekAdList[i], "standard") ) color = 2;
2168         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2169         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2170 }
2171
2172 void
2173 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2174 {
2175         char buf[MSG_SIZ], *ext = "";
2176         VariantClass v = StringToVariant(type);
2177         if(strstr(type, "wild")) {
2178             ext = type + 4; // append wild number
2179             if(v == VariantFischeRandom) type = "chess960"; else
2180             if(v == VariantLoadable) type = "setup"; else
2181             type = VariantName(v);
2182         }
2183         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2184         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2185             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2186             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2187             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2188             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2189             seekNrList[nrOfSeekAds] = nr;
2190             zList[nrOfSeekAds] = 0;
2191             seekAdList[nrOfSeekAds++] = StrSave(buf);
2192             if(plot) PlotSeekAd(nrOfSeekAds-1);
2193         }
2194 }
2195
2196 void
2197 EraseSeekDot(int i)
2198 {
2199     int x = xList[i], y = yList[i], d=squareSize/4, k;
2200     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2201     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2202     // now replot every dot that overlapped
2203     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2204         int xx = xList[k], yy = yList[k];
2205         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2206             DrawSeekDot(xx, yy, colorList[k]);
2207     }
2208 }
2209
2210 void
2211 RemoveSeekAd(int nr)
2212 {
2213         int i;
2214         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2215             EraseSeekDot(i);
2216             if(seekAdList[i]) free(seekAdList[i]);
2217             seekAdList[i] = seekAdList[--nrOfSeekAds];
2218             seekNrList[i] = seekNrList[nrOfSeekAds];
2219             ratingList[i] = ratingList[nrOfSeekAds];
2220             colorList[i]  = colorList[nrOfSeekAds];
2221             tcList[i] = tcList[nrOfSeekAds];
2222             xList[i]  = xList[nrOfSeekAds];
2223             yList[i]  = yList[nrOfSeekAds];
2224             zList[i]  = zList[nrOfSeekAds];
2225             seekAdList[nrOfSeekAds] = NULL;
2226             break;
2227         }
2228 }
2229
2230 Boolean
2231 MatchSoughtLine(char *line)
2232 {
2233     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2234     int nr, base, inc, u=0; char dummy;
2235
2236     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2237        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2238        (u=1) &&
2239        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2240         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2241         // match: compact and save the line
2242         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2243         return TRUE;
2244     }
2245     return FALSE;
2246 }
2247
2248 int
2249 DrawSeekGraph()
2250 {
2251     if(!seekGraphUp) return FALSE;
2252     int i;
2253     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2254     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2255
2256     DrawSeekBackground(0, 0, w, h);
2257     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2258     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2259     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2260         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2261         yy = h-1-yy;
2262         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2263         if(i%500 == 0) {
2264             char buf[MSG_SIZ];
2265             sprintf(buf, "%d", i);
2266             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2267         }
2268     }
2269     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2270     for(i=1; i<100; i+=(i<10?1:5)) {
2271         int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2272         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2273         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2274             char buf[MSG_SIZ];
2275             sprintf(buf, "%d", i);
2276             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2277         }
2278     }
2279     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2280     return TRUE;
2281 }
2282
2283 int SeekGraphClick(ClickType click, int x, int y, int moving)
2284 {
2285     static int lastDown = 0, displayed = 0, lastSecond;
2286     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2287         if(click == Release || moving) return FALSE;
2288         nrOfSeekAds = 0;
2289         soughtPending = TRUE;
2290         SendToICS(ics_prefix);
2291         SendToICS("sought\n"); // should this be "sought all"?
2292     } else { // issue challenge based on clicked ad
2293         int dist = 10000; int i, closest = 0, second = 0;
2294         for(i=0; i<nrOfSeekAds; i++) {
2295             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2296             if(d < dist) { dist = d; closest = i; }
2297             second += (d - zList[i] < 120); // count in-range ads
2298             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2299         }
2300         if(dist < 120) {
2301             char buf[MSG_SIZ];
2302             second = (second > 1);
2303             if(displayed != closest || second != lastSecond) {
2304                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2305                 lastSecond = second; displayed = closest;
2306             }
2307             sprintf(buf, "play %d\n", seekNrList[closest]);
2308             if(click == Press) {
2309                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2310                 lastDown = closest;
2311                 return TRUE;
2312             } // on press 'hit', only show info
2313             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2314             SendToICS(ics_prefix);
2315             SendToICS(buf); // should this be "sought all"?
2316         } else if(click == Release) { // release 'miss' is ignored
2317             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2318             if(moving == 2) { // right up-click
2319                 nrOfSeekAds = 0; // refresh graph
2320                 soughtPending = TRUE;
2321                 SendToICS(ics_prefix);
2322                 SendToICS("sought\n"); // should this be "sought all"?
2323             }
2324             return TRUE;
2325         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2326         // press miss or release hit 'pop down' seek graph
2327         seekGraphUp = FALSE;
2328         DrawPosition(TRUE, NULL);
2329     }
2330     return TRUE;
2331 }
2332
2333 void
2334 read_from_ics(isr, closure, data, count, error)
2335      InputSourceRef isr;
2336      VOIDSTAR closure;
2337      char *data;
2338      int count;
2339      int error;
2340 {
2341 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2342 #define STARTED_NONE 0
2343 #define STARTED_MOVES 1
2344 #define STARTED_BOARD 2
2345 #define STARTED_OBSERVE 3
2346 #define STARTED_HOLDINGS 4
2347 #define STARTED_CHATTER 5
2348 #define STARTED_COMMENT 6
2349 #define STARTED_MOVES_NOHIDE 7
2350
2351     static int started = STARTED_NONE;
2352     static char parse[20000];
2353     static int parse_pos = 0;
2354     static char buf[BUF_SIZE + 1];
2355     static int firstTime = TRUE, intfSet = FALSE;
2356     static ColorClass prevColor = ColorNormal;
2357     static int savingComment = FALSE;
2358     static int cmatch = 0; // continuation sequence match
2359     char *bp;
2360     char str[500];
2361     int i, oldi;
2362     int buf_len;
2363     int next_out;
2364     int tkind;
2365     int backup;    /* [DM] For zippy color lines */
2366     char *p;
2367     char talker[MSG_SIZ]; // [HGM] chat
2368     int channel;
2369
2370     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2371
2372     if (appData.debugMode) {
2373       if (!error) {
2374         fprintf(debugFP, "<ICS: ");
2375         show_bytes(debugFP, data, count);
2376         fprintf(debugFP, "\n");
2377       }
2378     }
2379
2380     if (appData.debugMode) { int f = forwardMostMove;
2381         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2382                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2383                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2384     }
2385     if (count > 0) {
2386         /* If last read ended with a partial line that we couldn't parse,
2387            prepend it to the new read and try again. */
2388         if (leftover_len > 0) {
2389             for (i=0; i<leftover_len; i++)
2390               buf[i] = buf[leftover_start + i];
2391         }
2392
2393     /* copy new characters into the buffer */
2394     bp = buf + leftover_len;
2395     buf_len=leftover_len;
2396     for (i=0; i<count; i++)
2397     {
2398         // ignore these
2399         if (data[i] == '\r')
2400             continue;
2401
2402         // join lines split by ICS?
2403         if (!appData.noJoin)
2404         {
2405             /*
2406                 Joining just consists of finding matches against the
2407                 continuation sequence, and discarding that sequence
2408                 if found instead of copying it.  So, until a match
2409                 fails, there's nothing to do since it might be the
2410                 complete sequence, and thus, something we don't want
2411                 copied.
2412             */
2413             if (data[i] == cont_seq[cmatch])
2414             {
2415                 cmatch++;
2416                 if (cmatch == strlen(cont_seq))
2417                 {
2418                     cmatch = 0; // complete match.  just reset the counter
2419
2420                     /*
2421                         it's possible for the ICS to not include the space
2422                         at the end of the last word, making our [correct]
2423                         join operation fuse two separate words.  the server
2424                         does this when the space occurs at the width setting.
2425                     */
2426                     if (!buf_len || buf[buf_len-1] != ' ')
2427                     {
2428                         *bp++ = ' ';
2429                         buf_len++;
2430                     }
2431                 }
2432                 continue;
2433             }
2434             else if (cmatch)
2435             {
2436                 /*
2437                     match failed, so we have to copy what matched before
2438                     falling through and copying this character.  In reality,
2439                     this will only ever be just the newline character, but
2440                     it doesn't hurt to be precise.
2441                 */
2442                 strncpy(bp, cont_seq, cmatch);
2443                 bp += cmatch;
2444                 buf_len += cmatch;
2445                 cmatch = 0;
2446             }
2447         }
2448
2449         // copy this char
2450         *bp++ = data[i];
2451         buf_len++;
2452     }
2453
2454         buf[buf_len] = NULLCHAR;
2455 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2456         next_out = 0;
2457         leftover_start = 0;
2458
2459         i = 0;
2460         while (i < buf_len) {
2461             /* Deal with part of the TELNET option negotiation
2462                protocol.  We refuse to do anything beyond the
2463                defaults, except that we allow the WILL ECHO option,
2464                which ICS uses to turn off password echoing when we are
2465                directly connected to it.  We reject this option
2466                if localLineEditing mode is on (always on in xboard)
2467                and we are talking to port 23, which might be a real
2468                telnet server that will try to keep WILL ECHO on permanently.
2469              */
2470             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2471                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2472                 unsigned char option;
2473                 oldi = i;
2474                 switch ((unsigned char) buf[++i]) {
2475                   case TN_WILL:
2476                     if (appData.debugMode)
2477                       fprintf(debugFP, "\n<WILL ");
2478                     switch (option = (unsigned char) buf[++i]) {
2479                       case TN_ECHO:
2480                         if (appData.debugMode)
2481                           fprintf(debugFP, "ECHO ");
2482                         /* Reply only if this is a change, according
2483                            to the protocol rules. */
2484                         if (remoteEchoOption) break;
2485                         if (appData.localLineEditing &&
2486                             atoi(appData.icsPort) == TN_PORT) {
2487                             TelnetRequest(TN_DONT, TN_ECHO);
2488                         } else {
2489                             EchoOff();
2490                             TelnetRequest(TN_DO, TN_ECHO);
2491                             remoteEchoOption = TRUE;
2492                         }
2493                         break;
2494                       default:
2495                         if (appData.debugMode)
2496                           fprintf(debugFP, "%d ", option);
2497                         /* Whatever this is, we don't want it. */
2498                         TelnetRequest(TN_DONT, option);
2499                         break;
2500                     }
2501                     break;
2502                   case TN_WONT:
2503                     if (appData.debugMode)
2504                       fprintf(debugFP, "\n<WONT ");
2505                     switch (option = (unsigned char) buf[++i]) {
2506                       case TN_ECHO:
2507                         if (appData.debugMode)
2508                           fprintf(debugFP, "ECHO ");
2509                         /* Reply only if this is a change, according
2510                            to the protocol rules. */
2511                         if (!remoteEchoOption) break;
2512                         EchoOn();
2513                         TelnetRequest(TN_DONT, TN_ECHO);
2514                         remoteEchoOption = FALSE;
2515                         break;
2516                       default:
2517                         if (appData.debugMode)
2518                           fprintf(debugFP, "%d ", (unsigned char) option);
2519                         /* Whatever this is, it must already be turned
2520                            off, because we never agree to turn on
2521                            anything non-default, so according to the
2522                            protocol rules, we don't reply. */
2523                         break;
2524                     }
2525                     break;
2526                   case TN_DO:
2527                     if (appData.debugMode)
2528                       fprintf(debugFP, "\n<DO ");
2529                     switch (option = (unsigned char) buf[++i]) {
2530                       default:
2531                         /* Whatever this is, we refuse to do it. */
2532                         if (appData.debugMode)
2533                           fprintf(debugFP, "%d ", option);
2534                         TelnetRequest(TN_WONT, option);
2535                         break;
2536                     }
2537                     break;
2538                   case TN_DONT:
2539                     if (appData.debugMode)
2540                       fprintf(debugFP, "\n<DONT ");
2541                     switch (option = (unsigned char) buf[++i]) {
2542                       default:
2543                         if (appData.debugMode)
2544                           fprintf(debugFP, "%d ", option);
2545                         /* Whatever this is, we are already not doing
2546                            it, because we never agree to do anything
2547                            non-default, so according to the protocol
2548                            rules, we don't reply. */
2549                         break;
2550                     }
2551                     break;
2552                   case TN_IAC:
2553                     if (appData.debugMode)
2554                       fprintf(debugFP, "\n<IAC ");
2555                     /* Doubled IAC; pass it through */
2556                     i--;
2557                     break;
2558                   default:
2559                     if (appData.debugMode)
2560                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2561                     /* Drop all other telnet commands on the floor */
2562                     break;
2563                 }
2564                 if (oldi > next_out)
2565                   SendToPlayer(&buf[next_out], oldi - next_out);
2566                 if (++i > next_out)
2567                   next_out = i;
2568                 continue;
2569             }
2570
2571             /* OK, this at least will *usually* work */
2572             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2573                 loggedOn = TRUE;
2574             }
2575
2576             if (loggedOn && !intfSet) {
2577                 if (ics_type == ICS_ICC) {
2578                   sprintf(str,
2579                           "/set-quietly interface %s\n/set-quietly style 12\n",
2580                           programVersion);
2581                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2582                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2583                 } else if (ics_type == ICS_CHESSNET) {
2584                   sprintf(str, "/style 12\n");
2585                 } else {
2586                   strcpy(str, "alias $ @\n$set interface ");
2587                   strcat(str, programVersion);
2588                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2589                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2590                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2591 #ifdef WIN32
2592                   strcat(str, "$iset nohighlight 1\n");
2593 #endif
2594                   strcat(str, "$iset lock 1\n$style 12\n");
2595                 }
2596                 SendToICS(str);
2597                 NotifyFrontendLogin();
2598                 intfSet = TRUE;
2599             }
2600
2601             if (started == STARTED_COMMENT) {
2602                 /* Accumulate characters in comment */
2603                 parse[parse_pos++] = buf[i];
2604                 if (buf[i] == '\n') {
2605                     parse[parse_pos] = NULLCHAR;
2606                     if(chattingPartner>=0) {
2607                         char mess[MSG_SIZ];
2608                         sprintf(mess, "%s%s", talker, parse);
2609                         OutputChatMessage(chattingPartner, mess);
2610                         chattingPartner = -1;
2611                     } else
2612                     if(!suppressKibitz) // [HGM] kibitz
2613                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2614                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2615                         int nrDigit = 0, nrAlph = 0, j;
2616                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2617                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2618                         parse[parse_pos] = NULLCHAR;
2619                         // try to be smart: if it does not look like search info, it should go to
2620                         // ICS interaction window after all, not to engine-output window.
2621                         for(j=0; j<parse_pos; j++) { // count letters and digits
2622                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2623                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2624                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2625                         }
2626                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2627                             int depth=0; float score;
2628                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2629                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2630                                 pvInfoList[forwardMostMove-1].depth = depth;
2631                                 pvInfoList[forwardMostMove-1].score = 100*score;
2632                             }
2633                             OutputKibitz(suppressKibitz, parse);
2634                             next_out = i+1; // [HGM] suppress printing in ICS window
2635                         } else {
2636                             char tmp[MSG_SIZ];
2637                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2638                             SendToPlayer(tmp, strlen(tmp));
2639                         }
2640                     }
2641                     started = STARTED_NONE;
2642                 } else {
2643                     /* Don't match patterns against characters in comment */
2644                     i++;
2645                     continue;
2646                 }
2647             }
2648             if (started == STARTED_CHATTER) {
2649                 if (buf[i] != '\n') {
2650                     /* Don't match patterns against characters in chatter */
2651                     i++;
2652                     continue;
2653                 }
2654                 started = STARTED_NONE;
2655             }
2656
2657             /* Kludge to deal with rcmd protocol */
2658             if (firstTime && looking_at(buf, &i, "\001*")) {
2659                 DisplayFatalError(&buf[1], 0, 1);
2660                 continue;
2661             } else {
2662                 firstTime = FALSE;
2663             }
2664
2665             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2666                 ics_type = ICS_ICC;
2667                 ics_prefix = "/";
2668                 if (appData.debugMode)
2669                   fprintf(debugFP, "ics_type %d\n", ics_type);
2670                 continue;
2671             }
2672             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2673                 ics_type = ICS_FICS;
2674                 ics_prefix = "$";
2675                 if (appData.debugMode)
2676                   fprintf(debugFP, "ics_type %d\n", ics_type);
2677                 continue;
2678             }
2679             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2680                 ics_type = ICS_CHESSNET;
2681                 ics_prefix = "/";
2682                 if (appData.debugMode)
2683                   fprintf(debugFP, "ics_type %d\n", ics_type);
2684                 continue;
2685             }
2686
2687             if (!loggedOn &&
2688                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2689                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2690                  looking_at(buf, &i, "will be \"*\""))) {
2691               strcpy(ics_handle, star_match[0]);
2692               continue;
2693             }
2694
2695             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2696               char buf[MSG_SIZ];
2697               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2698               DisplayIcsInteractionTitle(buf);
2699               have_set_title = TRUE;
2700             }
2701
2702             /* skip finger notes */
2703             if (started == STARTED_NONE &&
2704                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2705                  (buf[i] == '1' && buf[i+1] == '0')) &&
2706                 buf[i+2] == ':' && buf[i+3] == ' ') {
2707               started = STARTED_CHATTER;
2708               i += 3;
2709               continue;
2710             }
2711
2712             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2713             if(appData.seekGraph) {
2714                 if(soughtPending && MatchSoughtLine(buf+i)) {
2715                     i = strstr(buf+i, "rated") - buf;
2716                     next_out = leftover_start = i;
2717                     started = STARTED_CHATTER;
2718                     suppressKibitz = TRUE;
2719                     continue;
2720                 }
2721                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2722                         && looking_at(buf, &i, "* ads displayed")) {
2723                     soughtPending = FALSE;
2724                     seekGraphUp = TRUE;
2725                     DrawSeekGraph();
2726                     continue;
2727                 }
2728                 if(appData.autoRefresh) {
2729                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2730                         int s = (ics_type == ICS_ICC); // ICC format differs
2731                         if(seekGraphUp)
2732                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2733                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2734                         looking_at(buf, &i, "*% "); // eat prompt
2735                         next_out = i; // suppress
2736                         continue;
2737                     }
2738                     if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2739                         char *p = star_match[0];
2740                         while(*p) {
2741                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2742                             while(*p && *p++ != ' '); // next
2743                         }
2744                         looking_at(buf, &i, "*% "); // eat prompt
2745                         next_out = i;
2746                         continue;
2747                     }
2748                 }
2749             }
2750
2751             /* skip formula vars */
2752             if (started == STARTED_NONE &&
2753                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2754               started = STARTED_CHATTER;
2755               i += 3;
2756               continue;
2757             }
2758
2759             oldi = i;
2760             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2761             if (appData.autoKibitz && started == STARTED_NONE &&
2762                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2763                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2764                 if(looking_at(buf, &i, "* kibitzes: ") &&
2765                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2766                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2767                         suppressKibitz = TRUE;
2768                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2769                                 && (gameMode == IcsPlayingWhite)) ||
2770                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2771                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2772                             started = STARTED_CHATTER; // own kibitz we simply discard
2773                         else {
2774                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2775                             parse_pos = 0; parse[0] = NULLCHAR;
2776                             savingComment = TRUE;
2777                             suppressKibitz = gameMode != IcsObserving ? 2 :
2778                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2779                         }
2780                         continue;
2781                 } else
2782                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2783                     // suppress the acknowledgements of our own autoKibitz
2784                     char *p;
2785                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2786                     SendToPlayer(star_match[0], strlen(star_match[0]));
2787                     looking_at(buf, &i, "*% "); // eat prompt
2788                     next_out = i;
2789                 }
2790             } // [HGM] kibitz: end of patch
2791
2792 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2793
2794             // [HGM] chat: intercept tells by users for which we have an open chat window
2795             channel = -1;
2796             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2797                                            looking_at(buf, &i, "* whispers:") ||
2798                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2799                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2800                 int p;
2801                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2802                 chattingPartner = -1;
2803
2804                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2805                 for(p=0; p<MAX_CHAT; p++) {
2806                     if(channel == atoi(chatPartner[p])) {
2807                     talker[0] = '['; strcat(talker, "] ");
2808                     chattingPartner = p; break;
2809                     }
2810                 } else
2811                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2812                 for(p=0; p<MAX_CHAT; p++) {
2813                     if(!strcmp("WHISPER", chatPartner[p])) {
2814                         talker[0] = '['; strcat(talker, "] ");
2815                         chattingPartner = p; break;
2816                     }
2817                 }
2818                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2819                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2820                     talker[0] = 0;
2821                     chattingPartner = p; break;
2822                 }
2823                 if(chattingPartner<0) i = oldi; else {
2824                     started = STARTED_COMMENT;
2825                     parse_pos = 0; parse[0] = NULLCHAR;
2826                     savingComment = 3 + chattingPartner; // counts as TRUE
2827                     suppressKibitz = TRUE;
2828                 }
2829             } // [HGM] chat: end of patch
2830
2831             if (appData.zippyTalk || appData.zippyPlay) {
2832                 /* [DM] Backup address for color zippy lines */
2833                 backup = i;
2834 #if ZIPPY
2835        #ifdef WIN32
2836                if (loggedOn == TRUE)
2837                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2838                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2839        #else
2840                 if (ZippyControl(buf, &i) ||
2841                     ZippyConverse(buf, &i) ||
2842                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2843                       loggedOn = TRUE;
2844                       if (!appData.colorize) continue;
2845                 }
2846        #endif
2847 #endif
2848             } // [DM] 'else { ' deleted
2849                 if (
2850                     /* Regular tells and says */
2851                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2852                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2853                     looking_at(buf, &i, "* says: ") ||
2854                     /* Don't color "message" or "messages" output */
2855                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2856                     looking_at(buf, &i, "*. * at *:*: ") ||
2857                     looking_at(buf, &i, "--* (*:*): ") ||
2858                     /* Message notifications (same color as tells) */
2859                     looking_at(buf, &i, "* has left a message ") ||
2860                     looking_at(buf, &i, "* just sent you a message:\n") ||
2861                     /* Whispers and kibitzes */
2862                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2863                     looking_at(buf, &i, "* kibitzes: ") ||
2864                     /* Channel tells */
2865                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2866
2867                   if (tkind == 1 && strchr(star_match[0], ':')) {
2868                       /* Avoid "tells you:" spoofs in channels */
2869                      tkind = 3;
2870                   }
2871                   if (star_match[0][0] == NULLCHAR ||
2872                       strchr(star_match[0], ' ') ||
2873                       (tkind == 3 && strchr(star_match[1], ' '))) {
2874                     /* Reject bogus matches */
2875                     i = oldi;
2876                   } else {
2877                     if (appData.colorize) {
2878                       if (oldi > next_out) {
2879                         SendToPlayer(&buf[next_out], oldi - next_out);
2880                         next_out = oldi;
2881                       }
2882                       switch (tkind) {
2883                       case 1:
2884                         Colorize(ColorTell, FALSE);
2885                         curColor = ColorTell;
2886                         break;
2887                       case 2:
2888                         Colorize(ColorKibitz, FALSE);
2889                         curColor = ColorKibitz;
2890                         break;
2891                       case 3:
2892                         p = strrchr(star_match[1], '(');
2893                         if (p == NULL) {
2894                           p = star_match[1];
2895                         } else {
2896                           p++;
2897                         }
2898                         if (atoi(p) == 1) {
2899                           Colorize(ColorChannel1, FALSE);
2900                           curColor = ColorChannel1;
2901                         } else {
2902                           Colorize(ColorChannel, FALSE);
2903                           curColor = ColorChannel;
2904                         }
2905                         break;
2906                       case 5:
2907                         curColor = ColorNormal;
2908                         break;
2909                       }
2910                     }
2911                     if (started == STARTED_NONE && appData.autoComment &&
2912                         (gameMode == IcsObserving ||
2913                          gameMode == IcsPlayingWhite ||
2914                          gameMode == IcsPlayingBlack)) {
2915                       parse_pos = i - oldi;
2916                       memcpy(parse, &buf[oldi], parse_pos);
2917                       parse[parse_pos] = NULLCHAR;
2918                       started = STARTED_COMMENT;
2919                       savingComment = TRUE;
2920                     } else {
2921                       started = STARTED_CHATTER;
2922                       savingComment = FALSE;
2923                     }
2924                     loggedOn = TRUE;
2925                     continue;
2926                   }
2927                 }
2928
2929                 if (looking_at(buf, &i, "* s-shouts: ") ||
2930                     looking_at(buf, &i, "* c-shouts: ")) {
2931                     if (appData.colorize) {
2932                         if (oldi > next_out) {
2933                             SendToPlayer(&buf[next_out], oldi - next_out);
2934                             next_out = oldi;
2935                         }
2936                         Colorize(ColorSShout, FALSE);
2937                         curColor = ColorSShout;
2938                     }
2939                     loggedOn = TRUE;
2940                     started = STARTED_CHATTER;
2941                     continue;
2942                 }
2943
2944                 if (looking_at(buf, &i, "--->")) {
2945                     loggedOn = TRUE;
2946                     continue;
2947                 }
2948
2949                 if (looking_at(buf, &i, "* shouts: ") ||
2950                     looking_at(buf, &i, "--> ")) {
2951                     if (appData.colorize) {
2952                         if (oldi > next_out) {
2953                             SendToPlayer(&buf[next_out], oldi - next_out);
2954                             next_out = oldi;
2955                         }
2956                         Colorize(ColorShout, FALSE);
2957                         curColor = ColorShout;
2958                     }
2959                     loggedOn = TRUE;
2960                     started = STARTED_CHATTER;
2961                     continue;
2962                 }
2963
2964                 if (looking_at( buf, &i, "Challenge:")) {
2965                     if (appData.colorize) {
2966                         if (oldi > next_out) {
2967                             SendToPlayer(&buf[next_out], oldi - next_out);
2968                             next_out = oldi;
2969                         }
2970                         Colorize(ColorChallenge, FALSE);
2971                         curColor = ColorChallenge;
2972                     }
2973                     loggedOn = TRUE;
2974                     continue;
2975                 }
2976
2977                 if (looking_at(buf, &i, "* offers you") ||
2978                     looking_at(buf, &i, "* offers to be") ||
2979                     looking_at(buf, &i, "* would like to") ||
2980                     looking_at(buf, &i, "* requests to") ||
2981                     looking_at(buf, &i, "Your opponent offers") ||
2982                     looking_at(buf, &i, "Your opponent requests")) {
2983
2984                     if (appData.colorize) {
2985                         if (oldi > next_out) {
2986                             SendToPlayer(&buf[next_out], oldi - next_out);
2987                             next_out = oldi;
2988                         }
2989                         Colorize(ColorRequest, FALSE);
2990                         curColor = ColorRequest;
2991                     }
2992                     continue;
2993                 }
2994
2995                 if (looking_at(buf, &i, "* (*) seeking")) {
2996                     if (appData.colorize) {
2997                         if (oldi > next_out) {
2998                             SendToPlayer(&buf[next_out], oldi - next_out);
2999                             next_out = oldi;
3000                         }
3001                         Colorize(ColorSeek, FALSE);
3002                         curColor = ColorSeek;
3003                     }
3004                     continue;
3005             }
3006
3007             if (looking_at(buf, &i, "\\   ")) {
3008                 if (prevColor != ColorNormal) {
3009                     if (oldi > next_out) {
3010                         SendToPlayer(&buf[next_out], oldi - next_out);
3011                         next_out = oldi;
3012                     }
3013                     Colorize(prevColor, TRUE);
3014                     curColor = prevColor;
3015                 }
3016                 if (savingComment) {
3017                     parse_pos = i - oldi;
3018                     memcpy(parse, &buf[oldi], parse_pos);
3019                     parse[parse_pos] = NULLCHAR;
3020                     started = STARTED_COMMENT;
3021                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3022                         chattingPartner = savingComment - 3; // kludge to remember the box
3023                 } else {
3024                     started = STARTED_CHATTER;
3025                 }
3026                 continue;
3027             }
3028
3029             if (looking_at(buf, &i, "Black Strength :") ||
3030                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3031                 looking_at(buf, &i, "<10>") ||
3032                 looking_at(buf, &i, "#@#")) {
3033                 /* Wrong board style */
3034                 loggedOn = TRUE;
3035                 SendToICS(ics_prefix);
3036                 SendToICS("set style 12\n");
3037                 SendToICS(ics_prefix);
3038                 SendToICS("refresh\n");
3039                 continue;
3040             }
3041
3042             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3043                 ICSInitScript();
3044                 have_sent_ICS_logon = 1;
3045                 continue;
3046             }
3047
3048             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3049                 (looking_at(buf, &i, "\n<12> ") ||
3050                  looking_at(buf, &i, "<12> "))) {
3051                 loggedOn = TRUE;
3052                 if (oldi > next_out) {
3053                     SendToPlayer(&buf[next_out], oldi - next_out);
3054                 }
3055                 next_out = i;
3056                 started = STARTED_BOARD;
3057                 parse_pos = 0;
3058                 continue;
3059             }
3060
3061             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3062                 looking_at(buf, &i, "<b1> ")) {
3063                 if (oldi > next_out) {
3064                     SendToPlayer(&buf[next_out], oldi - next_out);
3065                 }
3066                 next_out = i;
3067                 started = STARTED_HOLDINGS;
3068                 parse_pos = 0;
3069                 continue;
3070             }
3071
3072             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3073                 loggedOn = TRUE;
3074                 /* Header for a move list -- first line */
3075
3076                 switch (ics_getting_history) {
3077                   case H_FALSE:
3078                     switch (gameMode) {
3079                       case IcsIdle:
3080                       case BeginningOfGame:
3081                         /* User typed "moves" or "oldmoves" while we
3082                            were idle.  Pretend we asked for these
3083                            moves and soak them up so user can step
3084                            through them and/or save them.
3085                            */
3086                         Reset(FALSE, TRUE);
3087                         gameMode = IcsObserving;
3088                         ModeHighlight();
3089                         ics_gamenum = -1;
3090                         ics_getting_history = H_GOT_UNREQ_HEADER;
3091                         break;
3092                       case EditGame: /*?*/
3093                       case EditPosition: /*?*/
3094                         /* Should above feature work in these modes too? */
3095                         /* For now it doesn't */
3096                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3097                         break;
3098                       default:
3099                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3100                         break;
3101                     }
3102                     break;
3103                   case H_REQUESTED:
3104                     /* Is this the right one? */
3105                     if (gameInfo.white && gameInfo.black &&
3106                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3107                         strcmp(gameInfo.black, star_match[2]) == 0) {
3108                         /* All is well */
3109                         ics_getting_history = H_GOT_REQ_HEADER;
3110                     }
3111                     break;
3112                   case H_GOT_REQ_HEADER:
3113                   case H_GOT_UNREQ_HEADER:
3114                   case H_GOT_UNWANTED_HEADER:
3115                   case H_GETTING_MOVES:
3116                     /* Should not happen */
3117                     DisplayError(_("Error gathering move list: two headers"), 0);
3118                     ics_getting_history = H_FALSE;
3119                     break;
3120                 }
3121
3122                 /* Save player ratings into gameInfo if needed */
3123                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3124                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3125                     (gameInfo.whiteRating == -1 ||
3126                      gameInfo.blackRating == -1)) {
3127
3128                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3129                     gameInfo.blackRating = string_to_rating(star_match[3]);
3130                     if (appData.debugMode)
3131                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3132                               gameInfo.whiteRating, gameInfo.blackRating);
3133                 }
3134                 continue;
3135             }
3136
3137             if (looking_at(buf, &i,
3138               "* * match, initial time: * minute*, increment: * second")) {
3139                 /* Header for a move list -- second line */
3140                 /* Initial board will follow if this is a wild game */
3141                 if (gameInfo.event != NULL) free(gameInfo.event);
3142                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3143                 gameInfo.event = StrSave(str);
3144                 /* [HGM] we switched variant. Translate boards if needed. */
3145                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3146                 continue;
3147             }
3148
3149             if (looking_at(buf, &i, "Move  ")) {
3150                 /* Beginning of a move list */
3151                 switch (ics_getting_history) {
3152                   case H_FALSE:
3153                     /* Normally should not happen */
3154                     /* Maybe user hit reset while we were parsing */
3155                     break;
3156                   case H_REQUESTED:
3157                     /* Happens if we are ignoring a move list that is not
3158                      * the one we just requested.  Common if the user
3159                      * tries to observe two games without turning off
3160                      * getMoveList */
3161                     break;
3162                   case H_GETTING_MOVES:
3163                     /* Should not happen */
3164                     DisplayError(_("Error gathering move list: nested"), 0);
3165                     ics_getting_history = H_FALSE;
3166                     break;
3167                   case H_GOT_REQ_HEADER:
3168                     ics_getting_history = H_GETTING_MOVES;
3169                     started = STARTED_MOVES;
3170                     parse_pos = 0;
3171                     if (oldi > next_out) {
3172                         SendToPlayer(&buf[next_out], oldi - next_out);
3173                     }
3174                     break;
3175                   case H_GOT_UNREQ_HEADER:
3176                     ics_getting_history = H_GETTING_MOVES;
3177                     started = STARTED_MOVES_NOHIDE;
3178                     parse_pos = 0;
3179                     break;
3180                   case H_GOT_UNWANTED_HEADER:
3181                     ics_getting_history = H_FALSE;
3182                     break;
3183                 }
3184                 continue;
3185             }
3186
3187             if (looking_at(buf, &i, "% ") ||
3188                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3189                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3190                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3191                     soughtPending = FALSE;
3192                     seekGraphUp = TRUE;
3193                     DrawSeekGraph();
3194                 }
3195                 if(suppressKibitz) next_out = i;
3196                 savingComment = FALSE;
3197                 suppressKibitz = 0;
3198                 switch (started) {
3199                   case STARTED_MOVES:
3200                   case STARTED_MOVES_NOHIDE:
3201                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3202                     parse[parse_pos + i - oldi] = NULLCHAR;
3203                     ParseGameHistory(parse);
3204 #if ZIPPY
3205                     if (appData.zippyPlay && first.initDone) {
3206                         FeedMovesToProgram(&first, forwardMostMove);
3207                         if (gameMode == IcsPlayingWhite) {
3208                             if (WhiteOnMove(forwardMostMove)) {
3209                                 if (first.sendTime) {
3210                                   if (first.useColors) {
3211                                     SendToProgram("black\n", &first);
3212                                   }
3213                                   SendTimeRemaining(&first, TRUE);
3214                                 }
3215                                 if (first.useColors) {
3216                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3217                                 }
3218                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3219                                 first.maybeThinking = TRUE;
3220                             } else {
3221                                 if (first.usePlayother) {
3222                                   if (first.sendTime) {
3223                                     SendTimeRemaining(&first, TRUE);
3224                                   }
3225                                   SendToProgram("playother\n", &first);
3226                                   firstMove = FALSE;
3227                                 } else {
3228                                   firstMove = TRUE;
3229                                 }
3230                             }
3231                         } else if (gameMode == IcsPlayingBlack) {
3232                             if (!WhiteOnMove(forwardMostMove)) {
3233                                 if (first.sendTime) {
3234                                   if (first.useColors) {
3235                                     SendToProgram("white\n", &first);
3236                                   }
3237                                   SendTimeRemaining(&first, FALSE);
3238                                 }
3239                                 if (first.useColors) {
3240                                   SendToProgram("black\n", &first);
3241                                 }
3242                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3243                                 first.maybeThinking = TRUE;
3244                             } else {
3245                                 if (first.usePlayother) {
3246                                   if (first.sendTime) {
3247                                     SendTimeRemaining(&first, FALSE);
3248                                   }
3249                                   SendToProgram("playother\n", &first);
3250                                   firstMove = FALSE;
3251                                 } else {
3252                                   firstMove = TRUE;
3253                                 }
3254                             }
3255                         }
3256                     }
3257 #endif
3258                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3259                         /* Moves came from oldmoves or moves command
3260                            while we weren't doing anything else.
3261                            */
3262                         currentMove = forwardMostMove;
3263                         ClearHighlights();/*!!could figure this out*/
3264                         flipView = appData.flipView;
3265                         DrawPosition(TRUE, boards[currentMove]);
3266                         DisplayBothClocks();
3267                         sprintf(str, "%s vs. %s",
3268                                 gameInfo.white, gameInfo.black);
3269                         DisplayTitle(str);
3270                         gameMode = IcsIdle;
3271                     } else {
3272                         /* Moves were history of an active game */
3273                         if (gameInfo.resultDetails != NULL) {
3274                             free(gameInfo.resultDetails);
3275                             gameInfo.resultDetails = NULL;
3276                         }
3277                     }
3278                     HistorySet(parseList, backwardMostMove,
3279                                forwardMostMove, currentMove-1);
3280                     DisplayMove(currentMove - 1);
3281                     if (started == STARTED_MOVES) next_out = i;
3282                     started = STARTED_NONE;
3283                     ics_getting_history = H_FALSE;
3284                     break;
3285
3286                   case STARTED_OBSERVE:
3287                     started = STARTED_NONE;
3288                     SendToICS(ics_prefix);
3289                     SendToICS("refresh\n");
3290                     break;
3291
3292                   default:
3293                     break;
3294                 }
3295                 if(bookHit) { // [HGM] book: simulate book reply
3296                     static char bookMove[MSG_SIZ]; // a bit generous?
3297
3298                     programStats.nodes = programStats.depth = programStats.time =
3299                     programStats.score = programStats.got_only_move = 0;
3300                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3301
3302                     strcpy(bookMove, "move ");
3303                     strcat(bookMove, bookHit);
3304                     HandleMachineMove(bookMove, &first);
3305                 }
3306                 continue;
3307             }
3308
3309             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3310                  started == STARTED_HOLDINGS ||
3311                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3312                 /* Accumulate characters in move list or board */
3313                 parse[parse_pos++] = buf[i];
3314             }
3315
3316             /* Start of game messages.  Mostly we detect start of game
3317                when the first board image arrives.  On some versions
3318                of the ICS, though, we need to do a "refresh" after starting
3319                to observe in order to get the current board right away. */
3320             if (looking_at(buf, &i, "Adding game * to observation list")) {
3321                 started = STARTED_OBSERVE;
3322                 continue;
3323             }
3324
3325             /* Handle auto-observe */
3326             if (appData.autoObserve &&
3327                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3328                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3329                 char *player;
3330                 /* Choose the player that was highlighted, if any. */
3331                 if (star_match[0][0] == '\033' ||
3332                     star_match[1][0] != '\033') {
3333                     player = star_match[0];
3334                 } else {
3335                     player = star_match[2];
3336                 }
3337                 sprintf(str, "%sobserve %s\n",
3338                         ics_prefix, StripHighlightAndTitle(player));
3339                 SendToICS(str);
3340
3341                 /* Save ratings from notify string */
3342                 strcpy(player1Name, star_match[0]);
3343                 player1Rating = string_to_rating(star_match[1]);
3344                 strcpy(player2Name, star_match[2]);
3345                 player2Rating = string_to_rating(star_match[3]);
3346
3347                 if (appData.debugMode)
3348                   fprintf(debugFP,
3349                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3350                           player1Name, player1Rating,
3351                           player2Name, player2Rating);
3352
3353                 continue;
3354             }
3355
3356             /* Deal with automatic examine mode after a game,
3357                and with IcsObserving -> IcsExamining transition */
3358             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3359                 looking_at(buf, &i, "has made you an examiner of game *")) {
3360
3361                 int gamenum = atoi(star_match[0]);
3362                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3363                     gamenum == ics_gamenum) {
3364                     /* We were already playing or observing this game;
3365                        no need to refetch history */
3366                     gameMode = IcsExamining;
3367                     if (pausing) {
3368                         pauseExamForwardMostMove = forwardMostMove;
3369                     } else if (currentMove < forwardMostMove) {
3370                         ForwardInner(forwardMostMove);
3371                     }
3372                 } else {
3373                     /* I don't think this case really can happen */
3374                     SendToICS(ics_prefix);
3375                     SendToICS("refresh\n");
3376                 }
3377                 continue;
3378             }
3379
3380             /* Error messages */
3381 //          if (ics_user_moved) {
3382             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3383                 if (looking_at(buf, &i, "Illegal move") ||
3384                     looking_at(buf, &i, "Not a legal move") ||
3385                     looking_at(buf, &i, "Your king is in check") ||
3386                     looking_at(buf, &i, "It isn't your turn") ||
3387                     looking_at(buf, &i, "It is not your move")) {
3388                     /* Illegal move */
3389                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3390                         currentMove = --forwardMostMove;
3391                         DisplayMove(currentMove - 1); /* before DMError */
3392                         DrawPosition(FALSE, boards[currentMove]);
3393                         SwitchClocks();
3394                         DisplayBothClocks();
3395                     }
3396                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3397                     ics_user_moved = 0;
3398                     continue;
3399                 }
3400             }
3401
3402             if (looking_at(buf, &i, "still have time") ||
3403                 looking_at(buf, &i, "not out of time") ||
3404                 looking_at(buf, &i, "either player is out of time") ||
3405                 looking_at(buf, &i, "has timeseal; checking")) {
3406                 /* We must have called his flag a little too soon */
3407                 whiteFlag = blackFlag = FALSE;
3408                 continue;
3409             }
3410
3411             if (looking_at(buf, &i, "added * seconds to") ||
3412                 looking_at(buf, &i, "seconds were added to")) {
3413                 /* Update the clocks */
3414                 SendToICS(ics_prefix);
3415                 SendToICS("refresh\n");
3416                 continue;
3417             }
3418
3419             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3420                 ics_clock_paused = TRUE;
3421                 StopClocks();
3422                 continue;
3423             }
3424
3425             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3426                 ics_clock_paused = FALSE;
3427                 StartClocks();
3428                 continue;
3429             }
3430
3431             /* Grab player ratings from the Creating: message.
3432                Note we have to check for the special case when
3433                the ICS inserts things like [white] or [black]. */
3434             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3435                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3436                 /* star_matches:
3437                    0    player 1 name (not necessarily white)
3438                    1    player 1 rating
3439                    2    empty, white, or black (IGNORED)
3440                    3    player 2 name (not necessarily black)
3441                    4    player 2 rating
3442
3443                    The names/ratings are sorted out when the game
3444                    actually starts (below).
3445                 */
3446                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3447                 player1Rating = string_to_rating(star_match[1]);
3448                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3449                 player2Rating = string_to_rating(star_match[4]);
3450
3451                 if (appData.debugMode)
3452                   fprintf(debugFP,
3453                           "Ratings from 'Creating:' %s %d, %s %d\n",
3454                           player1Name, player1Rating,
3455                           player2Name, player2Rating);
3456
3457                 continue;
3458             }
3459
3460             /* Improved generic start/end-of-game messages */
3461             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3462                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3463                 /* If tkind == 0: */
3464                 /* star_match[0] is the game number */
3465                 /*           [1] is the white player's name */
3466                 /*           [2] is the black player's name */
3467                 /* For end-of-game: */
3468                 /*           [3] is the reason for the game end */
3469                 /*           [4] is a PGN end game-token, preceded by " " */
3470                 /* For start-of-game: */
3471                 /*           [3] begins with "Creating" or "Continuing" */
3472                 /*           [4] is " *" or empty (don't care). */
3473                 int gamenum = atoi(star_match[0]);
3474                 char *whitename, *blackname, *why, *endtoken;
3475                 ChessMove endtype = (ChessMove) 0;
3476
3477                 if (tkind == 0) {
3478                   whitename = star_match[1];
3479                   blackname = star_match[2];
3480                   why = star_match[3];
3481                   endtoken = star_match[4];
3482                 } else {
3483                   whitename = star_match[1];
3484                   blackname = star_match[3];
3485                   why = star_match[5];
3486                   endtoken = star_match[6];
3487                 }
3488
3489                 /* Game start messages */
3490                 if (strncmp(why, "Creating ", 9) == 0 ||
3491                     strncmp(why, "Continuing ", 11) == 0) {
3492                     gs_gamenum = gamenum;
3493                     strcpy(gs_kind, strchr(why, ' ') + 1);
3494                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3495 #if ZIPPY
3496                     if (appData.zippyPlay) {
3497                         ZippyGameStart(whitename, blackname);
3498                     }
3499 #endif /*ZIPPY*/
3500                     continue;
3501                 }
3502
3503                 /* Game end messages */
3504                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3505                     ics_gamenum != gamenum) {
3506                     continue;
3507                 }
3508                 while (endtoken[0] == ' ') endtoken++;
3509                 switch (endtoken[0]) {
3510                   case '*':
3511                   default:
3512                     endtype = GameUnfinished;
3513                     break;
3514                   case '0':
3515                     endtype = BlackWins;
3516                     break;
3517                   case '1':
3518                     if (endtoken[1] == '/')
3519                       endtype = GameIsDrawn;
3520                     else
3521                       endtype = WhiteWins;
3522                     break;
3523                 }
3524                 GameEnds(endtype, why, GE_ICS);
3525 #if ZIPPY
3526                 if (appData.zippyPlay && first.initDone) {
3527                     ZippyGameEnd(endtype, why);
3528                     if (first.pr == NULL) {
3529                       /* Start the next process early so that we'll
3530                          be ready for the next challenge */
3531                       StartChessProgram(&first);
3532                     }
3533                     /* Send "new" early, in case this command takes
3534                        a long time to finish, so that we'll be ready
3535                        for the next challenge. */
3536                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3537                     Reset(TRUE, TRUE);
3538                 }
3539 #endif /*ZIPPY*/
3540                 continue;
3541             }
3542
3543             if (looking_at(buf, &i, "Removing game * from observation") ||
3544                 looking_at(buf, &i, "no longer observing game *") ||
3545                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3546                 if (gameMode == IcsObserving &&
3547                     atoi(star_match[0]) == ics_gamenum)
3548                   {
3549                       /* icsEngineAnalyze */
3550                       if (appData.icsEngineAnalyze) {
3551                             ExitAnalyzeMode();
3552                             ModeHighlight();
3553                       }
3554                       StopClocks();
3555                       gameMode = IcsIdle;
3556                       ics_gamenum = -1;
3557                       ics_user_moved = FALSE;
3558                   }
3559                 continue;
3560             }
3561
3562             if (looking_at(buf, &i, "no longer examining game *")) {
3563                 if (gameMode == IcsExamining &&
3564                     atoi(star_match[0]) == ics_gamenum)
3565                   {
3566                       gameMode = IcsIdle;
3567                       ics_gamenum = -1;
3568                       ics_user_moved = FALSE;
3569                   }
3570                 continue;
3571             }
3572
3573             /* Advance leftover_start past any newlines we find,
3574                so only partial lines can get reparsed */
3575             if (looking_at(buf, &i, "\n")) {
3576                 prevColor = curColor;
3577                 if (curColor != ColorNormal) {
3578                     if (oldi > next_out) {
3579                         SendToPlayer(&buf[next_out], oldi - next_out);
3580                         next_out = oldi;
3581                     }
3582                     Colorize(ColorNormal, FALSE);
3583                     curColor = ColorNormal;
3584                 }
3585                 if (started == STARTED_BOARD) {
3586                     started = STARTED_NONE;
3587                     parse[parse_pos] = NULLCHAR;
3588                     ParseBoard12(parse);
3589                     ics_user_moved = 0;
3590
3591                     /* Send premove here */
3592                     if (appData.premove) {
3593                       char str[MSG_SIZ];
3594                       if (currentMove == 0 &&
3595                           gameMode == IcsPlayingWhite &&
3596                           appData.premoveWhite) {
3597                         sprintf(str, "%s\n", appData.premoveWhiteText);
3598                         if (appData.debugMode)
3599                           fprintf(debugFP, "Sending premove:\n");
3600                         SendToICS(str);
3601                       } else if (currentMove == 1 &&
3602                                  gameMode == IcsPlayingBlack &&
3603                                  appData.premoveBlack) {
3604                         sprintf(str, "%s\n", appData.premoveBlackText);
3605                         if (appData.debugMode)
3606                           fprintf(debugFP, "Sending premove:\n");
3607                         SendToICS(str);
3608                       } else if (gotPremove) {
3609                         gotPremove = 0;
3610                         ClearPremoveHighlights();
3611                         if (appData.debugMode)
3612                           fprintf(debugFP, "Sending premove:\n");
3613                           UserMoveEvent(premoveFromX, premoveFromY,
3614                                         premoveToX, premoveToY,
3615                                         premovePromoChar);
3616                       }
3617                     }
3618
3619                     /* Usually suppress following prompt */
3620                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3621                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3622                         if (looking_at(buf, &i, "*% ")) {
3623                             savingComment = FALSE;
3624                             suppressKibitz = 0;
3625                         }
3626                     }
3627                     next_out = i;
3628                 } else if (started == STARTED_HOLDINGS) {
3629                     int gamenum;
3630                     char new_piece[MSG_SIZ];
3631                     started = STARTED_NONE;
3632                     parse[parse_pos] = NULLCHAR;
3633                     if (appData.debugMode)
3634                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3635                                                         parse, currentMove);
3636                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3637                         gamenum == ics_gamenum) {
3638                         if (gameInfo.variant == VariantNormal) {
3639                           /* [HGM] We seem to switch variant during a game!
3640                            * Presumably no holdings were displayed, so we have
3641                            * to move the position two files to the right to
3642                            * create room for them!
3643                            */
3644                           VariantClass newVariant;
3645                           switch(gameInfo.boardWidth) { // base guess on board width
3646                                 case 9:  newVariant = VariantShogi; break;
3647                                 case 10: newVariant = VariantGreat; break;
3648                                 default: newVariant = VariantCrazyhouse; break;
3649                           }
3650                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3651                           /* Get a move list just to see the header, which
3652                              will tell us whether this is really bug or zh */
3653                           if (ics_getting_history == H_FALSE) {
3654                             ics_getting_history = H_REQUESTED;
3655                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3656                             SendToICS(str);
3657                           }
3658                         }
3659                         new_piece[0] = NULLCHAR;
3660                         sscanf(parse, "game %d white [%s black [%s <- %s",
3661                                &gamenum, white_holding, black_holding,
3662                                new_piece);
3663                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3664                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3665                         /* [HGM] copy holdings to board holdings area */
3666                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3667                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3668                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3669 #if ZIPPY
3670                         if (appData.zippyPlay && first.initDone) {
3671                             ZippyHoldings(white_holding, black_holding,
3672                                           new_piece);
3673                         }
3674 #endif /*ZIPPY*/
3675                         if (tinyLayout || smallLayout) {
3676                             char wh[16], bh[16];
3677                             PackHolding(wh, white_holding);
3678                             PackHolding(bh, black_holding);
3679                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3680                                     gameInfo.white, gameInfo.black);
3681                         } else {
3682                             sprintf(str, "%s [%s] vs. %s [%s]",
3683                                     gameInfo.white, white_holding,
3684                                     gameInfo.black, black_holding);
3685                         }
3686
3687                         DrawPosition(FALSE, boards[currentMove]);
3688                         DisplayTitle(str);
3689                     }
3690                     /* Suppress following prompt */
3691                     if (looking_at(buf, &i, "*% ")) {
3692                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3693                         savingComment = FALSE;
3694                         suppressKibitz = 0;
3695                     }
3696                     next_out = i;
3697                 }
3698                 continue;
3699             }
3700
3701             i++;                /* skip unparsed character and loop back */
3702         }
3703         
3704         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3705 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3706 //          SendToPlayer(&buf[next_out], i - next_out);
3707             started != STARTED_HOLDINGS && leftover_start > next_out) {
3708             SendToPlayer(&buf[next_out], leftover_start - next_out);
3709             next_out = i;
3710         }
3711
3712         leftover_len = buf_len - leftover_start;
3713         /* if buffer ends with something we couldn't parse,
3714            reparse it after appending the next read */
3715
3716     } else if (count == 0) {
3717         RemoveInputSource(isr);
3718         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3719     } else {
3720         DisplayFatalError(_("Error reading from ICS"), error, 1);
3721     }
3722 }
3723
3724
3725 /* Board style 12 looks like this:
3726
3727    <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
3728
3729  * The "<12> " is stripped before it gets to this routine.  The two
3730  * trailing 0's (flip state and clock ticking) are later addition, and
3731  * some chess servers may not have them, or may have only the first.
3732  * Additional trailing fields may be added in the future.
3733  */
3734
3735 #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"
3736
3737 #define RELATION_OBSERVING_PLAYED    0
3738 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3739 #define RELATION_PLAYING_MYMOVE      1
3740 #define RELATION_PLAYING_NOTMYMOVE  -1
3741 #define RELATION_EXAMINING           2
3742 #define RELATION_ISOLATED_BOARD     -3
3743 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3744
3745 void
3746 ParseBoard12(string)
3747      char *string;
3748 {
3749     GameMode newGameMode;
3750     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3751     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3752     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3753     char to_play, board_chars[200];
3754     char move_str[500], str[500], elapsed_time[500];
3755     char black[32], white[32];
3756     Board board;
3757     int prevMove = currentMove;
3758     int ticking = 2;
3759     ChessMove moveType;
3760     int fromX, fromY, toX, toY;
3761     char promoChar;
3762     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3763     char *bookHit = NULL; // [HGM] book
3764     Boolean weird = FALSE, reqFlag = FALSE;
3765
3766     fromX = fromY = toX = toY = -1;
3767
3768     newGame = FALSE;
3769
3770     if (appData.debugMode)
3771       fprintf(debugFP, _("Parsing board: %s\n"), string);
3772
3773     move_str[0] = NULLCHAR;
3774     elapsed_time[0] = NULLCHAR;
3775     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3776         int  i = 0, j;
3777         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3778             if(string[i] == ' ') { ranks++; files = 0; }
3779             else files++;
3780             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3781             i++;
3782         }
3783         for(j = 0; j <i; j++) board_chars[j] = string[j];
3784         board_chars[i] = '\0';
3785         string += i + 1;
3786     }
3787     n = sscanf(string, PATTERN, &to_play, &double_push,
3788                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3789                &gamenum, white, black, &relation, &basetime, &increment,
3790                &white_stren, &black_stren, &white_time, &black_time,
3791                &moveNum, str, elapsed_time, move_str, &ics_flip,
3792                &ticking);
3793
3794     if (n < 21) {
3795         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3796         DisplayError(str, 0);
3797         return;
3798     }
3799
3800     /* Convert the move number to internal form */
3801     moveNum = (moveNum - 1) * 2;
3802     if (to_play == 'B') moveNum++;
3803     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3804       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3805                         0, 1);
3806       return;
3807     }
3808
3809     switch (relation) {
3810       case RELATION_OBSERVING_PLAYED:
3811       case RELATION_OBSERVING_STATIC:
3812         if (gamenum == -1) {
3813             /* Old ICC buglet */
3814             relation = RELATION_OBSERVING_STATIC;
3815         }
3816         newGameMode = IcsObserving;
3817         break;
3818       case RELATION_PLAYING_MYMOVE:
3819       case RELATION_PLAYING_NOTMYMOVE:
3820         newGameMode =
3821           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3822             IcsPlayingWhite : IcsPlayingBlack;
3823         break;
3824       case RELATION_EXAMINING:
3825         newGameMode = IcsExamining;
3826         break;
3827       case RELATION_ISOLATED_BOARD:
3828       default:
3829         /* Just display this board.  If user was doing something else,
3830            we will forget about it until the next board comes. */
3831         newGameMode = IcsIdle;
3832         break;
3833       case RELATION_STARTING_POSITION:
3834         newGameMode = gameMode;
3835         break;
3836     }
3837
3838     /* Modify behavior for initial board display on move listing
3839        of wild games.
3840        */
3841     switch (ics_getting_history) {
3842       case H_FALSE:
3843       case H_REQUESTED:
3844         break;
3845       case H_GOT_REQ_HEADER:
3846       case H_GOT_UNREQ_HEADER:
3847         /* This is the initial position of the current game */
3848         gamenum = ics_gamenum;
3849         moveNum = 0;            /* old ICS bug workaround */
3850         if (to_play == 'B') {
3851           startedFromSetupPosition = TRUE;
3852           blackPlaysFirst = TRUE;
3853           moveNum = 1;
3854           if (forwardMostMove == 0) forwardMostMove = 1;
3855           if (backwardMostMove == 0) backwardMostMove = 1;
3856           if (currentMove == 0) currentMove = 1;
3857         }
3858         newGameMode = gameMode;
3859         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3860         break;
3861       case H_GOT_UNWANTED_HEADER:
3862         /* This is an initial board that we don't want */
3863         return;
3864       case H_GETTING_MOVES:
3865         /* Should not happen */
3866         DisplayError(_("Error gathering move list: extra board"), 0);
3867         ics_getting_history = H_FALSE;
3868         return;
3869     }
3870
3871    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3872                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3873      /* [HGM] We seem to have switched variant unexpectedly
3874       * Try to guess new variant from board size
3875       */
3876           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3877           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3878           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3879           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3880           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3881           if(!weird) newVariant = VariantNormal;
3882           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3883           /* Get a move list just to see the header, which
3884              will tell us whether this is really bug or zh */
3885           if (ics_getting_history == H_FALSE) {
3886             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3887             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3888             SendToICS(str);
3889           }
3890     }
3891     
3892     /* Take action if this is the first board of a new game, or of a
3893        different game than is currently being displayed.  */
3894     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3895         relation == RELATION_ISOLATED_BOARD) {
3896
3897         /* Forget the old game and get the history (if any) of the new one */
3898         if (gameMode != BeginningOfGame) {
3899           Reset(TRUE, TRUE);
3900         }
3901         newGame = TRUE;
3902         if (appData.autoRaiseBoard) BoardToTop();
3903         prevMove = -3;
3904         if (gamenum == -1) {
3905             newGameMode = IcsIdle;
3906         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3907                    appData.getMoveList && !reqFlag) {
3908             /* Need to get game history */
3909             ics_getting_history = H_REQUESTED;
3910             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3911             SendToICS(str);
3912         }
3913
3914         /* Initially flip the board to have black on the bottom if playing
3915            black or if the ICS flip flag is set, but let the user change
3916            it with the Flip View button. */
3917         flipView = appData.autoFlipView ?
3918           (newGameMode == IcsPlayingBlack) || ics_flip :
3919           appData.flipView;
3920
3921         /* Done with values from previous mode; copy in new ones */
3922         gameMode = newGameMode;
3923         ModeHighlight();
3924         ics_gamenum = gamenum;
3925         if (gamenum == gs_gamenum) {
3926             int klen = strlen(gs_kind);
3927             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3928             sprintf(str, "ICS %s", gs_kind);
3929             gameInfo.event = StrSave(str);
3930         } else {
3931             gameInfo.event = StrSave("ICS game");
3932         }
3933         gameInfo.site = StrSave(appData.icsHost);
3934         gameInfo.date = PGNDate();
3935         gameInfo.round = StrSave("-");
3936         gameInfo.white = StrSave(white);
3937         gameInfo.black = StrSave(black);
3938         timeControl = basetime * 60 * 1000;
3939         timeControl_2 = 0;
3940         timeIncrement = increment * 1000;
3941         movesPerSession = 0;
3942         gameInfo.timeControl = TimeControlTagValue();
3943         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3944   if (appData.debugMode) {
3945     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3946     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3947     setbuf(debugFP, NULL);
3948   }
3949
3950         gameInfo.outOfBook = NULL;
3951
3952         /* Do we have the ratings? */
3953         if (strcmp(player1Name, white) == 0 &&
3954             strcmp(player2Name, black) == 0) {
3955             if (appData.debugMode)
3956               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3957                       player1Rating, player2Rating);
3958             gameInfo.whiteRating = player1Rating;
3959             gameInfo.blackRating = player2Rating;
3960         } else if (strcmp(player2Name, white) == 0 &&
3961                    strcmp(player1Name, black) == 0) {
3962             if (appData.debugMode)
3963               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3964                       player2Rating, player1Rating);
3965             gameInfo.whiteRating = player2Rating;
3966             gameInfo.blackRating = player1Rating;
3967         }
3968         player1Name[0] = player2Name[0] = NULLCHAR;
3969
3970         /* Silence shouts if requested */
3971         if (appData.quietPlay &&
3972             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3973             SendToICS(ics_prefix);
3974             SendToICS("set shout 0\n");
3975         }
3976     }
3977
3978     /* Deal with midgame name changes */
3979     if (!newGame) {
3980         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3981             if (gameInfo.white) free(gameInfo.white);
3982             gameInfo.white = StrSave(white);
3983         }
3984         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3985             if (gameInfo.black) free(gameInfo.black);
3986             gameInfo.black = StrSave(black);
3987         }
3988     }
3989
3990     /* Throw away game result if anything actually changes in examine mode */
3991     if (gameMode == IcsExamining && !newGame) {
3992         gameInfo.result = GameUnfinished;
3993         if (gameInfo.resultDetails != NULL) {
3994             free(gameInfo.resultDetails);
3995             gameInfo.resultDetails = NULL;
3996         }
3997     }
3998
3999     /* In pausing && IcsExamining mode, we ignore boards coming
4000        in if they are in a different variation than we are. */
4001     if (pauseExamInvalid) return;
4002     if (pausing && gameMode == IcsExamining) {
4003         if (moveNum <= pauseExamForwardMostMove) {
4004             pauseExamInvalid = TRUE;
4005             forwardMostMove = pauseExamForwardMostMove;
4006             return;
4007         }
4008     }
4009
4010   if (appData.debugMode) {
4011     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4012   }
4013     /* Parse the board */
4014     for (k = 0; k < ranks; k++) {
4015       for (j = 0; j < files; j++)
4016         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4017       if(gameInfo.holdingsWidth > 1) {
4018            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4019            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4020       }
4021     }
4022     CopyBoard(boards[moveNum], board);
4023     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4024     if (moveNum == 0) {
4025         startedFromSetupPosition =
4026           !CompareBoards(board, initialPosition);
4027         if(startedFromSetupPosition)
4028             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4029     }
4030
4031     /* [HGM] Set castling rights. Take the outermost Rooks,
4032        to make it also work for FRC opening positions. Note that board12
4033        is really defective for later FRC positions, as it has no way to
4034        indicate which Rook can castle if they are on the same side of King.
4035        For the initial position we grant rights to the outermost Rooks,
4036        and remember thos rights, and we then copy them on positions
4037        later in an FRC game. This means WB might not recognize castlings with
4038        Rooks that have moved back to their original position as illegal,
4039        but in ICS mode that is not its job anyway.
4040     */
4041     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4042     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4043
4044         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4045             if(board[0][i] == WhiteRook) j = i;
4046         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4047         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4048             if(board[0][i] == WhiteRook) j = i;
4049         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4050         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4051             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4052         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4053         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4054             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4055         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4056
4057         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4058         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4059             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4060         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4061             if(board[BOARD_HEIGHT-1][k] == bKing)
4062                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4063         if(gameInfo.variant == VariantTwoKings) {
4064             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4065             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4066             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4067         }
4068     } else { int r;
4069         r = boards[moveNum][CASTLING][0] = initialRights[0];
4070         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4071         r = boards[moveNum][CASTLING][1] = initialRights[1];
4072         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4073         r = boards[moveNum][CASTLING][3] = initialRights[3];
4074         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4075         r = boards[moveNum][CASTLING][4] = initialRights[4];
4076         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4077         /* wildcastle kludge: always assume King has rights */
4078         r = boards[moveNum][CASTLING][2] = initialRights[2];
4079         r = boards[moveNum][CASTLING][5] = initialRights[5];
4080     }
4081     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4082     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4083
4084
4085     if (ics_getting_history == H_GOT_REQ_HEADER ||
4086         ics_getting_history == H_GOT_UNREQ_HEADER) {
4087         /* This was an initial position from a move list, not
4088            the current position */
4089         return;
4090     }
4091
4092     /* Update currentMove and known move number limits */
4093     newMove = newGame || moveNum > forwardMostMove;
4094
4095     if (newGame) {
4096         forwardMostMove = backwardMostMove = currentMove = moveNum;
4097         if (gameMode == IcsExamining && moveNum == 0) {
4098           /* Workaround for ICS limitation: we are not told the wild
4099              type when starting to examine a game.  But if we ask for
4100              the move list, the move list header will tell us */
4101             ics_getting_history = H_REQUESTED;
4102             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4103             SendToICS(str);
4104         }
4105     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4106                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4107 #if ZIPPY
4108         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4109         /* [HGM] applied this also to an engine that is silently watching        */
4110         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4111             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4112             gameInfo.variant == currentlyInitializedVariant) {
4113           takeback = forwardMostMove - moveNum;
4114           for (i = 0; i < takeback; i++) {
4115             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4116             SendToProgram("undo\n", &first);
4117           }
4118         }
4119 #endif
4120
4121         forwardMostMove = moveNum;
4122         if (!pausing || currentMove > forwardMostMove)
4123           currentMove = forwardMostMove;
4124     } else {
4125         /* New part of history that is not contiguous with old part */
4126         if (pausing && gameMode == IcsExamining) {
4127             pauseExamInvalid = TRUE;
4128             forwardMostMove = pauseExamForwardMostMove;
4129             return;
4130         }
4131         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4132 #if ZIPPY
4133             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4134                 // [HGM] when we will receive the move list we now request, it will be
4135                 // fed to the engine from the first move on. So if the engine is not
4136                 // in the initial position now, bring it there.
4137                 InitChessProgram(&first, 0);
4138             }
4139 #endif
4140             ics_getting_history = H_REQUESTED;
4141             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4142             SendToICS(str);
4143         }
4144         forwardMostMove = backwardMostMove = currentMove = moveNum;
4145     }
4146
4147     /* Update the clocks */
4148     if (strchr(elapsed_time, '.')) {
4149       /* Time is in ms */
4150       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4151       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4152     } else {
4153       /* Time is in seconds */
4154       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4155       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4156     }
4157
4158
4159 #if ZIPPY
4160     if (appData.zippyPlay && newGame &&
4161         gameMode != IcsObserving && gameMode != IcsIdle &&
4162         gameMode != IcsExamining)
4163       ZippyFirstBoard(moveNum, basetime, increment);
4164 #endif
4165
4166     /* Put the move on the move list, first converting
4167        to canonical algebraic form. */
4168     if (moveNum > 0) {
4169   if (appData.debugMode) {
4170     if (appData.debugMode) { int f = forwardMostMove;
4171         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4172                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4173                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4174     }
4175     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4176     fprintf(debugFP, "moveNum = %d\n", moveNum);
4177     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4178     setbuf(debugFP, NULL);
4179   }
4180         if (moveNum <= backwardMostMove) {
4181             /* We don't know what the board looked like before
4182                this move.  Punt. */
4183             strcpy(parseList[moveNum - 1], move_str);
4184             strcat(parseList[moveNum - 1], " ");
4185             strcat(parseList[moveNum - 1], elapsed_time);
4186             moveList[moveNum - 1][0] = NULLCHAR;
4187         } else if (strcmp(move_str, "none") == 0) {
4188             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4189             /* Again, we don't know what the board looked like;
4190                this is really the start of the game. */
4191             parseList[moveNum - 1][0] = NULLCHAR;
4192             moveList[moveNum - 1][0] = NULLCHAR;
4193             backwardMostMove = moveNum;
4194             startedFromSetupPosition = TRUE;
4195             fromX = fromY = toX = toY = -1;
4196         } else {
4197           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4198           //                 So we parse the long-algebraic move string in stead of the SAN move
4199           int valid; char buf[MSG_SIZ], *prom;
4200
4201           // str looks something like "Q/a1-a2"; kill the slash
4202           if(str[1] == '/')
4203                 sprintf(buf, "%c%s", str[0], str+2);
4204           else  strcpy(buf, str); // might be castling
4205           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4206                 strcat(buf, prom); // long move lacks promo specification!
4207           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4208                 if(appData.debugMode)
4209                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4210                 strcpy(move_str, buf);
4211           }
4212           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4213                                 &fromX, &fromY, &toX, &toY, &promoChar)
4214                || ParseOneMove(buf, moveNum - 1, &moveType,
4215                                 &fromX, &fromY, &toX, &toY, &promoChar);
4216           // end of long SAN patch
4217           if (valid) {
4218             (void) CoordsToAlgebraic(boards[moveNum - 1],
4219                                      PosFlags(moveNum - 1),
4220                                      fromY, fromX, toY, toX, promoChar,
4221                                      parseList[moveNum-1]);
4222             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4223               case MT_NONE:
4224               case MT_STALEMATE:
4225               default:
4226                 break;
4227               case MT_CHECK:
4228                 if(gameInfo.variant != VariantShogi)
4229                     strcat(parseList[moveNum - 1], "+");
4230                 break;
4231               case MT_CHECKMATE:
4232               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4233                 strcat(parseList[moveNum - 1], "#");
4234                 break;
4235             }
4236             strcat(parseList[moveNum - 1], " ");
4237             strcat(parseList[moveNum - 1], elapsed_time);
4238             /* currentMoveString is set as a side-effect of ParseOneMove */
4239             strcpy(moveList[moveNum - 1], currentMoveString);
4240             strcat(moveList[moveNum - 1], "\n");
4241           } else {
4242             /* Move from ICS was illegal!?  Punt. */
4243   if (appData.debugMode) {
4244     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4245     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4246   }
4247             strcpy(parseList[moveNum - 1], move_str);
4248             strcat(parseList[moveNum - 1], " ");
4249             strcat(parseList[moveNum - 1], elapsed_time);
4250             moveList[moveNum - 1][0] = NULLCHAR;
4251             fromX = fromY = toX = toY = -1;
4252           }
4253         }
4254   if (appData.debugMode) {
4255     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4256     setbuf(debugFP, NULL);
4257   }
4258
4259 #if ZIPPY
4260         /* Send move to chess program (BEFORE animating it). */
4261         if (appData.zippyPlay && !newGame && newMove &&
4262            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4263
4264             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4265                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4266                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4267                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4268                             move_str);
4269                     DisplayError(str, 0);
4270                 } else {
4271                     if (first.sendTime) {
4272                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4273                     }
4274                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4275                     if (firstMove && !bookHit) {
4276                         firstMove = FALSE;
4277                         if (first.useColors) {
4278                           SendToProgram(gameMode == IcsPlayingWhite ?
4279                                         "white\ngo\n" :
4280                                         "black\ngo\n", &first);
4281                         } else {
4282                           SendToProgram("go\n", &first);
4283                         }
4284                         first.maybeThinking = TRUE;
4285                     }
4286                 }
4287             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4288               if (moveList[moveNum - 1][0] == NULLCHAR) {
4289                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4290                 DisplayError(str, 0);
4291               } else {
4292                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4293                 SendMoveToProgram(moveNum - 1, &first);
4294               }
4295             }
4296         }
4297 #endif
4298     }
4299
4300     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4301         /* If move comes from a remote source, animate it.  If it
4302            isn't remote, it will have already been animated. */
4303         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4304             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4305         }
4306         if (!pausing && appData.highlightLastMove) {
4307             SetHighlights(fromX, fromY, toX, toY);
4308         }
4309     }
4310
4311     /* Start the clocks */
4312     whiteFlag = blackFlag = FALSE;
4313     appData.clockMode = !(basetime == 0 && increment == 0);
4314     if (ticking == 0) {
4315       ics_clock_paused = TRUE;
4316       StopClocks();
4317     } else if (ticking == 1) {
4318       ics_clock_paused = FALSE;
4319     }
4320     if (gameMode == IcsIdle ||
4321         relation == RELATION_OBSERVING_STATIC ||
4322         relation == RELATION_EXAMINING ||
4323         ics_clock_paused)
4324       DisplayBothClocks();
4325     else
4326       StartClocks();
4327
4328     /* Display opponents and material strengths */
4329     if (gameInfo.variant != VariantBughouse &&
4330         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4331         if (tinyLayout || smallLayout) {
4332             if(gameInfo.variant == VariantNormal)
4333                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4334                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4335                     basetime, increment);
4336             else
4337                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4338                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4339                     basetime, increment, (int) gameInfo.variant);
4340         } else {
4341             if(gameInfo.variant == VariantNormal)
4342                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4343                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4344                     basetime, increment);
4345             else
4346                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4347                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4348                     basetime, increment, VariantName(gameInfo.variant));
4349         }
4350         DisplayTitle(str);
4351   if (appData.debugMode) {
4352     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4353   }
4354     }
4355
4356
4357     /* Display the board */
4358     if (!pausing && !appData.noGUI) {
4359       if (appData.premove)
4360           if (!gotPremove ||
4361              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4362              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4363               ClearPremoveHighlights();
4364
4365       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4366       DrawPosition(j, boards[currentMove]);
4367
4368       DisplayMove(moveNum - 1);
4369       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4370             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4371               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4372         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4373       }
4374     }
4375
4376     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4377 #if ZIPPY
4378     if(bookHit) { // [HGM] book: simulate book reply
4379         static char bookMove[MSG_SIZ]; // a bit generous?
4380
4381         programStats.nodes = programStats.depth = programStats.time =
4382         programStats.score = programStats.got_only_move = 0;
4383         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4384
4385         strcpy(bookMove, "move ");
4386         strcat(bookMove, bookHit);
4387         HandleMachineMove(bookMove, &first);
4388     }
4389 #endif
4390 }
4391
4392 void
4393 GetMoveListEvent()
4394 {
4395     char buf[MSG_SIZ];
4396     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4397         ics_getting_history = H_REQUESTED;
4398         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4399         SendToICS(buf);
4400     }
4401 }
4402
4403 void
4404 AnalysisPeriodicEvent(force)
4405      int force;
4406 {
4407     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4408          && !force) || !appData.periodicUpdates)
4409       return;
4410
4411     /* Send . command to Crafty to collect stats */
4412     SendToProgram(".\n", &first);
4413
4414     /* Don't send another until we get a response (this makes
4415        us stop sending to old Crafty's which don't understand
4416        the "." command (sending illegal cmds resets node count & time,
4417        which looks bad)) */
4418     programStats.ok_to_send = 0;
4419 }
4420
4421 void ics_update_width(new_width)
4422         int new_width;
4423 {
4424         ics_printf("set width %d\n", new_width);
4425 }
4426
4427 void
4428 SendMoveToProgram(moveNum, cps)
4429      int moveNum;
4430      ChessProgramState *cps;
4431 {
4432     char buf[MSG_SIZ];
4433
4434     if (cps->useUsermove) {
4435       SendToProgram("usermove ", cps);
4436     }
4437     if (cps->useSAN) {
4438       char *space;
4439       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4440         int len = space - parseList[moveNum];
4441         memcpy(buf, parseList[moveNum], len);
4442         buf[len++] = '\n';
4443         buf[len] = NULLCHAR;
4444       } else {
4445         sprintf(buf, "%s\n", parseList[moveNum]);
4446       }
4447       SendToProgram(buf, cps);
4448     } else {
4449       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4450         AlphaRank(moveList[moveNum], 4);
4451         SendToProgram(moveList[moveNum], cps);
4452         AlphaRank(moveList[moveNum], 4); // and back
4453       } else
4454       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4455        * the engine. It would be nice to have a better way to identify castle
4456        * moves here. */
4457       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4458                                                                          && cps->useOOCastle) {
4459         int fromX = moveList[moveNum][0] - AAA;
4460         int fromY = moveList[moveNum][1] - ONE;
4461         int toX = moveList[moveNum][2] - AAA;
4462         int toY = moveList[moveNum][3] - ONE;
4463         if((boards[moveNum][fromY][fromX] == WhiteKing
4464             && boards[moveNum][toY][toX] == WhiteRook)
4465            || (boards[moveNum][fromY][fromX] == BlackKing
4466                && boards[moveNum][toY][toX] == BlackRook)) {
4467           if(toX > fromX) SendToProgram("O-O\n", cps);
4468           else SendToProgram("O-O-O\n", cps);
4469         }
4470         else SendToProgram(moveList[moveNum], cps);
4471       }
4472       else SendToProgram(moveList[moveNum], cps);
4473       /* End of additions by Tord */
4474     }
4475
4476     /* [HGM] setting up the opening has brought engine in force mode! */
4477     /*       Send 'go' if we are in a mode where machine should play. */
4478     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4479         (gameMode == TwoMachinesPlay   ||
4480 #ifdef ZIPPY
4481          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4482 #endif
4483          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4484         SendToProgram("go\n", cps);
4485   if (appData.debugMode) {
4486     fprintf(debugFP, "(extra)\n");
4487   }
4488     }
4489     setboardSpoiledMachineBlack = 0;
4490 }
4491
4492 void
4493 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4494      ChessMove moveType;
4495      int fromX, fromY, toX, toY;
4496 {
4497     char user_move[MSG_SIZ];
4498
4499     switch (moveType) {
4500       default:
4501         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4502                 (int)moveType, fromX, fromY, toX, toY);
4503         DisplayError(user_move + strlen("say "), 0);
4504         break;
4505       case WhiteKingSideCastle:
4506       case BlackKingSideCastle:
4507       case WhiteQueenSideCastleWild:
4508       case BlackQueenSideCastleWild:
4509       /* PUSH Fabien */
4510       case WhiteHSideCastleFR:
4511       case BlackHSideCastleFR:
4512       /* POP Fabien */
4513         sprintf(user_move, "o-o\n");
4514         break;
4515       case WhiteQueenSideCastle:
4516       case BlackQueenSideCastle:
4517       case WhiteKingSideCastleWild:
4518       case BlackKingSideCastleWild:
4519       /* PUSH Fabien */
4520       case WhiteASideCastleFR:
4521       case BlackASideCastleFR:
4522       /* POP Fabien */
4523         sprintf(user_move, "o-o-o\n");
4524         break;
4525       case WhitePromotionQueen:
4526       case BlackPromotionQueen:
4527       case WhitePromotionRook:
4528       case BlackPromotionRook:
4529       case WhitePromotionBishop:
4530       case BlackPromotionBishop:
4531       case WhitePromotionKnight:
4532       case BlackPromotionKnight:
4533       case WhitePromotionKing:
4534       case BlackPromotionKing:
4535       case WhitePromotionChancellor:
4536       case BlackPromotionChancellor:
4537       case WhitePromotionArchbishop:
4538       case BlackPromotionArchbishop:
4539         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4540             sprintf(user_move, "%c%c%c%c=%c\n",
4541                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4542                 PieceToChar(WhiteFerz));
4543         else if(gameInfo.variant == VariantGreat)
4544             sprintf(user_move, "%c%c%c%c=%c\n",
4545                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4546                 PieceToChar(WhiteMan));
4547         else
4548             sprintf(user_move, "%c%c%c%c=%c\n",
4549                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4550                 PieceToChar(PromoPiece(moveType)));
4551         break;
4552       case WhiteDrop:
4553       case BlackDrop:
4554         sprintf(user_move, "%c@%c%c\n",
4555                 ToUpper(PieceToChar((ChessSquare) fromX)),
4556                 AAA + toX, ONE + toY);
4557         break;
4558       case NormalMove:
4559       case WhiteCapturesEnPassant:
4560       case BlackCapturesEnPassant:
4561       case IllegalMove:  /* could be a variant we don't quite understand */
4562         sprintf(user_move, "%c%c%c%c\n",
4563                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4564         break;
4565     }
4566     SendToICS(user_move);
4567     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4568         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4569 }
4570
4571 void
4572 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4573      int rf, ff, rt, ft;
4574      char promoChar;
4575      char move[7];
4576 {
4577     if (rf == DROP_RANK) {
4578         sprintf(move, "%c@%c%c\n",
4579                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4580     } else {
4581         if (promoChar == 'x' || promoChar == NULLCHAR) {
4582             sprintf(move, "%c%c%c%c\n",
4583                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4584         } else {
4585             sprintf(move, "%c%c%c%c%c\n",
4586                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4587         }
4588     }
4589 }
4590
4591 void
4592 ProcessICSInitScript(f)
4593      FILE *f;
4594 {
4595     char buf[MSG_SIZ];
4596
4597     while (fgets(buf, MSG_SIZ, f)) {
4598         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4599     }
4600
4601     fclose(f);
4602 }
4603
4604
4605 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4606 void
4607 AlphaRank(char *move, int n)
4608 {
4609 //    char *p = move, c; int x, y;
4610
4611     if (appData.debugMode) {
4612         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4613     }
4614
4615     if(move[1]=='*' &&
4616        move[2]>='0' && move[2]<='9' &&
4617        move[3]>='a' && move[3]<='x'    ) {
4618         move[1] = '@';
4619         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4620         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4621     } else
4622     if(move[0]>='0' && move[0]<='9' &&
4623        move[1]>='a' && move[1]<='x' &&
4624        move[2]>='0' && move[2]<='9' &&
4625        move[3]>='a' && move[3]<='x'    ) {
4626         /* input move, Shogi -> normal */
4627         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4628         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4629         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4630         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4631     } else
4632     if(move[1]=='@' &&
4633        move[3]>='0' && move[3]<='9' &&
4634        move[2]>='a' && move[2]<='x'    ) {
4635         move[1] = '*';
4636         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4637         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4638     } else
4639     if(
4640        move[0]>='a' && move[0]<='x' &&
4641        move[3]>='0' && move[3]<='9' &&
4642        move[2]>='a' && move[2]<='x'    ) {
4643          /* output move, normal -> Shogi */
4644         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4645         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4646         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4647         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4648         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4649     }
4650     if (appData.debugMode) {
4651         fprintf(debugFP, "   out = '%s'\n", move);
4652     }
4653 }
4654
4655 /* Parser for moves from gnuchess, ICS, or user typein box */
4656 Boolean
4657 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4658      char *move;
4659      int moveNum;
4660      ChessMove *moveType;
4661      int *fromX, *fromY, *toX, *toY;
4662      char *promoChar;
4663 {
4664     if (appData.debugMode) {
4665         fprintf(debugFP, "move to parse: %s\n", move);
4666     }
4667     *moveType = yylexstr(moveNum, move);
4668
4669     switch (*moveType) {
4670       case WhitePromotionChancellor:
4671       case BlackPromotionChancellor:
4672       case WhitePromotionArchbishop:
4673       case BlackPromotionArchbishop:
4674       case WhitePromotionQueen:
4675       case BlackPromotionQueen:
4676       case WhitePromotionRook:
4677       case BlackPromotionRook:
4678       case WhitePromotionBishop:
4679       case BlackPromotionBishop:
4680       case WhitePromotionKnight:
4681       case BlackPromotionKnight:
4682       case WhitePromotionKing:
4683       case BlackPromotionKing:
4684       case NormalMove:
4685       case WhiteCapturesEnPassant:
4686       case BlackCapturesEnPassant:
4687       case WhiteKingSideCastle:
4688       case WhiteQueenSideCastle:
4689       case BlackKingSideCastle:
4690       case BlackQueenSideCastle:
4691       case WhiteKingSideCastleWild:
4692       case WhiteQueenSideCastleWild:
4693       case BlackKingSideCastleWild:
4694       case BlackQueenSideCastleWild:
4695       /* Code added by Tord: */
4696       case WhiteHSideCastleFR:
4697       case WhiteASideCastleFR:
4698       case BlackHSideCastleFR:
4699       case BlackASideCastleFR:
4700       /* End of code added by Tord */
4701       case IllegalMove:         /* bug or odd chess variant */
4702         *fromX = currentMoveString[0] - AAA;
4703         *fromY = currentMoveString[1] - ONE;
4704         *toX = currentMoveString[2] - AAA;
4705         *toY = currentMoveString[3] - ONE;
4706         *promoChar = currentMoveString[4];
4707         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4708             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4709     if (appData.debugMode) {
4710         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4711     }
4712             *fromX = *fromY = *toX = *toY = 0;
4713             return FALSE;
4714         }
4715         if (appData.testLegality) {
4716           return (*moveType != IllegalMove);
4717         } else {
4718           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4719                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4720         }
4721
4722       case WhiteDrop:
4723       case BlackDrop:
4724         *fromX = *moveType == WhiteDrop ?
4725           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4726           (int) CharToPiece(ToLower(currentMoveString[0]));
4727         *fromY = DROP_RANK;
4728         *toX = currentMoveString[2] - AAA;
4729         *toY = currentMoveString[3] - ONE;
4730         *promoChar = NULLCHAR;
4731         return TRUE;
4732
4733       case AmbiguousMove:
4734       case ImpossibleMove:
4735       case (ChessMove) 0:       /* end of file */
4736       case ElapsedTime:
4737       case Comment:
4738       case PGNTag:
4739       case NAG:
4740       case WhiteWins:
4741       case BlackWins:
4742       case GameIsDrawn:
4743       default:
4744     if (appData.debugMode) {
4745         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4746     }
4747         /* bug? */
4748         *fromX = *fromY = *toX = *toY = 0;
4749         *promoChar = NULLCHAR;
4750         return FALSE;
4751     }
4752 }
4753
4754
4755 void
4756 ParsePV(char *pv)
4757 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4758   int fromX, fromY, toX, toY; char promoChar;
4759   ChessMove moveType;
4760   Boolean valid;
4761   int nr = 0;
4762
4763   endPV = forwardMostMove;
4764   do {
4765     while(*pv == ' ') pv++;
4766     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4767     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4768 if(appData.debugMode){
4769 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4770 }
4771     if(!valid && nr == 0 &&
4772        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4773         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4774     }
4775     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4776     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4777     nr++;
4778     if(endPV+1 > framePtr) break; // no space, truncate
4779     if(!valid) break;
4780     endPV++;
4781     CopyBoard(boards[endPV], boards[endPV-1]);
4782     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4783     moveList[endPV-1][0] = fromX + AAA;
4784     moveList[endPV-1][1] = fromY + ONE;
4785     moveList[endPV-1][2] = toX + AAA;
4786     moveList[endPV-1][3] = toY + ONE;
4787     parseList[endPV-1][0] = NULLCHAR;
4788   } while(valid);
4789   currentMove = endPV;
4790   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4791   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4792                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4793   DrawPosition(TRUE, boards[currentMove]);
4794 }
4795
4796 static int lastX, lastY;
4797
4798 Boolean
4799 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4800 {
4801         int startPV;
4802
4803         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4804         lastX = x; lastY = y;
4805         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4806         startPV = index;
4807       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4808       index = startPV;
4809         while(buf[index] && buf[index] != '\n') index++;
4810         buf[index] = 0;
4811         ParsePV(buf+startPV);
4812         *start = startPV; *end = index-1;
4813         return TRUE;
4814 }
4815
4816 Boolean
4817 LoadPV(int x, int y)
4818 { // called on right mouse click to load PV
4819   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4820   lastX = x; lastY = y;
4821   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4822   return TRUE;
4823 }
4824
4825 void
4826 UnLoadPV()
4827 {
4828   if(endPV < 0) return;
4829   endPV = -1;
4830   currentMove = forwardMostMove;
4831   ClearPremoveHighlights();
4832   DrawPosition(TRUE, boards[currentMove]);
4833 }
4834
4835 void
4836 MovePV(int x, int y, int h)
4837 { // step through PV based on mouse coordinates (called on mouse move)
4838   int margin = h>>3, step = 0;
4839
4840   if(endPV < 0) return;
4841   // we must somehow check if right button is still down (might be released off board!)
4842   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4843   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4844   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4845   if(!step) return;
4846   lastX = x; lastY = y;
4847   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4848   currentMove += step;
4849   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4850   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4851                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4852   DrawPosition(FALSE, boards[currentMove]);
4853 }
4854
4855
4856 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4857 // All positions will have equal probability, but the current method will not provide a unique
4858 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4859 #define DARK 1
4860 #define LITE 2
4861 #define ANY 3
4862
4863 int squaresLeft[4];
4864 int piecesLeft[(int)BlackPawn];
4865 int seed, nrOfShuffles;
4866
4867 void GetPositionNumber()
4868 {       // sets global variable seed
4869         int i;
4870
4871         seed = appData.defaultFrcPosition;
4872         if(seed < 0) { // randomize based on time for negative FRC position numbers
4873                 for(i=0; i<50; i++) seed += random();
4874                 seed = random() ^ random() >> 8 ^ random() << 8;
4875                 if(seed<0) seed = -seed;
4876         }
4877 }
4878
4879 int put(Board board, int pieceType, int rank, int n, int shade)
4880 // put the piece on the (n-1)-th empty squares of the given shade
4881 {
4882         int i;
4883
4884         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4885                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4886                         board[rank][i] = (ChessSquare) pieceType;
4887                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4888                         squaresLeft[ANY]--;
4889                         piecesLeft[pieceType]--;
4890                         return i;
4891                 }
4892         }
4893         return -1;
4894 }
4895
4896
4897 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4898 // calculate where the next piece goes, (any empty square), and put it there
4899 {
4900         int i;
4901
4902         i = seed % squaresLeft[shade];
4903         nrOfShuffles *= squaresLeft[shade];
4904         seed /= squaresLeft[shade];
4905         put(board, pieceType, rank, i, shade);
4906 }
4907
4908 void AddTwoPieces(Board board, int pieceType, int rank)
4909 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4910 {
4911         int i, n=squaresLeft[ANY], j=n-1, k;
4912
4913         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4914         i = seed % k;  // pick one
4915         nrOfShuffles *= k;
4916         seed /= k;
4917         while(i >= j) i -= j--;
4918         j = n - 1 - j; i += j;
4919         put(board, pieceType, rank, j, ANY);
4920         put(board, pieceType, rank, i, ANY);
4921 }
4922
4923 void SetUpShuffle(Board board, int number)
4924 {
4925         int i, p, first=1;
4926
4927         GetPositionNumber(); nrOfShuffles = 1;
4928
4929         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4930         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4931         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4932
4933         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4934
4935         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4936             p = (int) board[0][i];
4937             if(p < (int) BlackPawn) piecesLeft[p] ++;
4938             board[0][i] = EmptySquare;
4939         }
4940
4941         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4942             // shuffles restricted to allow normal castling put KRR first
4943             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4944                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4945             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4946                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4947             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4948                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4949             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4950                 put(board, WhiteRook, 0, 0, ANY);
4951             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4952         }
4953
4954         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4955             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4956             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4957                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4958                 while(piecesLeft[p] >= 2) {
4959                     AddOnePiece(board, p, 0, LITE);
4960                     AddOnePiece(board, p, 0, DARK);
4961                 }
4962                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4963             }
4964
4965         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4966             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4967             // but we leave King and Rooks for last, to possibly obey FRC restriction
4968             if(p == (int)WhiteRook) continue;
4969             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4970             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4971         }
4972
4973         // now everything is placed, except perhaps King (Unicorn) and Rooks
4974
4975         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4976             // Last King gets castling rights
4977             while(piecesLeft[(int)WhiteUnicorn]) {
4978                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4979                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4980             }
4981
4982             while(piecesLeft[(int)WhiteKing]) {
4983                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4984                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4985             }
4986
4987
4988         } else {
4989             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4990             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4991         }
4992
4993         // Only Rooks can be left; simply place them all
4994         while(piecesLeft[(int)WhiteRook]) {
4995                 i = put(board, WhiteRook, 0, 0, ANY);
4996                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4997                         if(first) {
4998                                 first=0;
4999                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5000                         }
5001                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5002                 }
5003         }
5004         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5005             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5006         }
5007
5008         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5009 }
5010
5011 int SetCharTable( char *table, const char * map )
5012 /* [HGM] moved here from winboard.c because of its general usefulness */
5013 /*       Basically a safe strcpy that uses the last character as King */
5014 {
5015     int result = FALSE; int NrPieces;
5016
5017     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5018                     && NrPieces >= 12 && !(NrPieces&1)) {
5019         int i; /* [HGM] Accept even length from 12 to 34 */
5020
5021         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5022         for( i=0; i<NrPieces/2-1; i++ ) {
5023             table[i] = map[i];
5024             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5025         }
5026         table[(int) WhiteKing]  = map[NrPieces/2-1];
5027         table[(int) BlackKing]  = map[NrPieces-1];
5028
5029         result = TRUE;
5030     }
5031
5032     return result;
5033 }
5034
5035 void Prelude(Board board)
5036 {       // [HGM] superchess: random selection of exo-pieces
5037         int i, j, k; ChessSquare p;
5038         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5039
5040         GetPositionNumber(); // use FRC position number
5041
5042         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5043             SetCharTable(pieceToChar, appData.pieceToCharTable);
5044             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5045                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5046         }
5047
5048         j = seed%4;                 seed /= 4;
5049         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5050         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5051         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5052         j = seed%3 + (seed%3 >= j); seed /= 3;
5053         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5054         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5055         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5056         j = seed%3;                 seed /= 3;
5057         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5058         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5059         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5060         j = seed%2 + (seed%2 >= j); seed /= 2;
5061         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5062         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5063         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5064         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5065         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5066         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5067         put(board, exoPieces[0],    0, 0, ANY);
5068         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5069 }
5070
5071 void
5072 InitPosition(redraw)
5073      int redraw;
5074 {
5075     ChessSquare (* pieces)[BOARD_FILES];
5076     int i, j, pawnRow, overrule,
5077     oldx = gameInfo.boardWidth,
5078     oldy = gameInfo.boardHeight,
5079     oldh = gameInfo.holdingsWidth,
5080     oldv = gameInfo.variant;
5081
5082     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5083
5084     /* [AS] Initialize pv info list [HGM] and game status */
5085     {
5086         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5087             pvInfoList[i].depth = 0;
5088             boards[i][EP_STATUS] = EP_NONE;
5089             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5090         }
5091
5092         initialRulePlies = 0; /* 50-move counter start */
5093
5094         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5095         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5096     }
5097
5098
5099     /* [HGM] logic here is completely changed. In stead of full positions */
5100     /* the initialized data only consist of the two backranks. The switch */
5101     /* selects which one we will use, which is than copied to the Board   */
5102     /* initialPosition, which for the rest is initialized by Pawns and    */
5103     /* empty squares. This initial position is then copied to boards[0],  */
5104     /* possibly after shuffling, so that it remains available.            */
5105
5106     gameInfo.holdingsWidth = 0; /* default board sizes */
5107     gameInfo.boardWidth    = 8;
5108     gameInfo.boardHeight   = 8;
5109     gameInfo.holdingsSize  = 0;
5110     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5111     for(i=0; i<BOARD_FILES-2; i++)
5112       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5113     initialPosition[EP_STATUS] = EP_NONE;
5114     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5115
5116     switch (gameInfo.variant) {
5117     case VariantFischeRandom:
5118       shuffleOpenings = TRUE;
5119     default:
5120       pieces = FIDEArray;
5121       break;
5122     case VariantShatranj:
5123       pieces = ShatranjArray;
5124       nrCastlingRights = 0;
5125       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5126       break;
5127     case VariantMakruk:
5128       pieces = makrukArray;
5129       nrCastlingRights = 0;
5130       startedFromSetupPosition = TRUE;
5131       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5132       break;
5133     case VariantTwoKings:
5134       pieces = twoKingsArray;
5135       break;
5136     case VariantCapaRandom:
5137       shuffleOpenings = TRUE;
5138     case VariantCapablanca:
5139       pieces = CapablancaArray;
5140       gameInfo.boardWidth = 10;
5141       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5142       break;
5143     case VariantGothic:
5144       pieces = GothicArray;
5145       gameInfo.boardWidth = 10;
5146       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5147       break;
5148     case VariantJanus:
5149       pieces = JanusArray;
5150       gameInfo.boardWidth = 10;
5151       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5152       nrCastlingRights = 6;
5153         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5154         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5155         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5156         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5157         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5158         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5159       break;
5160     case VariantFalcon:
5161       pieces = FalconArray;
5162       gameInfo.boardWidth = 10;
5163       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5164       break;
5165     case VariantXiangqi:
5166       pieces = XiangqiArray;
5167       gameInfo.boardWidth  = 9;
5168       gameInfo.boardHeight = 10;
5169       nrCastlingRights = 0;
5170       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5171       break;
5172     case VariantShogi:
5173       pieces = ShogiArray;
5174       gameInfo.boardWidth  = 9;
5175       gameInfo.boardHeight = 9;
5176       gameInfo.holdingsSize = 7;
5177       nrCastlingRights = 0;
5178       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5179       break;
5180     case VariantCourier:
5181       pieces = CourierArray;
5182       gameInfo.boardWidth  = 12;
5183       nrCastlingRights = 0;
5184       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5185       break;
5186     case VariantKnightmate:
5187       pieces = KnightmateArray;
5188       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5189       break;
5190     case VariantFairy:
5191       pieces = fairyArray;
5192       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5193       break;
5194     case VariantGreat:
5195       pieces = GreatArray;
5196       gameInfo.boardWidth = 10;
5197       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5198       gameInfo.holdingsSize = 8;
5199       break;
5200     case VariantSuper:
5201       pieces = FIDEArray;
5202       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5203       gameInfo.holdingsSize = 8;
5204       startedFromSetupPosition = TRUE;
5205       break;
5206     case VariantCrazyhouse:
5207     case VariantBughouse:
5208       pieces = FIDEArray;
5209       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5210       gameInfo.holdingsSize = 5;
5211       break;
5212     case VariantWildCastle:
5213       pieces = FIDEArray;
5214       /* !!?shuffle with kings guaranteed to be on d or e file */
5215       shuffleOpenings = 1;
5216       break;
5217     case VariantNoCastle:
5218       pieces = FIDEArray;
5219       nrCastlingRights = 0;
5220       /* !!?unconstrained back-rank shuffle */
5221       shuffleOpenings = 1;
5222       break;
5223     }
5224
5225     overrule = 0;
5226     if(appData.NrFiles >= 0) {
5227         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5228         gameInfo.boardWidth = appData.NrFiles;
5229     }
5230     if(appData.NrRanks >= 0) {
5231         gameInfo.boardHeight = appData.NrRanks;
5232     }
5233     if(appData.holdingsSize >= 0) {
5234         i = appData.holdingsSize;
5235         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5236         gameInfo.holdingsSize = i;
5237     }
5238     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5239     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5240         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5241
5242     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5243     if(pawnRow < 1) pawnRow = 1;
5244     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5245
5246     /* User pieceToChar list overrules defaults */
5247     if(appData.pieceToCharTable != NULL)
5248         SetCharTable(pieceToChar, appData.pieceToCharTable);
5249
5250     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5251
5252         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5253             s = (ChessSquare) 0; /* account holding counts in guard band */
5254         for( i=0; i<BOARD_HEIGHT; i++ )
5255             initialPosition[i][j] = s;
5256
5257         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5258         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5259         initialPosition[pawnRow][j] = WhitePawn;
5260         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5261         if(gameInfo.variant == VariantXiangqi) {
5262             if(j&1) {
5263                 initialPosition[pawnRow][j] =
5264                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5265                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5266                    initialPosition[2][j] = WhiteCannon;
5267                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5268                 }
5269             }
5270         }
5271         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5272     }
5273     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5274
5275             j=BOARD_LEFT+1;
5276             initialPosition[1][j] = WhiteBishop;
5277             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5278             j=BOARD_RGHT-2;
5279             initialPosition[1][j] = WhiteRook;
5280             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5281     }
5282
5283     if( nrCastlingRights == -1) {
5284         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5285         /*       This sets default castling rights from none to normal corners   */
5286         /* Variants with other castling rights must set them themselves above    */
5287         nrCastlingRights = 6;
5288         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5289         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5290         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5291         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5292         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5293         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5294      }
5295
5296      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5297      if(gameInfo.variant == VariantGreat) { // promotion commoners
5298         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5299         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5300         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5301         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5302      }
5303   if (appData.debugMode) {
5304     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5305   }
5306     if(shuffleOpenings) {
5307         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5308         startedFromSetupPosition = TRUE;
5309     }
5310     if(startedFromPositionFile) {
5311       /* [HGM] loadPos: use PositionFile for every new game */
5312       CopyBoard(initialPosition, filePosition);
5313       for(i=0; i<nrCastlingRights; i++)
5314           initialRights[i] = filePosition[CASTLING][i];
5315       startedFromSetupPosition = TRUE;
5316     }
5317
5318     CopyBoard(boards[0], initialPosition);
5319     if(oldx != gameInfo.boardWidth ||
5320        oldy != gameInfo.boardHeight ||
5321        oldh != gameInfo.holdingsWidth
5322 #ifdef GOTHIC
5323        || oldv == VariantGothic ||        // For licensing popups
5324        gameInfo.variant == VariantGothic
5325 #endif
5326 #ifdef FALCON
5327        || oldv == VariantFalcon ||
5328        gameInfo.variant == VariantFalcon
5329 #endif
5330                                          )
5331       {
5332             InitDrawingSizes(-2 ,0);
5333       }
5334
5335     if (redraw)
5336       DrawPosition(TRUE, boards[currentMove]);
5337
5338 }
5339
5340 void
5341 SendBoard(cps, moveNum)
5342      ChessProgramState *cps;
5343      int moveNum;
5344 {
5345     char message[MSG_SIZ];
5346
5347     if (cps->useSetboard) {
5348       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5349       sprintf(message, "setboard %s\n", fen);
5350       SendToProgram(message, cps);
5351       free(fen);
5352
5353     } else {
5354       ChessSquare *bp;
5355       int i, j;
5356       /* Kludge to set black to move, avoiding the troublesome and now
5357        * deprecated "black" command.
5358        */
5359       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5360
5361       SendToProgram("edit\n", cps);
5362       SendToProgram("#\n", cps);
5363       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5364         bp = &boards[moveNum][i][BOARD_LEFT];
5365         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5366           if ((int) *bp < (int) BlackPawn) {
5367             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5368                     AAA + j, ONE + i);
5369             if(message[0] == '+' || message[0] == '~') {
5370                 sprintf(message, "%c%c%c+\n",
5371                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5372                         AAA + j, ONE + i);
5373             }
5374             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5375                 message[1] = BOARD_RGHT   - 1 - j + '1';
5376                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5377             }
5378             SendToProgram(message, cps);
5379           }
5380         }
5381       }
5382
5383       SendToProgram("c\n", cps);
5384       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5385         bp = &boards[moveNum][i][BOARD_LEFT];
5386         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5387           if (((int) *bp != (int) EmptySquare)
5388               && ((int) *bp >= (int) BlackPawn)) {
5389             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5390                     AAA + j, ONE + i);
5391             if(message[0] == '+' || message[0] == '~') {
5392                 sprintf(message, "%c%c%c+\n",
5393                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5394                         AAA + j, ONE + i);
5395             }
5396             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5397                 message[1] = BOARD_RGHT   - 1 - j + '1';
5398                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5399             }
5400             SendToProgram(message, cps);
5401           }
5402         }
5403       }
5404
5405       SendToProgram(".\n", cps);
5406     }
5407     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5408 }
5409
5410 int
5411 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5412 {
5413     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5414     /* [HGM] add Shogi promotions */
5415     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5416     ChessSquare piece;
5417     ChessMove moveType;
5418     Boolean premove;
5419
5420     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5421     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5422
5423     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5424       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5425         return FALSE;
5426
5427     piece = boards[currentMove][fromY][fromX];
5428     if(gameInfo.variant == VariantShogi) {
5429         promotionZoneSize = 3;
5430         highestPromotingPiece = (int)WhiteFerz;
5431     } else if(gameInfo.variant == VariantMakruk) {
5432         promotionZoneSize = 3;
5433     }
5434
5435     // next weed out all moves that do not touch the promotion zone at all
5436     if((int)piece >= BlackPawn) {
5437         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5438              return FALSE;
5439         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5440     } else {
5441         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5442            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5443     }
5444
5445     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5446
5447     // weed out mandatory Shogi promotions
5448     if(gameInfo.variant == VariantShogi) {
5449         if(piece >= BlackPawn) {
5450             if(toY == 0 && piece == BlackPawn ||
5451                toY == 0 && piece == BlackQueen ||
5452                toY <= 1 && piece == BlackKnight) {
5453                 *promoChoice = '+';
5454                 return FALSE;
5455             }
5456         } else {
5457             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5458                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5459                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5460                 *promoChoice = '+';
5461                 return FALSE;
5462             }
5463         }
5464     }
5465
5466     // weed out obviously illegal Pawn moves
5467     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5468         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5469         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5470         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5471         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5472         // note we are not allowed to test for valid (non-)capture, due to premove
5473     }
5474
5475     // we either have a choice what to promote to, or (in Shogi) whether to promote
5476     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5477         *promoChoice = PieceToChar(BlackFerz);  // no choice
5478         return FALSE;
5479     }
5480     if(appData.alwaysPromoteToQueen) { // predetermined
5481         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5482              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5483         else *promoChoice = PieceToChar(BlackQueen);
5484         return FALSE;
5485     }
5486
5487     // suppress promotion popup on illegal moves that are not premoves
5488     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5489               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5490     if(appData.testLegality && !premove) {
5491         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5492                         fromY, fromX, toY, toX, NULLCHAR);
5493         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5494            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5495             return FALSE;
5496     }
5497
5498     return TRUE;
5499 }
5500
5501 int
5502 InPalace(row, column)
5503      int row, column;
5504 {   /* [HGM] for Xiangqi */
5505     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5506          column < (BOARD_WIDTH + 4)/2 &&
5507          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5508     return FALSE;
5509 }
5510
5511 int
5512 PieceForSquare (x, y)
5513      int x;
5514      int y;
5515 {
5516   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5517      return -1;
5518   else
5519      return boards[currentMove][y][x];
5520 }
5521
5522 int
5523 OKToStartUserMove(x, y)
5524      int x, y;
5525 {
5526     ChessSquare from_piece;
5527     int white_piece;
5528
5529     if (matchMode) return FALSE;
5530     if (gameMode == EditPosition) return TRUE;
5531
5532     if (x >= 0 && y >= 0)
5533       from_piece = boards[currentMove][y][x];
5534     else
5535       from_piece = EmptySquare;
5536
5537     if (from_piece == EmptySquare) return FALSE;
5538
5539     white_piece = (int)from_piece >= (int)WhitePawn &&
5540       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5541
5542     switch (gameMode) {
5543       case PlayFromGameFile:
5544       case AnalyzeFile:
5545       case TwoMachinesPlay:
5546       case EndOfGame:
5547         return FALSE;
5548
5549       case IcsObserving:
5550       case IcsIdle:
5551         return FALSE;
5552
5553       case MachinePlaysWhite:
5554       case IcsPlayingBlack:
5555         if (appData.zippyPlay) return FALSE;
5556         if (white_piece) {
5557             DisplayMoveError(_("You are playing Black"));
5558             return FALSE;
5559         }
5560         break;
5561
5562       case MachinePlaysBlack:
5563       case IcsPlayingWhite:
5564         if (appData.zippyPlay) return FALSE;
5565         if (!white_piece) {
5566             DisplayMoveError(_("You are playing White"));
5567             return FALSE;
5568         }
5569         break;
5570
5571       case EditGame:
5572         if (!white_piece && WhiteOnMove(currentMove)) {
5573             DisplayMoveError(_("It is White's turn"));
5574             return FALSE;
5575         }
5576         if (white_piece && !WhiteOnMove(currentMove)) {
5577             DisplayMoveError(_("It is Black's turn"));
5578             return FALSE;
5579         }
5580         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5581             /* Editing correspondence game history */
5582             /* Could disallow this or prompt for confirmation */
5583             cmailOldMove = -1;
5584         }
5585         break;
5586
5587       case BeginningOfGame:
5588         if (appData.icsActive) return FALSE;
5589         if (!appData.noChessProgram) {
5590             if (!white_piece) {
5591                 DisplayMoveError(_("You are playing White"));
5592                 return FALSE;
5593             }
5594         }
5595         break;
5596
5597       case Training:
5598         if (!white_piece && WhiteOnMove(currentMove)) {
5599             DisplayMoveError(_("It is White's turn"));
5600             return FALSE;
5601         }
5602         if (white_piece && !WhiteOnMove(currentMove)) {
5603             DisplayMoveError(_("It is Black's turn"));
5604             return FALSE;
5605         }
5606         break;
5607
5608       default:
5609       case IcsExamining:
5610         break;
5611     }
5612     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5613         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5614         && gameMode != AnalyzeFile && gameMode != Training) {
5615         DisplayMoveError(_("Displayed position is not current"));
5616         return FALSE;
5617     }
5618     return TRUE;
5619 }
5620
5621 Boolean
5622 OnlyMove(int *x, int *y) {
5623     DisambiguateClosure cl;
5624     if (appData.zippyPlay) return FALSE;
5625     switch(gameMode) {
5626       case MachinePlaysBlack:
5627       case IcsPlayingWhite:
5628       case BeginningOfGame:
5629         if(!WhiteOnMove(currentMove)) return FALSE;
5630         break;
5631       case MachinePlaysWhite:
5632       case IcsPlayingBlack:
5633         if(WhiteOnMove(currentMove)) return FALSE;
5634         break;
5635       default:
5636         return FALSE;
5637     }
5638     cl.pieceIn = EmptySquare; 
5639     cl.rfIn = *y;
5640     cl.ffIn = *x;
5641     cl.rtIn = -1;
5642     cl.ftIn = -1;
5643     cl.promoCharIn = NULLCHAR;
5644     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5645     if(cl.kind == NormalMove) {
5646       fromX = cl.ff;
5647       fromY = cl.rf;
5648       *x = cl.ft;
5649       *y = cl.rt;
5650       return TRUE;
5651     }
5652     if(cl.kind != ImpossibleMove) return FALSE;
5653     cl.pieceIn = EmptySquare;
5654     cl.rfIn = -1;
5655     cl.ffIn = -1;
5656     cl.rtIn = *y;
5657     cl.ftIn = *x;
5658     cl.promoCharIn = NULLCHAR;
5659     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5660     if(cl.kind == NormalMove) {
5661       fromX = cl.ff;
5662       fromY = cl.rf;
5663       *x = cl.ft;
5664       *y = cl.rt;
5665       return TRUE;
5666     }
5667     return FALSE;
5668 }
5669
5670 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5671 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5672 int lastLoadGameUseList = FALSE;
5673 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5674 ChessMove lastLoadGameStart = (ChessMove) 0;
5675
5676 ChessMove
5677 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5678      int fromX, fromY, toX, toY;
5679      int promoChar;
5680      Boolean captureOwn;
5681 {
5682     ChessMove moveType;
5683     ChessSquare pdown, pup;
5684
5685     /* Check if the user is playing in turn.  This is complicated because we
5686        let the user "pick up" a piece before it is his turn.  So the piece he
5687        tried to pick up may have been captured by the time he puts it down!
5688        Therefore we use the color the user is supposed to be playing in this
5689        test, not the color of the piece that is currently on the starting
5690        square---except in EditGame mode, where the user is playing both
5691        sides; fortunately there the capture race can't happen.  (It can
5692        now happen in IcsExamining mode, but that's just too bad.  The user
5693        will get a somewhat confusing message in that case.)
5694        */
5695
5696     switch (gameMode) {
5697       case PlayFromGameFile:
5698       case AnalyzeFile:
5699       case TwoMachinesPlay:
5700       case EndOfGame:
5701       case IcsObserving:
5702       case IcsIdle:
5703         /* We switched into a game mode where moves are not accepted,
5704            perhaps while the mouse button was down. */
5705         return ImpossibleMove;
5706
5707       case MachinePlaysWhite:
5708         /* User is moving for Black */
5709         if (WhiteOnMove(currentMove)) {
5710             DisplayMoveError(_("It is White's turn"));
5711             return ImpossibleMove;
5712         }
5713         break;
5714
5715       case MachinePlaysBlack:
5716         /* User is moving for White */
5717         if (!WhiteOnMove(currentMove)) {
5718             DisplayMoveError(_("It is Black's turn"));
5719             return ImpossibleMove;
5720         }
5721         break;
5722
5723       case EditGame:
5724       case IcsExamining:
5725       case BeginningOfGame:
5726       case AnalyzeMode:
5727       case Training:
5728         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5729             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5730             /* User is moving for Black */
5731             if (WhiteOnMove(currentMove)) {
5732                 DisplayMoveError(_("It is White's turn"));
5733                 return ImpossibleMove;
5734             }
5735         } else {
5736             /* User is moving for White */
5737             if (!WhiteOnMove(currentMove)) {
5738                 DisplayMoveError(_("It is Black's turn"));
5739                 return ImpossibleMove;
5740             }
5741         }
5742         break;
5743
5744       case IcsPlayingBlack:
5745         /* User is moving for Black */
5746         if (WhiteOnMove(currentMove)) {
5747             if (!appData.premove) {
5748                 DisplayMoveError(_("It is White's turn"));
5749             } else if (toX >= 0 && toY >= 0) {
5750                 premoveToX = toX;
5751                 premoveToY = toY;
5752                 premoveFromX = fromX;
5753                 premoveFromY = fromY;
5754                 premovePromoChar = promoChar;
5755                 gotPremove = 1;
5756                 if (appData.debugMode)
5757                     fprintf(debugFP, "Got premove: fromX %d,"
5758                             "fromY %d, toX %d, toY %d\n",
5759                             fromX, fromY, toX, toY);
5760             }
5761             return ImpossibleMove;
5762         }
5763         break;
5764
5765       case IcsPlayingWhite:
5766         /* User is moving for White */
5767         if (!WhiteOnMove(currentMove)) {
5768             if (!appData.premove) {
5769                 DisplayMoveError(_("It is Black's turn"));
5770             } else if (toX >= 0 && toY >= 0) {
5771                 premoveToX = toX;
5772                 premoveToY = toY;
5773                 premoveFromX = fromX;
5774                 premoveFromY = fromY;
5775                 premovePromoChar = promoChar;
5776                 gotPremove = 1;
5777                 if (appData.debugMode)
5778                     fprintf(debugFP, "Got premove: fromX %d,"
5779                             "fromY %d, toX %d, toY %d\n",
5780                             fromX, fromY, toX, toY);
5781             }
5782             return ImpossibleMove;
5783         }
5784         break;
5785
5786       default:
5787         break;
5788
5789       case EditPosition:
5790         /* EditPosition, empty square, or different color piece;
5791            click-click move is possible */
5792         if (toX == -2 || toY == -2) {
5793             boards[0][fromY][fromX] = EmptySquare;
5794             return AmbiguousMove;
5795         } else if (toX >= 0 && toY >= 0) {
5796             boards[0][toY][toX] = boards[0][fromY][fromX];
5797             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5798                 if(boards[0][fromY][0] != EmptySquare) {
5799                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5800                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5801                 }
5802             } else
5803             if(fromX == BOARD_RGHT+1) {
5804                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5805                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5806                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5807                 }
5808             } else
5809             boards[0][fromY][fromX] = EmptySquare;
5810             return AmbiguousMove;
5811         }
5812         return ImpossibleMove;
5813     }
5814
5815     if(toX < 0 || toY < 0) return ImpossibleMove;
5816     pdown = boards[currentMove][fromY][fromX];
5817     pup = boards[currentMove][toY][toX];
5818
5819     /* [HGM] If move started in holdings, it means a drop */
5820     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5821          if( pup != EmptySquare ) return ImpossibleMove;
5822          if(appData.testLegality) {
5823              /* it would be more logical if LegalityTest() also figured out
5824               * which drops are legal. For now we forbid pawns on back rank.
5825               * Shogi is on its own here...
5826               */
5827              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5828                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5829                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5830          }
5831          return WhiteDrop; /* Not needed to specify white or black yet */
5832     }
5833
5834
5835     /* [HGM] always test for legality, to get promotion info */
5836     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5837                                          fromY, fromX, toY, toX, promoChar);
5838     /* [HGM] but possibly ignore an IllegalMove result */
5839     if (appData.testLegality) {
5840         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5841             DisplayMoveError(_("Illegal move"));
5842             return ImpossibleMove;
5843         }
5844     }
5845
5846     return moveType;
5847     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5848        function is made into one that returns an OK move type if FinishMove
5849        should be called. This to give the calling driver routine the
5850        opportunity to finish the userMove input with a promotion popup,
5851        without bothering the user with this for invalid or illegal moves */
5852
5853 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5854 }
5855
5856 /* Common tail of UserMoveEvent and DropMenuEvent */
5857 int
5858 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5859      ChessMove moveType;
5860      int fromX, fromY, toX, toY;
5861      /*char*/int promoChar;
5862 {
5863   char *bookHit = 0;
5864
5865   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5866     {
5867       // [HGM] superchess: suppress promotions to non-available piece
5868       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5869       if(WhiteOnMove(currentMove))
5870         {
5871           if(!boards[currentMove][k][BOARD_WIDTH-2])
5872             return 0;
5873         }
5874       else
5875         {
5876           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5877             return 0;
5878         }
5879     }
5880   
5881   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5882      move type in caller when we know the move is a legal promotion */
5883   if(moveType == NormalMove && promoChar)
5884     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5885   
5886   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5887      move type in caller when we know the move is a legal promotion */
5888   if(moveType == NormalMove && promoChar)
5889     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5890   
5891   /* [HGM] convert drag-and-drop piece drops to standard form */
5892   if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5893     {
5894       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5895       if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5896                                     moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5897       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5898       if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5899       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5900       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5901       fromY = DROP_RANK;
5902     }
5903   
5904   /* [HGM] <popupFix> The following if has been moved here from
5905      UserMoveEvent(). Because it seemed to belong here (why not allow
5906      piece drops in training games?), and because it can only be
5907      performed after it is known to what we promote. */
5908   if (gameMode == Training) 
5909     {
5910       /* compare the move played on the board to the next move in the
5911        * game. If they match, display the move and the opponent's response.
5912        * If they don't match, display an error message.
5913        */
5914       int saveAnimate;
5915       Board testBoard;
5916       CopyBoard(testBoard, boards[currentMove]);
5917       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5918
5919       if (CompareBoards(testBoard, boards[currentMove+1]))
5920         {
5921           ForwardInner(currentMove+1);
5922
5923           /* Autoplay the opponent's response.
5924            * if appData.animate was TRUE when Training mode was entered,
5925            * the response will be animated.
5926            */
5927           saveAnimate = appData.animate;
5928           appData.animate = animateTraining;
5929           ForwardInner(currentMove+1);
5930           appData.animate = saveAnimate;
5931
5932           /* check for the end of the game */
5933           if (currentMove >= forwardMostMove)
5934             {
5935               gameMode = PlayFromGameFile;
5936               ModeHighlight();
5937               SetTrainingModeOff();
5938               DisplayInformation(_("End of game"));
5939             }
5940         }
5941       else
5942         {
5943           DisplayError(_("Incorrect move"), 0);
5944         }
5945       return 1;
5946     }
5947
5948   /* Ok, now we know that the move is good, so we can kill
5949      the previous line in Analysis Mode */
5950   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5951                                 && currentMove < forwardMostMove) {
5952     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5953   }
5954
5955   /* If we need the chess program but it's dead, restart it */
5956   ResurrectChessProgram();
5957
5958   /* A user move restarts a paused game*/
5959   if (pausing)
5960     PauseEvent();
5961
5962   thinkOutput[0] = NULLCHAR;
5963
5964   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5965
5966
5967  if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5968
5969 if (gameMode == BeginningOfGame)
5970     {
5971       if (appData.noChessProgram)
5972         {
5973           gameMode = EditGame;
5974           SetGameInfo();
5975         }
5976       else
5977         {
5978           char buf[MSG_SIZ];
5979           gameMode = MachinePlaysBlack;
5980           StartClocks();
5981           SetGameInfo();
5982           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5983           DisplayTitle(buf);
5984           if (first.sendName)
5985             {
5986               sprintf(buf, "name %s\n", gameInfo.white);
5987               SendToProgram(buf, &first);
5988             }
5989           StartClocks();
5990         }
5991       ModeHighlight();
5992
5993     }
5994
5995   /* Relay move to ICS or chess engine */
5996
5997   if (appData.icsActive) {
5998     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5999         gameMode == IcsExamining) {
6000       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6001         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6002         SendToICS("draw ");
6003         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6004       }
6005       // also send plain move, in case ICS does not understand atomic claims
6006       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6007       ics_user_moved = 1;
6008     }
6009   } else {
6010     if (first.sendTime && (gameMode == BeginningOfGame ||
6011                            gameMode == MachinePlaysWhite ||
6012                            gameMode == MachinePlaysBlack)) {
6013       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6014     }
6015     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6016          // [HGM] book: if program might be playing, let it use book
6017         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6018         first.maybeThinking = TRUE;
6019     } else SendMoveToProgram(forwardMostMove-1, &first);
6020     if (currentMove == cmailOldMove + 1) {
6021       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6022     }
6023     }
6024
6025   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6026
6027   switch (gameMode) 
6028     {
6029     case EditGame:
6030       switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) 
6031         {
6032         case MT_NONE:
6033         case MT_CHECK:
6034           break;
6035         case MT_CHECKMATE:
6036         case MT_STAINMATE:
6037           if (WhiteOnMove(currentMove)) {
6038             GameEnds(BlackWins, "Black mates", GE_PLAYER);
6039           } else {
6040             GameEnds(WhiteWins, "White mates", GE_PLAYER);
6041           }
6042           break;
6043         case MT_STALEMATE:
6044           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6045           break;
6046         }
6047       break;
6048       
6049     case MachinePlaysBlack:
6050     case MachinePlaysWhite:
6051       /* disable certain menu options while machine is thinking */
6052       SetMachineThinkingEnables();
6053       break;
6054       
6055     default:
6056       break;
6057     }
6058   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6059         
6060   if(bookHit)
6061     { // [HGM] book: simulate book reply
6062         static char bookMove[MSG_SIZ]; // a bit generous?
6063
6064
6065       programStats.nodes = programStats.depth = programStats.time =
6066         programStats.score = programStats.got_only_move = 0;
6067       sprintf(programStats.movelist, "%s (xbook)", bookHit);
6068
6069       strcpy(bookMove, "move ");
6070       strcat(bookMove, bookHit);
6071       HandleMachineMove(bookMove, &first);
6072     }
6073
6074   return 1;
6075 }
6076
6077 void
6078 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6079      int fromX, fromY, toX, toY;
6080      int promoChar;
6081 {
6082     /* [HGM] This routine was added to allow calling of its two logical
6083        parts from other modules in the old way. Before, UserMoveEvent()
6084        automatically called FinishMove() if the move was OK, and returned
6085        otherwise. I separated the two, in order to make it possible to
6086        slip a promotion popup in between. But that it always needs two
6087        calls, to the first part, (now called UserMoveTest() ), and to
6088        FinishMove if the first part succeeded. Calls that do not need
6089        to do anything in between, can call this routine the old way.
6090     */
6091   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6092   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6093   if(moveType == AmbiguousMove)
6094     DrawPosition(FALSE, boards[currentMove]);
6095   else if(moveType != ImpossibleMove && moveType != Comment)
6096     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6097 }
6098
6099 void
6100 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6101      Board board;
6102      int flags;
6103      ChessMove kind;
6104      int rf, ff, rt, ft;
6105      VOIDSTAR closure;
6106 {
6107     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6108     Markers *m = (Markers *) closure;
6109     if(rf == fromY && ff == fromX)
6110         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6111                          || kind == WhiteCapturesEnPassant
6112                          || kind == BlackCapturesEnPassant);
6113     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6114 }
6115
6116 void
6117 MarkTargetSquares(int clear)
6118 {
6119   int x, y;
6120   if(!appData.markers || !appData.highlightDragging || 
6121      !appData.testLegality || gameMode == EditPosition) return;
6122   if(clear) {
6123     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6124   } else {
6125     int capt = 0;
6126     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6127     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6128       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6129       if(capt)
6130       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6131     }
6132   }
6133   DrawPosition(TRUE, NULL);
6134 }
6135
6136 void LeftClick(ClickType clickType, int xPix, int yPix)
6137 {
6138     int x, y;
6139     Boolean saveAnimate;
6140     static int second = 0, promotionChoice = 0;
6141     char promoChoice = NULLCHAR;
6142
6143     if(appData.seekGraph && appData.icsActive && loggedOn &&
6144         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6145         SeekGraphClick(clickType, xPix, yPix, 0);
6146         return;
6147     }
6148
6149     if (clickType == Press) ErrorPopDown();
6150     MarkTargetSquares(1);
6151
6152     x = EventToSquare(xPix, BOARD_WIDTH);
6153     y = EventToSquare(yPix, BOARD_HEIGHT);
6154     if (!flipView && y >= 0) {
6155         y = BOARD_HEIGHT - 1 - y;
6156     }
6157     if (flipView && x >= 0) {
6158         x = BOARD_WIDTH - 1 - x;
6159     }
6160
6161     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6162         if(clickType == Release) return; // ignore upclick of click-click destination
6163         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6164         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6165         if(gameInfo.holdingsWidth && 
6166                 (WhiteOnMove(currentMove) 
6167                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6168                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6169             // click in right holdings, for determining promotion piece
6170             ChessSquare p = boards[currentMove][y][x];
6171             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6172             if(p != EmptySquare) {
6173                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6174                 fromX = fromY = -1;
6175                 return;
6176             }
6177         }
6178         DrawPosition(FALSE, boards[currentMove]);
6179         return;
6180     }
6181
6182     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6183     if(clickType == Press
6184             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6185               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6186               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6187         return;
6188
6189     if (fromX == -1) {
6190       if(!appData.oneClick || !OnlyMove(&x, &y)) {
6191         if (clickType == Press) {
6192             /* First square */
6193             if (OKToStartUserMove(x, y)) {
6194                 fromX = x;
6195                 fromY = y;
6196                 second = 0;
6197                 MarkTargetSquares(0);
6198                 DragPieceBegin(xPix, yPix);
6199                 if (appData.highlightDragging) {
6200                     SetHighlights(x, y, -1, -1);
6201                 }
6202             }
6203         }
6204         return;
6205       }
6206     }
6207
6208     /* fromX != -1 */
6209     if (clickType == Press && gameMode != EditPosition) {
6210         ChessSquare fromP;
6211         ChessSquare toP;
6212         int frc;
6213
6214         // ignore off-board to clicks
6215         if(y < 0 || x < 0) return;
6216
6217         /* Check if clicking again on the same color piece */
6218         fromP = boards[currentMove][fromY][fromX];
6219         toP = boards[currentMove][y][x];
6220         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6221         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6222              WhitePawn <= toP && toP <= WhiteKing &&
6223              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6224              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6225             (BlackPawn <= fromP && fromP <= BlackKing && 
6226              BlackPawn <= toP && toP <= BlackKing &&
6227              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6228              !(fromP == BlackKing && toP == BlackRook && frc))) {
6229             /* Clicked again on same color piece -- changed his mind */
6230             second = (x == fromX && y == fromY);
6231             if (appData.highlightDragging) {
6232                 SetHighlights(x, y, -1, -1);
6233             } else {
6234                 ClearHighlights();
6235             }
6236             if (OKToStartUserMove(x, y)) {
6237                 fromX = x;
6238                 fromY = y;
6239                 MarkTargetSquares(0);
6240                 DragPieceBegin(xPix, yPix);
6241             }
6242             return;
6243         }
6244         // ignore clicks on holdings
6245         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6246     }
6247
6248     if (clickType == Release && x == fromX && y == fromY) {
6249         DragPieceEnd(xPix, yPix);
6250         if (appData.animateDragging) {
6251             /* Undo animation damage if any */
6252             DrawPosition(FALSE, NULL);
6253         }
6254         if (second) {
6255             /* Second up/down in same square; just abort move */
6256             second = 0;
6257             fromX = fromY = -1;
6258             ClearHighlights();
6259             gotPremove = 0;
6260             ClearPremoveHighlights();
6261         } else {
6262             /* First upclick in same square; start click-click mode */
6263             SetHighlights(x, y, -1, -1);
6264         }
6265         return;
6266     }
6267
6268     /* we now have a different from- and (possibly off-board) to-square */
6269     /* Completed move */
6270     toX = x;
6271     toY = y;
6272     saveAnimate = appData.animate;
6273     if (clickType == Press) {
6274         /* Finish clickclick move */
6275         if (appData.animate || appData.highlightLastMove) {
6276             SetHighlights(fromX, fromY, toX, toY);
6277         } else {
6278             ClearHighlights();
6279         }
6280     } else {
6281         /* Finish drag move */
6282         if (appData.highlightLastMove) {
6283             SetHighlights(fromX, fromY, toX, toY);
6284         } else {
6285             ClearHighlights();
6286         }
6287         DragPieceEnd(xPix, yPix);
6288         /* Don't animate move and drag both */
6289         appData.animate = FALSE;
6290     }
6291
6292     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6293     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6294         ChessSquare piece = boards[currentMove][fromY][fromX];
6295         if(gameMode == EditPosition && piece != EmptySquare &&
6296            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6297             int n;
6298              
6299             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6300                 n = PieceToNumber(piece - (int)BlackPawn);
6301                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6302                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6303                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6304             } else
6305             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6306                 n = PieceToNumber(piece);
6307                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6308                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6309                 boards[currentMove][n][BOARD_WIDTH-2]++;
6310             }
6311             boards[currentMove][fromY][fromX] = EmptySquare;
6312         }
6313         ClearHighlights();
6314         fromX = fromY = -1;
6315         DrawPosition(TRUE, boards[currentMove]);
6316         return;
6317     }
6318
6319     // off-board moves should not be highlighted
6320     if(x < 0 || x < 0) ClearHighlights();
6321
6322     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6323         SetHighlights(fromX, fromY, toX, toY);
6324         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6325             // [HGM] super: promotion to captured piece selected from holdings
6326             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6327             promotionChoice = TRUE;
6328             // kludge follows to temporarily execute move on display, without promoting yet
6329             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6330             boards[currentMove][toY][toX] = p;
6331             DrawPosition(FALSE, boards[currentMove]);
6332             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6333             boards[currentMove][toY][toX] = q;
6334             DisplayMessage("Click in holdings to choose piece", "");
6335             return;
6336         }
6337         PromotionPopUp();
6338     } else {
6339         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6340         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6341         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6342         fromX = fromY = -1;
6343     }
6344     appData.animate = saveAnimate;
6345     if (appData.animate || appData.animateDragging) {
6346         /* Undo animation damage if needed */
6347         DrawPosition(FALSE, NULL);
6348     }
6349 }
6350
6351 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6352 {   // front-end-free part taken out of PieceMenuPopup
6353     int whichMenu; int xSqr, ySqr;
6354
6355     if(seekGraphUp) { // [HGM] seekgraph
6356         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6357         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6358         return -2;
6359     }
6360
6361     xSqr = EventToSquare(x, BOARD_WIDTH);
6362     ySqr = EventToSquare(y, BOARD_HEIGHT);
6363     if (action == Release) UnLoadPV(); // [HGM] pv
6364     if (action != Press) return -2; // return code to be ignored
6365     switch (gameMode) {
6366       case IcsExamining:
6367         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
6368       case EditPosition:
6369         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
6370         if (xSqr < 0 || ySqr < 0) return -1;
6371         whichMenu = 0; // edit-position menu
6372         break;
6373       case IcsObserving:
6374         if(!appData.icsEngineAnalyze) return -1;
6375       case IcsPlayingWhite:
6376       case IcsPlayingBlack:
6377         if(!appData.zippyPlay) goto noZip;
6378       case AnalyzeMode:
6379       case AnalyzeFile:
6380       case MachinePlaysWhite:
6381       case MachinePlaysBlack:
6382       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6383         if (!appData.dropMenu) {
6384           LoadPV(x, y);
6385           return 2; // flag front-end to grab mouse events
6386         }
6387         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6388            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6389       case EditGame:
6390       noZip:
6391         if (xSqr < 0 || ySqr < 0) return -1;
6392         if (!appData.dropMenu || appData.testLegality &&
6393             gameInfo.variant != VariantBughouse &&
6394             gameInfo.variant != VariantCrazyhouse) return -1;
6395         whichMenu = 1; // drop menu
6396         break;
6397       default:
6398         return -1;
6399     }
6400
6401     if (((*fromX = xSqr) < 0) ||
6402         ((*fromY = ySqr) < 0)) {
6403         *fromX = *fromY = -1;
6404         return -1;
6405     }
6406     if (flipView)
6407       *fromX = BOARD_WIDTH - 1 - *fromX;
6408     else
6409       *fromY = BOARD_HEIGHT - 1 - *fromY;
6410
6411     return whichMenu;
6412 }
6413
6414 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6415 {
6416 //    char * hint = lastHint;
6417     FrontEndProgramStats stats;
6418
6419     stats.which = cps == &first ? 0 : 1;
6420     stats.depth = cpstats->depth;
6421     stats.nodes = cpstats->nodes;
6422     stats.score = cpstats->score;
6423     stats.time = cpstats->time;
6424     stats.pv = cpstats->movelist;
6425     stats.hint = lastHint;
6426     stats.an_move_index = 0;
6427     stats.an_move_count = 0;
6428
6429     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6430         stats.hint = cpstats->move_name;
6431         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6432         stats.an_move_count = cpstats->nr_moves;
6433     }
6434
6435     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6436
6437     SetProgramStats( &stats );
6438 }
6439
6440 int
6441 Adjudicate(ChessProgramState *cps)
6442 {       // [HGM] some adjudications useful with buggy engines
6443         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6444         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6445         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6446         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6447         int k, count = 0; static int bare = 1;
6448         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6449         Boolean canAdjudicate = !appData.icsActive;
6450
6451         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6452         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6453             if( appData.testLegality )
6454             {   /* [HGM] Some more adjudications for obstinate engines */
6455                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6456                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6457                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6458                 static int moveCount = 6;
6459                 ChessMove result;
6460                 char *reason = NULL;
6461
6462
6463                 /* Count what is on board. */
6464                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6465                 {   ChessSquare p = boards[forwardMostMove][i][j];
6466                     int m=i;
6467
6468                     switch((int) p)
6469                     {   /* count B,N,R and other of each side */
6470                         case WhiteKing:
6471                         case BlackKing:
6472                              NrK++; break; // [HGM] atomic: count Kings
6473                         case WhiteKnight:
6474                              NrWN++; break;
6475                         case WhiteBishop:
6476                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6477                              bishopsColor |= 1 << ((i^j)&1);
6478                              NrWB++; break;
6479                         case BlackKnight:
6480                              NrBN++; break;
6481                         case BlackBishop:
6482                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6483                              bishopsColor |= 1 << ((i^j)&1);
6484                              NrBB++; break;
6485                         case WhiteRook:
6486                              NrWR++; break;
6487                         case BlackRook:
6488                              NrBR++; break;
6489                         case WhiteQueen:
6490                              NrWQ++; break;
6491                         case BlackQueen:
6492                              NrBQ++; break;
6493                         case EmptySquare: 
6494                              break;
6495                         case BlackPawn:
6496                              m = 7-i;
6497                         case WhitePawn:
6498                              PawnAdvance += m; NrPawns++;
6499                     }
6500                     NrPieces += (p != EmptySquare);
6501                     NrW += ((int)p < (int)BlackPawn);
6502                     if(gameInfo.variant == VariantXiangqi && 
6503                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6504                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6505                         NrW -= ((int)p < (int)BlackPawn);
6506                     }
6507                 }
6508
6509                 /* Some material-based adjudications that have to be made before stalemate test */
6510                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6511                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6512                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6513                      if(canAdjudicate && appData.checkMates) {
6514                          if(engineOpponent)
6515                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6516                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6517                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6518                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6519                          return 1;
6520                      }
6521                 }
6522
6523                 /* Bare King in Shatranj (loses) or Losers (wins) */
6524                 if( NrW == 1 || NrPieces - NrW == 1) {
6525                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6526                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6527                      if(canAdjudicate && appData.checkMates) {
6528                          if(engineOpponent)
6529                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6530                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6531                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6532                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6533                          return 1;
6534                      }
6535                   } else
6536                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6537                   {    /* bare King */
6538                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6539                         if(canAdjudicate && appData.checkMates) {
6540                             /* but only adjudicate if adjudication enabled */
6541                             if(engineOpponent)
6542                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6543                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6544                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6545                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6546                             return 1;
6547                         }
6548                   }
6549                 } else bare = 1;
6550
6551
6552             // don't wait for engine to announce game end if we can judge ourselves
6553             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6554               case MT_CHECK:
6555                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6556                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6557                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6558                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6559                             checkCnt++;
6560                         if(checkCnt >= 2) {
6561                             reason = "Xboard adjudication: 3rd check";
6562                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6563                             break;
6564                         }
6565                     }
6566                 }
6567               case MT_NONE:
6568               default:
6569                 break;
6570               case MT_STALEMATE:
6571               case MT_STAINMATE:
6572                 reason = "Xboard adjudication: Stalemate";
6573                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6574                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6575                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6576                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6577                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6578                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6579                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6580                                                                         EP_CHECKMATE : EP_WINS);
6581                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6582                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6583                 }
6584                 break;
6585               case MT_CHECKMATE:
6586                 reason = "Xboard adjudication: Checkmate";
6587                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6588                 break;
6589             }
6590
6591                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6592                     case EP_STALEMATE:
6593                         result = GameIsDrawn; break;
6594                     case EP_CHECKMATE:
6595                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6596                     case EP_WINS:
6597                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6598                     default:
6599                         result = (ChessMove) 0;
6600                 }
6601                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6602                     if(engineOpponent)
6603                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6604                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6605                     GameEnds( result, reason, GE_XBOARD );
6606                     return 1;
6607                 }
6608
6609                 /* Next absolutely insufficient mating material. */
6610                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6611                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6612                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6613                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6614                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6615
6616                      /* always flag draws, for judging claims */
6617                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6618
6619                      if(canAdjudicate && appData.materialDraws) {
6620                          /* but only adjudicate them if adjudication enabled */
6621                          if(engineOpponent) {
6622                            SendToProgram("force\n", engineOpponent); // suppress reply
6623                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6624                          }
6625                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6626                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6627                          return 1;
6628                      }
6629                 }
6630
6631                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6632                 if(NrPieces == 4 && 
6633                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6634                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6635                    || NrWN==2 || NrBN==2     /* KNNK */
6636                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6637                   ) ) {
6638                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6639                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6640                           if(engineOpponent) {
6641                             SendToProgram("force\n", engineOpponent); // suppress reply
6642                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6643                           }
6644                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6645                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6646                           return 1;
6647                      }
6648                 } else moveCount = 6;
6649             }
6650         }
6651           
6652         if (appData.debugMode) { int i;
6653             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6654                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6655                     appData.drawRepeats);
6656             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6657               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6658             
6659         }
6660
6661         // Repetition draws and 50-move rule can be applied independently of legality testing
6662
6663                 /* Check for rep-draws */
6664                 count = 0;
6665                 for(k = forwardMostMove-2;
6666                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6667                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6668                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6669                     k-=2)
6670                 {   int rights=0;
6671                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6672                         /* compare castling rights */
6673                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6674                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6675                                 rights++; /* King lost rights, while rook still had them */
6676                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6677                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6678                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6679                                    rights++; /* but at least one rook lost them */
6680                         }
6681                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6682                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6683                                 rights++; 
6684                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6685                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6686                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6687                                    rights++;
6688                         }
6689                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6690                             && appData.drawRepeats > 1) {
6691                              /* adjudicate after user-specified nr of repeats */
6692                              if(engineOpponent) {
6693                                SendToProgram("force\n", engineOpponent); // suppress reply
6694                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6695                              }
6696                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6697                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6698                                 // [HGM] xiangqi: check for forbidden perpetuals
6699                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6700                                 for(m=forwardMostMove; m>k; m-=2) {
6701                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6702                                         ourPerpetual = 0; // the current mover did not always check
6703                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6704                                         hisPerpetual = 0; // the opponent did not always check
6705                                 }
6706                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6707                                                                         ourPerpetual, hisPerpetual);
6708                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6709                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6710                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6711                                     return 1;
6712                                 }
6713                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6714                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6715                                 // Now check for perpetual chases
6716                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6717                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6718                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6719                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6720                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6721                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6722                                         return 1;
6723                                     }
6724                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6725                                         break; // Abort repetition-checking loop.
6726                                 }
6727                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6728                              }
6729                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6730                              return 1;
6731                         }
6732                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6733                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6734                     }
6735                 }
6736
6737                 /* Now we test for 50-move draws. Determine ply count */
6738                 count = forwardMostMove;
6739                 /* look for last irreversble move */
6740                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6741                     count--;
6742                 /* if we hit starting position, add initial plies */
6743                 if( count == backwardMostMove )
6744                     count -= initialRulePlies;
6745                 count = forwardMostMove - count; 
6746                 if( count >= 100)
6747                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6748                          /* this is used to judge if draw claims are legal */
6749                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6750                          if(engineOpponent) {
6751                            SendToProgram("force\n", engineOpponent); // suppress reply
6752                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6753                          }
6754                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6755                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6756                          return 1;
6757                 }
6758
6759                 /* if draw offer is pending, treat it as a draw claim
6760                  * when draw condition present, to allow engines a way to
6761                  * claim draws before making their move to avoid a race
6762                  * condition occurring after their move
6763                  */
6764                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6765                          char *p = NULL;
6766                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6767                              p = "Draw claim: 50-move rule";
6768                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6769                              p = "Draw claim: 3-fold repetition";
6770                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6771                              p = "Draw claim: insufficient mating material";
6772                          if( p != NULL && canAdjudicate) {
6773                              if(engineOpponent) {
6774                                SendToProgram("force\n", engineOpponent); // suppress reply
6775                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6776                              }
6777                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6778                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6779                              return 1;
6780                          }
6781                 }
6782
6783                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6784                     if(engineOpponent) {
6785                       SendToProgram("force\n", engineOpponent); // suppress reply
6786                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6787                     }
6788                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6789                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6790                     return 1;
6791                 }
6792         return 0;
6793 }
6794
6795 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6796 {   // [HGM] book: this routine intercepts moves to simulate book replies
6797     char *bookHit = NULL;
6798
6799     //first determine if the incoming move brings opponent into his book
6800     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6801         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6802     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6803     if(bookHit != NULL && !cps->bookSuspend) {
6804         // make sure opponent is not going to reply after receiving move to book position
6805         SendToProgram("force\n", cps);
6806         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6807     }
6808     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6809     // now arrange restart after book miss
6810     if(bookHit) {
6811         // after a book hit we never send 'go', and the code after the call to this routine
6812         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6813         char buf[MSG_SIZ];
6814         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6815         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6816         SendToProgram(buf, cps);
6817         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6818     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6819         SendToProgram("go\n", cps);
6820         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6821     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6822         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6823             SendToProgram("go\n", cps); 
6824         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6825     }
6826     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6827 }
6828
6829 char *savedMessage;
6830 ChessProgramState *savedState;
6831 void DeferredBookMove(void)
6832 {
6833         if(savedState->lastPing != savedState->lastPong)
6834                     ScheduleDelayedEvent(DeferredBookMove, 10);
6835         else
6836         HandleMachineMove(savedMessage, savedState);
6837 }
6838
6839 void
6840 HandleMachineMove(message, cps)
6841      char *message;
6842      ChessProgramState *cps;
6843 {
6844     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6845     char realname[MSG_SIZ];
6846     int fromX, fromY, toX, toY;
6847     ChessMove moveType;
6848     char promoChar;
6849     char *p;
6850     int machineWhite;
6851     char *bookHit;
6852
6853     cps->userError = 0;
6854
6855 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6856     /*
6857      * Kludge to ignore BEL characters
6858      */
6859     while (*message == '\007') message++;
6860
6861     /*
6862      * [HGM] engine debug message: ignore lines starting with '#' character
6863      */
6864     if(cps->debug && *message == '#') return;
6865
6866     /*
6867      * Look for book output
6868      */
6869     if (cps == &first && bookRequested) {
6870         if (message[0] == '\t' || message[0] == ' ') {
6871             /* Part of the book output is here; append it */
6872             strcat(bookOutput, message);
6873             strcat(bookOutput, "  \n");
6874             return;
6875         } else if (bookOutput[0] != NULLCHAR) {
6876             /* All of book output has arrived; display it */
6877             char *p = bookOutput;
6878             while (*p != NULLCHAR) {
6879                 if (*p == '\t') *p = ' ';
6880                 p++;
6881             }
6882             DisplayInformation(bookOutput);
6883             bookRequested = FALSE;
6884             /* Fall through to parse the current output */
6885         }
6886     }
6887
6888     /*
6889      * Look for machine move.
6890      */
6891     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6892         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6893     {
6894         /* This method is only useful on engines that support ping */
6895         if (cps->lastPing != cps->lastPong) {
6896           if (gameMode == BeginningOfGame) {
6897             /* Extra move from before last new; ignore */
6898             if (appData.debugMode) {
6899                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6900             }
6901           } else {
6902             if (appData.debugMode) {
6903                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6904                         cps->which, gameMode);
6905             }
6906
6907             SendToProgram("undo\n", cps);
6908           }
6909           return;
6910         }
6911
6912         switch (gameMode) {
6913           case BeginningOfGame:
6914             /* Extra move from before last reset; ignore */
6915             if (appData.debugMode) {
6916                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6917             }
6918             return;
6919
6920           case EndOfGame:
6921           case IcsIdle:
6922           default:
6923             /* Extra move after we tried to stop.  The mode test is
6924                not a reliable way of detecting this problem, but it's
6925                the best we can do on engines that don't support ping.
6926             */
6927             if (appData.debugMode) {
6928                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6929                         cps->which, gameMode);
6930             }
6931             SendToProgram("undo\n", cps);
6932             return;
6933
6934           case MachinePlaysWhite:
6935           case IcsPlayingWhite:
6936             machineWhite = TRUE;
6937             break;
6938
6939           case MachinePlaysBlack:
6940           case IcsPlayingBlack:
6941             machineWhite = FALSE;
6942             break;
6943
6944           case TwoMachinesPlay:
6945             machineWhite = (cps->twoMachinesColor[0] == 'w');
6946             break;
6947         }
6948         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6949             if (appData.debugMode) {
6950                 fprintf(debugFP,
6951                         "Ignoring move out of turn by %s, gameMode %d"
6952                         ", forwardMost %d\n",
6953                         cps->which, gameMode, forwardMostMove);
6954             }
6955             return;
6956         }
6957
6958     if (appData.debugMode) { int f = forwardMostMove;
6959         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6960                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6961                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6962     }
6963         if(cps->alphaRank) AlphaRank(machineMove, 4);
6964         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6965                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6966             /* Machine move could not be parsed; ignore it. */
6967             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6968                     machineMove, cps->which);
6969             DisplayError(buf1, 0);
6970             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6971                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6972             if (gameMode == TwoMachinesPlay) {
6973               GameEnds(machineWhite ? BlackWins : WhiteWins,
6974                        buf1, GE_XBOARD);
6975             }
6976             return;
6977         }
6978
6979         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6980         /* So we have to redo legality test with true e.p. status here,  */
6981         /* to make sure an illegal e.p. capture does not slip through,   */
6982         /* to cause a forfeit on a justified illegal-move complaint      */
6983         /* of the opponent.                                              */
6984         if( gameMode==TwoMachinesPlay && appData.testLegality
6985             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6986                                                               ) {
6987            ChessMove moveType;
6988            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6989                              fromY, fromX, toY, toX, promoChar);
6990             if (appData.debugMode) {
6991                 int i;
6992                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6993                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6994                 fprintf(debugFP, "castling rights\n");
6995             }
6996             if(moveType == IllegalMove) {
6997                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6998                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6999                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7000                            buf1, GE_XBOARD);
7001                 return;
7002            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7003            /* [HGM] Kludge to handle engines that send FRC-style castling
7004               when they shouldn't (like TSCP-Gothic) */
7005            switch(moveType) {
7006              case WhiteASideCastleFR:
7007              case BlackASideCastleFR:
7008                toX+=2;
7009                currentMoveString[2]++;
7010                break;
7011              case WhiteHSideCastleFR:
7012              case BlackHSideCastleFR:
7013                toX--;
7014                currentMoveString[2]--;
7015                break;
7016              default: ; // nothing to do, but suppresses warning of pedantic compilers
7017            }
7018         }
7019         hintRequested = FALSE;
7020         lastHint[0] = NULLCHAR;
7021         bookRequested = FALSE;
7022         /* Program may be pondering now */
7023         cps->maybeThinking = TRUE;
7024         if (cps->sendTime == 2) cps->sendTime = 1;
7025         if (cps->offeredDraw) cps->offeredDraw--;
7026
7027         /* currentMoveString is set as a side-effect of ParseOneMove */
7028         strcpy(machineMove, currentMoveString);
7029         strcat(machineMove, "\n");
7030         strcpy(moveList[forwardMostMove], machineMove);
7031
7032         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7033
7034         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7035         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7036             int count = 0;
7037
7038             while( count < adjudicateLossPlies ) {
7039                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7040
7041                 if( count & 1 ) {
7042                     score = -score; /* Flip score for winning side */
7043                 }
7044
7045                 if( score > adjudicateLossThreshold ) {
7046                     break;
7047                 }
7048
7049                 count++;
7050             }
7051
7052             if( count >= adjudicateLossPlies ) {
7053                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7054
7055                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7056                     "Xboard adjudication", 
7057                     GE_XBOARD );
7058
7059                 return;
7060             }
7061         }
7062
7063         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7064
7065 #if ZIPPY
7066         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7067             first.initDone) {
7068           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7069                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7070                 SendToICS("draw ");
7071                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7072           }
7073           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7074           ics_user_moved = 1;
7075           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7076                 char buf[3*MSG_SIZ];
7077
7078                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7079                         programStats.score / 100.,
7080                         programStats.depth,
7081                         programStats.time / 100.,
7082                         (unsigned int)programStats.nodes,
7083                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7084                         programStats.movelist);
7085                 SendToICS(buf);
7086 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7087           }
7088         }
7089 #endif
7090
7091         /* [AS] Save move info and clear stats for next move */
7092         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7093         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7094         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7095         ClearProgramStats();
7096         thinkOutput[0] = NULLCHAR;
7097         hiddenThinkOutputState = 0;
7098
7099         bookHit = NULL;
7100         if (gameMode == TwoMachinesPlay) {
7101             /* [HGM] relaying draw offers moved to after reception of move */
7102             /* and interpreting offer as claim if it brings draw condition */
7103             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7104                 SendToProgram("draw\n", cps->other);
7105             }
7106             if (cps->other->sendTime) {
7107                 SendTimeRemaining(cps->other,
7108                                   cps->other->twoMachinesColor[0] == 'w');
7109             }
7110             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7111             if (firstMove && !bookHit) {
7112                 firstMove = FALSE;
7113                 if (cps->other->useColors) {
7114                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7115                 }
7116                 SendToProgram("go\n", cps->other);
7117             }
7118             cps->other->maybeThinking = TRUE;
7119         }
7120
7121         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7122
7123         if (!pausing && appData.ringBellAfterMoves) {
7124             RingBell();
7125         }
7126
7127         /*
7128          * Reenable menu items that were disabled while
7129          * machine was thinking
7130          */
7131         if (gameMode != TwoMachinesPlay)
7132             SetUserThinkingEnables();
7133
7134         // [HGM] book: after book hit opponent has received move and is now in force mode
7135         // force the book reply into it, and then fake that it outputted this move by jumping
7136         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7137         if(bookHit) {
7138                 static char bookMove[MSG_SIZ]; // a bit generous?
7139
7140                 strcpy(bookMove, "move ");
7141                 strcat(bookMove, bookHit);
7142                 message = bookMove;
7143                 cps = cps->other;
7144                 programStats.nodes = programStats.depth = programStats.time =
7145                 programStats.score = programStats.got_only_move = 0;
7146                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7147
7148                 if(cps->lastPing != cps->lastPong) {
7149                     savedMessage = message; // args for deferred call
7150                     savedState = cps;
7151                     ScheduleDelayedEvent(DeferredBookMove, 10);
7152                     return;
7153                 }
7154                 goto FakeBookMove;
7155         }
7156
7157         return;
7158     }
7159
7160     /* Set special modes for chess engines.  Later something general
7161      *  could be added here; for now there is just one kludge feature,
7162      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7163      *  when "xboard" is given as an interactive command.
7164      */
7165     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7166         cps->useSigint = FALSE;
7167         cps->useSigterm = FALSE;
7168     }
7169     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7170       ParseFeatures(message+8, cps);
7171       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7172     }
7173
7174     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7175      * want this, I was asked to put it in, and obliged.
7176      */
7177     if (!strncmp(message, "setboard ", 9)) {
7178         Board initial_position;
7179
7180         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7181
7182         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7183             DisplayError(_("Bad FEN received from engine"), 0);
7184             return ;
7185         } else {
7186            Reset(TRUE, FALSE);
7187            CopyBoard(boards[0], initial_position);
7188            initialRulePlies = FENrulePlies;
7189            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7190            else gameMode = MachinePlaysBlack;
7191            DrawPosition(FALSE, boards[currentMove]);
7192         }
7193         return;
7194     }
7195
7196     /*
7197      * Look for communication commands
7198      */
7199     if (!strncmp(message, "telluser ", 9)) {
7200         DisplayNote(message + 9);
7201         return;
7202     }
7203     if (!strncmp(message, "tellusererror ", 14)) {
7204         cps->userError = 1;
7205         DisplayError(message + 14, 0);
7206         return;
7207     }
7208     if (!strncmp(message, "tellopponent ", 13)) {
7209       if (appData.icsActive) {
7210         if (loggedOn) {
7211           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7212           SendToICS(buf1);
7213         }
7214       } else {
7215         DisplayNote(message + 13);
7216       }
7217       return;
7218     }
7219     if (!strncmp(message, "tellothers ", 11)) {
7220       if (appData.icsActive) {
7221         if (loggedOn) {
7222           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7223           SendToICS(buf1);
7224         }
7225       }
7226       return;
7227     }
7228     if (!strncmp(message, "tellall ", 8)) {
7229       if (appData.icsActive) {
7230         if (loggedOn) {
7231           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7232           SendToICS(buf1);
7233         }
7234       } else {
7235         DisplayNote(message + 8);
7236       }
7237       return;
7238     }
7239     if (strncmp(message, "warning", 7) == 0) {
7240         /* Undocumented feature, use tellusererror in new code */
7241         DisplayError(message, 0);
7242         return;
7243     }
7244     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7245         strcpy(realname, cps->tidy);
7246         strcat(realname, " query");
7247         AskQuestion(realname, buf2, buf1, cps->pr);
7248         return;
7249     }
7250     /* Commands from the engine directly to ICS.  We don't allow these to be
7251      *  sent until we are logged on. Crafty kibitzes have been known to
7252      *  interfere with the login process.
7253      */
7254     if (loggedOn) {
7255         if (!strncmp(message, "tellics ", 8)) {
7256             SendToICS(message + 8);
7257             SendToICS("\n");
7258             return;
7259         }
7260         if (!strncmp(message, "tellicsnoalias ", 15)) {
7261             SendToICS(ics_prefix);
7262             SendToICS(message + 15);
7263             SendToICS("\n");
7264             return;
7265         }
7266         /* The following are for backward compatibility only */
7267         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7268             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7269             SendToICS(ics_prefix);
7270             SendToICS(message);
7271             SendToICS("\n");
7272             return;
7273         }
7274     }
7275     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7276         return;
7277     }
7278     /*
7279      * If the move is illegal, cancel it and redraw the board.
7280      * Also deal with other error cases.  Matching is rather loose
7281      * here to accommodate engines written before the spec.
7282      */
7283     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7284         strncmp(message, "Error", 5) == 0) {
7285         if (StrStr(message, "name") ||
7286             StrStr(message, "rating") || StrStr(message, "?") ||
7287             StrStr(message, "result") || StrStr(message, "board") ||
7288             StrStr(message, "bk") || StrStr(message, "computer") ||
7289             StrStr(message, "variant") || StrStr(message, "hint") ||
7290             StrStr(message, "random") || StrStr(message, "depth") ||
7291             StrStr(message, "accepted")) {
7292             return;
7293         }
7294         if (StrStr(message, "protover")) {
7295           /* Program is responding to input, so it's apparently done
7296              initializing, and this error message indicates it is
7297              protocol version 1.  So we don't need to wait any longer
7298              for it to initialize and send feature commands. */
7299           FeatureDone(cps, 1);
7300           cps->protocolVersion = 1;
7301           return;
7302         }
7303         cps->maybeThinking = FALSE;
7304
7305         if (StrStr(message, "draw")) {
7306             /* Program doesn't have "draw" command */
7307             cps->sendDrawOffers = 0;
7308             return;
7309         }
7310         if (cps->sendTime != 1 &&
7311             (StrStr(message, "time") || StrStr(message, "otim"))) {
7312           /* Program apparently doesn't have "time" or "otim" command */
7313           cps->sendTime = 0;
7314           return;
7315         }
7316         if (StrStr(message, "analyze")) {
7317             cps->analysisSupport = FALSE;
7318             cps->analyzing = FALSE;
7319             Reset(FALSE, TRUE);
7320             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7321             DisplayError(buf2, 0);
7322             return;
7323         }
7324         if (StrStr(message, "(no matching move)st")) {
7325           /* Special kludge for GNU Chess 4 only */
7326           cps->stKludge = TRUE;
7327           SendTimeControl(cps, movesPerSession, timeControl,
7328                           timeIncrement, appData.searchDepth,
7329                           searchTime);
7330           return;
7331         }
7332         if (StrStr(message, "(no matching move)sd")) {
7333           /* Special kludge for GNU Chess 4 only */
7334           cps->sdKludge = TRUE;
7335           SendTimeControl(cps, movesPerSession, timeControl,
7336                           timeIncrement, appData.searchDepth,
7337                           searchTime);
7338           return;
7339         }
7340         if (!StrStr(message, "llegal")) {
7341             return;
7342         }
7343         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7344             gameMode == IcsIdle) return;
7345         if (forwardMostMove <= backwardMostMove) return;
7346         if (pausing) PauseEvent();
7347       if(appData.forceIllegal) {
7348             // [HGM] illegal: machine refused move; force position after move into it
7349           SendToProgram("force\n", cps);
7350           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7351                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7352                 // when black is to move, while there might be nothing on a2 or black
7353                 // might already have the move. So send the board as if white has the move.
7354                 // But first we must change the stm of the engine, as it refused the last move
7355                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7356                 if(WhiteOnMove(forwardMostMove)) {
7357                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7358                     SendBoard(cps, forwardMostMove); // kludgeless board
7359                 } else {
7360                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7361                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7362                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7363                 }
7364           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7365             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7366                  gameMode == TwoMachinesPlay)
7367               SendToProgram("go\n", cps);
7368             return;
7369       } else
7370         if (gameMode == PlayFromGameFile) {
7371             /* Stop reading this game file */
7372             gameMode = EditGame;
7373             ModeHighlight();
7374         }
7375         currentMove = --forwardMostMove;
7376         DisplayMove(currentMove-1); /* before DisplayMoveError */
7377         SwitchClocks();
7378         DisplayBothClocks();
7379         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7380                 parseList[currentMove], cps->which);
7381         DisplayMoveError(buf1);
7382         DrawPosition(FALSE, boards[currentMove]);
7383
7384         /* [HGM] illegal-move claim should forfeit game when Xboard */
7385         /* only passes fully legal moves                            */
7386         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7387             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7388                                 "False illegal-move claim", GE_XBOARD );
7389         }
7390         return;
7391     }
7392     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7393         /* Program has a broken "time" command that
7394            outputs a string not ending in newline.
7395            Don't use it. */
7396         cps->sendTime = 0;
7397     }
7398
7399     /*
7400      * If chess program startup fails, exit with an error message.
7401      * Attempts to recover here are futile.
7402      */
7403     if ((StrStr(message, "unknown host") != NULL)
7404         || (StrStr(message, "No remote directory") != NULL)
7405         || (StrStr(message, "not found") != NULL)
7406         || (StrStr(message, "No such file") != NULL)
7407         || (StrStr(message, "can't alloc") != NULL)
7408         || (StrStr(message, "Permission denied") != NULL)) {
7409
7410         cps->maybeThinking = FALSE;
7411         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7412                 cps->which, cps->program, cps->host, message);
7413         RemoveInputSource(cps->isr);
7414         DisplayFatalError(buf1, 0, 1);
7415         return;
7416     }
7417
7418     /*
7419      * Look for hint output
7420      */
7421     if (sscanf(message, "Hint: %s", buf1) == 1) {
7422         if (cps == &first && hintRequested) {
7423             hintRequested = FALSE;
7424             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7425                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7426                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7427                                     PosFlags(forwardMostMove),
7428                                     fromY, fromX, toY, toX, promoChar, buf1);
7429                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7430                 DisplayInformation(buf2);
7431             } else {
7432                 /* Hint move could not be parsed!? */
7433               snprintf(buf2, sizeof(buf2),
7434                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7435                         buf1, cps->which);
7436                 DisplayError(buf2, 0);
7437             }
7438         } else {
7439             strcpy(lastHint, buf1);
7440         }
7441         return;
7442     }
7443
7444     /*
7445      * Ignore other messages if game is not in progress
7446      */
7447     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7448         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7449
7450     /*
7451      * look for win, lose, draw, or draw offer
7452      */
7453     if (strncmp(message, "1-0", 3) == 0) {
7454         char *p, *q, *r = "";
7455         p = strchr(message, '{');
7456         if (p) {
7457             q = strchr(p, '}');
7458             if (q) {
7459                 *q = NULLCHAR;
7460                 r = p + 1;
7461             }
7462         }
7463         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7464         return;
7465     } else if (strncmp(message, "0-1", 3) == 0) {
7466         char *p, *q, *r = "";
7467         p = strchr(message, '{');
7468         if (p) {
7469             q = strchr(p, '}');
7470             if (q) {
7471                 *q = NULLCHAR;
7472                 r = p + 1;
7473             }
7474         }
7475         /* Kludge for Arasan 4.1 bug */
7476         if (strcmp(r, "Black resigns") == 0) {
7477             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7478             return;
7479         }
7480         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7481         return;
7482     } else if (strncmp(message, "1/2", 3) == 0) {
7483         char *p, *q, *r = "";
7484         p = strchr(message, '{');
7485         if (p) {
7486             q = strchr(p, '}');
7487             if (q) {
7488                 *q = NULLCHAR;
7489                 r = p + 1;
7490             }
7491         }
7492
7493         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7494         return;
7495
7496     } else if (strncmp(message, "White resign", 12) == 0) {
7497         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7498         return;
7499     } else if (strncmp(message, "Black resign", 12) == 0) {
7500         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7501         return;
7502     } else if (strncmp(message, "White matches", 13) == 0 ||
7503                strncmp(message, "Black matches", 13) == 0   ) {
7504         /* [HGM] ignore GNUShogi noises */
7505         return;
7506     } else if (strncmp(message, "White", 5) == 0 &&
7507                message[5] != '(' &&
7508                StrStr(message, "Black") == NULL) {
7509         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7510         return;
7511     } else if (strncmp(message, "Black", 5) == 0 &&
7512                message[5] != '(') {
7513         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7514         return;
7515     } else if (strcmp(message, "resign") == 0 ||
7516                strcmp(message, "computer resigns") == 0) {
7517         switch (gameMode) {
7518           case MachinePlaysBlack:
7519           case IcsPlayingBlack:
7520             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7521             break;
7522           case MachinePlaysWhite:
7523           case IcsPlayingWhite:
7524             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7525             break;
7526           case TwoMachinesPlay:
7527             if (cps->twoMachinesColor[0] == 'w')
7528               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7529             else
7530               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7531             break;
7532           default:
7533             /* can't happen */
7534             break;
7535         }
7536         return;
7537     } else if (strncmp(message, "opponent mates", 14) == 0) {
7538         switch (gameMode) {
7539           case MachinePlaysBlack:
7540           case IcsPlayingBlack:
7541             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7542             break;
7543           case MachinePlaysWhite:
7544           case IcsPlayingWhite:
7545             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7546             break;
7547           case TwoMachinesPlay:
7548             if (cps->twoMachinesColor[0] == 'w')
7549               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7550             else
7551               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7552             break;
7553           default:
7554             /* can't happen */
7555             break;
7556         }
7557         return;
7558     } else if (strncmp(message, "computer mates", 14) == 0) {
7559         switch (gameMode) {
7560           case MachinePlaysBlack:
7561           case IcsPlayingBlack:
7562             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7563             break;
7564           case MachinePlaysWhite:
7565           case IcsPlayingWhite:
7566             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7567             break;
7568           case TwoMachinesPlay:
7569             if (cps->twoMachinesColor[0] == 'w')
7570               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7571             else
7572               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7573             break;
7574           default:
7575             /* can't happen */
7576             break;
7577         }
7578         return;
7579     } else if (strncmp(message, "checkmate", 9) == 0) {
7580         if (WhiteOnMove(forwardMostMove)) {
7581             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7582         } else {
7583             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7584         }
7585         return;
7586     } else if (strstr(message, "Draw") != NULL ||
7587                strstr(message, "game is a draw") != NULL) {
7588         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7589         return;
7590     } else if (strstr(message, "offer") != NULL &&
7591                strstr(message, "draw") != NULL) {
7592 #if ZIPPY
7593         if (appData.zippyPlay && first.initDone) {
7594             /* Relay offer to ICS */
7595             SendToICS(ics_prefix);
7596             SendToICS("draw\n");
7597         }
7598 #endif
7599         cps->offeredDraw = 2; /* valid until this engine moves twice */
7600         if (gameMode == TwoMachinesPlay) {
7601             if (cps->other->offeredDraw) {
7602                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7603             /* [HGM] in two-machine mode we delay relaying draw offer      */
7604             /* until after we also have move, to see if it is really claim */
7605             }
7606         } else if (gameMode == MachinePlaysWhite ||
7607                    gameMode == MachinePlaysBlack) {
7608           if (userOfferedDraw) {
7609             DisplayInformation(_("Machine accepts your draw offer"));
7610             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7611           } else {
7612             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7613           }
7614         }
7615     }
7616
7617
7618     /*
7619      * Look for thinking output
7620      */
7621     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7622           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7623                                 ) {
7624         int plylev, mvleft, mvtot, curscore, time;
7625         char mvname[MOVE_LEN];
7626         u64 nodes; // [DM]
7627         char plyext;
7628         int ignore = FALSE;
7629         int prefixHint = FALSE;
7630         mvname[0] = NULLCHAR;
7631
7632         switch (gameMode) {
7633           case MachinePlaysBlack:
7634           case IcsPlayingBlack:
7635             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7636             break;
7637           case MachinePlaysWhite:
7638           case IcsPlayingWhite:
7639             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7640             break;
7641           case AnalyzeMode:
7642           case AnalyzeFile:
7643             break;
7644           case IcsObserving: /* [DM] icsEngineAnalyze */
7645             if (!appData.icsEngineAnalyze) ignore = TRUE;
7646             break;
7647           case TwoMachinesPlay:
7648             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7649                 ignore = TRUE;
7650             }
7651             break;
7652           default:
7653             ignore = TRUE;
7654             break;
7655         }
7656
7657         if (!ignore) {
7658             buf1[0] = NULLCHAR;
7659             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7660                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7661
7662                 if (plyext != ' ' && plyext != '\t') {
7663                     time *= 100;
7664                 }
7665
7666                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7667                 if( cps->scoreIsAbsolute && 
7668                     ( gameMode == MachinePlaysBlack ||
7669                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7670                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7671                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7672                      !WhiteOnMove(currentMove)
7673                     ) )
7674                 {
7675                     curscore = -curscore;
7676                 }
7677
7678
7679                 programStats.depth = plylev;
7680                 programStats.nodes = nodes;
7681                 programStats.time = time;
7682                 programStats.score = curscore;
7683                 programStats.got_only_move = 0;
7684
7685                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7686                         int ticklen;
7687
7688                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7689                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7690                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7691                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7692                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7693                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7694                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7695                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7696                 }
7697
7698                 /* Buffer overflow protection */
7699                 if (buf1[0] != NULLCHAR) {
7700                     if (strlen(buf1) >= sizeof(programStats.movelist)
7701                         && appData.debugMode) {
7702                         fprintf(debugFP,
7703                                 "PV is too long; using the first %u bytes.\n",
7704                                 (unsigned) sizeof(programStats.movelist) - 1);
7705                     }
7706
7707                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7708                 } else {
7709                     sprintf(programStats.movelist, " no PV\n");
7710                 }
7711
7712                 if (programStats.seen_stat) {
7713                     programStats.ok_to_send = 1;
7714                 }
7715
7716                 if (strchr(programStats.movelist, '(') != NULL) {
7717                     programStats.line_is_book = 1;
7718                     programStats.nr_moves = 0;
7719                     programStats.moves_left = 0;
7720                 } else {
7721                     programStats.line_is_book = 0;
7722                 }
7723
7724                 SendProgramStatsToFrontend( cps, &programStats );
7725
7726                 /*
7727                     [AS] Protect the thinkOutput buffer from overflow... this
7728                     is only useful if buf1 hasn't overflowed first!
7729                 */
7730                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7731                         plylev,
7732                         (gameMode == TwoMachinesPlay ?
7733                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7734                         ((double) curscore) / 100.0,
7735                         prefixHint ? lastHint : "",
7736                         prefixHint ? " " : "" );
7737
7738                 if( buf1[0] != NULLCHAR ) {
7739                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7740
7741                     if( strlen(buf1) > max_len ) {
7742                         if( appData.debugMode) {
7743                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7744                         }
7745                         buf1[max_len+1] = '\0';
7746                     }
7747
7748                     strcat( thinkOutput, buf1 );
7749                 }
7750
7751                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7752                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7753                     DisplayMove(currentMove - 1);
7754                 }
7755                 return;
7756
7757             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7758                 /* crafty (9.25+) says "(only move) <move>"
7759                  * if there is only 1 legal move
7760                  */
7761                 sscanf(p, "(only move) %s", buf1);
7762                 sprintf(thinkOutput, "%s (only move)", buf1);
7763                 sprintf(programStats.movelist, "%s (only move)", buf1);
7764                 programStats.depth = 1;
7765                 programStats.nr_moves = 1;
7766                 programStats.moves_left = 1;
7767                 programStats.nodes = 1;
7768                 programStats.time = 1;
7769                 programStats.got_only_move = 1;
7770
7771                 /* Not really, but we also use this member to
7772                    mean "line isn't going to change" (Crafty
7773                    isn't searching, so stats won't change) */
7774                 programStats.line_is_book = 1;
7775
7776                 SendProgramStatsToFrontend( cps, &programStats );
7777
7778                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7779                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7780                     DisplayMove(currentMove - 1);
7781                 }
7782                 return;
7783             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7784                               &time, &nodes, &plylev, &mvleft,
7785                               &mvtot, mvname) >= 5) {
7786                 /* The stat01: line is from Crafty (9.29+) in response
7787                    to the "." command */
7788                 programStats.seen_stat = 1;
7789                 cps->maybeThinking = TRUE;
7790
7791                 if (programStats.got_only_move || !appData.periodicUpdates)
7792                   return;
7793
7794                 programStats.depth = plylev;
7795                 programStats.time = time;
7796                 programStats.nodes = nodes;
7797                 programStats.moves_left = mvleft;
7798                 programStats.nr_moves = mvtot;
7799                 strcpy(programStats.move_name, mvname);
7800                 programStats.ok_to_send = 1;
7801                 programStats.movelist[0] = '\0';
7802
7803                 SendProgramStatsToFrontend( cps, &programStats );
7804
7805                 return;
7806
7807             } else if (strncmp(message,"++",2) == 0) {
7808                 /* Crafty 9.29+ outputs this */
7809                 programStats.got_fail = 2;
7810                 return;
7811
7812             } else if (strncmp(message,"--",2) == 0) {
7813                 /* Crafty 9.29+ outputs this */
7814                 programStats.got_fail = 1;
7815                 return;
7816
7817             } else if (thinkOutput[0] != NULLCHAR &&
7818                        strncmp(message, "    ", 4) == 0) {
7819                 unsigned message_len;
7820
7821                 p = message;
7822                 while (*p && *p == ' ') p++;
7823
7824                 message_len = strlen( p );
7825
7826                 /* [AS] Avoid buffer overflow */
7827                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7828                     strcat(thinkOutput, " ");
7829                     strcat(thinkOutput, p);
7830                 }
7831
7832                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7833                     strcat(programStats.movelist, " ");
7834                     strcat(programStats.movelist, p);
7835                 }
7836
7837                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7838                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7839                     DisplayMove(currentMove - 1);
7840                 }
7841                 return;
7842             }
7843         }
7844         else {
7845             buf1[0] = NULLCHAR;
7846
7847             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7848                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7849             {
7850                 ChessProgramStats cpstats;
7851
7852                 if (plyext != ' ' && plyext != '\t') {
7853                     time *= 100;
7854                 }
7855
7856                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7857                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7858                     curscore = -curscore;
7859                 }
7860
7861                 cpstats.depth = plylev;
7862                 cpstats.nodes = nodes;
7863                 cpstats.time = time;
7864                 cpstats.score = curscore;
7865                 cpstats.got_only_move = 0;
7866                 cpstats.movelist[0] = '\0';
7867
7868                 if (buf1[0] != NULLCHAR) {
7869                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7870                 }
7871
7872                 cpstats.ok_to_send = 0;
7873                 cpstats.line_is_book = 0;
7874                 cpstats.nr_moves = 0;
7875                 cpstats.moves_left = 0;
7876
7877                 SendProgramStatsToFrontend( cps, &cpstats );
7878             }
7879         }
7880     }
7881 }
7882
7883
7884 /* Parse a game score from the character string "game", and
7885    record it as the history of the current game.  The game
7886    score is NOT assumed to start from the standard position.
7887    The display is not updated in any way.
7888    */
7889 void
7890 ParseGameHistory(game)
7891      char *game;
7892 {
7893     ChessMove moveType;
7894     int fromX, fromY, toX, toY, boardIndex;
7895     char promoChar;
7896     char *p, *q;
7897     char buf[MSG_SIZ];
7898
7899     if (appData.debugMode)
7900       fprintf(debugFP, "Parsing game history: %s\n", game);
7901
7902     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7903     gameInfo.site = StrSave(appData.icsHost);
7904     gameInfo.date = PGNDate();
7905     gameInfo.round = StrSave("-");
7906
7907     /* Parse out names of players */
7908     while (*game == ' ') game++;
7909     p = buf;
7910     while (*game != ' ') *p++ = *game++;
7911     *p = NULLCHAR;
7912     gameInfo.white = StrSave(buf);
7913     while (*game == ' ') game++;
7914     p = buf;
7915     while (*game != ' ' && *game != '\n') *p++ = *game++;
7916     *p = NULLCHAR;
7917     gameInfo.black = StrSave(buf);
7918
7919     /* Parse moves */
7920     boardIndex = blackPlaysFirst ? 1 : 0;
7921     yynewstr(game);
7922     for (;;) {
7923         yyboardindex = boardIndex;
7924         moveType = (ChessMove) yylex();
7925         switch (moveType) {
7926           case IllegalMove:             /* maybe suicide chess, etc. */
7927   if (appData.debugMode) {
7928     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7929     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7930     setbuf(debugFP, NULL);
7931   }
7932           case WhitePromotionChancellor:
7933           case BlackPromotionChancellor:
7934           case WhitePromotionArchbishop:
7935           case BlackPromotionArchbishop:
7936           case WhitePromotionQueen:
7937           case BlackPromotionQueen:
7938           case WhitePromotionRook:
7939           case BlackPromotionRook:
7940           case WhitePromotionBishop:
7941           case BlackPromotionBishop:
7942           case WhitePromotionKnight:
7943           case BlackPromotionKnight:
7944           case WhitePromotionKing:
7945           case BlackPromotionKing:
7946           case NormalMove:
7947           case WhiteCapturesEnPassant:
7948           case BlackCapturesEnPassant:
7949           case WhiteKingSideCastle:
7950           case WhiteQueenSideCastle:
7951           case BlackKingSideCastle:
7952           case BlackQueenSideCastle:
7953           case WhiteKingSideCastleWild:
7954           case WhiteQueenSideCastleWild:
7955           case BlackKingSideCastleWild:
7956           case BlackQueenSideCastleWild:
7957           /* PUSH Fabien */
7958           case WhiteHSideCastleFR:
7959           case WhiteASideCastleFR:
7960           case BlackHSideCastleFR:
7961           case BlackASideCastleFR:
7962           /* POP Fabien */
7963             fromX = currentMoveString[0] - AAA;
7964             fromY = currentMoveString[1] - ONE;
7965             toX = currentMoveString[2] - AAA;
7966             toY = currentMoveString[3] - ONE;
7967             promoChar = currentMoveString[4];
7968             break;
7969           case WhiteDrop:
7970           case BlackDrop:
7971             fromX = moveType == WhiteDrop ?
7972               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7973             (int) CharToPiece(ToLower(currentMoveString[0]));
7974             fromY = DROP_RANK;
7975             toX = currentMoveString[2] - AAA;
7976             toY = currentMoveString[3] - ONE;
7977             promoChar = NULLCHAR;
7978             break;
7979           case AmbiguousMove:
7980             /* bug? */
7981             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7982   if (appData.debugMode) {
7983     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7984     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7985     setbuf(debugFP, NULL);
7986   }
7987             DisplayError(buf, 0);
7988             return;
7989           case ImpossibleMove:
7990             /* bug? */
7991             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7992   if (appData.debugMode) {
7993     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7994     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7995     setbuf(debugFP, NULL);
7996   }
7997             DisplayError(buf, 0);
7998             return;
7999           case (ChessMove) 0:   /* end of file */
8000             if (boardIndex < backwardMostMove) {
8001                 /* Oops, gap.  How did that happen? */
8002                 DisplayError(_("Gap in move list"), 0);
8003                 return;
8004             }
8005             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8006             if (boardIndex > forwardMostMove) {
8007                 forwardMostMove = boardIndex;
8008             }
8009             return;
8010           case ElapsedTime:
8011             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8012                 strcat(parseList[boardIndex-1], " ");
8013                 strcat(parseList[boardIndex-1], yy_text);
8014             }
8015             continue;
8016           case Comment:
8017           case PGNTag:
8018           case NAG:
8019           default:
8020             /* ignore */
8021             continue;
8022           case WhiteWins:
8023           case BlackWins:
8024           case GameIsDrawn:
8025           case GameUnfinished:
8026             if (gameMode == IcsExamining) {
8027                 if (boardIndex < backwardMostMove) {
8028                     /* Oops, gap.  How did that happen? */
8029                     return;
8030                 }
8031                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8032                 return;
8033             }
8034             gameInfo.result = moveType;
8035             p = strchr(yy_text, '{');
8036             if (p == NULL) p = strchr(yy_text, '(');
8037             if (p == NULL) {
8038                 p = yy_text;
8039                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8040             } else {
8041                 q = strchr(p, *p == '{' ? '}' : ')');
8042                 if (q != NULL) *q = NULLCHAR;
8043                 p++;
8044             }
8045             gameInfo.resultDetails = StrSave(p);
8046             continue;
8047         }
8048         if (boardIndex >= forwardMostMove &&
8049             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8050             backwardMostMove = blackPlaysFirst ? 1 : 0;
8051             return;
8052         }
8053         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8054                                  fromY, fromX, toY, toX, promoChar,
8055                                  parseList[boardIndex]);
8056         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8057         /* currentMoveString is set as a side-effect of yylex */
8058         strcpy(moveList[boardIndex], currentMoveString);
8059         strcat(moveList[boardIndex], "\n");
8060         boardIndex++;
8061         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8062         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8063           case MT_NONE:
8064           case MT_STALEMATE:
8065           default:
8066             break;
8067           case MT_CHECK:
8068             if(gameInfo.variant != VariantShogi)
8069                 strcat(parseList[boardIndex - 1], "+");
8070             break;
8071           case MT_CHECKMATE:
8072           case MT_STAINMATE:
8073             strcat(parseList[boardIndex - 1], "#");
8074             break;
8075         }
8076     }
8077 }
8078
8079
8080 /* Apply a move to the given board  */
8081 void
8082 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8083      int fromX, fromY, toX, toY;
8084      int promoChar;
8085      Board board;
8086 {
8087   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8088   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8089
8090     /* [HGM] compute & store e.p. status and castling rights for new position */
8091     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8092     { int i;
8093
8094       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8095       oldEP = (signed char)board[EP_STATUS];
8096       board[EP_STATUS] = EP_NONE;
8097
8098       if( board[toY][toX] != EmptySquare ) 
8099            board[EP_STATUS] = EP_CAPTURE;  
8100
8101       if( board[fromY][fromX] == WhitePawn ) {
8102            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8103                board[EP_STATUS] = EP_PAWN_MOVE;
8104            if( toY-fromY==2) {
8105                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8106                         gameInfo.variant != VariantBerolina || toX < fromX)
8107                       board[EP_STATUS] = toX | berolina;
8108                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8109                   gameInfo.variant != VariantBerolina || toX > fromX) 
8110                  board[EP_STATUS] = toX;
8111            }
8112       } else
8113       if( board[fromY][fromX] == BlackPawn ) {
8114            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8115                board[EP_STATUS] = EP_PAWN_MOVE; 
8116            if( toY-fromY== -2) {
8117                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8118                         gameInfo.variant != VariantBerolina || toX < fromX)
8119                       board[EP_STATUS] = toX | berolina;
8120                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8121                         gameInfo.variant != VariantBerolina || toX > fromX) 
8122                       board[EP_STATUS] = toX;
8123            }
8124        }
8125
8126        for(i=0; i<nrCastlingRights; i++) {
8127            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8128               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8129              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8130        }
8131
8132     }
8133
8134   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8135   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8136        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8137
8138   if (fromX == toX && fromY == toY) return;
8139
8140   if (fromY == DROP_RANK) {
8141         /* must be first */
8142         piece = board[toY][toX] = (ChessSquare) fromX;
8143   } else {
8144      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8145      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8146      if(gameInfo.variant == VariantKnightmate)
8147          king += (int) WhiteUnicorn - (int) WhiteKing;
8148
8149     /* Code added by Tord: */
8150     /* FRC castling assumed when king captures friendly rook. */
8151     if (board[fromY][fromX] == WhiteKing &&
8152              board[toY][toX] == WhiteRook) {
8153       board[fromY][fromX] = EmptySquare;
8154       board[toY][toX] = EmptySquare;
8155       if(toX > fromX) {
8156         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8157       } else {
8158         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8159       }
8160     } else if (board[fromY][fromX] == BlackKing &&
8161                board[toY][toX] == BlackRook) {
8162       board[fromY][fromX] = EmptySquare;
8163       board[toY][toX] = EmptySquare;
8164       if(toX > fromX) {
8165         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8166       } else {
8167         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8168       }
8169     /* End of code added by Tord */
8170
8171     } else if (board[fromY][fromX] == king
8172         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8173         && toY == fromY && toX > fromX+1) {
8174         board[fromY][fromX] = EmptySquare;
8175         board[toY][toX] = king;
8176         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8177         board[fromY][BOARD_RGHT-1] = EmptySquare;
8178     } else if (board[fromY][fromX] == king
8179         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8180                && toY == fromY && toX < fromX-1) {
8181         board[fromY][fromX] = EmptySquare;
8182         board[toY][toX] = king;
8183         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8184         board[fromY][BOARD_LEFT] = EmptySquare;
8185     } else if (board[fromY][fromX] == WhitePawn
8186                && toY >= BOARD_HEIGHT-promoRank
8187                && gameInfo.variant != VariantXiangqi
8188                ) {
8189         /* white pawn promotion */
8190         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8191         if (board[toY][toX] == EmptySquare) {
8192             board[toY][toX] = WhiteQueen;
8193         }
8194         if(gameInfo.variant==VariantBughouse ||
8195            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8196             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8197         board[fromY][fromX] = EmptySquare;
8198     } else if ((fromY == BOARD_HEIGHT-4)
8199                && (toX != fromX)
8200                && gameInfo.variant != VariantXiangqi
8201                && gameInfo.variant != VariantBerolina
8202                && (board[fromY][fromX] == WhitePawn)
8203                && (board[toY][toX] == EmptySquare)) {
8204         board[fromY][fromX] = EmptySquare;
8205         board[toY][toX] = WhitePawn;
8206         captured = board[toY - 1][toX];
8207         board[toY - 1][toX] = EmptySquare;
8208     } else if ((fromY == BOARD_HEIGHT-4)
8209                && (toX == fromX)
8210                && gameInfo.variant == VariantBerolina
8211                && (board[fromY][fromX] == WhitePawn)
8212                && (board[toY][toX] == EmptySquare)) {
8213         board[fromY][fromX] = EmptySquare;
8214         board[toY][toX] = WhitePawn;
8215         if(oldEP & EP_BEROLIN_A) {
8216                 captured = board[fromY][fromX-1];
8217                 board[fromY][fromX-1] = EmptySquare;
8218         }else{  captured = board[fromY][fromX+1];
8219                 board[fromY][fromX+1] = EmptySquare;
8220         }
8221     } else if (board[fromY][fromX] == king
8222         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8223                && toY == fromY && toX > fromX+1) {
8224         board[fromY][fromX] = EmptySquare;
8225         board[toY][toX] = king;
8226         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8227         board[fromY][BOARD_RGHT-1] = EmptySquare;
8228     } else if (board[fromY][fromX] == king
8229         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8230                && toY == fromY && toX < fromX-1) {
8231         board[fromY][fromX] = EmptySquare;
8232         board[toY][toX] = king;
8233         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8234         board[fromY][BOARD_LEFT] = EmptySquare;
8235     } else if (fromY == 7 && fromX == 3
8236                && board[fromY][fromX] == BlackKing
8237                && toY == 7 && toX == 5) {
8238         board[fromY][fromX] = EmptySquare;
8239         board[toY][toX] = BlackKing;
8240         board[fromY][7] = EmptySquare;
8241         board[toY][4] = BlackRook;
8242     } else if (fromY == 7 && fromX == 3
8243                && board[fromY][fromX] == BlackKing
8244                && toY == 7 && toX == 1) {
8245         board[fromY][fromX] = EmptySquare;
8246         board[toY][toX] = BlackKing;
8247         board[fromY][0] = EmptySquare;
8248         board[toY][2] = BlackRook;
8249     } else if (board[fromY][fromX] == BlackPawn
8250                && toY < promoRank
8251                && gameInfo.variant != VariantXiangqi
8252                ) {
8253         /* black pawn promotion */
8254         board[toY][toX] = CharToPiece(ToLower(promoChar));
8255         if (board[toY][toX] == EmptySquare) {
8256             board[toY][toX] = BlackQueen;
8257         }
8258         if(gameInfo.variant==VariantBughouse ||
8259            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8260             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8261         board[fromY][fromX] = EmptySquare;
8262     } else if ((fromY == 3)
8263                && (toX != fromX)
8264                && gameInfo.variant != VariantXiangqi
8265                && gameInfo.variant != VariantBerolina
8266                && (board[fromY][fromX] == BlackPawn)
8267                && (board[toY][toX] == EmptySquare)) {
8268         board[fromY][fromX] = EmptySquare;
8269         board[toY][toX] = BlackPawn;
8270         captured = board[toY + 1][toX];
8271         board[toY + 1][toX] = EmptySquare;
8272     } else if ((fromY == 3)
8273                && (toX == fromX)
8274                && gameInfo.variant == VariantBerolina
8275                && (board[fromY][fromX] == BlackPawn)
8276                && (board[toY][toX] == EmptySquare)) {
8277         board[fromY][fromX] = EmptySquare;
8278         board[toY][toX] = BlackPawn;
8279         if(oldEP & EP_BEROLIN_A) {
8280                 captured = board[fromY][fromX-1];
8281                 board[fromY][fromX-1] = EmptySquare;
8282         }else{  captured = board[fromY][fromX+1];
8283                 board[fromY][fromX+1] = EmptySquare;
8284         }
8285     } else {
8286         board[toY][toX] = board[fromY][fromX];
8287         board[fromY][fromX] = EmptySquare;
8288     }
8289
8290     /* [HGM] now we promote for Shogi, if needed */
8291     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8292         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8293   }
8294
8295     if (gameInfo.holdingsWidth != 0) {
8296
8297       /* !!A lot more code needs to be written to support holdings  */
8298       /* [HGM] OK, so I have written it. Holdings are stored in the */
8299       /* penultimate board files, so they are automaticlly stored   */
8300       /* in the game history.                                       */
8301       if (fromY == DROP_RANK) {
8302         /* Delete from holdings, by decreasing count */
8303         /* and erasing image if necessary            */
8304         p = (int) fromX;
8305         if(p < (int) BlackPawn) { /* white drop */
8306              p -= (int)WhitePawn;
8307                  p = PieceToNumber((ChessSquare)p);
8308              if(p >= gameInfo.holdingsSize) p = 0;
8309              if(--board[p][BOARD_WIDTH-2] <= 0)
8310                   board[p][BOARD_WIDTH-1] = EmptySquare;
8311              if((int)board[p][BOARD_WIDTH-2] < 0)
8312                         board[p][BOARD_WIDTH-2] = 0;
8313         } else {                  /* black drop */
8314              p -= (int)BlackPawn;
8315                  p = PieceToNumber((ChessSquare)p);
8316              if(p >= gameInfo.holdingsSize) p = 0;
8317              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8318                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8319              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8320                         board[BOARD_HEIGHT-1-p][1] = 0;
8321         }
8322       }
8323       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8324           && gameInfo.variant != VariantBughouse        ) {
8325         /* [HGM] holdings: Add to holdings, if holdings exist */
8326         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8327                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8328                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8329         }
8330         p = (int) captured;
8331         if (p >= (int) BlackPawn) {
8332           p -= (int)BlackPawn;
8333           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8334                   /* in Shogi restore piece to its original  first */
8335                   captured = (ChessSquare) (DEMOTED captured);
8336                   p = DEMOTED p;
8337           }
8338           p = PieceToNumber((ChessSquare)p);
8339           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8340           board[p][BOARD_WIDTH-2]++;
8341           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8342         } else {
8343           p -= (int)WhitePawn;
8344           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8345                   captured = (ChessSquare) (DEMOTED captured);
8346                   p = DEMOTED p;
8347           }
8348           p = PieceToNumber((ChessSquare)p);
8349           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8350           board[BOARD_HEIGHT-1-p][1]++;
8351           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8352         }
8353       }
8354     } else if (gameInfo.variant == VariantAtomic) {
8355       if (captured != EmptySquare) {
8356         int y, x;
8357         for (y = toY-1; y <= toY+1; y++) {
8358           for (x = toX-1; x <= toX+1; x++) {
8359             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8360                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8361               board[y][x] = EmptySquare;
8362             }
8363           }
8364         }
8365         board[toY][toX] = EmptySquare;
8366       }
8367     }
8368     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8369         /* [HGM] Shogi promotions */
8370         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8371     }
8372
8373     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8374                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8375         // [HGM] superchess: take promotion piece out of holdings
8376         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8377         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8378             if(!--board[k][BOARD_WIDTH-2])
8379                 board[k][BOARD_WIDTH-1] = EmptySquare;
8380         } else {
8381             if(!--board[BOARD_HEIGHT-1-k][1])
8382                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8383         }
8384     }
8385
8386 }
8387
8388 /* Updates forwardMostMove */
8389 void
8390 MakeMove(fromX, fromY, toX, toY, promoChar)
8391      int fromX, fromY, toX, toY;
8392      int promoChar;
8393 {
8394 //    forwardMostMove++; // [HGM] bare: moved downstream
8395
8396     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8397         int timeLeft; static int lastLoadFlag=0; int king, piece;
8398         piece = boards[forwardMostMove][fromY][fromX];
8399         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8400         if(gameInfo.variant == VariantKnightmate)
8401             king += (int) WhiteUnicorn - (int) WhiteKing;
8402         if(forwardMostMove == 0) {
8403             if(blackPlaysFirst)
8404                 fprintf(serverMoves, "%s;", second.tidy);
8405             fprintf(serverMoves, "%s;", first.tidy);
8406             if(!blackPlaysFirst)
8407                 fprintf(serverMoves, "%s;", second.tidy);
8408         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8409         lastLoadFlag = loadFlag;
8410         // print base move
8411         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8412         // print castling suffix
8413         if( toY == fromY && piece == king ) {
8414             if(toX-fromX > 1)
8415                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8416             if(fromX-toX >1)
8417                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8418         }
8419         // e.p. suffix
8420         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8421              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8422              boards[forwardMostMove][toY][toX] == EmptySquare
8423              && fromX != toX )
8424                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8425         // promotion suffix
8426         if(promoChar != NULLCHAR)
8427                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8428         if(!loadFlag) {
8429             fprintf(serverMoves, "/%d/%d",
8430                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8431             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8432             else                      timeLeft = blackTimeRemaining/1000;
8433             fprintf(serverMoves, "/%d", timeLeft);
8434         }
8435         fflush(serverMoves);
8436     }
8437
8438     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8439       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8440                         0, 1);
8441       return;
8442     }
8443     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8444     if (commentList[forwardMostMove+1] != NULL) {
8445         free(commentList[forwardMostMove+1]);
8446         commentList[forwardMostMove+1] = NULL;
8447     }
8448     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8449     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8450     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8451     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8452     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8453     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8454     gameInfo.result = GameUnfinished;
8455     if (gameInfo.resultDetails != NULL) {
8456         free(gameInfo.resultDetails);
8457         gameInfo.resultDetails = NULL;
8458     }
8459     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8460                               moveList[forwardMostMove - 1]);
8461     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8462                              PosFlags(forwardMostMove - 1),
8463                              fromY, fromX, toY, toX, promoChar,
8464                              parseList[forwardMostMove - 1]);
8465     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8466       case MT_NONE:
8467       case MT_STALEMATE:
8468       default:
8469         break;
8470       case MT_CHECK:
8471         if(gameInfo.variant != VariantShogi)
8472             strcat(parseList[forwardMostMove - 1], "+");
8473         break;
8474       case MT_CHECKMATE:
8475       case MT_STAINMATE:
8476         strcat(parseList[forwardMostMove - 1], "#");
8477         break;
8478     }
8479     if (appData.debugMode) {
8480         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8481     }
8482
8483 }
8484
8485 /* Updates currentMove if not pausing */
8486 void
8487 ShowMove(fromX, fromY, toX, toY)
8488 {
8489     int instant = (gameMode == PlayFromGameFile) ?
8490         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8491
8492     if(appData.noGUI) return;
8493
8494     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
8495       {
8496         if (!instant)
8497           {
8498             if (forwardMostMove == currentMove + 1)
8499               {
8500 //TODO
8501 //              AnimateMove(boards[forwardMostMove - 1],
8502 //                          fromX, fromY, toX, toY);
8503               }
8504             if (appData.highlightLastMove)
8505               {
8506                 SetHighlights(fromX, fromY, toX, toY);
8507               }
8508           }
8509         currentMove = forwardMostMove;
8510     }
8511
8512     if (instant) return;
8513
8514     DisplayMove(currentMove - 1);
8515     DrawPosition(FALSE, boards[currentMove]);
8516     DisplayBothClocks();
8517     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8518
8519     return;
8520 }
8521
8522 void SendEgtPath(ChessProgramState *cps)
8523 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8524         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8525
8526         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8527
8528         while(*p) {
8529             char c, *q = name+1, *r, *s;
8530
8531             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8532             while(*p && *p != ',') *q++ = *p++;
8533             *q++ = ':'; *q = 0;
8534             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8535                 strcmp(name, ",nalimov:") == 0 ) {
8536                 // take nalimov path from the menu-changeable option first, if it is defined
8537                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8538                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8539             } else
8540             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8541                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8542                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8543                 s = r = StrStr(s, ":") + 1; // beginning of path info
8544                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8545                 c = *r; *r = 0;             // temporarily null-terminate path info
8546                     *--q = 0;               // strip of trailig ':' from name
8547                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8548                 *r = c;
8549                 SendToProgram(buf,cps);     // send egtbpath command for this format
8550             }
8551             if(*p == ',') p++; // read away comma to position for next format name
8552         }
8553 }
8554
8555 void
8556 InitChessProgram(cps, setup)
8557      ChessProgramState *cps;
8558      int setup; /* [HGM] needed to setup FRC opening position */
8559 {
8560     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8561     if (appData.noChessProgram) return;
8562     hintRequested = FALSE;
8563     bookRequested = FALSE;
8564
8565     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8566     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8567     if(cps->memSize) { /* [HGM] memory */
8568         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8569         SendToProgram(buf, cps);
8570     }
8571     SendEgtPath(cps); /* [HGM] EGT */
8572     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8573         sprintf(buf, "cores %d\n", appData.smpCores);
8574         SendToProgram(buf, cps);
8575     }
8576
8577     SendToProgram(cps->initString, cps);
8578     if (gameInfo.variant != VariantNormal &&
8579         gameInfo.variant != VariantLoadable
8580         /* [HGM] also send variant if board size non-standard */
8581         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8582                                             ) {
8583       char *v = VariantName(gameInfo.variant);
8584       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8585         /* [HGM] in protocol 1 we have to assume all variants valid */
8586         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8587         DisplayFatalError(buf, 0, 1);
8588         return;
8589       }
8590
8591       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8592       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8593       if( gameInfo.variant == VariantXiangqi )
8594            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8595       if( gameInfo.variant == VariantShogi )
8596            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8597       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8598            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8599       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8600                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8601            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8602       if( gameInfo.variant == VariantCourier )
8603            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8604       if( gameInfo.variant == VariantSuper )
8605            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8606       if( gameInfo.variant == VariantGreat )
8607            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8608
8609       if(overruled) {
8610            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8611                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8612            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8613            if(StrStr(cps->variants, b) == NULL) {
8614                // specific sized variant not known, check if general sizing allowed
8615                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8616                    if(StrStr(cps->variants, "boardsize") == NULL) {
8617                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8618                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8619                        DisplayFatalError(buf, 0, 1);
8620                        return;
8621                    }
8622                    /* [HGM] here we really should compare with the maximum supported board size */
8623                }
8624            }
8625       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8626       sprintf(buf, "variant %s\n", b);
8627       SendToProgram(buf, cps);
8628     }
8629     currentlyInitializedVariant = gameInfo.variant;
8630
8631     /* [HGM] send opening position in FRC to first engine */
8632     if(setup) {
8633           SendToProgram("force\n", cps);
8634           SendBoard(cps, 0);
8635           /* engine is now in force mode! Set flag to wake it up after first move. */
8636           setboardSpoiledMachineBlack = 1;
8637     }
8638
8639     if (cps->sendICS) {
8640       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8641       SendToProgram(buf, cps);
8642     }
8643     cps->maybeThinking = FALSE;
8644     cps->offeredDraw = 0;
8645     if (!appData.icsActive) {
8646         SendTimeControl(cps, movesPerSession, timeControl,
8647                         timeIncrement, appData.searchDepth,
8648                         searchTime);
8649     }
8650     if (appData.showThinking
8651         // [HGM] thinking: four options require thinking output to be sent
8652         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8653                                 ) {
8654         SendToProgram("post\n", cps);
8655     }
8656     SendToProgram("hard\n", cps);
8657     if (!appData.ponderNextMove) {
8658         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8659            it without being sure what state we are in first.  "hard"
8660            is not a toggle, so that one is OK.
8661          */
8662         SendToProgram("easy\n", cps);
8663     }
8664     if (cps->usePing) {
8665       sprintf(buf, "ping %d\n", ++cps->lastPing);
8666       SendToProgram(buf, cps);
8667     }
8668     cps->initDone = TRUE;
8669 }
8670
8671
8672 void
8673 StartChessProgram(cps)
8674      ChessProgramState *cps;
8675 {
8676     char buf[MSG_SIZ];
8677     int err;
8678
8679     if (appData.noChessProgram) return;
8680     cps->initDone = FALSE;
8681
8682     if (strcmp(cps->host, "localhost") == 0) {
8683         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8684     } else if (*appData.remoteShell == NULLCHAR) {
8685         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8686     } else {
8687         if (*appData.remoteUser == NULLCHAR) {
8688           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8689                     cps->program);
8690         } else {
8691           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8692                     cps->host, appData.remoteUser, cps->program);
8693         }
8694         err = StartChildProcess(buf, "", &cps->pr);
8695     }
8696
8697     if (err != 0) {
8698         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8699         DisplayFatalError(buf, err, 1);
8700         cps->pr = NoProc;
8701         cps->isr = NULL;
8702         return;
8703     }
8704
8705     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8706     if (cps->protocolVersion > 1) {
8707       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8708       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8709       cps->comboCnt = 0;  //                and values of combo boxes
8710       SendToProgram(buf, cps);
8711     } else {
8712       SendToProgram("xboard\n", cps);
8713     }
8714 }
8715
8716
8717 void
8718 TwoMachinesEventIfReady P((void))
8719 {
8720   if (first.lastPing != first.lastPong) {
8721     DisplayMessage("", _("Waiting for first chess program"));
8722     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8723     return;
8724   }
8725   if (second.lastPing != second.lastPong) {
8726     DisplayMessage("", _("Waiting for second chess program"));
8727     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8728     return;
8729   }
8730   ThawUI();
8731   TwoMachinesEvent();
8732 }
8733
8734 void
8735 NextMatchGame P((void))
8736 {
8737     int index; /* [HGM] autoinc: step load index during match */
8738     Reset(FALSE, TRUE);
8739     if (*appData.loadGameFile != NULLCHAR) {
8740         index = appData.loadGameIndex;
8741         if(index < 0) { // [HGM] autoinc
8742             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8743             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8744         }
8745         LoadGameFromFile(appData.loadGameFile,
8746                          index,
8747                          appData.loadGameFile, FALSE);
8748     } else if (*appData.loadPositionFile != NULLCHAR) {
8749         index = appData.loadPositionIndex;
8750         if(index < 0) { // [HGM] autoinc
8751             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8752             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8753         }
8754         LoadPositionFromFile(appData.loadPositionFile,
8755                              index,
8756                              appData.loadPositionFile);
8757     }
8758     TwoMachinesEventIfReady();
8759 }
8760
8761 void UserAdjudicationEvent( int result )
8762 {
8763     ChessMove gameResult = GameIsDrawn;
8764
8765     if( result > 0 ) {
8766         gameResult = WhiteWins;
8767     }
8768     else if( result < 0 ) {
8769         gameResult = BlackWins;
8770     }
8771
8772     if( gameMode == TwoMachinesPlay ) {
8773         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8774     }
8775 }
8776
8777
8778 // [HGM] save: calculate checksum of game to make games easily identifiable
8779 int StringCheckSum(char *s)
8780 {
8781         int i = 0;
8782         if(s==NULL) return 0;
8783         while(*s) i = i*259 + *s++;
8784         return i;
8785 }
8786
8787 int GameCheckSum()
8788 {
8789         int i, sum=0;
8790         for(i=backwardMostMove; i<forwardMostMove; i++) {
8791                 sum += pvInfoList[i].depth;
8792                 sum += StringCheckSum(parseList[i]);
8793                 sum += StringCheckSum(commentList[i]);
8794                 sum *= 261;
8795         }
8796         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8797         return sum + StringCheckSum(commentList[i]);
8798 } // end of save patch
8799
8800 void
8801 GameEnds(result, resultDetails, whosays)
8802      ChessMove result;
8803      char *resultDetails;
8804      int whosays;
8805 {
8806     GameMode nextGameMode;
8807     int isIcsGame;
8808     char buf[MSG_SIZ];
8809
8810     if(endingGame) return; /* [HGM] crash: forbid recursion */
8811     endingGame = 1;
8812
8813     if (appData.debugMode) {
8814       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8815               result, resultDetails ? resultDetails : "(null)", whosays);
8816     }
8817
8818     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8819         /* If we are playing on ICS, the server decides when the
8820            game is over, but the engine can offer to draw, claim
8821            a draw, or resign.
8822          */
8823 #if ZIPPY
8824         if (appData.zippyPlay && first.initDone) {
8825             if (result == GameIsDrawn) {
8826                 /* In case draw still needs to be claimed */
8827                 SendToICS(ics_prefix);
8828                 SendToICS("draw\n");
8829             } else if (StrCaseStr(resultDetails, "resign")) {
8830                 SendToICS(ics_prefix);
8831                 SendToICS("resign\n");
8832             }
8833         }
8834 #endif
8835         endingGame = 0; /* [HGM] crash */
8836         return;
8837     }
8838
8839     /* If we're loading the game from a file, stop */
8840     if (whosays == GE_FILE) {
8841       (void) StopLoadGameTimer();
8842       gameFileFP = NULL;
8843     }
8844
8845     /* Cancel draw offers */
8846     first.offeredDraw = second.offeredDraw = 0;
8847
8848     /* If this is an ICS game, only ICS can really say it's done;
8849        if not, anyone can. */
8850     isIcsGame = (gameMode == IcsPlayingWhite ||
8851                  gameMode == IcsPlayingBlack ||
8852                  gameMode == IcsObserving    ||
8853                  gameMode == IcsExamining);
8854
8855     if (!isIcsGame || whosays == GE_ICS) {
8856         /* OK -- not an ICS game, or ICS said it was done */
8857         StopClocks();
8858         if (!isIcsGame && !appData.noChessProgram)
8859           SetUserThinkingEnables();
8860
8861         /* [HGM] if a machine claims the game end we verify this claim */
8862         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8863             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8864                 char claimer;
8865                 ChessMove trueResult = (ChessMove) -1;
8866
8867                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8868                                             first.twoMachinesColor[0] :
8869                                             second.twoMachinesColor[0] ;
8870
8871                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8872                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8873                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8874                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8875                 } else
8876                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8877                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8878                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8879                 } else
8880                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8881                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8882                 }
8883
8884                 // now verify win claims, but not in drop games, as we don't understand those yet
8885                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8886                                                  || gameInfo.variant == VariantGreat) &&
8887                     (result == WhiteWins && claimer == 'w' ||
8888                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8889                       if (appData.debugMode) {
8890                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8891                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8892                       }
8893                       if(result != trueResult) {
8894                               sprintf(buf, "False win claim: '%s'", resultDetails);
8895                               result = claimer == 'w' ? BlackWins : WhiteWins;
8896                               resultDetails = buf;
8897                       }
8898                 } else
8899                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8900                     && (forwardMostMove <= backwardMostMove ||
8901                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8902                         (claimer=='b')==(forwardMostMove&1))
8903                                                                                   ) {
8904                       /* [HGM] verify: draws that were not flagged are false claims */
8905                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8906                       result = claimer == 'w' ? BlackWins : WhiteWins;
8907                       resultDetails = buf;
8908                 }
8909                 /* (Claiming a loss is accepted no questions asked!) */
8910             }
8911
8912             /* [HGM] bare: don't allow bare King to win */
8913             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8914                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8915                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8916                && result != GameIsDrawn)
8917             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8918                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8919                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8920                         if(p >= 0 && p <= (int)WhiteKing) k++;
8921                 }
8922                 if (appData.debugMode) {
8923                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8924                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8925                 }
8926                 if(k <= 1) {
8927                         result = GameIsDrawn;
8928                         sprintf(buf, "%s but bare king", resultDetails);
8929                         resultDetails = buf;
8930                 }
8931             }
8932         }
8933
8934         if(serverMoves != NULL && !loadFlag) { char c = '=';
8935             if(result==WhiteWins) c = '+';
8936             if(result==BlackWins) c = '-';
8937             if(resultDetails != NULL)
8938                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8939         }
8940         if (resultDetails != NULL) {
8941             gameInfo.result = result;
8942             gameInfo.resultDetails = StrSave(resultDetails);
8943
8944             /* display last move only if game was not loaded from file */
8945             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8946                 DisplayMove(currentMove - 1);
8947
8948             if (forwardMostMove != 0) {
8949                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8950                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8951                                                                 ) {
8952                     if (*appData.saveGameFile != NULLCHAR) {
8953                         SaveGameToFile(appData.saveGameFile, TRUE);
8954                     } else if (appData.autoSaveGames) {
8955                         AutoSaveGame();
8956                     }
8957                     if (*appData.savePositionFile != NULLCHAR) {
8958                         SavePositionToFile(appData.savePositionFile);
8959                     }
8960                 }
8961             }
8962
8963             /* Tell program how game ended in case it is learning */
8964             /* [HGM] Moved this to after saving the PGN, just in case */
8965             /* engine died and we got here through time loss. In that */
8966             /* case we will get a fatal error writing the pipe, which */
8967             /* would otherwise lose us the PGN.                       */
8968             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8969             /* output during GameEnds should never be fatal anymore   */
8970             if (gameMode == MachinePlaysWhite ||
8971                 gameMode == MachinePlaysBlack ||
8972                 gameMode == TwoMachinesPlay ||
8973                 gameMode == IcsPlayingWhite ||
8974                 gameMode == IcsPlayingBlack ||
8975                 gameMode == BeginningOfGame) {
8976                 char buf[MSG_SIZ];
8977                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8978                         resultDetails);
8979                 if (first.pr != NoProc) {
8980                     SendToProgram(buf, &first);
8981                 }
8982                 if (second.pr != NoProc &&
8983                     gameMode == TwoMachinesPlay) {
8984                     SendToProgram(buf, &second);
8985                 }
8986             }
8987         }
8988
8989         if (appData.icsActive) {
8990             if (appData.quietPlay &&
8991                 (gameMode == IcsPlayingWhite ||
8992                  gameMode == IcsPlayingBlack)) {
8993                 SendToICS(ics_prefix);
8994                 SendToICS("set shout 1\n");
8995             }
8996             nextGameMode = IcsIdle;
8997             ics_user_moved = FALSE;
8998             /* clean up premove.  It's ugly when the game has ended and the
8999              * premove highlights are still on the board.
9000              */
9001             if (gotPremove) {
9002               gotPremove = FALSE;
9003               ClearPremoveHighlights();
9004               DrawPosition(FALSE, boards[currentMove]);
9005             }
9006             if (whosays == GE_ICS) {
9007                 switch (result) {
9008                 case WhiteWins:
9009                     if (gameMode == IcsPlayingWhite)
9010                         PlayIcsWinSound();
9011                     else if(gameMode == IcsPlayingBlack)
9012                         PlayIcsLossSound();
9013                     break;
9014                 case BlackWins:
9015                     if (gameMode == IcsPlayingBlack)
9016                         PlayIcsWinSound();
9017                     else if(gameMode == IcsPlayingWhite)
9018                         PlayIcsLossSound();
9019                     break;
9020                 case GameIsDrawn:
9021                     PlayIcsDrawSound();
9022                     break;
9023                 default:
9024                     PlayIcsUnfinishedSound();
9025                 }
9026             }
9027         } else if (gameMode == EditGame ||
9028                    gameMode == PlayFromGameFile ||
9029                    gameMode == AnalyzeMode ||
9030                    gameMode == AnalyzeFile) {
9031             nextGameMode = gameMode;
9032         } else {
9033             nextGameMode = EndOfGame;
9034         }
9035         pausing = FALSE;
9036         ModeHighlight();
9037     } else {
9038         nextGameMode = gameMode;
9039     }
9040
9041     if (appData.noChessProgram) {
9042         gameMode = nextGameMode;
9043         ModeHighlight();
9044         endingGame = 0; /* [HGM] crash */
9045         return;
9046     }
9047
9048     if (first.reuse) {
9049         /* Put first chess program into idle state */
9050         if (first.pr != NoProc &&
9051             (gameMode == MachinePlaysWhite ||
9052              gameMode == MachinePlaysBlack ||
9053              gameMode == TwoMachinesPlay ||
9054              gameMode == IcsPlayingWhite ||
9055              gameMode == IcsPlayingBlack ||
9056              gameMode == BeginningOfGame)) {
9057             SendToProgram("force\n", &first);
9058             if (first.usePing) {
9059               char buf[MSG_SIZ];
9060               sprintf(buf, "ping %d\n", ++first.lastPing);
9061               SendToProgram(buf, &first);
9062             }
9063         }
9064     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9065         /* Kill off first chess program */
9066         if (first.isr != NULL)
9067           RemoveInputSource(first.isr);
9068         first.isr = NULL;
9069
9070         if (first.pr != NoProc) {
9071             ExitAnalyzeMode();
9072             DoSleep( appData.delayBeforeQuit );
9073             SendToProgram("quit\n", &first);
9074             DoSleep( appData.delayAfterQuit );
9075             DestroyChildProcess(first.pr, first.useSigterm);
9076         }
9077         first.pr = NoProc;
9078     }
9079     if (second.reuse) {
9080         /* Put second chess program into idle state */
9081         if (second.pr != NoProc &&
9082             gameMode == TwoMachinesPlay) {
9083             SendToProgram("force\n", &second);
9084             if (second.usePing) {
9085               char buf[MSG_SIZ];
9086               sprintf(buf, "ping %d\n", ++second.lastPing);
9087               SendToProgram(buf, &second);
9088             }
9089         }
9090     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9091         /* Kill off second chess program */
9092         if (second.isr != NULL)
9093           RemoveInputSource(second.isr);
9094         second.isr = NULL;
9095
9096         if (second.pr != NoProc) {
9097             DoSleep( appData.delayBeforeQuit );
9098             SendToProgram("quit\n", &second);
9099             DoSleep( appData.delayAfterQuit );
9100             DestroyChildProcess(second.pr, second.useSigterm);
9101         }
9102         second.pr = NoProc;
9103     }
9104
9105     if (matchMode && gameMode == TwoMachinesPlay) {
9106         switch (result) {
9107         case WhiteWins:
9108           if (first.twoMachinesColor[0] == 'w') {
9109             first.matchWins++;
9110           } else {
9111             second.matchWins++;
9112           }
9113           break;
9114         case BlackWins:
9115           if (first.twoMachinesColor[0] == 'b') {
9116             first.matchWins++;
9117           } else {
9118             second.matchWins++;
9119           }
9120           break;
9121         default:
9122           break;
9123         }
9124         if (matchGame < appData.matchGames) {
9125             char *tmp;
9126             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9127                 tmp = first.twoMachinesColor;
9128                 first.twoMachinesColor = second.twoMachinesColor;
9129                 second.twoMachinesColor = tmp;
9130             }
9131             gameMode = nextGameMode;
9132             matchGame++;
9133             if(appData.matchPause>10000 || appData.matchPause<10)
9134                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9135             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9136             endingGame = 0; /* [HGM] crash */
9137             return;
9138         } else {
9139             char buf[MSG_SIZ];
9140             gameMode = nextGameMode;
9141             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9142                     first.tidy, second.tidy,
9143                     first.matchWins, second.matchWins,
9144                     appData.matchGames - (first.matchWins + second.matchWins));
9145             DisplayFatalError(buf, 0, 0);
9146         }
9147     }
9148     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9149         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9150       ExitAnalyzeMode();
9151     gameMode = nextGameMode;
9152     ModeHighlight();
9153     endingGame = 0;  /* [HGM] crash */
9154 }
9155
9156 /* Assumes program was just initialized (initString sent).
9157    Leaves program in force mode. */
9158 void
9159 FeedMovesToProgram(cps, upto)
9160      ChessProgramState *cps;
9161      int upto;
9162 {
9163     int i;
9164
9165     if (appData.debugMode)
9166       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9167               startedFromSetupPosition ? "position and " : "",
9168               backwardMostMove, upto, cps->which);
9169     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9170         // [HGM] variantswitch: make engine aware of new variant
9171         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9172                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9173         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9174         SendToProgram(buf, cps);
9175         currentlyInitializedVariant = gameInfo.variant;
9176     }
9177     SendToProgram("force\n", cps);
9178     if (startedFromSetupPosition) {
9179         SendBoard(cps, backwardMostMove);
9180     if (appData.debugMode) {
9181         fprintf(debugFP, "feedMoves\n");
9182     }
9183     }
9184     for (i = backwardMostMove; i < upto; i++) {
9185         SendMoveToProgram(i, cps);
9186     }
9187 }
9188
9189
9190 void
9191 ResurrectChessProgram()
9192 {
9193      /* The chess program may have exited.
9194         If so, restart it and feed it all the moves made so far. */
9195
9196     if (appData.noChessProgram || first.pr != NoProc) return;
9197
9198     StartChessProgram(&first);
9199     InitChessProgram(&first, FALSE);
9200     FeedMovesToProgram(&first, currentMove);
9201
9202     if (!first.sendTime) {
9203         /* can't tell gnuchess what its clock should read,
9204            so we bow to its notion. */
9205         ResetClocks();
9206         timeRemaining[0][currentMove] = whiteTimeRemaining;
9207         timeRemaining[1][currentMove] = blackTimeRemaining;
9208     }
9209
9210     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9211                 appData.icsEngineAnalyze) && first.analysisSupport) {
9212       SendToProgram("analyze\n", &first);
9213       first.analyzing = TRUE;
9214     }
9215 }
9216
9217 /*
9218  * Button procedures
9219  */
9220 void
9221 Reset(redraw, init)
9222      int redraw, init;
9223 {
9224     int i;
9225
9226     if (appData.debugMode) {
9227         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9228                 redraw, init, gameMode);
9229     }
9230     CleanupTail(); // [HGM] vari: delete any stored variations
9231     pausing = pauseExamInvalid = FALSE;
9232     startedFromSetupPosition = blackPlaysFirst = FALSE;
9233     firstMove = TRUE;
9234     whiteFlag = blackFlag = FALSE;
9235     userOfferedDraw = FALSE;
9236     hintRequested = bookRequested = FALSE;
9237     first.maybeThinking = FALSE;
9238     second.maybeThinking = FALSE;
9239     first.bookSuspend = FALSE; // [HGM] book
9240     second.bookSuspend = FALSE;
9241     thinkOutput[0] = NULLCHAR;
9242     lastHint[0] = NULLCHAR;
9243     ClearGameInfo(&gameInfo);
9244     gameInfo.variant = StringToVariant(appData.variant);
9245     ics_user_moved = ics_clock_paused = FALSE;
9246     ics_getting_history = H_FALSE;
9247     ics_gamenum = -1;
9248     white_holding[0] = black_holding[0] = NULLCHAR;
9249     ClearProgramStats();
9250     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9251
9252     ResetFrontEnd();
9253     ClearHighlights();
9254     flipView = appData.flipView;
9255     ClearPremoveHighlights();
9256     gotPremove = FALSE;
9257     alarmSounded = FALSE;
9258
9259     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9260     if(appData.serverMovesName != NULL) {
9261         /* [HGM] prepare to make moves file for broadcasting */
9262         clock_t t = clock();
9263         if(serverMoves != NULL) fclose(serverMoves);
9264         serverMoves = fopen(appData.serverMovesName, "r");
9265         if(serverMoves != NULL) {
9266             fclose(serverMoves);
9267             /* delay 15 sec before overwriting, so all clients can see end */
9268             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9269         }
9270         serverMoves = fopen(appData.serverMovesName, "w");
9271     }
9272
9273     ExitAnalyzeMode();
9274     gameMode = BeginningOfGame;
9275     ModeHighlight();
9276
9277     if(appData.icsActive) gameInfo.variant = VariantNormal;
9278     currentMove = forwardMostMove = backwardMostMove = 0;
9279     InitPosition(redraw);
9280     for (i = 0; i < MAX_MOVES; i++) {
9281         if (commentList[i] != NULL) {
9282             free(commentList[i]);
9283             commentList[i] = NULL;
9284         }
9285     }
9286
9287     ResetClocks();
9288     timeRemaining[0][0] = whiteTimeRemaining;
9289     timeRemaining[1][0] = blackTimeRemaining;
9290     if (first.pr == NULL) {
9291         StartChessProgram(&first);
9292     }
9293     if (init) {
9294             InitChessProgram(&first, startedFromSetupPosition);
9295     }
9296
9297     DisplayTitle("");
9298     DisplayMessage("", "");
9299     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9300     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9301     return;
9302 }
9303
9304 void
9305 AutoPlayGameLoop()
9306 {
9307     for (;;) {
9308         if (!AutoPlayOneMove())
9309           return;
9310         if (matchMode || appData.timeDelay == 0)
9311           continue;
9312         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9313           return;
9314         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9315         break;
9316     }
9317 }
9318
9319
9320 int
9321 AutoPlayOneMove()
9322 {
9323     int fromX, fromY, toX, toY;
9324
9325     if (appData.debugMode) {
9326       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9327     }
9328
9329     if (gameMode != PlayFromGameFile)
9330       return FALSE;
9331
9332     if (currentMove >= forwardMostMove) {
9333       gameMode = EditGame;
9334       ModeHighlight();
9335
9336       /* [AS] Clear current move marker at the end of a game */
9337       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9338
9339       return FALSE;
9340     }
9341
9342     toX = moveList[currentMove][2] - AAA;
9343     toY = moveList[currentMove][3] - ONE;
9344
9345     if (moveList[currentMove][1] == '@') {
9346         if (appData.highlightLastMove) {
9347             SetHighlights(-1, -1, toX, toY);
9348         }
9349     } else {
9350         fromX = moveList[currentMove][0] - AAA;
9351         fromY = moveList[currentMove][1] - ONE;
9352
9353         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9354
9355         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9356
9357         if (appData.highlightLastMove) {
9358             SetHighlights(fromX, fromY, toX, toY);
9359         }
9360     }
9361     DisplayMove(currentMove);
9362     SendMoveToProgram(currentMove++, &first);
9363     DisplayBothClocks();
9364     DrawPosition(FALSE, boards[currentMove]);
9365     // [HGM] PV info: always display, routine tests if empty
9366     DisplayComment(currentMove - 1, commentList[currentMove]);
9367     return TRUE;
9368 }
9369
9370
9371 int
9372 LoadGameOneMove(readAhead)
9373      ChessMove readAhead;
9374 {
9375     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9376     char promoChar = NULLCHAR;
9377     ChessMove moveType;
9378     char move[MSG_SIZ];
9379     char *p, *q;
9380
9381     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9382         gameMode != AnalyzeMode && gameMode != Training) {
9383         gameFileFP = NULL;
9384         return FALSE;
9385     }
9386
9387     yyboardindex = forwardMostMove;
9388     if (readAhead != (ChessMove)0) {
9389       moveType = readAhead;
9390     } else {
9391       if (gameFileFP == NULL)
9392           return FALSE;
9393       moveType = (ChessMove) yylex();
9394     }
9395
9396     done = FALSE;
9397     switch (moveType) {
9398       case Comment:
9399         if (appData.debugMode)
9400           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9401         p = yy_text;
9402
9403         /* append the comment but don't display it */
9404         AppendComment(currentMove, p, FALSE);
9405         return TRUE;
9406
9407       case WhiteCapturesEnPassant:
9408       case BlackCapturesEnPassant:
9409       case WhitePromotionChancellor:
9410       case BlackPromotionChancellor:
9411       case WhitePromotionArchbishop:
9412       case BlackPromotionArchbishop:
9413       case WhitePromotionCentaur:
9414       case BlackPromotionCentaur:
9415       case WhitePromotionQueen:
9416       case BlackPromotionQueen:
9417       case WhitePromotionRook:
9418       case BlackPromotionRook:
9419       case WhitePromotionBishop:
9420       case BlackPromotionBishop:
9421       case WhitePromotionKnight:
9422       case BlackPromotionKnight:
9423       case WhitePromotionKing:
9424       case BlackPromotionKing:
9425       case NormalMove:
9426       case WhiteKingSideCastle:
9427       case WhiteQueenSideCastle:
9428       case BlackKingSideCastle:
9429       case BlackQueenSideCastle:
9430       case WhiteKingSideCastleWild:
9431       case WhiteQueenSideCastleWild:
9432       case BlackKingSideCastleWild:
9433       case BlackQueenSideCastleWild:
9434       /* PUSH Fabien */
9435       case WhiteHSideCastleFR:
9436       case WhiteASideCastleFR:
9437       case BlackHSideCastleFR:
9438       case BlackASideCastleFR:
9439       /* POP Fabien */
9440         if (appData.debugMode)
9441           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9442         fromX = currentMoveString[0] - AAA;
9443         fromY = currentMoveString[1] - ONE;
9444         toX = currentMoveString[2] - AAA;
9445         toY = currentMoveString[3] - ONE;
9446         promoChar = currentMoveString[4];
9447         break;
9448
9449       case WhiteDrop:
9450       case BlackDrop:
9451         if (appData.debugMode)
9452           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9453         fromX = moveType == WhiteDrop ?
9454           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9455         (int) CharToPiece(ToLower(currentMoveString[0]));
9456         fromY = DROP_RANK;
9457         toX = currentMoveString[2] - AAA;
9458         toY = currentMoveString[3] - ONE;
9459         break;
9460
9461       case WhiteWins:
9462       case BlackWins:
9463       case GameIsDrawn:
9464       case GameUnfinished:
9465         if (appData.debugMode)
9466           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9467         p = strchr(yy_text, '{');
9468         if (p == NULL) p = strchr(yy_text, '(');
9469         if (p == NULL) {
9470             p = yy_text;
9471             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9472         } else {
9473             q = strchr(p, *p == '{' ? '}' : ')');
9474             if (q != NULL) *q = NULLCHAR;
9475             p++;
9476         }
9477         GameEnds(moveType, p, GE_FILE);
9478         done = TRUE;
9479         if (cmailMsgLoaded) {
9480             ClearHighlights();
9481             flipView = WhiteOnMove(currentMove);
9482             if (moveType == GameUnfinished) flipView = !flipView;
9483             if (appData.debugMode)
9484               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9485         }
9486         break;
9487
9488       case (ChessMove) 0:       /* end of file */
9489         if (appData.debugMode)
9490           fprintf(debugFP, "Parser hit end of file\n");
9491         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9492           case MT_NONE:
9493           case MT_CHECK:
9494             break;
9495           case MT_CHECKMATE:
9496           case MT_STAINMATE:
9497             if (WhiteOnMove(currentMove)) {
9498                 GameEnds(BlackWins, "Black mates", GE_FILE);
9499             } else {
9500                 GameEnds(WhiteWins, "White mates", GE_FILE);
9501             }
9502             break;
9503           case MT_STALEMATE:
9504             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9505             break;
9506         }
9507         done = TRUE;
9508         break;
9509
9510       case MoveNumberOne:
9511         if (lastLoadGameStart == GNUChessGame) {
9512             /* GNUChessGames have numbers, but they aren't move numbers */
9513             if (appData.debugMode)
9514               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9515                       yy_text, (int) moveType);
9516             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9517         }
9518         /* else fall thru */
9519
9520       case XBoardGame:
9521       case GNUChessGame:
9522       case PGNTag:
9523         /* Reached start of next game in file */
9524         if (appData.debugMode)
9525           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9526         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9527           case MT_NONE:
9528           case MT_CHECK:
9529             break;
9530           case MT_CHECKMATE:
9531           case MT_STAINMATE:
9532             if (WhiteOnMove(currentMove)) {
9533                 GameEnds(BlackWins, "Black mates", GE_FILE);
9534             } else {
9535                 GameEnds(WhiteWins, "White mates", GE_FILE);
9536             }
9537             break;
9538           case MT_STALEMATE:
9539             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9540             break;
9541         }
9542         done = TRUE;
9543         break;
9544
9545       case PositionDiagram:     /* should not happen; ignore */
9546       case ElapsedTime:         /* ignore */
9547       case NAG:                 /* ignore */
9548         if (appData.debugMode)
9549           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9550                   yy_text, (int) moveType);
9551         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9552
9553       case IllegalMove:
9554         if (appData.testLegality) {
9555             if (appData.debugMode)
9556               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9557             sprintf(move, _("Illegal move: %d.%s%s"),
9558                     (forwardMostMove / 2) + 1,
9559                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9560             DisplayError(move, 0);
9561             done = TRUE;
9562         } else {
9563             if (appData.debugMode)
9564               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9565                       yy_text, currentMoveString);
9566             fromX = currentMoveString[0] - AAA;
9567             fromY = currentMoveString[1] - ONE;
9568             toX = currentMoveString[2] - AAA;
9569             toY = currentMoveString[3] - ONE;
9570             promoChar = currentMoveString[4];
9571         }
9572         break;
9573
9574       case AmbiguousMove:
9575         if (appData.debugMode)
9576           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9577         sprintf(move, _("Ambiguous move: %d.%s%s"),
9578                 (forwardMostMove / 2) + 1,
9579                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9580         DisplayError(move, 0);
9581         done = TRUE;
9582         break;
9583
9584       default:
9585       case ImpossibleMove:
9586         if (appData.debugMode)
9587           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9588         sprintf(move, _("Illegal move: %d.%s%s"),
9589                 (forwardMostMove / 2) + 1,
9590                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9591         DisplayError(move, 0);
9592         done = TRUE;
9593         break;
9594     }
9595
9596     if (done) {
9597         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9598             DrawPosition(FALSE, boards[currentMove]);
9599             DisplayBothClocks();
9600             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9601               DisplayComment(currentMove - 1, commentList[currentMove]);
9602         }
9603         (void) StopLoadGameTimer();
9604         gameFileFP = NULL;
9605         cmailOldMove = forwardMostMove;
9606         return FALSE;
9607     } else {
9608         /* currentMoveString is set as a side-effect of yylex */
9609         strcat(currentMoveString, "\n");
9610         strcpy(moveList[forwardMostMove], currentMoveString);
9611
9612         thinkOutput[0] = NULLCHAR;
9613         MakeMove(fromX, fromY, toX, toY, promoChar);
9614         currentMove = forwardMostMove;
9615         return TRUE;
9616     }
9617 }
9618
9619 /* Load the nth game from the given file */
9620 int
9621 LoadGameFromFile(filename, n, title, useList)
9622      char *filename;
9623      int n;
9624      char *title;
9625      /*Boolean*/ int useList;
9626 {
9627     FILE *f;
9628     char buf[MSG_SIZ];
9629
9630     if (strcmp(filename, "-") == 0) {
9631         f = stdin;
9632         title = "stdin";
9633     } else {
9634         f = fopen(filename, "rb");
9635         if (f == NULL) {
9636           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9637             DisplayError(buf, errno);
9638             return FALSE;
9639         }
9640     }
9641     if (fseek(f, 0, 0) == -1) {
9642         /* f is not seekable; probably a pipe */
9643         useList = FALSE;
9644     }
9645     if (useList && n == 0) {
9646         int error = GameListBuild(f);
9647         if (error) {
9648             DisplayError(_("Cannot build game list"), error);
9649         } else if (!ListEmpty(&gameList) &&
9650                    ((ListGame *) gameList.tailPred)->number > 1) {
9651           // TODO convert to GTK
9652           //        GameListPopUp(f, title);
9653             return TRUE;
9654         }
9655         GameListDestroy();
9656         n = 1;
9657     }
9658     if (n == 0) n = 1;
9659     return LoadGame(f, n, title, FALSE);
9660 }
9661
9662
9663 void
9664 MakeRegisteredMove()
9665 {
9666     int fromX, fromY, toX, toY;
9667     char promoChar;
9668     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9669         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9670           case CMAIL_MOVE:
9671           case CMAIL_DRAW:
9672             if (appData.debugMode)
9673               fprintf(debugFP, "Restoring %s for game %d\n",
9674                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9675
9676             thinkOutput[0] = NULLCHAR;
9677             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9678             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9679             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9680             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9681             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9682             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9683             MakeMove(fromX, fromY, toX, toY, promoChar);
9684             ShowMove(fromX, fromY, toX, toY);
9685             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9686               case MT_NONE:
9687               case MT_CHECK:
9688                 break;
9689
9690               case MT_CHECKMATE:
9691               case MT_STAINMATE:
9692                 if (WhiteOnMove(currentMove)) {
9693                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9694                 } else {
9695                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9696                 }
9697                 break;
9698
9699               case MT_STALEMATE:
9700                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9701                 break;
9702             }
9703
9704             break;
9705
9706           case CMAIL_RESIGN:
9707             if (WhiteOnMove(currentMove)) {
9708                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9709             } else {
9710                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9711             }
9712             break;
9713
9714           case CMAIL_ACCEPT:
9715             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9716             break;
9717
9718           default:
9719             break;
9720         }
9721     }
9722
9723     return;
9724 }
9725
9726 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9727 int
9728 CmailLoadGame(f, gameNumber, title, useList)
9729      FILE *f;
9730      int gameNumber;
9731      char *title;
9732      int useList;
9733 {
9734     int retVal;
9735
9736     if (gameNumber > nCmailGames) {
9737         DisplayError(_("No more games in this message"), 0);
9738         return FALSE;
9739     }
9740     if (f == lastLoadGameFP) {
9741         int offset = gameNumber - lastLoadGameNumber;
9742         if (offset == 0) {
9743             cmailMsg[0] = NULLCHAR;
9744             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9745                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9746                 nCmailMovesRegistered--;
9747             }
9748             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9749             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9750                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9751             }
9752         } else {
9753             if (! RegisterMove()) return FALSE;
9754         }
9755     }
9756
9757     retVal = LoadGame(f, gameNumber, title, useList);
9758
9759     /* Make move registered during previous look at this game, if any */
9760     MakeRegisteredMove();
9761
9762     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9763         commentList[currentMove]
9764           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9765         DisplayComment(currentMove - 1, commentList[currentMove]);
9766     }
9767
9768     return retVal;
9769 }
9770
9771 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9772 int
9773 ReloadGame(offset)
9774      int offset;
9775 {
9776     int gameNumber = lastLoadGameNumber + offset;
9777     if (lastLoadGameFP == NULL) {
9778         DisplayError(_("No game has been loaded yet"), 0);
9779         return FALSE;
9780     }
9781     if (gameNumber <= 0) {
9782         DisplayError(_("Can't back up any further"), 0);
9783         return FALSE;
9784     }
9785     if (cmailMsgLoaded) {
9786         return CmailLoadGame(lastLoadGameFP, gameNumber,
9787                              lastLoadGameTitle, lastLoadGameUseList);
9788     } else {
9789         return LoadGame(lastLoadGameFP, gameNumber,
9790                         lastLoadGameTitle, lastLoadGameUseList);
9791     }
9792 }
9793
9794
9795
9796 /* Load the nth game from open file f */
9797 int
9798 LoadGame(f, gameNumber, title, useList)
9799      FILE *f;
9800      int gameNumber;
9801      char *title;
9802      int useList;
9803 {
9804     ChessMove cm;
9805     char buf[MSG_SIZ];
9806     int gn = gameNumber;
9807     ListGame *lg = NULL;
9808     int numPGNTags = 0;
9809     int err;
9810     GameMode oldGameMode;
9811     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9812
9813     if (appData.debugMode)
9814         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9815
9816     if (gameMode == Training )
9817         SetTrainingModeOff();
9818
9819     oldGameMode = gameMode;
9820     if (gameMode != BeginningOfGame) 
9821       {
9822         Reset(FALSE, TRUE);
9823       };
9824
9825     gameFileFP = f;
9826     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9827       {
9828         fclose(lastLoadGameFP);
9829       };
9830
9831     if (useList) 
9832       {
9833         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9834         
9835         if (lg) 
9836           {
9837             fseek(f, lg->offset, 0);
9838             GameListHighlight(gameNumber);
9839             gn = 1;
9840           }
9841         else 
9842           {
9843             DisplayError(_("Game number out of range"), 0);
9844             return FALSE;
9845           };
9846       } 
9847     else 
9848       {
9849         GameListDestroy();
9850         if (fseek(f, 0, 0) == -1) 
9851           {
9852             if (f == lastLoadGameFP ?
9853                 gameNumber == lastLoadGameNumber + 1 :
9854                 gameNumber == 1) 
9855               {
9856                 gn = 1;
9857               } 
9858             else 
9859               {
9860                 DisplayError(_("Can't seek on game file"), 0);
9861                 return FALSE;
9862               };
9863           };
9864       };
9865
9866     lastLoadGameFP      = f;
9867     lastLoadGameNumber  = gameNumber;
9868     strcpy(lastLoadGameTitle, title);
9869     lastLoadGameUseList = useList;
9870
9871     yynewfile(f);
9872
9873     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9874       {
9875         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9876                  lg->gameInfo.black);
9877         DisplayTitle(buf);
9878       } 
9879     else if (*title != NULLCHAR) 
9880       {
9881         if (gameNumber > 1) 
9882           {
9883             sprintf(buf, "%s %d", title, gameNumber);
9884             DisplayTitle(buf);
9885           } 
9886         else 
9887           {
9888             DisplayTitle(title);
9889           };
9890       };
9891
9892     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9893       {
9894         gameMode = PlayFromGameFile;
9895         ModeHighlight();
9896       };
9897
9898     currentMove = forwardMostMove = backwardMostMove = 0;
9899     CopyBoard(boards[0], initialPosition);
9900     StopClocks();
9901
9902     /*
9903      * Skip the first gn-1 games in the file.
9904      * Also skip over anything that precedes an identifiable
9905      * start of game marker, to avoid being confused by
9906      * garbage at the start of the file.  Currently
9907      * recognized start of game markers are the move number "1",
9908      * the pattern "gnuchess .* game", the pattern
9909      * "^[#;%] [^ ]* game file", and a PGN tag block.
9910      * A game that starts with one of the latter two patterns
9911      * will also have a move number 1, possibly
9912      * following a position diagram.
9913      * 5-4-02: Let's try being more lenient and allowing a game to
9914      * start with an unnumbered move.  Does that break anything?
9915      */
9916     cm = lastLoadGameStart = (ChessMove) 0;
9917     while (gn > 0) {
9918         yyboardindex = forwardMostMove;
9919         cm = (ChessMove) yylex();
9920         switch (cm) {
9921           case (ChessMove) 0:
9922             if (cmailMsgLoaded) {
9923                 nCmailGames = CMAIL_MAX_GAMES - gn;
9924             } else {
9925                 Reset(TRUE, TRUE);
9926                 DisplayError(_("Game not found in file"), 0);
9927             }
9928             return FALSE;
9929
9930           case GNUChessGame:
9931           case XBoardGame:
9932             gn--;
9933             lastLoadGameStart = cm;
9934             break;
9935
9936           case MoveNumberOne:
9937             switch (lastLoadGameStart) {
9938               case GNUChessGame:
9939               case XBoardGame:
9940               case PGNTag:
9941                 break;
9942               case MoveNumberOne:
9943               case (ChessMove) 0:
9944                 gn--;           /* count this game */
9945                 lastLoadGameStart = cm;
9946                 break;
9947               default:
9948                 /* impossible */
9949                 break;
9950             }
9951             break;
9952
9953           case PGNTag:
9954             switch (lastLoadGameStart) {
9955               case GNUChessGame:
9956               case PGNTag:
9957               case MoveNumberOne:
9958               case (ChessMove) 0:
9959                 gn--;           /* count this game */
9960                 lastLoadGameStart = cm;
9961                 break;
9962               case XBoardGame:
9963                 lastLoadGameStart = cm; /* game counted already */
9964                 break;
9965               default:
9966                 /* impossible */
9967                 break;
9968             }
9969             if (gn > 0) {
9970                 do {
9971                     yyboardindex = forwardMostMove;
9972                     cm = (ChessMove) yylex();
9973                 } while (cm == PGNTag || cm == Comment);
9974             }
9975             break;
9976
9977           case WhiteWins:
9978           case BlackWins:
9979           case GameIsDrawn:
9980             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9981                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9982                     != CMAIL_OLD_RESULT) {
9983                     nCmailResults ++ ;
9984                     cmailResult[  CMAIL_MAX_GAMES
9985                                 - gn - 1] = CMAIL_OLD_RESULT;
9986                 }
9987             }
9988             break;
9989
9990           case NormalMove:
9991             /* Only a NormalMove can be at the start of a game
9992              * without a position diagram. */
9993             if (lastLoadGameStart == (ChessMove) 0) {
9994               gn--;
9995               lastLoadGameStart = MoveNumberOne;
9996             }
9997             break;
9998
9999           default:
10000             break;
10001         }
10002     }
10003
10004     if (appData.debugMode)
10005       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10006
10007     if (cm == XBoardGame) {
10008         /* Skip any header junk before position diagram and/or move 1 */
10009         for (;;) {
10010             yyboardindex = forwardMostMove;
10011             cm = (ChessMove) yylex();
10012
10013             if (cm == (ChessMove) 0 ||
10014                 cm == GNUChessGame || cm == XBoardGame) {
10015                 /* Empty game; pretend end-of-file and handle later */
10016                 cm = (ChessMove) 0;
10017                 break;
10018             }
10019
10020             if (cm == MoveNumberOne || cm == PositionDiagram ||
10021                 cm == PGNTag || cm == Comment)
10022               break;
10023         }
10024     } else if (cm == GNUChessGame) {
10025         if (gameInfo.event != NULL) {
10026             free(gameInfo.event);
10027         }
10028         gameInfo.event = StrSave(yy_text);
10029     }
10030
10031     startedFromSetupPosition = FALSE;
10032     while (cm == PGNTag) {
10033         if (appData.debugMode)
10034           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10035         err = ParsePGNTag(yy_text, &gameInfo);
10036         if (!err) numPGNTags++;
10037
10038         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10039         if(gameInfo.variant != oldVariant) {
10040             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10041             InitPosition(TRUE);
10042             oldVariant = gameInfo.variant;
10043             if (appData.debugMode)
10044               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10045         }
10046
10047
10048         if (gameInfo.fen != NULL) {
10049           Board initial_position;
10050           startedFromSetupPosition = TRUE;
10051           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10052             Reset(TRUE, TRUE);
10053             DisplayError(_("Bad FEN position in file"), 0);
10054             return FALSE;
10055           }
10056           CopyBoard(boards[0], initial_position);
10057           if (blackPlaysFirst) {
10058             currentMove = forwardMostMove = backwardMostMove = 1;
10059             CopyBoard(boards[1], initial_position);
10060             strcpy(moveList[0], "");
10061             strcpy(parseList[0], "");
10062             timeRemaining[0][1] = whiteTimeRemaining;
10063             timeRemaining[1][1] = blackTimeRemaining;
10064             if (commentList[0] != NULL) {
10065               commentList[1] = commentList[0];
10066               commentList[0] = NULL;
10067             }
10068           } else {
10069             currentMove = forwardMostMove = backwardMostMove = 0;
10070           }
10071           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10072           {   int i;
10073               initialRulePlies = FENrulePlies;
10074               for( i=0; i< nrCastlingRights; i++ )
10075                   initialRights[i] = initial_position[CASTLING][i];
10076           }
10077           yyboardindex = forwardMostMove;
10078           free(gameInfo.fen);
10079           gameInfo.fen = NULL;
10080         }
10081
10082         yyboardindex = forwardMostMove;
10083         cm = (ChessMove) yylex();
10084
10085         /* Handle comments interspersed among the tags */
10086         while (cm == Comment) {
10087             char *p;
10088             if (appData.debugMode)
10089               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10090             p = yy_text;
10091             AppendComment(currentMove, p, FALSE);
10092             yyboardindex = forwardMostMove;
10093             cm = (ChessMove) yylex();
10094         }
10095     }
10096
10097     /* don't rely on existence of Event tag since if game was
10098      * pasted from clipboard the Event tag may not exist
10099      */
10100     if (numPGNTags > 0){
10101         char *tags;
10102         if (gameInfo.variant == VariantNormal) {
10103           gameInfo.variant = StringToVariant(gameInfo.event);
10104         }
10105         if (!matchMode) {
10106           if( appData.autoDisplayTags ) {
10107             tags = PGNTags(&gameInfo);
10108             TagsPopUp(tags, CmailMsg());
10109             free(tags);
10110           }
10111         }
10112     } else {
10113         /* Make something up, but don't display it now */
10114         SetGameInfo();
10115         TagsPopDown();
10116     }
10117
10118     if (cm == PositionDiagram) {
10119         int i, j;
10120         char *p;
10121         Board initial_position;
10122
10123         if (appData.debugMode)
10124           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10125
10126         if (!startedFromSetupPosition) {
10127             p = yy_text;
10128             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10129               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10130                 switch (*p) {
10131                   case '[':
10132                   case '-':
10133                   case ' ':
10134                   case '\t':
10135                   case '\n':
10136                   case '\r':
10137                     break;
10138                   default:
10139                     initial_position[i][j++] = CharToPiece(*p);
10140                     break;
10141                 }
10142             while (*p == ' ' || *p == '\t' ||
10143                    *p == '\n' || *p == '\r') p++;
10144
10145             if (strncmp(p, "black", strlen("black"))==0)
10146               blackPlaysFirst = TRUE;
10147             else
10148               blackPlaysFirst = FALSE;
10149             startedFromSetupPosition = TRUE;
10150
10151             CopyBoard(boards[0], initial_position);
10152             if (blackPlaysFirst) {
10153                 currentMove = forwardMostMove = backwardMostMove = 1;
10154                 CopyBoard(boards[1], initial_position);
10155                 strcpy(moveList[0], "");
10156                 strcpy(parseList[0], "");
10157                 timeRemaining[0][1] = whiteTimeRemaining;
10158                 timeRemaining[1][1] = blackTimeRemaining;
10159                 if (commentList[0] != NULL) {
10160                     commentList[1] = commentList[0];
10161                     commentList[0] = NULL;
10162                 }
10163             } else {
10164                 currentMove = forwardMostMove = backwardMostMove = 0;
10165             }
10166         }
10167         yyboardindex = forwardMostMove;
10168         cm = (ChessMove) yylex();
10169     }
10170
10171     if (first.pr == NoProc) {
10172         StartChessProgram(&first);
10173     }
10174     InitChessProgram(&first, FALSE);
10175     SendToProgram("force\n", &first);
10176     if (startedFromSetupPosition) {
10177         SendBoard(&first, forwardMostMove);
10178     if (appData.debugMode) {
10179         fprintf(debugFP, "Load Game\n");
10180     }
10181         DisplayBothClocks();
10182     }
10183
10184     /* [HGM] server: flag to write setup moves in broadcast file as one */
10185     loadFlag = appData.suppressLoadMoves;
10186
10187     while (cm == Comment) {
10188         char *p;
10189         if (appData.debugMode)
10190           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10191         p = yy_text;
10192         AppendComment(currentMove, p, FALSE);
10193         yyboardindex = forwardMostMove;
10194         cm = (ChessMove) yylex();
10195     }
10196
10197     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10198         cm == WhiteWins || cm == BlackWins ||
10199         cm == GameIsDrawn || cm == GameUnfinished) {
10200         DisplayMessage("", _("No moves in game"));
10201         if (cmailMsgLoaded) {
10202             if (appData.debugMode)
10203               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10204             ClearHighlights();
10205             flipView = FALSE;
10206         }
10207         DrawPosition(FALSE, boards[currentMove]);
10208         DisplayBothClocks();
10209         gameMode = EditGame;
10210         ModeHighlight();
10211         gameFileFP = NULL;
10212         cmailOldMove = 0;
10213         return TRUE;
10214     }
10215
10216     // [HGM] PV info: routine tests if comment empty
10217     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10218         DisplayComment(currentMove - 1, commentList[currentMove]);
10219     }
10220     if (!matchMode && appData.timeDelay != 0)
10221       DrawPosition(FALSE, boards[currentMove]);
10222
10223     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10224       programStats.ok_to_send = 1;
10225     }
10226
10227     /* if the first token after the PGN tags is a move
10228      * and not move number 1, retrieve it from the parser
10229      */
10230     if (cm != MoveNumberOne)
10231         LoadGameOneMove(cm);
10232
10233     /* load the remaining moves from the file */
10234     while (LoadGameOneMove((ChessMove)0)) {
10235       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10236       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10237     }
10238
10239     /* rewind to the start of the game */
10240     currentMove = backwardMostMove;
10241
10242     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10243
10244     if (oldGameMode == AnalyzeFile ||
10245         oldGameMode == AnalyzeMode) {
10246       AnalyzeFileEvent();
10247     }
10248
10249     if (matchMode || appData.timeDelay == 0) {
10250       ToEndEvent();
10251       gameMode = EditGame;
10252       ModeHighlight();
10253     } else if (appData.timeDelay > 0) {
10254       AutoPlayGameLoop();
10255     }
10256
10257     if (appData.debugMode)
10258         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10259
10260     loadFlag = 0; /* [HGM] true game starts */
10261     return TRUE;
10262 }
10263
10264 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10265 int
10266 ReloadPosition(offset)
10267      int offset;
10268 {
10269     int positionNumber = lastLoadPositionNumber + offset;
10270     if (lastLoadPositionFP == NULL) {
10271         DisplayError(_("No position has been loaded yet"), 0);
10272         return FALSE;
10273     }
10274     if (positionNumber <= 0) {
10275         DisplayError(_("Can't back up any further"), 0);
10276         return FALSE;
10277     }
10278     return LoadPosition(lastLoadPositionFP, positionNumber,
10279                         lastLoadPositionTitle);
10280 }
10281
10282 /* Load the nth position from the given file */
10283 int
10284 LoadPositionFromFile(filename, n, title)
10285      char *filename;
10286      int n;
10287      char *title;
10288 {
10289     FILE *f;
10290     char buf[MSG_SIZ];
10291
10292     if (strcmp(filename, "-") == 0) {
10293         return LoadPosition(stdin, n, "stdin");
10294     } else {
10295         f = fopen(filename, "rb");
10296         if (f == NULL) {
10297             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10298             DisplayError(buf, errno);
10299             return FALSE;
10300         } else {
10301             return LoadPosition(f, n, title);
10302         }
10303     }
10304 }
10305
10306 /* Load the nth position from the given open file, and close it */
10307 int
10308 LoadPosition(f, positionNumber, title)
10309      FILE *f;
10310      int positionNumber;
10311      char *title;
10312 {
10313     char *p, line[MSG_SIZ];
10314     Board initial_position;
10315     int i, j, fenMode, pn;
10316
10317     if (gameMode == Training )
10318         SetTrainingModeOff();
10319
10320     if (gameMode != BeginningOfGame) {
10321         Reset(FALSE, TRUE);
10322     }
10323     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10324         fclose(lastLoadPositionFP);
10325     }
10326     if (positionNumber == 0) positionNumber = 1;
10327     lastLoadPositionFP = f;
10328     lastLoadPositionNumber = positionNumber;
10329     strcpy(lastLoadPositionTitle, title);
10330     if (first.pr == NoProc) {
10331       StartChessProgram(&first);
10332       InitChessProgram(&first, FALSE);
10333     }
10334     pn = positionNumber;
10335     if (positionNumber < 0) {
10336         /* Negative position number means to seek to that byte offset */
10337         if (fseek(f, -positionNumber, 0) == -1) {
10338             DisplayError(_("Can't seek on position file"), 0);
10339             return FALSE;
10340         };
10341         pn = 1;
10342     } else {
10343         if (fseek(f, 0, 0) == -1) {
10344             if (f == lastLoadPositionFP ?
10345                 positionNumber == lastLoadPositionNumber + 1 :
10346                 positionNumber == 1) {
10347                 pn = 1;
10348             } else {
10349                 DisplayError(_("Can't seek on position file"), 0);
10350                 return FALSE;
10351             }
10352         }
10353     }
10354     /* See if this file is FEN or old-style xboard */
10355     if (fgets(line, MSG_SIZ, f) == NULL) {
10356         DisplayError(_("Position not found in file"), 0);
10357         return FALSE;
10358     }
10359     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10360     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10361
10362     if (pn >= 2) {
10363         if (fenMode || line[0] == '#') pn--;
10364         while (pn > 0) {
10365             /* skip positions before number pn */
10366             if (fgets(line, MSG_SIZ, f) == NULL) {
10367                 Reset(TRUE, TRUE);
10368                 DisplayError(_("Position not found in file"), 0);
10369                 return FALSE;
10370             }
10371             if (fenMode || line[0] == '#') pn--;
10372         }
10373     }
10374
10375     if (fenMode) {
10376         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10377             DisplayError(_("Bad FEN position in file"), 0);
10378             return FALSE;
10379         }
10380     } else {
10381         (void) fgets(line, MSG_SIZ, f);
10382         (void) fgets(line, MSG_SIZ, f);
10383
10384         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10385             (void) fgets(line, MSG_SIZ, f);
10386             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10387                 if (*p == ' ')
10388                   continue;
10389                 initial_position[i][j++] = CharToPiece(*p);
10390             }
10391         }
10392
10393         blackPlaysFirst = FALSE;
10394         if (!feof(f)) {
10395             (void) fgets(line, MSG_SIZ, f);
10396             if (strncmp(line, "black", strlen("black"))==0)
10397               blackPlaysFirst = TRUE;
10398         }
10399     }
10400     startedFromSetupPosition = TRUE;
10401
10402     SendToProgram("force\n", &first);
10403     CopyBoard(boards[0], initial_position);
10404     if (blackPlaysFirst) {
10405         currentMove = forwardMostMove = backwardMostMove = 1;
10406         strcpy(moveList[0], "");
10407         strcpy(parseList[0], "");
10408         CopyBoard(boards[1], initial_position);
10409         DisplayMessage("", _("Black to play"));
10410     } else {
10411         currentMove = forwardMostMove = backwardMostMove = 0;
10412         DisplayMessage("", _("White to play"));
10413     }
10414     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10415     SendBoard(&first, forwardMostMove);
10416     if (appData.debugMode) {
10417 int i, j;
10418   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10419   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10420         fprintf(debugFP, "Load Position\n");
10421     }
10422
10423     if (positionNumber > 1) {
10424         sprintf(line, "%s %d", title, positionNumber);
10425         DisplayTitle(line);
10426     } else {
10427         DisplayTitle(title);
10428     }
10429     gameMode = EditGame;
10430     ModeHighlight();
10431     ResetClocks();
10432     timeRemaining[0][1] = whiteTimeRemaining;
10433     timeRemaining[1][1] = blackTimeRemaining;
10434     DrawPosition(FALSE, boards[currentMove]);
10435
10436     return TRUE;
10437 }
10438
10439
10440 void
10441 CopyPlayerNameIntoFileName(dest, src)
10442      char **dest, *src;
10443 {
10444     while (*src != NULLCHAR && *src != ',') {
10445         if (*src == ' ') {
10446             *(*dest)++ = '_';
10447             src++;
10448         } else {
10449             *(*dest)++ = *src++;
10450         }
10451     }
10452 }
10453
10454 char *DefaultFileName(ext)
10455      char *ext;
10456 {
10457     static char def[MSG_SIZ];
10458     char *p;
10459
10460     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10461         p = def;
10462         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10463         *p++ = '-';
10464         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10465         *p++ = '.';
10466         strcpy(p, ext);
10467     } else {
10468         def[0] = NULLCHAR;
10469     }
10470     return def;
10471 }
10472
10473 /* Save the current game to the given file */
10474 int
10475 SaveGameToFile(filename, append)
10476      char *filename;
10477      int append;
10478 {
10479     FILE *f;
10480     char buf[MSG_SIZ];
10481
10482     if (strcmp(filename, "-") == 0) {
10483         return SaveGame(stdout, 0, NULL);
10484     } else {
10485         f = fopen(filename, append ? "a" : "w");
10486         if (f == NULL) {
10487             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10488             DisplayError(buf, errno);
10489             return FALSE;
10490         } else {
10491             return SaveGame(f, 0, NULL);
10492         }
10493     }
10494 }
10495
10496 char *
10497 SavePart(str)
10498      char *str;
10499 {
10500     static char buf[MSG_SIZ];
10501     char *p;
10502
10503     p = strchr(str, ' ');
10504     if (p == NULL) return str;
10505     strncpy(buf, str, p - str);
10506     buf[p - str] = NULLCHAR;
10507     return buf;
10508 }
10509
10510 #define PGN_MAX_LINE 75
10511
10512 #define PGN_SIDE_WHITE  0
10513 #define PGN_SIDE_BLACK  1
10514
10515 /* [AS] */
10516 static int FindFirstMoveOutOfBook( int side )
10517 {
10518     int result = -1;
10519
10520     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10521         int index = backwardMostMove;
10522         int has_book_hit = 0;
10523
10524         if( (index % 2) != side ) {
10525             index++;
10526         }
10527
10528         while( index < forwardMostMove ) {
10529             /* Check to see if engine is in book */
10530             int depth = pvInfoList[index].depth;
10531             int score = pvInfoList[index].score;
10532             int in_book = 0;
10533
10534             if( depth <= 2 ) {
10535                 in_book = 1;
10536             }
10537             else if( score == 0 && depth == 63 ) {
10538                 in_book = 1; /* Zappa */
10539             }
10540             else if( score == 2 && depth == 99 ) {
10541                 in_book = 1; /* Abrok */
10542             }
10543
10544             has_book_hit += in_book;
10545
10546             if( ! in_book ) {
10547                 result = index;
10548
10549                 break;
10550             }
10551
10552             index += 2;
10553         }
10554     }
10555
10556     return result;
10557 }
10558
10559 /* [AS] */
10560 void GetOutOfBookInfo( char * buf )
10561 {
10562     int oob[2];
10563     int i;
10564     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10565
10566     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10567     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10568
10569     *buf = '\0';
10570
10571     if( oob[0] >= 0 || oob[1] >= 0 ) {
10572         for( i=0; i<2; i++ ) {
10573             int idx = oob[i];
10574
10575             if( idx >= 0 ) {
10576                 if( i > 0 && oob[0] >= 0 ) {
10577                     strcat( buf, "   " );
10578                 }
10579
10580                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10581                 sprintf( buf+strlen(buf), "%s%.2f",
10582                     pvInfoList[idx].score >= 0 ? "+" : "",
10583                     pvInfoList[idx].score / 100.0 );
10584             }
10585         }
10586     }
10587 }
10588
10589 /* Save game in PGN style and close the file */
10590 int
10591 SaveGamePGN(f)
10592      FILE *f;
10593 {
10594     int i, offset, linelen, newblock;
10595     time_t tm;
10596 //    char *movetext;
10597     char numtext[32];
10598     int movelen, numlen, blank;
10599     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10600
10601     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10602
10603     tm = time((time_t *) NULL);
10604
10605     PrintPGNTags(f, &gameInfo);
10606
10607     if (backwardMostMove > 0 || startedFromSetupPosition) {
10608         char *fen = PositionToFEN(backwardMostMove, NULL);
10609         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10610         fprintf(f, "\n{--------------\n");
10611         PrintPosition(f, backwardMostMove);
10612         fprintf(f, "--------------}\n");
10613         free(fen);
10614     }
10615     else {
10616         /* [AS] Out of book annotation */
10617         if( appData.saveOutOfBookInfo ) {
10618             char buf[64];
10619
10620             GetOutOfBookInfo( buf );
10621
10622             if( buf[0] != '\0' ) {
10623                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10624             }
10625         }
10626
10627         fprintf(f, "\n");
10628     }
10629
10630     i = backwardMostMove;
10631     linelen = 0;
10632     newblock = TRUE;
10633
10634     while (i < forwardMostMove) {
10635         /* Print comments preceding this move */
10636         if (commentList[i] != NULL) {
10637             if (linelen > 0) fprintf(f, "\n");
10638             fprintf(f, "%s", commentList[i]);
10639             linelen = 0;
10640             newblock = TRUE;
10641         }
10642
10643         /* Format move number */
10644         if ((i % 2) == 0) {
10645             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10646         } else {
10647             if (newblock) {
10648                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10649             } else {
10650                 numtext[0] = NULLCHAR;
10651             }
10652         }
10653         numlen = strlen(numtext);
10654         newblock = FALSE;
10655
10656         /* Print move number */
10657         blank = linelen > 0 && numlen > 0;
10658         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10659             fprintf(f, "\n");
10660             linelen = 0;
10661             blank = 0;
10662         }
10663         if (blank) {
10664             fprintf(f, " ");
10665             linelen++;
10666         }
10667         fprintf(f, "%s", numtext);
10668         linelen += numlen;
10669
10670         /* Get move */
10671         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10672         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10673
10674         /* Print move */
10675         blank = linelen > 0 && movelen > 0;
10676         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10677             fprintf(f, "\n");
10678             linelen = 0;
10679             blank = 0;
10680         }
10681         if (blank) {
10682             fprintf(f, " ");
10683             linelen++;
10684         }
10685         fprintf(f, "%s", move_buffer);
10686         linelen += movelen;
10687
10688         /* [AS] Add PV info if present */
10689         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10690             /* [HGM] add time */
10691             char buf[MSG_SIZ]; int seconds;
10692
10693             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10694
10695             if( seconds <= 0) buf[0] = 0; else
10696             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10697                 seconds = (seconds + 4)/10; // round to full seconds
10698                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10699                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10700             }
10701
10702             sprintf( move_buffer, "{%s%.2f/%d%s}",
10703                 pvInfoList[i].score >= 0 ? "+" : "",
10704                 pvInfoList[i].score / 100.0,
10705                 pvInfoList[i].depth,
10706                 buf );
10707
10708             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10709
10710             /* Print score/depth */
10711             blank = linelen > 0 && movelen > 0;
10712             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10713                 fprintf(f, "\n");
10714                 linelen = 0;
10715                 blank = 0;
10716             }
10717             if (blank) {
10718                 fprintf(f, " ");
10719                 linelen++;
10720             }
10721             fprintf(f, "%s", move_buffer);
10722             linelen += movelen;
10723         }
10724
10725         i++;
10726     }
10727
10728     /* Start a new line */
10729     if (linelen > 0) fprintf(f, "\n");
10730
10731     /* Print comments after last move */
10732     if (commentList[i] != NULL) {
10733         fprintf(f, "%s\n", commentList[i]);
10734     }
10735
10736     /* Print result */
10737     if (gameInfo.resultDetails != NULL &&
10738         gameInfo.resultDetails[0] != NULLCHAR) {
10739         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10740                 PGNResult(gameInfo.result));
10741     } else {
10742         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10743     }
10744
10745     fclose(f);
10746     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10747     return TRUE;
10748 }
10749
10750 /* Save game in old style and close the file */
10751 int
10752 SaveGameOldStyle(f)
10753      FILE *f;
10754 {
10755     int i, offset;
10756     time_t tm;
10757
10758     tm = time((time_t *) NULL);
10759
10760     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10761     PrintOpponents(f);
10762
10763     if (backwardMostMove > 0 || startedFromSetupPosition) {
10764         fprintf(f, "\n[--------------\n");
10765         PrintPosition(f, backwardMostMove);
10766         fprintf(f, "--------------]\n");
10767     } else {
10768         fprintf(f, "\n");
10769     }
10770
10771     i = backwardMostMove;
10772     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10773
10774     while (i < forwardMostMove) {
10775         if (commentList[i] != NULL) {
10776             fprintf(f, "[%s]\n", commentList[i]);
10777         }
10778
10779         if ((i % 2) == 1) {
10780             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10781             i++;
10782         } else {
10783             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10784             i++;
10785             if (commentList[i] != NULL) {
10786                 fprintf(f, "\n");
10787                 continue;
10788             }
10789             if (i >= forwardMostMove) {
10790                 fprintf(f, "\n");
10791                 break;
10792             }
10793             fprintf(f, "%s\n", parseList[i]);
10794             i++;
10795         }
10796     }
10797
10798     if (commentList[i] != NULL) {
10799         fprintf(f, "[%s]\n", commentList[i]);
10800     }
10801
10802     /* This isn't really the old style, but it's close enough */
10803     if (gameInfo.resultDetails != NULL &&
10804         gameInfo.resultDetails[0] != NULLCHAR) {
10805         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10806                 gameInfo.resultDetails);
10807     } else {
10808         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10809     }
10810
10811     fclose(f);
10812     return TRUE;
10813 }
10814
10815 /* Save the current game to open file f and close the file */
10816 int
10817 SaveGame(f, dummy, dummy2)
10818      FILE *f;
10819      int dummy;
10820      char *dummy2;
10821 {
10822     if (gameMode == EditPosition) EditPositionDone(TRUE);
10823     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10824     if (appData.oldSaveStyle)
10825       return SaveGameOldStyle(f);
10826     else
10827       return SaveGamePGN(f);
10828 }
10829
10830 /* Save the current position to the given file */
10831 int
10832 SavePositionToFile(filename)
10833      char *filename;
10834 {
10835     FILE *f;
10836     char buf[MSG_SIZ];
10837
10838     if (strcmp(filename, "-") == 0) {
10839         return SavePosition(stdout, 0, NULL);
10840     } else {
10841         f = fopen(filename, "a");
10842         if (f == NULL) {
10843             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10844             DisplayError(buf, errno);
10845             return FALSE;
10846         } else {
10847             SavePosition(f, 0, NULL);
10848             return TRUE;
10849         }
10850     }
10851 }
10852
10853 /* Save the current position to the given open file and close the file */
10854 int
10855 SavePosition(f, dummy, dummy2)
10856      FILE *f;
10857      int dummy;
10858      char *dummy2;
10859 {
10860     time_t tm;
10861     char *fen;
10862     if (gameMode == EditPosition) EditPositionDone(TRUE);
10863     if (appData.oldSaveStyle) {
10864         tm = time((time_t *) NULL);
10865
10866         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10867         PrintOpponents(f);
10868         fprintf(f, "[--------------\n");
10869         PrintPosition(f, currentMove);
10870         fprintf(f, "--------------]\n");
10871     } else {
10872         fen = PositionToFEN(currentMove, NULL);
10873         fprintf(f, "%s\n", fen);
10874         free(fen);
10875     }
10876     fclose(f);
10877     return TRUE;
10878 }
10879
10880 void
10881 ReloadCmailMsgEvent(unregister)
10882      int unregister;
10883 {
10884 #if !WIN32
10885     static char *inFilename = NULL;
10886     static char *outFilename;
10887     int i;
10888     struct stat inbuf, outbuf;
10889     int status;
10890
10891     /* Any registered moves are unregistered if unregister is set, */
10892     /* i.e. invoked by the signal handler */
10893     if (unregister) {
10894         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10895             cmailMoveRegistered[i] = FALSE;
10896             if (cmailCommentList[i] != NULL) {
10897                 free(cmailCommentList[i]);
10898                 cmailCommentList[i] = NULL;
10899             }
10900         }
10901         nCmailMovesRegistered = 0;
10902     }
10903
10904     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10905         cmailResult[i] = CMAIL_NOT_RESULT;
10906     }
10907     nCmailResults = 0;
10908
10909     if (inFilename == NULL) {
10910         /* Because the filenames are static they only get malloced once  */
10911         /* and they never get freed                                      */
10912         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10913         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10914
10915         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10916         sprintf(outFilename, "%s.out", appData.cmailGameName);
10917     }
10918
10919     status = stat(outFilename, &outbuf);
10920     if (status < 0) {
10921         cmailMailedMove = FALSE;
10922     } else {
10923         status = stat(inFilename, &inbuf);
10924         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10925     }
10926
10927     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10928        counts the games, notes how each one terminated, etc.
10929
10930        It would be nice to remove this kludge and instead gather all
10931        the information while building the game list.  (And to keep it
10932        in the game list nodes instead of having a bunch of fixed-size
10933        parallel arrays.)  Note this will require getting each game's
10934        termination from the PGN tags, as the game list builder does
10935        not process the game moves.  --mann
10936        */
10937     cmailMsgLoaded = TRUE;
10938     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10939
10940     /* Load first game in the file or popup game menu */
10941     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10942
10943 #endif /* !WIN32 */
10944     return;
10945 }
10946
10947 int
10948 RegisterMove()
10949 {
10950     FILE *f;
10951     char string[MSG_SIZ];
10952
10953     if (   cmailMailedMove
10954         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10955         return TRUE;            /* Allow free viewing  */
10956     }
10957
10958     /* Unregister move to ensure that we don't leave RegisterMove        */
10959     /* with the move registered when the conditions for registering no   */
10960     /* longer hold                                                       */
10961     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10962         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10963         nCmailMovesRegistered --;
10964
10965         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10966           {
10967               free(cmailCommentList[lastLoadGameNumber - 1]);
10968               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10969           }
10970     }
10971
10972     if (cmailOldMove == -1) {
10973         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10974         return FALSE;
10975     }
10976
10977     if (currentMove > cmailOldMove + 1) {
10978         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10979         return FALSE;
10980     }
10981
10982     if (currentMove < cmailOldMove) {
10983         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10984         return FALSE;
10985     }
10986
10987     if (forwardMostMove > currentMove) {
10988         /* Silently truncate extra moves */
10989         TruncateGame();
10990     }
10991
10992     if (   (currentMove == cmailOldMove + 1)
10993         || (   (currentMove == cmailOldMove)
10994             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10995                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10996         if (gameInfo.result != GameUnfinished) {
10997             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10998         }
10999
11000         if (commentList[currentMove] != NULL) {
11001             cmailCommentList[lastLoadGameNumber - 1]
11002               = StrSave(commentList[currentMove]);
11003         }
11004         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11005
11006         if (appData.debugMode)
11007           fprintf(debugFP, "Saving %s for game %d\n",
11008                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11009
11010         sprintf(string,
11011                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11012
11013         f = fopen(string, "w");
11014         if (appData.oldSaveStyle) {
11015             SaveGameOldStyle(f); /* also closes the file */
11016
11017             sprintf(string, "%s.pos.out", appData.cmailGameName);
11018             f = fopen(string, "w");
11019             SavePosition(f, 0, NULL); /* also closes the file */
11020         } else {
11021             fprintf(f, "{--------------\n");
11022             PrintPosition(f, currentMove);
11023             fprintf(f, "--------------}\n\n");
11024
11025             SaveGame(f, 0, NULL); /* also closes the file*/
11026         }
11027
11028         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11029         nCmailMovesRegistered ++;
11030     } else if (nCmailGames == 1) {
11031         DisplayError(_("You have not made a move yet"), 0);
11032         return FALSE;
11033     }
11034
11035     return TRUE;
11036 }
11037
11038 void
11039 MailMoveEvent()
11040 {
11041 #if !WIN32
11042     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11043     FILE *commandOutput;
11044     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11045     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11046     int nBuffers;
11047     int i;
11048     int archived;
11049     char *arcDir;
11050
11051     if (! cmailMsgLoaded) {
11052         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11053         return;
11054     }
11055
11056     if (nCmailGames == nCmailResults) {
11057         DisplayError(_("No unfinished games"), 0);
11058         return;
11059     }
11060
11061 #if CMAIL_PROHIBIT_REMAIL
11062     if (cmailMailedMove) {
11063         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);
11064         DisplayError(msg, 0);
11065         return;
11066     }
11067 #endif
11068
11069     if (! (cmailMailedMove || RegisterMove())) return;
11070
11071     if (   cmailMailedMove
11072         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11073         sprintf(string, partCommandString,
11074                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11075         commandOutput = popen(string, "r");
11076
11077         if (commandOutput == NULL) {
11078             DisplayError(_("Failed to invoke cmail"), 0);
11079         } else {
11080             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11081                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11082             }
11083             if (nBuffers > 1) {
11084                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11085                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11086                 nBytes = MSG_SIZ - 1;
11087             } else {
11088                 (void) memcpy(msg, buffer, nBytes);
11089             }
11090             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11091
11092             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11093                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11094
11095                 archived = TRUE;
11096                 for (i = 0; i < nCmailGames; i ++) {
11097                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11098                         archived = FALSE;
11099                     }
11100                 }
11101                 if (   archived
11102                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11103                         != NULL)) {
11104                     sprintf(buffer, "%s/%s.%s.archive",
11105                             arcDir,
11106                             appData.cmailGameName,
11107                             gameInfo.date);
11108                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11109                     cmailMsgLoaded = FALSE;
11110                 }
11111             }
11112
11113             DisplayInformation(msg);
11114             pclose(commandOutput);
11115         }
11116     } else {
11117         if ((*cmailMsg) != '\0') {
11118             DisplayInformation(cmailMsg);
11119         }
11120     }
11121
11122     return;
11123 #endif /* !WIN32 */
11124 }
11125
11126 char *
11127 CmailMsg()
11128 {
11129 #if WIN32
11130     return NULL;
11131 #else
11132     int  prependComma = 0;
11133     char number[5];
11134     char string[MSG_SIZ];       /* Space for game-list */
11135     int  i;
11136
11137     if (!cmailMsgLoaded) return "";
11138
11139     if (cmailMailedMove) {
11140         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11141     } else {
11142         /* Create a list of games left */
11143         sprintf(string, "[");
11144         for (i = 0; i < nCmailGames; i ++) {
11145             if (! (   cmailMoveRegistered[i]
11146                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11147                 if (prependComma) {
11148                     sprintf(number, ",%d", i + 1);
11149                 } else {
11150                     sprintf(number, "%d", i + 1);
11151                     prependComma = 1;
11152                 }
11153
11154                 strcat(string, number);
11155             }
11156         }
11157         strcat(string, "]");
11158
11159         if (nCmailMovesRegistered + nCmailResults == 0) {
11160             switch (nCmailGames) {
11161               case 1:
11162                 sprintf(cmailMsg,
11163                         _("Still need to make move for game\n"));
11164                 break;
11165
11166               case 2:
11167                 sprintf(cmailMsg,
11168                         _("Still need to make moves for both games\n"));
11169                 break;
11170
11171               default:
11172                 sprintf(cmailMsg,
11173                         _("Still need to make moves for all %d games\n"),
11174                         nCmailGames);
11175                 break;
11176             }
11177         } else {
11178             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11179               case 1:
11180                 sprintf(cmailMsg,
11181                         _("Still need to make a move for game %s\n"),
11182                         string);
11183                 break;
11184
11185               case 0:
11186                 if (nCmailResults == nCmailGames) {
11187                     sprintf(cmailMsg, _("No unfinished games\n"));
11188                 } else {
11189                     sprintf(cmailMsg, _("Ready to send mail\n"));
11190                 }
11191                 break;
11192
11193               default:
11194                 sprintf(cmailMsg,
11195                         _("Still need to make moves for games %s\n"),
11196                         string);
11197             }
11198         }
11199     }
11200     return cmailMsg;
11201 #endif /* WIN32 */
11202 }
11203
11204 void
11205 ResetGameEvent()
11206 {
11207     if (gameMode == Training)
11208       SetTrainingModeOff();
11209
11210     Reset(TRUE, TRUE);
11211     cmailMsgLoaded = FALSE;
11212     if (appData.icsActive) {
11213       SendToICS(ics_prefix);
11214       SendToICS("refresh\n");
11215     }
11216 }
11217
11218 void
11219 ExitEvent(status)
11220      int status;
11221 {
11222     exiting++;
11223     if (exiting > 2) {
11224       /* Give up on clean exit */
11225       exit(status);
11226     }
11227     if (exiting > 1) {
11228       /* Keep trying for clean exit */
11229       return;
11230     }
11231
11232     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11233
11234     if (telnetISR != NULL) {
11235       RemoveInputSource(telnetISR);
11236     }
11237     if (icsPR != NoProc) {
11238       DestroyChildProcess(icsPR, TRUE);
11239     }
11240
11241     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11242     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11243
11244     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11245     /* make sure this other one finishes before killing it!                  */
11246     if(endingGame) { int count = 0;
11247         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11248         while(endingGame && count++ < 10) DoSleep(1);
11249         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11250     }
11251
11252     /* Kill off chess programs */
11253     if (first.pr != NoProc) {
11254         ExitAnalyzeMode();
11255
11256         DoSleep( appData.delayBeforeQuit );
11257         SendToProgram("quit\n", &first);
11258         DoSleep( appData.delayAfterQuit );
11259         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11260     }
11261     if (second.pr != NoProc) {
11262         DoSleep( appData.delayBeforeQuit );
11263         SendToProgram("quit\n", &second);
11264         DoSleep( appData.delayAfterQuit );
11265         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11266     }
11267     if (first.isr != NULL) {
11268         RemoveInputSource(first.isr);
11269     }
11270     if (second.isr != NULL) {
11271         RemoveInputSource(second.isr);
11272     }
11273
11274     ShutDownFrontEnd();
11275     exit(status);
11276 }
11277
11278 void
11279 PauseEvent()
11280 {
11281     if (appData.debugMode)
11282         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11283     if (pausing) {
11284         pausing = FALSE;
11285         ModeHighlight();
11286         if (gameMode == MachinePlaysWhite ||
11287             gameMode == MachinePlaysBlack) {
11288             StartClocks();
11289         } else {
11290             DisplayBothClocks();
11291         }
11292         if (gameMode == PlayFromGameFile) {
11293             if (appData.timeDelay >= 0)
11294                 AutoPlayGameLoop();
11295         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11296             Reset(FALSE, TRUE);
11297             SendToICS(ics_prefix);
11298             SendToICS("refresh\n");
11299         } else if (currentMove < forwardMostMove) {
11300             ForwardInner(forwardMostMove);
11301         }
11302         pauseExamInvalid = FALSE;
11303     } else {
11304         switch (gameMode) {
11305           default:
11306             return;
11307           case IcsExamining:
11308             pauseExamForwardMostMove = forwardMostMove;
11309             pauseExamInvalid = FALSE;
11310             /* fall through */
11311           case IcsObserving:
11312           case IcsPlayingWhite:
11313           case IcsPlayingBlack:
11314             pausing = TRUE;
11315             ModeHighlight();
11316             return;
11317           case PlayFromGameFile:
11318             (void) StopLoadGameTimer();
11319             pausing = TRUE;
11320             ModeHighlight();
11321             break;
11322           case BeginningOfGame:
11323             if (appData.icsActive) return;
11324             /* else fall through */
11325           case MachinePlaysWhite:
11326           case MachinePlaysBlack:
11327           case TwoMachinesPlay:
11328             if (forwardMostMove == 0)
11329               return;           /* don't pause if no one has moved */
11330             if ((gameMode == MachinePlaysWhite &&
11331                  !WhiteOnMove(forwardMostMove)) ||
11332                 (gameMode == MachinePlaysBlack &&
11333                  WhiteOnMove(forwardMostMove))) {
11334                 StopClocks();
11335             }
11336             pausing = TRUE;
11337             ModeHighlight();
11338             break;
11339         }
11340     }
11341 }
11342
11343 void
11344 EditCommentEvent()
11345 {
11346     char title[MSG_SIZ];
11347
11348     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11349         strcpy(title, _("Edit comment"));
11350     } else {
11351         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11352                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11353                 parseList[currentMove - 1]);
11354     }
11355
11356     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11357 }
11358
11359
11360 void
11361 EditTagsEvent()
11362 {
11363     char *tags = PGNTags(&gameInfo);
11364     EditTagsPopUp(tags);
11365     free(tags);
11366 }
11367
11368 void
11369 AnalyzeModeEvent()
11370 {
11371     if (appData.noChessProgram || gameMode == AnalyzeMode)
11372       return;
11373
11374     if (gameMode != AnalyzeFile) {
11375         if (!appData.icsEngineAnalyze) {
11376                EditGameEvent();
11377                if (gameMode != EditGame) return;
11378         }
11379         ResurrectChessProgram();
11380         SendToProgram("analyze\n", &first);
11381         first.analyzing = TRUE;
11382         /*first.maybeThinking = TRUE;*/
11383         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11384         EngineOutputPopUp();
11385     }
11386     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11387     pausing = FALSE;
11388     ModeHighlight();
11389     SetGameInfo();
11390
11391     StartAnalysisClock();
11392     GetTimeMark(&lastNodeCountTime);
11393     lastNodeCount = 0;
11394 }
11395
11396 void
11397 AnalyzeFileEvent()
11398 {
11399     if (appData.noChessProgram || gameMode == AnalyzeFile)
11400       return;
11401
11402     if (gameMode != AnalyzeMode) {
11403         EditGameEvent();
11404         if (gameMode != EditGame) return;
11405         ResurrectChessProgram();
11406         SendToProgram("analyze\n", &first);
11407         first.analyzing = TRUE;
11408         /*first.maybeThinking = TRUE;*/
11409         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11410         EngineOutputPopUp();
11411     }
11412     gameMode = AnalyzeFile;
11413     pausing = FALSE;
11414     ModeHighlight();
11415     SetGameInfo();
11416
11417     StartAnalysisClock();
11418     GetTimeMark(&lastNodeCountTime);
11419     lastNodeCount = 0;
11420 }
11421
11422 void
11423 MachineWhiteEvent()
11424 {
11425     char buf[MSG_SIZ];
11426     char *bookHit = NULL;
11427
11428     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11429       return;
11430
11431
11432     if (gameMode == PlayFromGameFile ||
11433         gameMode == TwoMachinesPlay  ||
11434         gameMode == Training         ||
11435         gameMode == AnalyzeMode      ||
11436         gameMode == EndOfGame)
11437         EditGameEvent();
11438
11439     if (gameMode == EditPosition) 
11440         EditPositionDone(TRUE);
11441
11442     if (!WhiteOnMove(currentMove)) {
11443         DisplayError(_("It is not White's turn"), 0);
11444         return;
11445     }
11446
11447     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11448       ExitAnalyzeMode();
11449
11450     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11451         gameMode == AnalyzeFile)
11452         TruncateGame();
11453
11454     ResurrectChessProgram();    /* in case it isn't running */
11455     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11456         gameMode = MachinePlaysWhite;
11457         ResetClocks();
11458     } else
11459     gameMode = MachinePlaysWhite;
11460     pausing = FALSE;
11461     ModeHighlight();
11462     SetGameInfo();
11463     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11464     DisplayTitle(buf);
11465     if (first.sendName) {
11466       sprintf(buf, "name %s\n", gameInfo.black);
11467       SendToProgram(buf, &first);
11468     }
11469     if (first.sendTime) {
11470       if (first.useColors) {
11471         SendToProgram("black\n", &first); /*gnu kludge*/
11472       }
11473       SendTimeRemaining(&first, TRUE);
11474     }
11475     if (first.useColors) {
11476       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11477     }
11478     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11479     SetMachineThinkingEnables();
11480     first.maybeThinking = TRUE;
11481     StartClocks();
11482     firstMove = FALSE;
11483
11484     if (appData.autoFlipView && !flipView) {
11485       flipView = !flipView;
11486       DrawPosition(FALSE, NULL);
11487       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11488     }
11489
11490     if(bookHit) { // [HGM] book: simulate book reply
11491         static char bookMove[MSG_SIZ]; // a bit generous?
11492
11493         programStats.nodes = programStats.depth = programStats.time =
11494         programStats.score = programStats.got_only_move = 0;
11495         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11496
11497         strcpy(bookMove, "move ");
11498         strcat(bookMove, bookHit);
11499         HandleMachineMove(bookMove, &first);
11500     }
11501 }
11502
11503 void
11504 MachineBlackEvent()
11505 {
11506   char buf[MSG_SIZ];
11507   char *bookHit = NULL;
11508   
11509   if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11510     return;
11511   
11512   
11513   if (gameMode == PlayFromGameFile 
11514       || gameMode == TwoMachinesPlay  
11515       || gameMode == Training     
11516       || gameMode == AnalyzeMode
11517       || gameMode == EndOfGame)
11518     EditGameEvent();
11519   
11520   if (gameMode == EditPosition) 
11521     EditPositionDone(TRUE);
11522   
11523   if (WhiteOnMove(currentMove)) 
11524     {
11525       DisplayError(_("It is not Black's turn"), 0);
11526       return;
11527     }
11528   
11529   if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11530     ExitAnalyzeMode();
11531   
11532   if (gameMode == EditGame || gameMode == AnalyzeMode 
11533       || gameMode == AnalyzeFile)
11534     TruncateGame();
11535   
11536   ResurrectChessProgram();      /* in case it isn't running */
11537   gameMode = MachinePlaysBlack;
11538   pausing  = FALSE;
11539   ModeHighlight();
11540   SetGameInfo();
11541   sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11542   DisplayTitle(buf);
11543   if (first.sendName) 
11544     {
11545       sprintf(buf, "name %s\n", gameInfo.white);
11546       SendToProgram(buf, &first);
11547     }
11548   if (first.sendTime) 
11549     {
11550       if (first.useColors) 
11551         {
11552           SendToProgram("white\n", &first); /*gnu kludge*/
11553         }
11554       SendTimeRemaining(&first, FALSE);
11555     }
11556   if (first.useColors) 
11557     {
11558       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11559     }
11560   bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11561   SetMachineThinkingEnables();
11562   first.maybeThinking = TRUE;
11563   StartClocks();
11564   
11565   if (appData.autoFlipView && flipView) 
11566     {
11567       flipView = !flipView;
11568       DrawPosition(FALSE, NULL);
11569       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11570     }
11571   if(bookHit) 
11572     { // [HGM] book: simulate book reply
11573       static char bookMove[MSG_SIZ]; // a bit generous?
11574       
11575       programStats.nodes = programStats.depth = programStats.time 
11576         = programStats.score = programStats.got_only_move = 0;
11577       sprintf(programStats.movelist, "%s (xbook)", bookHit);
11578       
11579       strcpy(bookMove, "move ");
11580       strcat(bookMove, bookHit);
11581       HandleMachineMove(bookMove, &first);
11582     }
11583   return;
11584 }
11585
11586
11587 void
11588 DisplayTwoMachinesTitle()
11589 {
11590     char buf[MSG_SIZ];
11591     if (appData.matchGames > 0) {
11592         if (first.twoMachinesColor[0] == 'w') {
11593             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11594                     gameInfo.white, gameInfo.black,
11595                     first.matchWins, second.matchWins,
11596                     matchGame - 1 - (first.matchWins + second.matchWins));
11597         } else {
11598             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11599                     gameInfo.white, gameInfo.black,
11600                     second.matchWins, first.matchWins,
11601                     matchGame - 1 - (first.matchWins + second.matchWins));
11602         }
11603     } else {
11604         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11605     }
11606     DisplayTitle(buf);
11607 }
11608
11609 void
11610 TwoMachinesEvent P((void))
11611 {
11612     int i;
11613     char buf[MSG_SIZ];
11614     ChessProgramState *onmove;
11615     char *bookHit = NULL;
11616
11617     if (appData.noChessProgram) return;
11618
11619     switch (gameMode) {
11620       case TwoMachinesPlay:
11621         return;
11622       case MachinePlaysWhite:
11623       case MachinePlaysBlack:
11624         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11625             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11626             return;
11627         }
11628         /* fall through */
11629       case BeginningOfGame:
11630       case PlayFromGameFile:
11631       case EndOfGame:
11632         EditGameEvent();
11633         if (gameMode != EditGame) return;
11634         break;
11635       case EditPosition:
11636         EditPositionDone(TRUE);
11637         break;
11638       case AnalyzeMode:
11639       case AnalyzeFile:
11640         ExitAnalyzeMode();
11641         break;
11642       case EditGame:
11643       default:
11644         break;
11645     }
11646
11647 //    forwardMostMove = currentMove;
11648     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11649     ResurrectChessProgram();    /* in case first program isn't running */
11650
11651     if (second.pr == NULL) {
11652         StartChessProgram(&second);
11653         if (second.protocolVersion == 1) {
11654           TwoMachinesEventIfReady();
11655         } else {
11656           /* kludge: allow timeout for initial "feature" command */
11657           FreezeUI();
11658           DisplayMessage("", _("Starting second chess program"));
11659           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11660         }
11661         return;
11662     }
11663     DisplayMessage("", "");
11664     InitChessProgram(&second, FALSE);
11665     SendToProgram("force\n", &second);
11666     if (startedFromSetupPosition) {
11667         SendBoard(&second, backwardMostMove);
11668     if (appData.debugMode) {
11669         fprintf(debugFP, "Two Machines\n");
11670     }
11671     }
11672     for (i = backwardMostMove; i < forwardMostMove; i++) {
11673         SendMoveToProgram(i, &second);
11674     }
11675
11676     gameMode = TwoMachinesPlay;
11677     pausing = FALSE;
11678     ModeHighlight();
11679     SetGameInfo();
11680     DisplayTwoMachinesTitle();
11681     firstMove = TRUE;
11682     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11683         onmove = &first;
11684     } else {
11685         onmove = &second;
11686     }
11687
11688     SendToProgram(first.computerString, &first);
11689     if (first.sendName) {
11690       sprintf(buf, "name %s\n", second.tidy);
11691       SendToProgram(buf, &first);
11692     }
11693     SendToProgram(second.computerString, &second);
11694     if (second.sendName) {
11695       sprintf(buf, "name %s\n", first.tidy);
11696       SendToProgram(buf, &second);
11697     }
11698
11699     ResetClocks();
11700     if (!first.sendTime || !second.sendTime) {
11701         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11702         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11703     }
11704     if (onmove->sendTime) {
11705       if (onmove->useColors) {
11706         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11707       }
11708       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11709     }
11710     if (onmove->useColors) {
11711       SendToProgram(onmove->twoMachinesColor, onmove);
11712     }
11713     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11714 //    SendToProgram("go\n", onmove);
11715     onmove->maybeThinking = TRUE;
11716     SetMachineThinkingEnables();
11717
11718     StartClocks();
11719
11720     if(bookHit) { // [HGM] book: simulate book reply
11721         static char bookMove[MSG_SIZ]; // a bit generous?
11722
11723         programStats.nodes = programStats.depth = programStats.time =
11724         programStats.score = programStats.got_only_move = 0;
11725         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11726
11727         strcpy(bookMove, "move ");
11728         strcat(bookMove, bookHit);
11729         savedMessage = bookMove; // args for deferred call
11730         savedState = onmove;
11731         ScheduleDelayedEvent(DeferredBookMove, 1);
11732     }
11733 }
11734
11735 void
11736 TrainingEvent()
11737 {
11738     if (gameMode == Training) {
11739       SetTrainingModeOff();
11740       gameMode = PlayFromGameFile;
11741       DisplayMessage("", _("Training mode off"));
11742     } else {
11743       gameMode = Training;
11744       animateTraining = appData.animate;
11745
11746       /* make sure we are not already at the end of the game */
11747       if (currentMove < forwardMostMove) {
11748         SetTrainingModeOn();
11749         DisplayMessage("", _("Training mode on"));
11750       } else {
11751         gameMode = PlayFromGameFile;
11752         DisplayError(_("Already at end of game"), 0);
11753       }
11754     }
11755     ModeHighlight();
11756 }
11757
11758 void
11759 IcsClientEvent()
11760 {
11761     if (!appData.icsActive) return;
11762     switch (gameMode) {
11763       case IcsPlayingWhite:
11764       case IcsPlayingBlack:
11765       case IcsObserving:
11766       case IcsIdle:
11767       case BeginningOfGame:
11768       case IcsExamining:
11769         return;
11770
11771       case EditGame:
11772         break;
11773
11774       case EditPosition:
11775         EditPositionDone(TRUE);
11776         break;
11777
11778       case AnalyzeMode:
11779       case AnalyzeFile:
11780         ExitAnalyzeMode();
11781         break;
11782
11783       default:
11784         EditGameEvent();
11785         break;
11786     }
11787
11788     gameMode = IcsIdle;
11789     ModeHighlight();
11790     return;
11791 }
11792
11793
11794 void
11795 EditGameEvent()
11796 {
11797     int i;
11798
11799     switch (gameMode) {
11800       case Training:
11801         SetTrainingModeOff();
11802         break;
11803       case MachinePlaysWhite:
11804       case MachinePlaysBlack:
11805       case BeginningOfGame:
11806         SendToProgram("force\n", &first);
11807         SetUserThinkingEnables();
11808         break;
11809       case PlayFromGameFile:
11810         (void) StopLoadGameTimer();
11811         if (gameFileFP != NULL) {
11812             gameFileFP = NULL;
11813         }
11814         break;
11815       case EditPosition:
11816         EditPositionDone(TRUE);
11817         break;
11818       case AnalyzeMode:
11819       case AnalyzeFile:
11820         ExitAnalyzeMode();
11821         SendToProgram("force\n", &first);
11822         break;
11823       case TwoMachinesPlay:
11824         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11825         ResurrectChessProgram();
11826         SetUserThinkingEnables();
11827         break;
11828       case EndOfGame:
11829         ResurrectChessProgram();
11830         break;
11831       case IcsPlayingBlack:
11832       case IcsPlayingWhite:
11833         DisplayError(_("Warning: You are still playing a game"), 0);
11834         break;
11835       case IcsObserving:
11836         DisplayError(_("Warning: You are still observing a game"), 0);
11837         break;
11838       case IcsExamining:
11839         DisplayError(_("Warning: You are still examining a game"), 0);
11840         break;
11841       case IcsIdle:
11842         break;
11843       case EditGame:
11844       default:
11845         return;
11846     }
11847
11848     pausing = FALSE;
11849     StopClocks();
11850     first.offeredDraw = second.offeredDraw = 0;
11851
11852     if (gameMode == PlayFromGameFile) {
11853         whiteTimeRemaining = timeRemaining[0][currentMove];
11854         blackTimeRemaining = timeRemaining[1][currentMove];
11855         DisplayTitle("");
11856     }
11857
11858     if (gameMode == MachinePlaysWhite ||
11859         gameMode == MachinePlaysBlack ||
11860         gameMode == TwoMachinesPlay ||
11861         gameMode == EndOfGame) {
11862         i = forwardMostMove;
11863         while (i > currentMove) {
11864             SendToProgram("undo\n", &first);
11865             i--;
11866         }
11867         whiteTimeRemaining = timeRemaining[0][currentMove];
11868         blackTimeRemaining = timeRemaining[1][currentMove];
11869         DisplayBothClocks();
11870         if (whiteFlag || blackFlag) {
11871             whiteFlag = blackFlag = 0;
11872         }
11873         DisplayTitle("");
11874     }
11875
11876     gameMode = EditGame;
11877     ModeHighlight();
11878     SetGameInfo();
11879 }
11880
11881
11882 void
11883 EditPositionEvent()
11884 {
11885     if (gameMode == EditPosition) {
11886         EditGameEvent();
11887         return;
11888     }
11889
11890     EditGameEvent();
11891     if (gameMode != EditGame) return;
11892
11893     gameMode = EditPosition;
11894     ModeHighlight();
11895     SetGameInfo();
11896     if (currentMove > 0)
11897       CopyBoard(boards[0], boards[currentMove]);
11898
11899     blackPlaysFirst = !WhiteOnMove(currentMove);
11900     ResetClocks();
11901     currentMove = forwardMostMove = backwardMostMove = 0;
11902     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11903     DisplayMove(-1);
11904 }
11905
11906 void
11907 ExitAnalyzeMode()
11908 {
11909     /* [DM] icsEngineAnalyze - possible call from other functions */
11910     if (appData.icsEngineAnalyze) {
11911         appData.icsEngineAnalyze = FALSE;
11912
11913         DisplayMessage("",_("Close ICS engine analyze..."));
11914     }
11915     if (first.analysisSupport && first.analyzing) {
11916       SendToProgram("exit\n", &first);
11917       first.analyzing = FALSE;
11918     }
11919     thinkOutput[0] = NULLCHAR;
11920 }
11921
11922 void
11923 EditPositionDone(Boolean fakeRights)
11924 {
11925     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11926
11927     startedFromSetupPosition = TRUE;
11928     InitChessProgram(&first, FALSE);
11929     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11930       boards[0][EP_STATUS] = EP_NONE;
11931       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11932     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11933         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11934         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11935       } else boards[0][CASTLING][2] = NoRights;
11936     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11937         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11938         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11939       } else boards[0][CASTLING][5] = NoRights;
11940     }
11941     SendToProgram("force\n", &first);
11942     if (blackPlaysFirst) {
11943         strcpy(moveList[0], "");
11944         strcpy(parseList[0], "");
11945         currentMove = forwardMostMove = backwardMostMove = 1;
11946         CopyBoard(boards[1], boards[0]);
11947     } else {
11948         currentMove = forwardMostMove = backwardMostMove = 0;
11949     }
11950     SendBoard(&first, forwardMostMove);
11951     if (appData.debugMode) {
11952         fprintf(debugFP, "EditPosDone\n");
11953     }
11954     DisplayTitle("");
11955     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11956     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11957     gameMode = EditGame;
11958     ModeHighlight();
11959     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11960     ClearHighlights(); /* [AS] */
11961 }
11962
11963 /* Pause for `ms' milliseconds */
11964 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11965 void
11966 TimeDelay(ms)
11967      long ms;
11968 {
11969     TimeMark m1, m2;
11970
11971     GetTimeMark(&m1);
11972     do {
11973         GetTimeMark(&m2);
11974     } while (SubtractTimeMarks(&m2, &m1) < ms);
11975 }
11976
11977 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11978 void
11979 SendMultiLineToICS(buf)
11980      char *buf;
11981 {
11982     char temp[MSG_SIZ+1], *p;
11983     int len;
11984
11985     len = strlen(buf);
11986     if (len > MSG_SIZ)
11987       len = MSG_SIZ;
11988
11989     strncpy(temp, buf, len);
11990     temp[len] = 0;
11991
11992     p = temp;
11993     while (*p) {
11994         if (*p == '\n' || *p == '\r')
11995           *p = ' ';
11996         ++p;
11997     }
11998
11999     strcat(temp, "\n");
12000     SendToICS(temp);
12001     SendToPlayer(temp, strlen(temp));
12002 }
12003
12004 void
12005 SetWhiteToPlayEvent()
12006 {
12007     if (gameMode == EditPosition) {
12008         blackPlaysFirst = FALSE;
12009         DisplayBothClocks();    /* works because currentMove is 0 */
12010     } else if (gameMode == IcsExamining) {
12011         SendToICS(ics_prefix);
12012         SendToICS("tomove white\n");
12013     }
12014 }
12015
12016 void
12017 SetBlackToPlayEvent()
12018 {
12019     if (gameMode == EditPosition) {
12020         blackPlaysFirst = TRUE;
12021         currentMove = 1;        /* kludge */
12022         DisplayBothClocks();
12023         currentMove = 0;
12024     } else if (gameMode == IcsExamining) {
12025         SendToICS(ics_prefix);
12026         SendToICS("tomove black\n");
12027     }
12028 }
12029
12030 void
12031 EditPositionMenuEvent(selection, x, y)
12032      ChessSquare selection;
12033      int x, y;
12034 {
12035     char buf[MSG_SIZ];
12036     ChessSquare piece = boards[0][y][x];
12037
12038     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12039
12040     switch (selection) {
12041       case ClearBoard:
12042         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12043             SendToICS(ics_prefix);
12044             SendToICS("bsetup clear\n");
12045         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12046             SendToICS(ics_prefix);
12047             SendToICS("clearboard\n");
12048         } else {
12049             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12050                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12051                 for (y = 0; y < BOARD_HEIGHT; y++) {
12052                     if (gameMode == IcsExamining) {
12053                         if (boards[currentMove][y][x] != EmptySquare) {
12054                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12055                                     AAA + x, ONE + y);
12056                             SendToICS(buf);
12057                         }
12058                     } else {
12059                         boards[0][y][x] = p;
12060                     }
12061                 }
12062             }
12063         }
12064         if (gameMode == EditPosition) {
12065             DrawPosition(FALSE, boards[0]);
12066         }
12067         break;
12068
12069       case WhitePlay:
12070         SetWhiteToPlayEvent();
12071         break;
12072
12073       case BlackPlay:
12074         SetBlackToPlayEvent();
12075         break;
12076
12077       case EmptySquare:
12078         if (gameMode == IcsExamining) {
12079             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12080             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12081             SendToICS(buf);
12082         } else {
12083             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12084                 if(x == BOARD_LEFT-2) {
12085                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12086                     boards[0][y][1] = 0;
12087                 } else
12088                 if(x == BOARD_RGHT+1) {
12089                     if(y >= gameInfo.holdingsSize) break;
12090                     boards[0][y][BOARD_WIDTH-2] = 0;
12091                 } else break;
12092             }
12093             boards[0][y][x] = EmptySquare;
12094             DrawPosition(FALSE, boards[0]);
12095         }
12096         break;
12097
12098       case PromotePiece:
12099         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12100            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12101             selection = (ChessSquare) (PROMOTED piece);
12102         } else if(piece == EmptySquare) selection = WhiteSilver;
12103         else selection = (ChessSquare)((int)piece - 1);
12104         goto defaultlabel;
12105
12106       case DemotePiece:
12107         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12108            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12109             selection = (ChessSquare) (DEMOTED piece);
12110         } else if(piece == EmptySquare) selection = BlackSilver;
12111         else selection = (ChessSquare)((int)piece + 1);
12112         goto defaultlabel;
12113
12114       case WhiteQueen:
12115       case BlackQueen:
12116         if(gameInfo.variant == VariantShatranj ||
12117            gameInfo.variant == VariantXiangqi  ||
12118            gameInfo.variant == VariantCourier  ||
12119            gameInfo.variant == VariantMakruk     )
12120             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12121         goto defaultlabel;
12122
12123       case WhiteKing:
12124       case BlackKing:
12125         if(gameInfo.variant == VariantXiangqi)
12126             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12127         if(gameInfo.variant == VariantKnightmate)
12128             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12129       default:
12130         defaultlabel:
12131         if (gameMode == IcsExamining) {
12132             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12133             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12134                     PieceToChar(selection), AAA + x, ONE + y);
12135             SendToICS(buf);
12136         } else {
12137             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12138                 int n;
12139                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12140                     n = PieceToNumber(selection - BlackPawn);
12141                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12142                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12143                     boards[0][BOARD_HEIGHT-1-n][1]++;
12144                 } else
12145                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12146                     n = PieceToNumber(selection);
12147                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12148                     boards[0][n][BOARD_WIDTH-1] = selection;
12149                     boards[0][n][BOARD_WIDTH-2]++;
12150                 }
12151             } else
12152             boards[0][y][x] = selection;
12153             DrawPosition(TRUE, boards[0]);
12154         }
12155         break;
12156     }
12157 }
12158
12159
12160 void
12161 DropMenuEvent(selection, x, y)
12162      ChessSquare selection;
12163      int x, y;
12164 {
12165     ChessMove moveType;
12166
12167     switch (gameMode) {
12168       case IcsPlayingWhite:
12169       case MachinePlaysBlack:
12170         if (!WhiteOnMove(currentMove)) {
12171             DisplayMoveError(_("It is Black's turn"));
12172             return;
12173         }
12174         moveType = WhiteDrop;
12175         break;
12176       case IcsPlayingBlack:
12177       case MachinePlaysWhite:
12178         if (WhiteOnMove(currentMove)) {
12179             DisplayMoveError(_("It is White's turn"));
12180             return;
12181         }
12182         moveType = BlackDrop;
12183         break;
12184       case EditGame:
12185         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12186         break;
12187       default:
12188         return;
12189     }
12190
12191     if (moveType == BlackDrop && selection < BlackPawn) {
12192       selection = (ChessSquare) ((int) selection
12193                                  + (int) BlackPawn - (int) WhitePawn);
12194     }
12195     if (boards[currentMove][y][x] != EmptySquare) {
12196         DisplayMoveError(_("That square is occupied"));
12197         return;
12198     }
12199
12200     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12201 }
12202
12203 void
12204 AcceptEvent()
12205 {
12206     /* Accept a pending offer of any kind from opponent */
12207
12208     if (appData.icsActive) {
12209         SendToICS(ics_prefix);
12210         SendToICS("accept\n");
12211     } else if (cmailMsgLoaded) {
12212         if (currentMove == cmailOldMove &&
12213             commentList[cmailOldMove] != NULL &&
12214             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12215                    "Black offers a draw" : "White offers a draw")) {
12216             TruncateGame();
12217             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12218             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12219         } else {
12220             DisplayError(_("There is no pending offer on this move"), 0);
12221             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12222         }
12223     } else {
12224         /* Not used for offers from chess program */
12225     }
12226 }
12227
12228 void
12229 DeclineEvent()
12230 {
12231     /* Decline a pending offer of any kind from opponent */
12232
12233     if (appData.icsActive) {
12234         SendToICS(ics_prefix);
12235         SendToICS("decline\n");
12236     } else if (cmailMsgLoaded) {
12237         if (currentMove == cmailOldMove &&
12238             commentList[cmailOldMove] != NULL &&
12239             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12240                    "Black offers a draw" : "White offers a draw")) {
12241 #ifdef NOTDEF
12242             AppendComment(cmailOldMove, "Draw declined", TRUE);
12243             DisplayComment(cmailOldMove - 1, "Draw declined");
12244 #endif /*NOTDEF*/
12245         } else {
12246             DisplayError(_("There is no pending offer on this move"), 0);
12247         }
12248     } else {
12249         /* Not used for offers from chess program */
12250     }
12251 }
12252
12253 void
12254 RematchEvent()
12255 {
12256     /* Issue ICS rematch command */
12257     if (appData.icsActive) {
12258         SendToICS(ics_prefix);
12259         SendToICS("rematch\n");
12260     }
12261 }
12262
12263 void
12264 CallFlagEvent()
12265 {
12266     /* Call your opponent's flag (claim a win on time) */
12267     if (appData.icsActive) {
12268         SendToICS(ics_prefix);
12269         SendToICS("flag\n");
12270     } else {
12271         switch (gameMode) {
12272           default:
12273             return;
12274           case MachinePlaysWhite:
12275             if (whiteFlag) {
12276                 if (blackFlag)
12277                   GameEnds(GameIsDrawn, "Both players ran out of time",
12278                            GE_PLAYER);
12279                 else
12280                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12281             } else {
12282                 DisplayError(_("Your opponent is not out of time"), 0);
12283             }
12284             break;
12285           case MachinePlaysBlack:
12286             if (blackFlag) {
12287                 if (whiteFlag)
12288                   GameEnds(GameIsDrawn, "Both players ran out of time",
12289                            GE_PLAYER);
12290                 else
12291                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12292             } else {
12293                 DisplayError(_("Your opponent is not out of time"), 0);
12294             }
12295             break;
12296         }
12297     }
12298 }
12299
12300 void
12301 DrawEvent()
12302 {
12303     /* Offer draw or accept pending draw offer from opponent */
12304
12305     if (appData.icsActive) {
12306         /* Note: tournament rules require draw offers to be
12307            made after you make your move but before you punch
12308            your clock.  Currently ICS doesn't let you do that;
12309            instead, you immediately punch your clock after making
12310            a move, but you can offer a draw at any time. */
12311
12312         SendToICS(ics_prefix);
12313         SendToICS("draw\n");
12314         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12315     } else if (cmailMsgLoaded) {
12316         if (currentMove == cmailOldMove &&
12317             commentList[cmailOldMove] != NULL &&
12318             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12319                    "Black offers a draw" : "White offers a draw")) {
12320             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12321             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12322         } else if (currentMove == cmailOldMove + 1) {
12323             char *offer = WhiteOnMove(cmailOldMove) ?
12324               "White offers a draw" : "Black offers a draw";
12325             AppendComment(currentMove, offer, TRUE);
12326             DisplayComment(currentMove - 1, offer);
12327             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12328         } else {
12329             DisplayError(_("You must make your move before offering a draw"), 0);
12330             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12331         }
12332     } else if (first.offeredDraw) {
12333         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12334     } else {
12335         if (first.sendDrawOffers) {
12336             SendToProgram("draw\n", &first);
12337             userOfferedDraw = TRUE;
12338         }
12339     }
12340 }
12341
12342 void
12343 AdjournEvent()
12344 {
12345     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12346
12347     if (appData.icsActive) {
12348         SendToICS(ics_prefix);
12349         SendToICS("adjourn\n");
12350     } else {
12351         /* Currently GNU Chess doesn't offer or accept Adjourns */
12352     }
12353 }
12354
12355
12356 void
12357 AbortEvent()
12358 {
12359     /* Offer Abort or accept pending Abort offer from opponent */
12360
12361     if (appData.icsActive) {
12362         SendToICS(ics_prefix);
12363         SendToICS("abort\n");
12364     } else {
12365         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12366     }
12367 }
12368
12369 void
12370 ResignEvent()
12371 {
12372     /* Resign.  You can do this even if it's not your turn. */
12373
12374     if (appData.icsActive) {
12375         SendToICS(ics_prefix);
12376         SendToICS("resign\n");
12377     } else {
12378         switch (gameMode) {
12379           case MachinePlaysWhite:
12380             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12381             break;
12382           case MachinePlaysBlack:
12383             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12384             break;
12385           case EditGame:
12386             if (cmailMsgLoaded) {
12387                 TruncateGame();
12388                 if (WhiteOnMove(cmailOldMove)) {
12389                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12390                 } else {
12391                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12392                 }
12393                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12394             }
12395             break;
12396           default:
12397             break;
12398         }
12399     }
12400 }
12401
12402
12403 void
12404 StopObservingEvent()
12405 {
12406     /* Stop observing current games */
12407     SendToICS(ics_prefix);
12408     SendToICS("unobserve\n");
12409 }
12410
12411 void
12412 StopExaminingEvent()
12413 {
12414     /* Stop observing current game */
12415     SendToICS(ics_prefix);
12416     SendToICS("unexamine\n");
12417 }
12418
12419 void
12420 ForwardInner(target)
12421      int target;
12422 {
12423     int limit;
12424
12425     if (appData.debugMode)
12426         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12427                 target, currentMove, forwardMostMove);
12428
12429     if (gameMode == EditPosition)
12430       return;
12431
12432     if (gameMode == PlayFromGameFile && !pausing)
12433       PauseEvent();
12434
12435     if (gameMode == IcsExamining && pausing)
12436       limit = pauseExamForwardMostMove;
12437     else
12438       limit = forwardMostMove;
12439
12440     if (target > limit) target = limit;
12441
12442     if (target > 0 && moveList[target - 1][0]) {
12443         int fromX, fromY, toX, toY;
12444         toX = moveList[target - 1][2] - AAA;
12445         toY = moveList[target - 1][3] - ONE;
12446         if (moveList[target - 1][1] == '@') {
12447             if (appData.highlightLastMove) {
12448                 SetHighlights(-1, -1, toX, toY);
12449             }
12450         } else {
12451             fromX = moveList[target - 1][0] - AAA;
12452             fromY = moveList[target - 1][1] - ONE;
12453             if (target == currentMove + 1) {
12454                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12455             }
12456             if (appData.highlightLastMove) {
12457                 SetHighlights(fromX, fromY, toX, toY);
12458             }
12459         }
12460     }
12461     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12462         gameMode == Training || gameMode == PlayFromGameFile ||
12463         gameMode == AnalyzeFile) {
12464         while (currentMove < target) {
12465             SendMoveToProgram(currentMove++, &first);
12466         }
12467     } else {
12468         currentMove = target;
12469     }
12470
12471     if (gameMode == EditGame || gameMode == EndOfGame) {
12472         whiteTimeRemaining = timeRemaining[0][currentMove];
12473         blackTimeRemaining = timeRemaining[1][currentMove];
12474     }
12475     DisplayBothClocks();
12476     DisplayMove(currentMove - 1);
12477     DrawPosition(FALSE, boards[currentMove]);
12478     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12479     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12480         DisplayComment(currentMove - 1, commentList[currentMove]);
12481     }
12482 }
12483
12484
12485 void
12486 ForwardEvent()
12487 {
12488     if (gameMode == IcsExamining && !pausing) {
12489         SendToICS(ics_prefix);
12490         SendToICS("forward\n");
12491     } else {
12492         ForwardInner(currentMove + 1);
12493     }
12494 }
12495
12496 void
12497 ToEndEvent()
12498 {
12499     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12500         /* to optimze, we temporarily turn off analysis mode while we feed
12501          * the remaining moves to the engine. Otherwise we get analysis output
12502          * after each move.
12503          */
12504         if (first.analysisSupport) {
12505           SendToProgram("exit\nforce\n", &first);
12506           first.analyzing = FALSE;
12507         }
12508     }
12509
12510     if (gameMode == IcsExamining && !pausing) {
12511         SendToICS(ics_prefix);
12512         SendToICS("forward 999999\n");
12513     } else {
12514         ForwardInner(forwardMostMove);
12515     }
12516
12517     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12518         /* we have fed all the moves, so reactivate analysis mode */
12519         SendToProgram("analyze\n", &first);
12520         first.analyzing = TRUE;
12521         /*first.maybeThinking = TRUE;*/
12522         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12523     }
12524 }
12525
12526 void
12527 BackwardInner(target)
12528      int target;
12529 {
12530     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12531
12532     if (appData.debugMode)
12533         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12534                 target, currentMove, forwardMostMove);
12535
12536     if (gameMode == EditPosition) return;
12537     if (currentMove <= backwardMostMove) {
12538         ClearHighlights();
12539         DrawPosition(full_redraw, boards[currentMove]);
12540         return;
12541     }
12542     if (gameMode == PlayFromGameFile && !pausing)
12543       PauseEvent();
12544
12545     if (moveList[target][0]) {
12546         int fromX, fromY, toX, toY;
12547         toX = moveList[target][2] - AAA;
12548         toY = moveList[target][3] - ONE;
12549         if (moveList[target][1] == '@') {
12550             if (appData.highlightLastMove) {
12551                 SetHighlights(-1, -1, toX, toY);
12552             }
12553         } else {
12554             fromX = moveList[target][0] - AAA;
12555             fromY = moveList[target][1] - ONE;
12556             if (target == currentMove - 1) {
12557                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12558             }
12559             if (appData.highlightLastMove) {
12560                 SetHighlights(fromX, fromY, toX, toY);
12561             }
12562         }
12563     }
12564     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12565         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12566         while (currentMove > target) {
12567             SendToProgram("undo\n", &first);
12568             currentMove--;
12569         }
12570     } else {
12571         currentMove = target;
12572     }
12573
12574     if (gameMode == EditGame || gameMode == EndOfGame) {
12575         whiteTimeRemaining = timeRemaining[0][currentMove];
12576         blackTimeRemaining = timeRemaining[1][currentMove];
12577     }
12578     DisplayBothClocks();
12579     DisplayMove(currentMove - 1);
12580     DrawPosition(full_redraw, boards[currentMove]);
12581     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12582     // [HGM] PV info: routine tests if comment empty
12583     DisplayComment(currentMove - 1, commentList[currentMove]);
12584 }
12585
12586 void
12587 BackwardEvent()
12588 {
12589     if (gameMode == IcsExamining && !pausing) {
12590         SendToICS(ics_prefix);
12591         SendToICS("backward\n");
12592     } else {
12593         BackwardInner(currentMove - 1);
12594     }
12595 }
12596
12597 void
12598 ToStartEvent()
12599 {
12600     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12601         /* to optimize, we temporarily turn off analysis mode while we undo
12602          * all the moves. Otherwise we get analysis output after each undo.
12603          */
12604         if (first.analysisSupport) {
12605           SendToProgram("exit\nforce\n", &first);
12606           first.analyzing = FALSE;
12607         }
12608     }
12609
12610     if (gameMode == IcsExamining && !pausing) {
12611         SendToICS(ics_prefix);
12612         SendToICS("backward 999999\n");
12613     } else {
12614         BackwardInner(backwardMostMove);
12615     }
12616
12617     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12618         /* we have fed all the moves, so reactivate analysis mode */
12619         SendToProgram("analyze\n", &first);
12620         first.analyzing = TRUE;
12621         /*first.maybeThinking = TRUE;*/
12622         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12623     }
12624 }
12625
12626 void
12627 ToNrEvent(int to)
12628 {
12629   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12630   if (to >= forwardMostMove) to = forwardMostMove;
12631   if (to <= backwardMostMove) to = backwardMostMove;
12632   if (to < currentMove) {
12633     BackwardInner(to);
12634   } else {
12635     ForwardInner(to);
12636   }
12637 }
12638
12639 void
12640 RevertEvent()
12641 {
12642     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12643         return;
12644     }
12645     if (gameMode != IcsExamining) {
12646         DisplayError(_("You are not examining a game"), 0);
12647         return;
12648     }
12649     if (pausing) {
12650         DisplayError(_("You can't revert while pausing"), 0);
12651         return;
12652     }
12653     SendToICS(ics_prefix);
12654     SendToICS("revert\n");
12655 }
12656
12657 void
12658 RetractMoveEvent()
12659 {
12660     switch (gameMode) {
12661       case MachinePlaysWhite:
12662       case MachinePlaysBlack:
12663         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12664             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12665             return;
12666         }
12667         if (forwardMostMove < 2) return;
12668         currentMove = forwardMostMove = forwardMostMove - 2;
12669         whiteTimeRemaining = timeRemaining[0][currentMove];
12670         blackTimeRemaining = timeRemaining[1][currentMove];
12671         DisplayBothClocks();
12672         DisplayMove(currentMove - 1);
12673         ClearHighlights();/*!! could figure this out*/
12674         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12675         SendToProgram("remove\n", &first);
12676         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12677         break;
12678
12679       case BeginningOfGame:
12680       default:
12681         break;
12682
12683       case IcsPlayingWhite:
12684       case IcsPlayingBlack:
12685         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12686             SendToICS(ics_prefix);
12687             SendToICS("takeback 2\n");
12688         } else {
12689             SendToICS(ics_prefix);
12690             SendToICS("takeback 1\n");
12691         }
12692         break;
12693     }
12694 }
12695
12696 void
12697 MoveNowEvent()
12698 {
12699     ChessProgramState *cps;
12700
12701     switch (gameMode) {
12702       case MachinePlaysWhite:
12703         if (!WhiteOnMove(forwardMostMove)) {
12704             DisplayError(_("It is your turn"), 0);
12705             return;
12706         }
12707         cps = &first;
12708         break;
12709       case MachinePlaysBlack:
12710         if (WhiteOnMove(forwardMostMove)) {
12711             DisplayError(_("It is your turn"), 0);
12712             return;
12713         }
12714         cps = &first;
12715         break;
12716       case TwoMachinesPlay:
12717         if (WhiteOnMove(forwardMostMove) ==
12718             (first.twoMachinesColor[0] == 'w')) {
12719             cps = &first;
12720         } else {
12721             cps = &second;
12722         }
12723         break;
12724       case BeginningOfGame:
12725       default:
12726         return;
12727     }
12728     SendToProgram("?\n", cps);
12729 }
12730
12731 void
12732 TruncateGameEvent()
12733 {
12734     EditGameEvent();
12735     if (gameMode != EditGame) return;
12736     TruncateGame();
12737 }
12738
12739 void
12740 TruncateGame()
12741 {
12742     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12743     if (forwardMostMove > currentMove) {
12744         if (gameInfo.resultDetails != NULL) {
12745             free(gameInfo.resultDetails);
12746             gameInfo.resultDetails = NULL;
12747             gameInfo.result = GameUnfinished;
12748         }
12749         forwardMostMove = currentMove;
12750         HistorySet(parseList, backwardMostMove, forwardMostMove,
12751                    currentMove-1);
12752     }
12753 }
12754
12755 void
12756 HintEvent()
12757 {
12758     if (appData.noChessProgram) return;
12759     switch (gameMode) {
12760       case MachinePlaysWhite:
12761         if (WhiteOnMove(forwardMostMove)) {
12762             DisplayError(_("Wait until your turn"), 0);
12763             return;
12764         }
12765         break;
12766       case BeginningOfGame:
12767       case MachinePlaysBlack:
12768         if (!WhiteOnMove(forwardMostMove)) {
12769             DisplayError(_("Wait until your turn"), 0);
12770             return;
12771         }
12772         break;
12773       default:
12774         DisplayError(_("No hint available"), 0);
12775         return;
12776     }
12777     SendToProgram("hint\n", &first);
12778     hintRequested = TRUE;
12779 }
12780
12781 void
12782 BookEvent()
12783 {
12784     if (appData.noChessProgram) return;
12785     switch (gameMode) {
12786       case MachinePlaysWhite:
12787         if (WhiteOnMove(forwardMostMove)) {
12788             DisplayError(_("Wait until your turn"), 0);
12789             return;
12790         }
12791         break;
12792       case BeginningOfGame:
12793       case MachinePlaysBlack:
12794         if (!WhiteOnMove(forwardMostMove)) {
12795             DisplayError(_("Wait until your turn"), 0);
12796             return;
12797         }
12798         break;
12799       case EditPosition:
12800         EditPositionDone(TRUE);
12801         break;
12802       case TwoMachinesPlay:
12803         return;
12804       default:
12805         break;
12806     }
12807     SendToProgram("bk\n", &first);
12808     bookOutput[0] = NULLCHAR;
12809     bookRequested = TRUE;
12810 }
12811
12812 void
12813 AboutGameEvent()
12814 {
12815     char *tags = PGNTags(&gameInfo);
12816     TagsPopUp(tags, CmailMsg());
12817     free(tags);
12818 }
12819
12820 /* end button procedures */
12821
12822 void
12823 PrintPosition(fp, move)
12824      FILE *fp;
12825      int move;
12826 {
12827     int i, j;
12828
12829     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12830         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12831             char c = PieceToChar(boards[move][i][j]);
12832             fputc(c == 'x' ? '.' : c, fp);
12833             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12834         }
12835     }
12836     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12837       fprintf(fp, "white to play\n");
12838     else
12839       fprintf(fp, "black to play\n");
12840 }
12841
12842 void
12843 PrintOpponents(fp)
12844      FILE *fp;
12845 {
12846     if (gameInfo.white != NULL) {
12847         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12848     } else {
12849         fprintf(fp, "\n");
12850     }
12851 }
12852
12853 /* Find last component of program's own name, using some heuristics */
12854 void
12855 TidyProgramName(prog, host, buf)
12856      char *prog, *host, buf[MSG_SIZ];
12857 {
12858     char *p, *q;
12859     int local = (strcmp(host, "localhost") == 0);
12860     while (!local && (p = strchr(prog, ';')) != NULL) {
12861         p++;
12862         while (*p == ' ') p++;
12863         prog = p;
12864     }
12865     if (*prog == '"' || *prog == '\'') {
12866         q = strchr(prog + 1, *prog);
12867     } else {
12868         q = strchr(prog, ' ');
12869     }
12870     if (q == NULL) q = prog + strlen(prog);
12871     p = q;
12872     while (p >= prog && *p != '/' && *p != '\\') p--;
12873     p++;
12874     if(p == prog && *p == '"') p++;
12875     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12876     memcpy(buf, p, q - p);
12877     buf[q - p] = NULLCHAR;
12878     if (!local) {
12879         strcat(buf, "@");
12880         strcat(buf, host);
12881     }
12882 }
12883
12884 char *
12885 TimeControlTagValue()
12886 {
12887     char buf[MSG_SIZ];
12888     if (!appData.clockMode) {
12889         strcpy(buf, "-");
12890     } else if (movesPerSession > 0) {
12891         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12892     } else if (timeIncrement == 0) {
12893         sprintf(buf, "%ld", timeControl/1000);
12894     } else {
12895         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12896     }
12897     return StrSave(buf);
12898 }
12899
12900 void
12901 SetGameInfo()
12902 {
12903     /* This routine is used only for certain modes */
12904     VariantClass v = gameInfo.variant;
12905     ChessMove r = GameUnfinished;
12906     char *p = NULL;
12907
12908     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12909         r = gameInfo.result; 
12910         p = gameInfo.resultDetails; 
12911         gameInfo.resultDetails = NULL;
12912     }
12913     ClearGameInfo(&gameInfo);
12914     gameInfo.variant = v;
12915
12916     switch (gameMode) {
12917       case MachinePlaysWhite:
12918         gameInfo.event = StrSave( appData.pgnEventHeader );
12919         gameInfo.site = StrSave(HostName());
12920         gameInfo.date = PGNDate();
12921         gameInfo.round = StrSave("-");
12922         gameInfo.white = StrSave(first.tidy);
12923         gameInfo.black = StrSave(UserName());
12924         gameInfo.timeControl = TimeControlTagValue();
12925         break;
12926
12927       case MachinePlaysBlack:
12928         gameInfo.event = StrSave( appData.pgnEventHeader );
12929         gameInfo.site = StrSave(HostName());
12930         gameInfo.date = PGNDate();
12931         gameInfo.round = StrSave("-");
12932         gameInfo.white = StrSave(UserName());
12933         gameInfo.black = StrSave(first.tidy);
12934         gameInfo.timeControl = TimeControlTagValue();
12935         break;
12936
12937       case TwoMachinesPlay:
12938         gameInfo.event = StrSave( appData.pgnEventHeader );
12939         gameInfo.site = StrSave(HostName());
12940         gameInfo.date = PGNDate();
12941         if (matchGame > 0) {
12942             char buf[MSG_SIZ];
12943             sprintf(buf, "%d", matchGame);
12944             gameInfo.round = StrSave(buf);
12945         } else {
12946             gameInfo.round = StrSave("-");
12947         }
12948         if (first.twoMachinesColor[0] == 'w') {
12949             gameInfo.white = StrSave(first.tidy);
12950             gameInfo.black = StrSave(second.tidy);
12951         } else {
12952             gameInfo.white = StrSave(second.tidy);
12953             gameInfo.black = StrSave(first.tidy);
12954         }
12955         gameInfo.timeControl = TimeControlTagValue();
12956         break;
12957
12958       case EditGame:
12959         gameInfo.event = StrSave("Edited game");
12960         gameInfo.site = StrSave(HostName());
12961         gameInfo.date = PGNDate();
12962         gameInfo.round = StrSave("-");
12963         gameInfo.white = StrSave("-");
12964         gameInfo.black = StrSave("-");
12965         gameInfo.result = r;
12966         gameInfo.resultDetails = p;
12967         break;
12968
12969       case EditPosition:
12970         gameInfo.event = StrSave("Edited position");
12971         gameInfo.site = StrSave(HostName());
12972         gameInfo.date = PGNDate();
12973         gameInfo.round = StrSave("-");
12974         gameInfo.white = StrSave("-");
12975         gameInfo.black = StrSave("-");
12976         break;
12977
12978       case IcsPlayingWhite:
12979       case IcsPlayingBlack:
12980       case IcsObserving:
12981       case IcsExamining:
12982         break;
12983
12984       case PlayFromGameFile:
12985         gameInfo.event = StrSave("Game from non-PGN file");
12986         gameInfo.site = StrSave(HostName());
12987         gameInfo.date = PGNDate();
12988         gameInfo.round = StrSave("-");
12989         gameInfo.white = StrSave("?");
12990         gameInfo.black = StrSave("?");
12991         break;
12992
12993       default:
12994         break;
12995     }
12996 }
12997
12998 void
12999 ReplaceComment(index, text)
13000      int index;
13001      char *text;
13002 {
13003     int len;
13004
13005     while (*text == '\n') text++;
13006     len = strlen(text);
13007     while (len > 0 && text[len - 1] == '\n') len--;
13008
13009     if (commentList[index] != NULL)
13010       free(commentList[index]);
13011
13012     if (len == 0) {
13013         commentList[index] = NULL;
13014         return;
13015     }
13016   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13017       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13018       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13019     commentList[index] = (char *) malloc(len + 2);
13020     strncpy(commentList[index], text, len);
13021     commentList[index][len] = '\n';
13022     commentList[index][len + 1] = NULLCHAR;
13023   } else { 
13024     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13025     char *p;
13026     commentList[index] = (char *) malloc(len + 6);
13027     strcpy(commentList[index], "{\n");
13028     strncpy(commentList[index]+2, text, len);
13029     commentList[index][len+2] = NULLCHAR;
13030     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13031     strcat(commentList[index], "\n}\n");
13032   }
13033 }
13034
13035 void
13036 CrushCRs(text)
13037      char *text;
13038 {
13039   char *p = text;
13040   char *q = text;
13041   char ch;
13042
13043   do {
13044     ch = *p++;
13045     if (ch == '\r') continue;
13046     *q++ = ch;
13047   } while (ch != '\0');
13048 }
13049
13050 void
13051 AppendComment(index, text, addBraces)
13052      int index;
13053      char *text;
13054      Boolean addBraces; // [HGM] braces: tells if we should add {}
13055 {
13056     int oldlen, len;
13057     char *old;
13058
13059 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13060     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13061
13062     CrushCRs(text);
13063     while (*text == '\n') text++;
13064     len = strlen(text);
13065     while (len > 0 && text[len - 1] == '\n') len--;
13066
13067     if (len == 0) return;
13068
13069     if (commentList[index] != NULL) {
13070         old = commentList[index];
13071         oldlen = strlen(old);
13072         while(commentList[index][oldlen-1] ==  '\n')
13073           commentList[index][--oldlen] = NULLCHAR;
13074         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13075         strcpy(commentList[index], old);
13076         free(old);
13077         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13078         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13079           if(addBraces) addBraces = FALSE; else { text++; len--; }
13080           while (*text == '\n') { text++; len--; }
13081           commentList[index][--oldlen] = NULLCHAR;
13082       }
13083         if(addBraces) strcat(commentList[index], "\n{\n");
13084         else          strcat(commentList[index], "\n");
13085         strcat(commentList[index], text);
13086         if(addBraces) strcat(commentList[index], "\n}\n");
13087         else          strcat(commentList[index], "\n");
13088     } else {
13089         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13090         if(addBraces)
13091              strcpy(commentList[index], "{\n");
13092         else commentList[index][0] = NULLCHAR;
13093         strcat(commentList[index], text);
13094         strcat(commentList[index], "\n");
13095         if(addBraces) strcat(commentList[index], "}\n");
13096     }
13097 }
13098
13099 static char * FindStr( char * text, char * sub_text )
13100 {
13101     char * result = strstr( text, sub_text );
13102
13103     if( result != NULL ) {
13104         result += strlen( sub_text );
13105     }
13106
13107     return result;
13108 }
13109
13110 /* [AS] Try to extract PV info from PGN comment */
13111 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13112 char *GetInfoFromComment( int index, char * text )
13113 {
13114     char * sep = text;
13115
13116     if( text != NULL && index > 0 ) {
13117         int score = 0;
13118         int depth = 0;
13119         int time = -1, sec = 0, deci;
13120         char * s_eval = FindStr( text, "[%eval " );
13121         char * s_emt = FindStr( text, "[%emt " );
13122
13123         if( s_eval != NULL || s_emt != NULL ) {
13124             /* New style */
13125             char delim;
13126
13127             if( s_eval != NULL ) {
13128                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13129                     return text;
13130                 }
13131
13132                 if( delim != ']' ) {
13133                     return text;
13134                 }
13135             }
13136
13137             if( s_emt != NULL ) {
13138             }
13139                 return text;
13140         }
13141         else {
13142             /* We expect something like: [+|-]nnn.nn/dd */
13143             int score_lo = 0;
13144
13145             if(*text != '{') return text; // [HGM] braces: must be normal comment
13146
13147             sep = strchr( text, '/' );
13148             if( sep == NULL || sep < (text+4) ) {
13149                 return text;
13150             }
13151
13152             time = -1; sec = -1; deci = -1;
13153             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13154                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13155                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13156                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13157                 return text;
13158             }
13159
13160             if( score_lo < 0 || score_lo >= 100 ) {
13161                 return text;
13162             }
13163
13164             if(sec >= 0) time = 600*time + 10*sec; else
13165             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13166
13167             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13168
13169             /* [HGM] PV time: now locate end of PV info */
13170             while( *++sep >= '0' && *sep <= '9'); // strip depth
13171             if(time >= 0)
13172             while( *++sep >= '0' && *sep <= '9'); // strip time
13173             if(sec >= 0)
13174             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13175             if(deci >= 0)
13176             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13177             while(*sep == ' ') sep++;
13178         }
13179
13180         if( depth <= 0 ) {
13181             return text;
13182         }
13183
13184         if( time < 0 ) {
13185             time = -1;
13186         }
13187
13188         pvInfoList[index-1].depth = depth;
13189         pvInfoList[index-1].score = score;
13190         pvInfoList[index-1].time  = 10*time; // centi-sec
13191         if(*sep == '}') *sep = 0; else *--sep = '{';
13192     }
13193     return sep;
13194 }
13195
13196 void
13197 SendToProgram(message, cps)
13198      char *message;
13199      ChessProgramState *cps;
13200 {
13201     int count, outCount, error;
13202     char buf[MSG_SIZ];
13203
13204     if (cps->pr == NULL) return;
13205     Attention(cps);
13206
13207     if (appData.debugMode) {
13208         TimeMark now;
13209         GetTimeMark(&now);
13210         fprintf(debugFP, "%ld >%-6s: %s",
13211                 SubtractTimeMarks(&now, &programStartTime),
13212                 cps->which, message);
13213     }
13214
13215     count = strlen(message);
13216     outCount = OutputToProcess(cps->pr, message, count, &error);
13217     if (outCount < count && !exiting
13218                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13219         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13220         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13221             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13222                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13223                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13224             } else {
13225                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13226             }
13227             gameInfo.resultDetails = StrSave(buf);
13228         }
13229         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13230     }
13231 }
13232
13233 void
13234 ReceiveFromProgram(isr, closure, message, count, error)
13235      InputSourceRef isr;
13236      VOIDSTAR closure;
13237      char *message;
13238      int count;
13239      int error;
13240 {
13241     char *end_str;
13242     char buf[MSG_SIZ];
13243     ChessProgramState *cps = (ChessProgramState *)closure;
13244
13245     if (isr != cps->isr) return; /* Killed intentionally */
13246     if (count <= 0) {
13247         if (count == 0) {
13248             sprintf(buf,
13249                     _("Error: %s chess program (%s) exited unexpectedly"),
13250                     cps->which, cps->program);
13251         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13252                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13253                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13254                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13255                 } else {
13256                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13257                 }
13258                 gameInfo.resultDetails = StrSave(buf);
13259             }
13260             RemoveInputSource(cps->isr);
13261             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13262         } else {
13263             sprintf(buf,
13264                     _("Error reading from %s chess program (%s)"),
13265                     cps->which, cps->program);
13266             RemoveInputSource(cps->isr);
13267
13268             /* [AS] Program is misbehaving badly... kill it */
13269             if( count == -2 ) {
13270                 DestroyChildProcess( cps->pr, 9 );
13271                 cps->pr = NoProc;
13272             }
13273
13274             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13275         }
13276         return;
13277     }
13278
13279     if ((end_str = strchr(message, '\r')) != NULL)
13280       *end_str = NULLCHAR;
13281     if ((end_str = strchr(message, '\n')) != NULL)
13282       *end_str = NULLCHAR;
13283
13284     if (appData.debugMode) {
13285         TimeMark now; int print = 1;
13286         char *quote = ""; char c; int i;
13287
13288         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13289                 char start = message[0];
13290                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13291                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13292                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13293                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13294                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13295                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13296                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13297                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13298                         { quote = "# "; print = (appData.engineComments == 2); }
13299                 message[0] = start; // restore original message
13300         }
13301         if(print) {
13302                 GetTimeMark(&now);
13303                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13304                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13305                         quote,
13306                         message);
13307         }
13308     }
13309
13310     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13311     if (appData.icsEngineAnalyze) {
13312         if (strstr(message, "whisper") != NULL ||
13313              strstr(message, "kibitz") != NULL ||
13314             strstr(message, "tellics") != NULL) return;
13315     }
13316
13317     HandleMachineMove(message, cps);
13318 }
13319
13320
13321 void
13322 SendTimeControl(cps, mps, tc, inc, sd, st)
13323      ChessProgramState *cps;
13324      int mps, inc, sd, st;
13325      long tc;
13326 {
13327     char buf[MSG_SIZ];
13328     int seconds;
13329
13330     if( timeControl_2 > 0 ) {
13331         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13332             tc = timeControl_2;
13333         }
13334     }
13335     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13336     inc /= cps->timeOdds;
13337     st  /= cps->timeOdds;
13338
13339     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13340
13341     if (st > 0) {
13342       /* Set exact time per move, normally using st command */
13343       if (cps->stKludge) {
13344         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13345         seconds = st % 60;
13346         if (seconds == 0) {
13347           sprintf(buf, "level 1 %d\n", st/60);
13348         } else {
13349           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13350         }
13351       } else {
13352         sprintf(buf, "st %d\n", st);
13353       }
13354     } else {
13355       /* Set conventional or incremental time control, using level command */
13356       if (seconds == 0) {
13357         /* Note old gnuchess bug -- minutes:seconds used to not work.
13358            Fixed in later versions, but still avoid :seconds
13359            when seconds is 0. */
13360         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13361       } else {
13362         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13363                 seconds, inc/1000);
13364       }
13365     }
13366     SendToProgram(buf, cps);
13367
13368     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13369     /* Orthogonally, limit search to given depth */
13370     if (sd > 0) {
13371       if (cps->sdKludge) {
13372         sprintf(buf, "depth\n%d\n", sd);
13373       } else {
13374         sprintf(buf, "sd %d\n", sd);
13375       }
13376       SendToProgram(buf, cps);
13377     }
13378
13379     if(cps->nps > 0) { /* [HGM] nps */
13380         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13381         else {
13382                 sprintf(buf, "nps %d\n", cps->nps);
13383               SendToProgram(buf, cps);
13384         }
13385     }
13386 }
13387
13388 ChessProgramState *WhitePlayer()
13389 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13390 {
13391     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13392        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13393         return &second;
13394     return &first;
13395 }
13396
13397 void
13398 SendTimeRemaining(cps, machineWhite)
13399      ChessProgramState *cps;
13400      int /*boolean*/ machineWhite;
13401 {
13402     char message[MSG_SIZ];
13403     long time, otime;
13404
13405     /* Note: this routine must be called when the clocks are stopped
13406        or when they have *just* been set or switched; otherwise
13407        it will be off by the time since the current tick started.
13408     */
13409     if (machineWhite) {
13410         time = whiteTimeRemaining / 10;
13411         otime = blackTimeRemaining / 10;
13412     } else {
13413         time = blackTimeRemaining / 10;
13414         otime = whiteTimeRemaining / 10;
13415     }
13416     /* [HGM] translate opponent's time by time-odds factor */
13417     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13418     if (appData.debugMode) {
13419         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13420     }
13421
13422     if (time <= 0) time = 1;
13423     if (otime <= 0) otime = 1;
13424
13425     sprintf(message, "time %ld\n", time);
13426     SendToProgram(message, cps);
13427
13428     sprintf(message, "otim %ld\n", otime);
13429     SendToProgram(message, cps);
13430 }
13431
13432 int
13433 BoolFeature(p, name, loc, cps)
13434      char **p;
13435      char *name;
13436      int *loc;
13437      ChessProgramState *cps;
13438 {
13439   char buf[MSG_SIZ];
13440   int len = strlen(name);
13441   int val;
13442   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13443     (*p) += len + 1;
13444     sscanf(*p, "%d", &val);
13445     *loc = (val != 0);
13446     while (**p && **p != ' ') (*p)++;
13447     sprintf(buf, "accepted %s\n", name);
13448     SendToProgram(buf, cps);
13449     return TRUE;
13450   }
13451   return FALSE;
13452 }
13453
13454 int
13455 IntFeature(p, name, loc, cps)
13456      char **p;
13457      char *name;
13458      int *loc;
13459      ChessProgramState *cps;
13460 {
13461   char buf[MSG_SIZ];
13462   int len = strlen(name);
13463   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13464     (*p) += len + 1;
13465     sscanf(*p, "%d", loc);
13466     while (**p && **p != ' ') (*p)++;
13467     sprintf(buf, "accepted %s\n", name);
13468     SendToProgram(buf, cps);
13469     return TRUE;
13470   }
13471   return FALSE;
13472 }
13473
13474 int
13475 StringFeature(p, name, loc, cps)
13476      char **p;
13477      char *name;
13478      char loc[];
13479      ChessProgramState *cps;
13480 {
13481   char buf[MSG_SIZ];
13482   int len = strlen(name);
13483   if (strncmp((*p), name, len) == 0
13484       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13485     (*p) += len + 2;
13486     sscanf(*p, "%[^\"]", loc);
13487     while (**p && **p != '\"') (*p)++;
13488     if (**p == '\"') (*p)++;
13489     sprintf(buf, "accepted %s\n", name);
13490     SendToProgram(buf, cps);
13491     return TRUE;
13492   }
13493   return FALSE;
13494 }
13495
13496 int
13497 ParseOption(Option *opt, ChessProgramState *cps)
13498 // [HGM] options: process the string that defines an engine option, and determine
13499 // name, type, default value, and allowed value range
13500 {
13501         char *p, *q, buf[MSG_SIZ];
13502         int n, min = (-1)<<31, max = 1<<31, def;
13503
13504         if(p = strstr(opt->name, " -spin ")) {
13505             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13506             if(max < min) max = min; // enforce consistency
13507             if(def < min) def = min;
13508             if(def > max) def = max;
13509             opt->value = def;
13510             opt->min = min;
13511             opt->max = max;
13512             opt->type = Spin;
13513         } else if((p = strstr(opt->name, " -slider "))) {
13514             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13515             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13516             if(max < min) max = min; // enforce consistency
13517             if(def < min) def = min;
13518             if(def > max) def = max;
13519             opt->value = def;
13520             opt->min = min;
13521             opt->max = max;
13522             opt->type = Spin; // Slider;
13523         } else if((p = strstr(opt->name, " -string "))) {
13524             opt->textValue = p+9;
13525             opt->type = TextBox;
13526         } else if((p = strstr(opt->name, " -file "))) {
13527             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13528             opt->textValue = p+7;
13529             opt->type = TextBox; // FileName;
13530         } else if((p = strstr(opt->name, " -path "))) {
13531             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13532             opt->textValue = p+7;
13533             opt->type = TextBox; // PathName;
13534         } else if(p = strstr(opt->name, " -check ")) {
13535             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13536             opt->value = (def != 0);
13537             opt->type = CheckBox;
13538         } else if(p = strstr(opt->name, " -combo ")) {
13539             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13540             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13541             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13542             opt->value = n = 0;
13543             while(q = StrStr(q, " /// ")) {
13544                 n++; *q = 0;    // count choices, and null-terminate each of them
13545                 q += 5;
13546                 if(*q == '*') { // remember default, which is marked with * prefix
13547                     q++;
13548                     opt->value = n;
13549                 }
13550                 cps->comboList[cps->comboCnt++] = q;
13551             }
13552             cps->comboList[cps->comboCnt++] = NULL;
13553             opt->max = n + 1;
13554             opt->type = ComboBox;
13555         } else if(p = strstr(opt->name, " -button")) {
13556             opt->type = Button;
13557         } else if(p = strstr(opt->name, " -save")) {
13558             opt->type = SaveButton;
13559         } else return FALSE;
13560         *p = 0; // terminate option name
13561         // now look if the command-line options define a setting for this engine option.
13562         if(cps->optionSettings && cps->optionSettings[0])
13563             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13564         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13565                 sprintf(buf, "option %s", p);
13566                 if(p = strstr(buf, ",")) *p = 0;
13567                 strcat(buf, "\n");
13568                 SendToProgram(buf, cps);
13569         }
13570         return TRUE;
13571 }
13572
13573 void
13574 FeatureDone(cps, val)
13575      ChessProgramState* cps;
13576      int val;
13577 {
13578   DelayedEventCallback cb = GetDelayedEvent();
13579   if ((cb == InitBackEnd3 && cps == &first) ||
13580       (cb == TwoMachinesEventIfReady && cps == &second)) {
13581     CancelDelayedEvent();
13582     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13583   }
13584   cps->initDone = val;
13585 }
13586
13587 /* Parse feature command from engine */
13588 void
13589 ParseFeatures(args, cps)
13590      char* args;
13591      ChessProgramState *cps;
13592 {
13593   char *p = args;
13594   char *q;
13595   int val;
13596   char buf[MSG_SIZ];
13597
13598   for (;;) {
13599     while (*p == ' ') p++;
13600     if (*p == NULLCHAR) return;
13601
13602     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13603     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13604     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13605     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13606     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13607     if (BoolFeature(&p, "reuse", &val, cps)) {
13608       /* Engine can disable reuse, but can't enable it if user said no */
13609       if (!val) cps->reuse = FALSE;
13610       continue;
13611     }
13612     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13613     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13614       if (gameMode == TwoMachinesPlay) {
13615         DisplayTwoMachinesTitle();
13616       } else {
13617         DisplayTitle("");
13618       }
13619       continue;
13620     }
13621     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13622     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13623     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13624     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13625     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13626     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13627     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13628     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13629     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13630     if (IntFeature(&p, "done", &val, cps)) {
13631       FeatureDone(cps, val);
13632       continue;
13633     }
13634     /* Added by Tord: */
13635     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13636     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13637     /* End of additions by Tord */
13638
13639     /* [HGM] added features: */
13640     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13641     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13642     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13643     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13644     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13645     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13646     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13647         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13648             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13649             SendToProgram(buf, cps);
13650             continue;
13651         }
13652         if(cps->nrOptions >= MAX_OPTIONS) {
13653             cps->nrOptions--;
13654             sprintf(buf, "%s engine has too many options\n", cps->which);
13655             DisplayError(buf, 0);
13656         }
13657         continue;
13658     }
13659     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13660     /* End of additions by HGM */
13661
13662     /* unknown feature: complain and skip */
13663     q = p;
13664     while (*q && *q != '=') q++;
13665     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13666     SendToProgram(buf, cps);
13667     p = q;
13668     if (*p == '=') {
13669       p++;
13670       if (*p == '\"') {
13671         p++;
13672         while (*p && *p != '\"') p++;
13673         if (*p == '\"') p++;
13674       } else {
13675         while (*p && *p != ' ') p++;
13676       }
13677     }
13678   }
13679
13680 }
13681
13682 void
13683 PeriodicUpdatesEvent(newState)
13684      int newState;
13685 {
13686     if (newState == appData.periodicUpdates)
13687       return;
13688
13689     appData.periodicUpdates=newState;
13690
13691     /* Display type changes, so update it now */
13692 //    DisplayAnalysis();
13693
13694     /* Get the ball rolling again... */
13695     if (newState) {
13696         AnalysisPeriodicEvent(1);
13697         StartAnalysisClock();
13698     }
13699 }
13700
13701 void
13702 PonderNextMoveEvent(newState)
13703      int newState;
13704 {
13705     if (newState == appData.ponderNextMove) return;
13706     if (gameMode == EditPosition) EditPositionDone(TRUE);
13707     if (newState) {
13708         SendToProgram("hard\n", &first);
13709         if (gameMode == TwoMachinesPlay) {
13710             SendToProgram("hard\n", &second);
13711         }
13712     } else {
13713         SendToProgram("easy\n", &first);
13714         thinkOutput[0] = NULLCHAR;
13715         if (gameMode == TwoMachinesPlay) {
13716             SendToProgram("easy\n", &second);
13717         }
13718     }
13719     appData.ponderNextMove = newState;
13720 }
13721
13722 void
13723 NewSettingEvent(option, command, value)
13724      char *command;
13725      int option, value;
13726 {
13727     char buf[MSG_SIZ];
13728
13729     if (gameMode == EditPosition) EditPositionDone(TRUE);
13730     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13731     SendToProgram(buf, &first);
13732     if (gameMode == TwoMachinesPlay) {
13733         SendToProgram(buf, &second);
13734     }
13735 }
13736
13737 void
13738 ShowThinkingEvent()
13739 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13740 {
13741     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13742     int newState = appData.showThinking
13743         // [HGM] thinking: other features now need thinking output as well
13744         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13745
13746     if (oldState == newState) return;
13747     oldState = newState;
13748     if (gameMode == EditPosition) EditPositionDone(TRUE);
13749     if (oldState) {
13750         SendToProgram("post\n", &first);
13751         if (gameMode == TwoMachinesPlay) {
13752             SendToProgram("post\n", &second);
13753         }
13754     } else {
13755         SendToProgram("nopost\n", &first);
13756         thinkOutput[0] = NULLCHAR;
13757         if (gameMode == TwoMachinesPlay) {
13758             SendToProgram("nopost\n", &second);
13759         }
13760     }
13761 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13762 }
13763
13764 void
13765 AskQuestionEvent(title, question, replyPrefix, which)
13766      char *title; char *question; char *replyPrefix; char *which;
13767 {
13768   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13769   if (pr == NoProc) return;
13770   AskQuestion(title, question, replyPrefix, pr);
13771 }
13772
13773 void
13774 DisplayMove(moveNumber)
13775      int moveNumber;
13776 {
13777     char message[MSG_SIZ];
13778     char res[MSG_SIZ];
13779     char cpThinkOutput[MSG_SIZ];
13780
13781     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13782
13783     if (moveNumber == forwardMostMove - 1 ||
13784         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13785
13786         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13787
13788         if (strchr(cpThinkOutput, '\n')) {
13789             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13790         }
13791     } else {
13792         *cpThinkOutput = NULLCHAR;
13793     }
13794
13795     /* [AS] Hide thinking from human user */
13796     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13797         *cpThinkOutput = NULLCHAR;
13798         if( thinkOutput[0] != NULLCHAR ) {
13799             int i;
13800
13801             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13802                 cpThinkOutput[i] = '.';
13803             }
13804             cpThinkOutput[i] = NULLCHAR;
13805             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13806         }
13807     }
13808
13809     if (moveNumber == forwardMostMove - 1 &&
13810         gameInfo.resultDetails != NULL) {
13811         if (gameInfo.resultDetails[0] == NULLCHAR) {
13812             sprintf(res, " %s", PGNResult(gameInfo.result));
13813         } else {
13814             sprintf(res, " {%s} %s",
13815                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13816         }
13817     } else {
13818         res[0] = NULLCHAR;
13819     }
13820
13821     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13822         DisplayMessage(res, cpThinkOutput);
13823     } else {
13824         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13825                 WhiteOnMove(moveNumber) ? " " : ".. ",
13826                 parseList[moveNumber], res);
13827         DisplayMessage(message, cpThinkOutput);
13828     }
13829 }
13830
13831 void
13832 DisplayComment(moveNumber, text)
13833      int moveNumber;
13834      char *text;
13835 {
13836     char title[MSG_SIZ];
13837     char buf[8000]; // comment can be long!
13838     int score, depth;
13839     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13840       strcpy(title, "Comment");
13841     } else {
13842       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13843               WhiteOnMove(moveNumber) ? " " : ".. ",
13844               parseList[moveNumber]);
13845     }
13846     // [HGM] PV info: display PV info together with (or as) comment
13847     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13848       if(text == NULL) text = "";                                           
13849       score = pvInfoList[moveNumber].score;
13850       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13851               depth, (pvInfoList[moveNumber].time+50)/100, text);
13852       text = buf;
13853     }
13854     if (text != NULL && (appData.autoDisplayComment || commentUp))
13855       CommentPopUp(title, text);
13856 }
13857
13858 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13859  * might be busy thinking or pondering.  It can be omitted if your
13860  * gnuchess is configured to stop thinking immediately on any user
13861  * input.  However, that gnuchess feature depends on the FIONREAD
13862  * ioctl, which does not work properly on some flavors of Unix.
13863  */
13864 void
13865 Attention(cps)
13866      ChessProgramState *cps;
13867 {
13868 #if ATTENTION
13869     if (!cps->useSigint) return;
13870     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13871     switch (gameMode) {
13872       case MachinePlaysWhite:
13873       case MachinePlaysBlack:
13874       case TwoMachinesPlay:
13875       case IcsPlayingWhite:
13876       case IcsPlayingBlack:
13877       case AnalyzeMode:
13878       case AnalyzeFile:
13879         /* Skip if we know it isn't thinking */
13880         if (!cps->maybeThinking) return;
13881         if (appData.debugMode)
13882           fprintf(debugFP, "Interrupting %s\n", cps->which);
13883         InterruptChildProcess(cps->pr);
13884         cps->maybeThinking = FALSE;
13885         break;
13886       default:
13887         break;
13888     }
13889 #endif /*ATTENTION*/
13890 }
13891
13892 int
13893 CheckFlags()
13894 {
13895     if (whiteTimeRemaining <= 0) {
13896         if (!whiteFlag) {
13897             whiteFlag = TRUE;
13898             if (appData.icsActive) {
13899                 if (appData.autoCallFlag &&
13900                     gameMode == IcsPlayingBlack && !blackFlag) {
13901                   SendToICS(ics_prefix);
13902                   SendToICS("flag\n");
13903                 }
13904             } else {
13905                 if (blackFlag) {
13906                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13907                 } else {
13908                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13909                     if (appData.autoCallFlag) {
13910                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13911                         return TRUE;
13912                     }
13913                 }
13914             }
13915         }
13916     }
13917     if (blackTimeRemaining <= 0) {
13918         if (!blackFlag) {
13919             blackFlag = TRUE;
13920             if (appData.icsActive) {
13921                 if (appData.autoCallFlag &&
13922                     gameMode == IcsPlayingWhite && !whiteFlag) {
13923                   SendToICS(ics_prefix);
13924                   SendToICS("flag\n");
13925                 }
13926             } else {
13927                 if (whiteFlag) {
13928                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13929                 } else {
13930                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13931                     if (appData.autoCallFlag) {
13932                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13933                         return TRUE;
13934                     }
13935                 }
13936             }
13937         }
13938     }
13939     return FALSE;
13940 }
13941
13942 void
13943 CheckTimeControl()
13944 {
13945     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13946         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13947
13948     /*
13949      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13950      */
13951     if ( !WhiteOnMove(forwardMostMove) )
13952         /* White made time control */
13953         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13954         /* [HGM] time odds: correct new time quota for time odds! */
13955                                             / WhitePlayer()->timeOdds;
13956       else
13957         /* Black made time control */
13958         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13959                                             / WhitePlayer()->other->timeOdds;
13960 }
13961
13962 void
13963 DisplayBothClocks()
13964 {
13965     int wom = gameMode == EditPosition ?
13966       !blackPlaysFirst : WhiteOnMove(currentMove);
13967     DisplayWhiteClock(whiteTimeRemaining, wom);
13968     DisplayBlackClock(blackTimeRemaining, !wom);
13969 }
13970
13971
13972 /* Timekeeping seems to be a portability nightmare.  I think everyone
13973    has ftime(), but I'm really not sure, so I'm including some ifdefs
13974    to use other calls if you don't.  Clocks will be less accurate if
13975    you have neither ftime nor gettimeofday.
13976 */
13977
13978 /* VS 2008 requires the #include outside of the function */
13979 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13980 #include <sys/timeb.h>
13981 #endif
13982
13983 /* Get the current time as a TimeMark */
13984 void
13985 GetTimeMark(tm)
13986      TimeMark *tm;
13987 {
13988 #if HAVE_GETTIMEOFDAY
13989
13990     struct timeval timeVal;
13991     struct timezone timeZone;
13992
13993     gettimeofday(&timeVal, &timeZone);
13994     tm->sec = (long) timeVal.tv_sec;
13995     tm->ms = (int) (timeVal.tv_usec / 1000L);
13996
13997 #else /*!HAVE_GETTIMEOFDAY*/
13998 #if HAVE_FTIME
13999
14000 // include <sys/timeb.h> / moved to just above start of function
14001     struct timeb timeB;
14002
14003     ftime(&timeB);
14004     tm->sec = (long) timeB.time;
14005     tm->ms = (int) timeB.millitm;
14006
14007 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14008     tm->sec = (long) time(NULL);
14009     tm->ms = 0;
14010 #endif
14011 #endif
14012 }
14013
14014 /* Return the difference in milliseconds between two
14015    time marks.  We assume the difference will fit in a long!
14016 */
14017 long
14018 SubtractTimeMarks(tm2, tm1)
14019      TimeMark *tm2, *tm1;
14020 {
14021     return 1000L*(tm2->sec - tm1->sec) +
14022            (long) (tm2->ms - tm1->ms);
14023 }
14024
14025
14026 /*
14027  * Code to manage the game clocks.
14028  *
14029  * In tournament play, black starts the clock and then white makes a move.
14030  * We give the human user a slight advantage if he is playing white---the
14031  * clocks don't run until he makes his first move, so it takes zero time.
14032  * Also, we don't account for network lag, so we could get out of sync
14033  * with GNU Chess's clock -- but then, referees are always right.
14034  */
14035
14036 static TimeMark tickStartTM;
14037 static long intendedTickLength;
14038
14039 long
14040 NextTickLength(timeRemaining)
14041      long timeRemaining;
14042 {
14043     long nominalTickLength, nextTickLength;
14044
14045     if (timeRemaining > 0L && timeRemaining <= 10000L)
14046       nominalTickLength = 100L;
14047     else
14048       nominalTickLength = 1000L;
14049     nextTickLength = timeRemaining % nominalTickLength;
14050     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14051
14052     return nextTickLength;
14053 }
14054
14055 /* Adjust clock one minute up or down */
14056 void
14057 AdjustClock(Boolean which, int dir)
14058 {
14059     if(which) blackTimeRemaining += 60000*dir;
14060     else      whiteTimeRemaining += 60000*dir;
14061     DisplayBothClocks();
14062 }
14063
14064 /* Stop clocks and reset to a fresh time control */
14065 void
14066 ResetClocks()
14067 {
14068     (void) StopClockTimer();
14069     if (appData.icsActive) {
14070         whiteTimeRemaining = blackTimeRemaining = 0;
14071     } else if (searchTime) {
14072         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14073         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14074     } else { /* [HGM] correct new time quote for time odds */
14075         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14076         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14077     }
14078     if (whiteFlag || blackFlag) {
14079         DisplayTitle("");
14080         whiteFlag = blackFlag = FALSE;
14081     }
14082     DisplayBothClocks();
14083 }
14084
14085 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14086
14087 /* Decrement running clock by amount of time that has passed */
14088 void
14089 DecrementClocks()
14090 {
14091     long timeRemaining;
14092     long lastTickLength, fudge;
14093     TimeMark now;
14094
14095     if (!appData.clockMode) return;
14096     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14097
14098     GetTimeMark(&now);
14099
14100     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14101
14102     /* Fudge if we woke up a little too soon */
14103     fudge = intendedTickLength - lastTickLength;
14104     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14105
14106     if (WhiteOnMove(forwardMostMove)) {
14107         if(whiteNPS >= 0) lastTickLength = 0;
14108         timeRemaining = whiteTimeRemaining -= lastTickLength;
14109         DisplayWhiteClock(whiteTimeRemaining - fudge,
14110                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14111     } else {
14112         if(blackNPS >= 0) lastTickLength = 0;
14113         timeRemaining = blackTimeRemaining -= lastTickLength;
14114         DisplayBlackClock(blackTimeRemaining - fudge,
14115                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14116     }
14117
14118     if (CheckFlags()) return;
14119
14120     tickStartTM = now;
14121     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14122     StartClockTimer(intendedTickLength);
14123
14124     /* if the time remaining has fallen below the alarm threshold, sound the
14125      * alarm. if the alarm has sounded and (due to a takeback or time control
14126      * with increment) the time remaining has increased to a level above the
14127      * threshold, reset the alarm so it can sound again.
14128      */
14129
14130     if (appData.icsActive && appData.icsAlarm) {
14131
14132         /* make sure we are dealing with the user's clock */
14133         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14134                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14135            )) return;
14136
14137         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14138             alarmSounded = FALSE;
14139         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14140             PlayAlarmSound();
14141             alarmSounded = TRUE;
14142         }
14143     }
14144 }
14145
14146
14147 /* A player has just moved, so stop the previously running
14148    clock and (if in clock mode) start the other one.
14149    We redisplay both clocks in case we're in ICS mode, because
14150    ICS gives us an update to both clocks after every move.
14151    Note that this routine is called *after* forwardMostMove
14152    is updated, so the last fractional tick must be subtracted
14153    from the color that is *not* on move now.
14154 */
14155 void
14156 SwitchClocks()
14157 {
14158     long lastTickLength;
14159     TimeMark now;
14160     int flagged = FALSE;
14161
14162     GetTimeMark(&now);
14163
14164     if (StopClockTimer() && appData.clockMode) {
14165         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14166         if (WhiteOnMove(forwardMostMove)) {
14167             if(blackNPS >= 0) lastTickLength = 0;
14168             blackTimeRemaining -= lastTickLength;
14169            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14170 //         if(pvInfoList[forwardMostMove-1].time == -1)
14171                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14172                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14173         } else {
14174            if(whiteNPS >= 0) lastTickLength = 0;
14175            whiteTimeRemaining -= lastTickLength;
14176            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14177 //         if(pvInfoList[forwardMostMove-1].time == -1)
14178                  pvInfoList[forwardMostMove-1].time =
14179                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14180         }
14181         flagged = CheckFlags();
14182     }
14183     CheckTimeControl();
14184
14185     if (flagged || !appData.clockMode) return;
14186
14187     switch (gameMode) {
14188       case MachinePlaysBlack:
14189       case MachinePlaysWhite:
14190       case BeginningOfGame:
14191         if (pausing) return;
14192         break;
14193
14194       case EditGame:
14195       case PlayFromGameFile:
14196       case IcsExamining:
14197         return;
14198
14199       default:
14200         break;
14201     }
14202
14203     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14204         if(WhiteOnMove(forwardMostMove))
14205              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14206         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14207     }
14208
14209     tickStartTM = now;
14210     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14211       whiteTimeRemaining : blackTimeRemaining);
14212     StartClockTimer(intendedTickLength);
14213 }
14214
14215
14216 /* Stop both clocks */
14217 void
14218 StopClocks()
14219 {
14220     long lastTickLength;
14221     TimeMark now;
14222
14223     if (!StopClockTimer()) return;
14224     if (!appData.clockMode) return;
14225
14226     GetTimeMark(&now);
14227
14228     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14229     if (WhiteOnMove(forwardMostMove)) {
14230         if(whiteNPS >= 0) lastTickLength = 0;
14231         whiteTimeRemaining -= lastTickLength;
14232         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14233     } else {
14234         if(blackNPS >= 0) lastTickLength = 0;
14235         blackTimeRemaining -= lastTickLength;
14236         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14237     }
14238     CheckFlags();
14239 }
14240
14241 /* Start clock of player on move.  Time may have been reset, so
14242    if clock is already running, stop and restart it. */
14243 void
14244 StartClocks()
14245 {
14246     (void) StopClockTimer(); /* in case it was running already */
14247     DisplayBothClocks();
14248     if (CheckFlags()) return;
14249
14250     if (!appData.clockMode) return;
14251     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14252
14253     GetTimeMark(&tickStartTM);
14254     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14255       whiteTimeRemaining : blackTimeRemaining);
14256
14257    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14258     whiteNPS = blackNPS = -1;
14259     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14260        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14261         whiteNPS = first.nps;
14262     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14263        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14264         blackNPS = first.nps;
14265     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14266         whiteNPS = second.nps;
14267     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14268         blackNPS = second.nps;
14269     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14270
14271     StartClockTimer(intendedTickLength);
14272 }
14273
14274 char *
14275 TimeString(ms)
14276      long ms;
14277 {
14278     long second, minute, hour, day;
14279     char *sign = "";
14280     static char buf[32];
14281
14282     if (ms > 0 && ms <= 9900) {
14283       /* convert milliseconds to tenths, rounding up */
14284       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14285
14286       sprintf(buf, " %03.1f ", tenths/10.0);
14287       return buf;
14288     }
14289
14290     /* convert milliseconds to seconds, rounding up */
14291     /* use floating point to avoid strangeness of integer division
14292        with negative dividends on many machines */
14293     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14294
14295     if (second < 0) {
14296         sign = "-";
14297         second = -second;
14298     }
14299
14300     day = second / (60 * 60 * 24);
14301     second = second % (60 * 60 * 24);
14302     hour = second / (60 * 60);
14303     second = second % (60 * 60);
14304     minute = second / 60;
14305     second = second % 60;
14306
14307     if (day > 0)
14308       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14309               sign, day, hour, minute, second);
14310     else if (hour > 0)
14311       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14312     else
14313       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14314
14315     return buf;
14316 }
14317
14318
14319 /*
14320  * This is necessary because some C libraries aren't ANSI C compliant yet.
14321  */
14322 char *
14323 StrStr(string, match)
14324      char *string, *match;
14325 {
14326     int i, length;
14327
14328     length = strlen(match);
14329
14330     for (i = strlen(string) - length; i >= 0; i--, string++)
14331       if (!strncmp(match, string, length))
14332         return string;
14333
14334     return NULL;
14335 }
14336
14337 char *
14338 StrCaseStr(string, match)
14339      char *string, *match;
14340 {
14341     int i, j, length;
14342
14343     length = strlen(match);
14344
14345     for (i = strlen(string) - length; i >= 0; i--, string++) {
14346         for (j = 0; j < length; j++) {
14347             if (ToLower(match[j]) != ToLower(string[j]))
14348               break;
14349         }
14350         if (j == length) return string;
14351     }
14352
14353     return NULL;
14354 }
14355
14356 #ifndef _amigados
14357 int
14358 StrCaseCmp(s1, s2)
14359      char *s1, *s2;
14360 {
14361     char c1, c2;
14362
14363     for (;;) {
14364         c1 = ToLower(*s1++);
14365         c2 = ToLower(*s2++);
14366         if (c1 > c2) return 1;
14367         if (c1 < c2) return -1;
14368         if (c1 == NULLCHAR) return 0;
14369     }
14370 }
14371
14372
14373 int
14374 ToLower(c)
14375      int c;
14376 {
14377     return isupper(c) ? tolower(c) : c;
14378 }
14379
14380
14381 int
14382 ToUpper(c)
14383      int c;
14384 {
14385     return islower(c) ? toupper(c) : c;
14386 }
14387 #endif /* !_amigados    */
14388
14389 char *
14390 StrSave(s)
14391      char *s;
14392 {
14393     char *ret;
14394
14395     if ((ret = (char *) malloc(strlen(s) + 1))) {
14396         strcpy(ret, s);
14397     }
14398     return ret;
14399 }
14400
14401 char *
14402 StrSavePtr(s, savePtr)
14403      char *s, **savePtr;
14404 {
14405     if (*savePtr) {
14406         free(*savePtr);
14407     }
14408     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14409         strcpy(*savePtr, s);
14410     }
14411     return(*savePtr);
14412 }
14413
14414 char *
14415 PGNDate()
14416 {
14417     time_t clock;
14418     struct tm *tm;
14419     char buf[MSG_SIZ];
14420
14421     clock = time((time_t *)NULL);
14422     tm = localtime(&clock);
14423     sprintf(buf, "%04d.%02d.%02d",
14424             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14425     return StrSave(buf);
14426 }
14427
14428
14429 char *
14430 PositionToFEN(move, overrideCastling)
14431      int move;
14432      char *overrideCastling;
14433 {
14434     int i, j, fromX, fromY, toX, toY;
14435     int whiteToPlay;
14436     char buf[128];
14437     char *p, *q;
14438     int emptycount;
14439     ChessSquare piece;
14440
14441     whiteToPlay = (gameMode == EditPosition) ?
14442       !blackPlaysFirst : (move % 2 == 0);
14443     p = buf;
14444
14445     /* Piece placement data */
14446     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14447         emptycount = 0;
14448         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14449             if (boards[move][i][j] == EmptySquare) {
14450                 emptycount++;
14451             } else { ChessSquare piece = boards[move][i][j];
14452                 if (emptycount > 0) {
14453                     if(emptycount<10) /* [HGM] can be >= 10 */
14454                         *p++ = '0' + emptycount;
14455                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14456                     emptycount = 0;
14457                 }
14458                 if(PieceToChar(piece) == '+') {
14459                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14460                     *p++ = '+';
14461                     piece = (ChessSquare)(DEMOTED piece);
14462                 }
14463                 *p++ = PieceToChar(piece);
14464                 if(p[-1] == '~') {
14465                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14466                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14467                     *p++ = '~';
14468                 }
14469             }
14470         }
14471         if (emptycount > 0) {
14472             if(emptycount<10) /* [HGM] can be >= 10 */
14473                 *p++ = '0' + emptycount;
14474             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14475             emptycount = 0;
14476         }
14477         *p++ = '/';
14478     }
14479     *(p - 1) = ' ';
14480
14481     /* [HGM] print Crazyhouse or Shogi holdings */
14482     if( gameInfo.holdingsWidth ) {
14483         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14484         q = p;
14485         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14486             piece = boards[move][i][BOARD_WIDTH-1];
14487             if( piece != EmptySquare )
14488               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14489                   *p++ = PieceToChar(piece);
14490         }
14491         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14492             piece = boards[move][BOARD_HEIGHT-i-1][0];
14493             if( piece != EmptySquare )
14494               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14495                   *p++ = PieceToChar(piece);
14496         }
14497
14498         if( q == p ) *p++ = '-';
14499         *p++ = ']';
14500         *p++ = ' ';
14501     }
14502
14503     /* Active color */
14504     *p++ = whiteToPlay ? 'w' : 'b';
14505     *p++ = ' ';
14506
14507   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14508     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14509   } else {
14510   if(nrCastlingRights) {
14511      q = p;
14512      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14513        /* [HGM] write directly from rights */
14514            if(boards[move][CASTLING][2] != NoRights &&
14515               boards[move][CASTLING][0] != NoRights   )
14516                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14517            if(boards[move][CASTLING][2] != NoRights &&
14518               boards[move][CASTLING][1] != NoRights   )
14519                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14520            if(boards[move][CASTLING][5] != NoRights &&
14521               boards[move][CASTLING][3] != NoRights   )
14522                 *p++ = boards[move][CASTLING][3] + AAA;
14523            if(boards[move][CASTLING][5] != NoRights &&
14524               boards[move][CASTLING][4] != NoRights   )
14525                 *p++ = boards[move][CASTLING][4] + AAA;
14526      } else {
14527
14528         /* [HGM] write true castling rights */
14529         if( nrCastlingRights == 6 ) {
14530             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14531                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14532             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14533                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14534             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14535                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14536             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14537                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14538         }
14539      }
14540      if (q == p) *p++ = '-'; /* No castling rights */
14541      *p++ = ' ';
14542   }
14543
14544   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14545      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14546     /* En passant target square */
14547     if (move > backwardMostMove) {
14548         fromX = moveList[move - 1][0] - AAA;
14549         fromY = moveList[move - 1][1] - ONE;
14550         toX = moveList[move - 1][2] - AAA;
14551         toY = moveList[move - 1][3] - ONE;
14552         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14553             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14554             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14555             fromX == toX) {
14556             /* 2-square pawn move just happened */
14557             *p++ = toX + AAA;
14558             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14559         } else {
14560             *p++ = '-';
14561         }
14562     } else if(move == backwardMostMove) {
14563         // [HGM] perhaps we should always do it like this, and forget the above?
14564         if((signed char)boards[move][EP_STATUS] >= 0) {
14565             *p++ = boards[move][EP_STATUS] + AAA;
14566             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14567         } else {
14568             *p++ = '-';
14569         }
14570     } else {
14571         *p++ = '-';
14572     }
14573     *p++ = ' ';
14574   }
14575   }
14576
14577     /* [HGM] find reversible plies */
14578     {   int i = 0, j=move;
14579
14580         if (appData.debugMode) { int k;
14581             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14582             for(k=backwardMostMove; k<=forwardMostMove; k++)
14583                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14584
14585         }
14586
14587         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14588         if( j == backwardMostMove ) i += initialRulePlies;
14589         sprintf(p, "%d ", i);
14590         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14591     }
14592     /* Fullmove number */
14593     sprintf(p, "%d", (move / 2) + 1);
14594
14595     return StrSave(buf);
14596 }
14597
14598 Boolean
14599 ParseFEN(board, blackPlaysFirst, fen)
14600     Board board;
14601      int *blackPlaysFirst;
14602      char *fen;
14603 {
14604     int i, j;
14605     char *p;
14606     int emptycount;
14607     ChessSquare piece;
14608
14609     p = fen;
14610
14611     /* [HGM] by default clear Crazyhouse holdings, if present */
14612     if(gameInfo.holdingsWidth) {
14613        for(i=0; i<BOARD_HEIGHT; i++) {
14614            board[i][0]             = EmptySquare; /* black holdings */
14615            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14616            board[i][1]             = (ChessSquare) 0; /* black counts */
14617            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14618        }
14619     }
14620
14621     /* Piece placement data */
14622     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14623         j = 0;
14624         for (;;) {
14625             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14626                 if (*p == '/') p++;
14627                 emptycount = gameInfo.boardWidth - j;
14628                 while (emptycount--)
14629                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14630                 break;
14631 #if(BOARD_FILES >= 10)
14632             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14633                 p++; emptycount=10;
14634                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14635                 while (emptycount--)
14636                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14637 #endif
14638             } else if (isdigit(*p)) {
14639                 emptycount = *p++ - '0';
14640                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14641                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14642                 while (emptycount--)
14643                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14644             } else if (*p == '+' || isalpha(*p)) {
14645                 if (j >= gameInfo.boardWidth) return FALSE;
14646                 if(*p=='+') {
14647                     piece = CharToPiece(*++p);
14648                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14649                     piece = (ChessSquare) (PROMOTED piece ); p++;
14650                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14651                 } else piece = CharToPiece(*p++);
14652
14653                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14654                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14655                     piece = (ChessSquare) (PROMOTED piece);
14656                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14657                     p++;
14658                 }
14659                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14660             } else {
14661                 return FALSE;
14662             }
14663         }
14664     }
14665     while (*p == '/' || *p == ' ') p++;
14666
14667     /* [HGM] look for Crazyhouse holdings here */
14668     while(*p==' ') p++;
14669     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14670         if(*p == '[') p++;
14671         if(*p == '-' ) *p++; /* empty holdings */ else {
14672             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14673             /* if we would allow FEN reading to set board size, we would   */
14674             /* have to add holdings and shift the board read so far here   */
14675             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14676                 *p++;
14677                 if((int) piece >= (int) BlackPawn ) {
14678                     i = (int)piece - (int)BlackPawn;
14679                     i = PieceToNumber((ChessSquare)i);
14680                     if( i >= gameInfo.holdingsSize ) return FALSE;
14681                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14682                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14683                 } else {
14684                     i = (int)piece - (int)WhitePawn;
14685                     i = PieceToNumber((ChessSquare)i);
14686                     if( i >= gameInfo.holdingsSize ) return FALSE;
14687                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14688                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14689                 }
14690             }
14691         }
14692         if(*p == ']') *p++;
14693     }
14694
14695     while(*p == ' ') p++;
14696
14697     /* Active color */
14698     switch (*p++) {
14699       case 'w':
14700         *blackPlaysFirst = FALSE;
14701         break;
14702       case 'b':
14703         *blackPlaysFirst = TRUE;
14704         break;
14705       default:
14706         return FALSE;
14707     }
14708
14709     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14710     /* return the extra info in global variiables             */
14711
14712     /* set defaults in case FEN is incomplete */
14713     board[EP_STATUS] = EP_UNKNOWN;
14714     for(i=0; i<nrCastlingRights; i++ ) {
14715         board[CASTLING][i] =
14716             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14717     }   /* assume possible unless obviously impossible */
14718     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14719     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14720     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14721                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14722     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14723     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14724     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14725                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14726     FENrulePlies = 0;
14727
14728     while(*p==' ') p++;
14729     if(nrCastlingRights) {
14730       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14731           /* castling indicator present, so default becomes no castlings */
14732           for(i=0; i<nrCastlingRights; i++ ) {
14733                  board[CASTLING][i] = NoRights;
14734           }
14735       }
14736       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14737              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14738              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14739              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14740         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14741
14742         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14743             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14744             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14745         }
14746         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14747             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14748         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14749                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14750         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14751                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14752         switch(c) {
14753           case'K':
14754               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14755               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14756               board[CASTLING][2] = whiteKingFile;
14757               break;
14758           case'Q':
14759               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14760               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14761               board[CASTLING][2] = whiteKingFile;
14762               break;
14763           case'k':
14764               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14765               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14766               board[CASTLING][5] = blackKingFile;
14767               break;
14768           case'q':
14769               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14770               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14771               board[CASTLING][5] = blackKingFile;
14772           case '-':
14773               break;
14774           default: /* FRC castlings */
14775               if(c >= 'a') { /* black rights */
14776                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14777                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14778                   if(i == BOARD_RGHT) break;
14779                   board[CASTLING][5] = i;
14780                   c -= AAA;
14781                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14782                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14783                   if(c > i)
14784                       board[CASTLING][3] = c;
14785                   else
14786                       board[CASTLING][4] = c;
14787               } else { /* white rights */
14788                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14789                     if(board[0][i] == WhiteKing) break;
14790                   if(i == BOARD_RGHT) break;
14791                   board[CASTLING][2] = i;
14792                   c -= AAA - 'a' + 'A';
14793                   if(board[0][c] >= WhiteKing) break;
14794                   if(c > i)
14795                       board[CASTLING][0] = c;
14796                   else
14797                       board[CASTLING][1] = c;
14798               }
14799         }
14800       }
14801       for(i=0; i<nrCastlingRights; i++)
14802         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14803     if (appData.debugMode) {
14804         fprintf(debugFP, "FEN castling rights:");
14805         for(i=0; i<nrCastlingRights; i++)
14806         fprintf(debugFP, " %d", board[CASTLING][i]);
14807         fprintf(debugFP, "\n");
14808     }
14809
14810       while(*p==' ') p++;
14811     }
14812
14813     /* read e.p. field in games that know e.p. capture */
14814     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14815        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14816       if(*p=='-') {
14817         p++; board[EP_STATUS] = EP_NONE;
14818       } else {
14819          char c = *p++ - AAA;
14820
14821          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14822          if(*p >= '0' && *p <='9') *p++;
14823          board[EP_STATUS] = c;
14824       }
14825     }
14826
14827
14828     if(sscanf(p, "%d", &i) == 1) {
14829         FENrulePlies = i; /* 50-move ply counter */
14830         /* (The move number is still ignored)    */
14831     }
14832
14833     return TRUE;
14834 }
14835
14836 void
14837 EditPositionPasteFEN(char *fen)
14838 {
14839   if (fen != NULL) {
14840     Board initial_position;
14841
14842     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14843       DisplayError(_("Bad FEN position in clipboard"), 0);
14844       return ;
14845     } else {
14846       int savedBlackPlaysFirst = blackPlaysFirst;
14847       EditPositionEvent();
14848       blackPlaysFirst = savedBlackPlaysFirst;
14849       CopyBoard(boards[0], initial_position);
14850       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14851       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14852       DisplayBothClocks();
14853       DrawPosition(FALSE, boards[currentMove]);
14854     }
14855   }
14856 }
14857
14858 static char cseq[12] = "\\   ";
14859
14860 Boolean set_cont_sequence(char *new_seq)
14861 {
14862     int len;
14863     Boolean ret;
14864
14865     // handle bad attempts to set the sequence
14866         if (!new_seq)
14867                 return 0; // acceptable error - no debug
14868
14869     len = strlen(new_seq);
14870     ret = (len > 0) && (len < sizeof(cseq));
14871     if (ret)
14872         strcpy(cseq, new_seq);
14873     else if (appData.debugMode)
14874         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14875     return ret;
14876 }
14877
14878 /*
14879     reformat a source message so words don't cross the width boundary.  internal
14880     newlines are not removed.  returns the wrapped size (no null character unless
14881     included in source message).  If dest is NULL, only calculate the size required
14882     for the dest buffer.  lp argument indicats line position upon entry, and it's
14883     passed back upon exit.
14884 */
14885 int wrap(char *dest, char *src, int count, int width, int *lp)
14886 {
14887     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14888
14889     cseq_len = strlen(cseq);
14890     old_line = line = *lp;
14891     ansi = len = clen = 0;
14892
14893     for (i=0; i < count; i++)
14894     {
14895         if (src[i] == '\033')
14896             ansi = 1;
14897
14898         // if we hit the width, back up
14899         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14900         {
14901             // store i & len in case the word is too long
14902             old_i = i, old_len = len;
14903
14904             // find the end of the last word
14905             while (i && src[i] != ' ' && src[i] != '\n')
14906             {
14907                 i--;
14908                 len--;
14909             }
14910
14911             // word too long?  restore i & len before splitting it
14912             if ((old_i-i+clen) >= width)
14913             {
14914                 i = old_i;
14915                 len = old_len;
14916             }
14917
14918             // extra space?
14919             if (i && src[i-1] == ' ')
14920                 len--;
14921
14922             if (src[i] != ' ' && src[i] != '\n')
14923             {
14924                 i--;
14925                 if (len)
14926                     len--;
14927             }
14928
14929             // now append the newline and continuation sequence
14930             if (dest)
14931                 dest[len] = '\n';
14932             len++;
14933             if (dest)
14934                 strncpy(dest+len, cseq, cseq_len);
14935             len += cseq_len;
14936             line = cseq_len;
14937             clen = cseq_len;
14938             continue;
14939         }
14940
14941         if (dest)
14942             dest[len] = src[i];
14943         len++;
14944         if (!ansi)
14945             line++;
14946         if (src[i] == '\n')
14947             line = 0;
14948         if (src[i] == 'm')
14949             ansi = 0;
14950     }
14951     if (dest && appData.debugMode)
14952     {
14953         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14954             count, width, line, len, *lp);
14955         show_bytes(debugFP, src, count);
14956         fprintf(debugFP, "\ndest: ");
14957         show_bytes(debugFP, dest, len);
14958         fprintf(debugFP, "\n");
14959     }
14960     *lp = dest ? line : old_line;
14961
14962     return len;
14963 }
14964
14965 // [HGM] vari: routines for shelving variations
14966
14967 void 
14968 PushTail(int firstMove, int lastMove)
14969 {
14970         int i, j, nrMoves = lastMove - firstMove;
14971
14972         if(appData.icsActive) { // only in local mode
14973                 forwardMostMove = currentMove; // mimic old ICS behavior
14974                 return;
14975         }
14976         if(storedGames >= MAX_VARIATIONS-1) return;
14977
14978         // push current tail of game on stack
14979         savedResult[storedGames] = gameInfo.result;
14980         savedDetails[storedGames] = gameInfo.resultDetails;
14981         gameInfo.resultDetails = NULL;
14982         savedFirst[storedGames] = firstMove;
14983         savedLast [storedGames] = lastMove;
14984         savedFramePtr[storedGames] = framePtr;
14985         framePtr -= nrMoves; // reserve space for the boards
14986         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14987             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14988             for(j=0; j<MOVE_LEN; j++)
14989                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14990             for(j=0; j<2*MOVE_LEN; j++)
14991                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14992             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14993             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14994             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14995             pvInfoList[firstMove+i-1].depth = 0;
14996             commentList[framePtr+i] = commentList[firstMove+i];
14997             commentList[firstMove+i] = NULL;
14998         }
14999
15000         storedGames++;
15001         forwardMostMove = currentMove; // truncte game so we can start variation
15002         if(storedGames == 1) GreyRevert(FALSE);
15003 }
15004
15005 Boolean
15006 PopTail(Boolean annotate)
15007 {
15008         int i, j, nrMoves;
15009         char buf[8000], moveBuf[20];
15010
15011         if(appData.icsActive) return FALSE; // only in local mode
15012         if(!storedGames) return FALSE; // sanity
15013
15014         storedGames--;
15015         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15016         nrMoves = savedLast[storedGames] - currentMove;
15017         if(annotate) {
15018                 int cnt = 10;
15019                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15020                 else strcpy(buf, "(");
15021                 for(i=currentMove; i<forwardMostMove; i++) {
15022                         if(WhiteOnMove(i))
15023                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15024                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15025                         strcat(buf, moveBuf);
15026                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15027                 }
15028                 strcat(buf, ")");
15029         }
15030         for(i=1; i<nrMoves; i++) { // copy last variation back
15031             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15032             for(j=0; j<MOVE_LEN; j++)
15033                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15034             for(j=0; j<2*MOVE_LEN; j++)
15035                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15036             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15037             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15038             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15039             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15040             commentList[currentMove+i] = commentList[framePtr+i];
15041             commentList[framePtr+i] = NULL;
15042         }
15043         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15044         framePtr = savedFramePtr[storedGames];
15045         gameInfo.result = savedResult[storedGames];
15046         if(gameInfo.resultDetails != NULL) {
15047             free(gameInfo.resultDetails);
15048       }
15049         gameInfo.resultDetails = savedDetails[storedGames];
15050         forwardMostMove = currentMove + nrMoves;
15051         if(storedGames == 0) GreyRevert(TRUE);
15052         return TRUE;
15053 }
15054
15055 void 
15056 CleanupTail()
15057 {       // remove all shelved variations
15058         int i;
15059         for(i=0; i<storedGames; i++) {
15060             if(savedDetails[i])
15061                 free(savedDetails[i]);
15062             savedDetails[i] = NULL;
15063         }
15064         for(i=framePtr; i<MAX_MOVES; i++) {
15065                 if(commentList[i]) free(commentList[i]);
15066                 commentList[i] = NULL;
15067         }
15068         framePtr = MAX_MOVES-1;
15069         storedGames = 0;
15070 }