8feb74a81db19a540a91bc3b4cad50a013aac2c6
[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 int hTab[(int)EmptySquare/2+1] = { 1,1,1,1,1,1,2,1,2,3,2,3,3,3,2,3,4,3,3,4,4,3,4 };
6100 int wTab[(int)EmptySquare/2+1] = { 1,1,2,3,4,5,3,7,4,3,5,4,4,5,7,5,4,6,6,5,5,7,6 };
6101 Board promoBoard;
6102 int promotionChoice = 0;
6103
6104 void
6105 PiecePopUp(int x, int y)
6106 {
6107     int i, j, h, w, nWhite=0, nBlack=0;
6108     ChessSquare list[EmptySquare];
6109     for(i=0; i<EmptySquare/2; i++) {
6110         if(PieceToChar(i) != '.') list[nWhite++] = i;
6111         if(PieceToChar(i+EmptySquare/2) != '.') list[EmptySquare - ++nBlack] = i + EmptySquare/2;
6112     }
6113     CopyBoard(promoBoard, boards[currentMove]);
6114     for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) promoBoard[i][j] = EmptySquare;
6115     j = nWhite + nBlack + 1;
6116     h = sqrt((j+1)/2 + 1.); w = (j+h-1)/h;
6117     if(w>BOARD_RGHT-BOARD_LEFT) { w = BOARD_RGHT - BOARD_LEFT; h = (j+w-1)/w; }
6118     for(i=0; i<nWhite; i++) promoBoard[i/w][BOARD_LEFT+i%w] = list[nWhite-1-i];
6119     if(h==2 && nWhite == nBlack)
6120         for(i=0; i<nWhite; i++) promoBoard[1][BOARD_LEFT+i%w] = list[EmptySquare-nBlack+i];
6121     else
6122         for(i=0; i<nBlack; i++) promoBoard[h-1-i/w][BOARD_LEFT+w-1-i%w] = list[EmptySquare-nBlack+i];
6123     promotionChoice = 3;
6124     ClearHighlights();
6125     PromoDialog(h, w, promoBoard, TRUE, _("Select piece:"), x, y);
6126 }
6127
6128 void
6129 PromoPopUp(ChessSquare piece)
6130 {   // determine the layout of the piece-choice dialog
6131     int w, h, i, j, nr;
6132     ChessSquare list[EmptySquare];
6133
6134     for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) promoBoard[i][j] = EmptySquare;
6135     if(gameInfo.variant == VariantShogi) {
6136         // non-Pawn promotes; must be shogi
6137         h = 1; w = 1; promoBoard[0][BOARD_LEFT+0] = piece;
6138         if(PieceToChar(PROMOTED piece) != '.') {
6139             // promoted version is enabled
6140             w = 2; promoBoard[0][BOARD_LEFT+1] = PROMOTED piece;
6141         }
6142     } else {
6143         // Pawn, promotes to any enabled other piece
6144         h = 1; w = nr = 0;
6145         for(i=1; i<EmptySquare/2; i++) {
6146             if(PieceToChar(piece+i) != '.' 
6147            && PieceToChar(piece + i) != '~' // suppress bughouse true pieces
6148                                                         ) {
6149                 list[w++] = piece+i; nr++;
6150             }
6151         }
6152         if(appData.testLegality && gameInfo.variant != VariantSuicide
6153                         && gameInfo.variant != VariantGiveaway) nr--,w--; // remove King
6154         h = hTab[nr]; w = wTab[nr]; // factorize with nice ratio
6155         for(i=0; i < nr; i++) promoBoard[i/w][BOARD_LEFT+i%w] = list[i]; // layout
6156     }
6157     promotionChoice = 2; // wait for click on board
6158     PromoDialog(h, w, promoBoard, FALSE, _("Promote to:"), -1, -1);
6159 }
6160
6161 void
6162 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6163      Board board;
6164      int flags;
6165      ChessMove kind;
6166      int rf, ff, rt, ft;
6167      VOIDSTAR closure;
6168 {
6169     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6170     Markers *m = (Markers *) closure;
6171     if(rf == fromY && ff == fromX)
6172         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6173                          || kind == WhiteCapturesEnPassant
6174                          || kind == BlackCapturesEnPassant);
6175     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6176 }
6177
6178 void
6179 MarkTargetSquares(int clear)
6180 {
6181   int x, y;
6182   if(!appData.markers || !appData.highlightDragging || 
6183      !appData.testLegality || gameMode == EditPosition) return;
6184   if(clear) {
6185     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6186   } else {
6187     int capt = 0;
6188     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6189     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6190       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6191       if(capt)
6192       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6193     }
6194   }
6195   DrawPosition(TRUE, NULL);
6196 }
6197
6198 void LeftClick(ClickType clickType, int xPix, int yPix)
6199 {
6200     int x, y;
6201     Boolean saveAnimate;
6202     static int second = 0;
6203     char promoChoice = NULLCHAR;
6204
6205     if(appData.seekGraph && appData.icsActive && loggedOn &&
6206         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6207         SeekGraphClick(clickType, xPix, yPix, 0);
6208         return;
6209     }
6210
6211     if (clickType == Press) ErrorPopDown();
6212     MarkTargetSquares(1);
6213
6214     x = EventToSquare(xPix, BOARD_WIDTH);
6215     y = EventToSquare(yPix, BOARD_HEIGHT);
6216     if (!flipView && y >= 0) {
6217         y = BOARD_HEIGHT - 1 - y;
6218     }
6219     if (flipView && x >= 0) {
6220         x = BOARD_WIDTH - 1 - x;
6221     }
6222
6223     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6224         ChessSquare p = EmptySquare; Boolean inHoldings;
6225         if(promotionChoice == 3) {
6226             if(clickType == Press) EditPositionMenuEvent(promoBoard[y][x], fromX, fromY);
6227             else if(clickType == Release) promotionChoice = 0;
6228             fromX = fromY = -1;
6229             return;
6230         }
6231         if(clickType == Release) return; // ignore upclick of click-click destination
6232         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6233         inHoldings = gameInfo.holdingsWidth &&
6234                 (WhiteOnMove(currentMove) 
6235                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6236                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1);
6237             // click in right holdings, for determining promotion piece
6238         if(promotionChoice == 1 && inHoldings || promotionChoice == 2 && x >= BOARD_LEFT && x < BOARD_RGHT) {
6239             p = promoBoard[y][x];
6240             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6241             if(p != EmptySquare) {
6242                 char promoChar = PieceToChar(p);
6243                 if(gameInfo.variant == VariantShogi && promoChar != '+') promoChar = '=';
6244                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(promoChar));
6245                 fromX = fromY = -1;
6246                 promotionChoice = 0;
6247                 return;
6248             }
6249         }
6250         promotionChoice = 0; // only one chance: if click not OK it is interpreted as cancel
6251         DrawPosition(FALSE, boards[currentMove]);
6252         return;
6253     }
6254
6255     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6256     if(clickType == Press
6257             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6258               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6259               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6260         return;
6261
6262     if (fromX == -1) {
6263       if(!appData.oneClick || !OnlyMove(&x, &y)) {
6264         if (clickType == Press) {
6265             /* First square */
6266             if (OKToStartUserMove(x, y)) {
6267                 fromX = x;
6268                 fromY = y;
6269                 second = 0;
6270                 MarkTargetSquares(0);
6271                 DragPieceBegin(xPix, yPix);
6272                 if (appData.highlightDragging) {
6273                     SetHighlights(x, y, -1, -1);
6274                 }
6275             }
6276         }
6277         return;
6278       }
6279     }
6280
6281     /* fromX != -1 */
6282     if (clickType == Press && gameMode != EditPosition) {
6283         ChessSquare fromP;
6284         ChessSquare toP;
6285         int frc;
6286
6287         // ignore off-board to clicks
6288         if(y < 0 || x < 0) return;
6289
6290         /* Check if clicking again on the same color piece */
6291         fromP = boards[currentMove][fromY][fromX];
6292         toP = boards[currentMove][y][x];
6293         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6294         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6295              WhitePawn <= toP && toP <= WhiteKing &&
6296              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6297              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6298             (BlackPawn <= fromP && fromP <= BlackKing && 
6299              BlackPawn <= toP && toP <= BlackKing &&
6300              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6301              !(fromP == BlackKing && toP == BlackRook && frc))) {
6302             /* Clicked again on same color piece -- changed his mind */
6303             second = (x == fromX && y == fromY);
6304             if (appData.highlightDragging) {
6305                 SetHighlights(x, y, -1, -1);
6306             } else {
6307                 ClearHighlights();
6308             }
6309             if (OKToStartUserMove(x, y)) {
6310                 fromX = x;
6311                 fromY = y;
6312                 MarkTargetSquares(0);
6313                 DragPieceBegin(xPix, yPix);
6314             }
6315             return;
6316         }
6317         // ignore clicks on holdings
6318         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6319     }
6320
6321     if (clickType == Release && x == fromX && y == fromY) {
6322         DragPieceEnd(xPix, yPix);
6323         if (appData.animateDragging) {
6324             /* Undo animation damage if any */
6325             DrawPosition(FALSE, NULL);
6326         }
6327         if (second) {
6328             /* Second up/down in same square; just abort move */
6329             second = 0;
6330             fromX = fromY = -1;
6331             ClearHighlights();
6332             gotPremove = 0;
6333             ClearPremoveHighlights();
6334         } else {
6335             /* First upclick in same square; start click-click mode */
6336             SetHighlights(x, y, -1, -1);
6337         }
6338         return;
6339     }
6340
6341     /* we now have a different from- and (possibly off-board) to-square */
6342     /* Completed move */
6343     toX = x;
6344     toY = y;
6345     saveAnimate = appData.animate;
6346     if (clickType == Press) {
6347         /* Finish clickclick move */
6348         if (appData.animate || appData.highlightLastMove) {
6349             SetHighlights(fromX, fromY, toX, toY);
6350         } else {
6351             ClearHighlights();
6352         }
6353     } else {
6354         /* Finish drag move */
6355         if (appData.highlightLastMove) {
6356             SetHighlights(fromX, fromY, toX, toY);
6357         } else {
6358             ClearHighlights();
6359         }
6360         DragPieceEnd(xPix, yPix);
6361         /* Don't animate move and drag both */
6362         appData.animate = FALSE;
6363     }
6364
6365     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6366     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6367         ChessSquare piece = boards[currentMove][fromY][fromX];
6368         if(gameMode == EditPosition && piece != EmptySquare &&
6369            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6370             int n;
6371              
6372             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6373                 n = PieceToNumber(piece - (int)BlackPawn);
6374                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6375                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6376                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6377             } else
6378             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6379                 n = PieceToNumber(piece);
6380                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6381                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6382                 boards[currentMove][n][BOARD_WIDTH-2]++;
6383             }
6384             boards[currentMove][fromY][fromX] = EmptySquare;
6385         }
6386         ClearHighlights();
6387         fromX = fromY = -1;
6388         DrawPosition(TRUE, boards[currentMove]);
6389         return;
6390     }
6391
6392     // off-board moves should not be highlighted
6393     if(x < 0 || x < 0) ClearHighlights();
6394
6395     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6396         SetHighlights(fromX, fromY, toX, toY);
6397         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6398             // [HGM] super: promotion to captured piece selected from holdings
6399             ChessSquare p = boards[currentMove][fromY][fromX];
6400             promotionChoice = 1;
6401             CopyBoard(promoBoard, boards[currentMove]);
6402             // kludge follows to temporarily execute move on display, without promoting yet
6403             promoBoard[fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6404             promoBoard[toY][toX] = p;
6405             DrawPosition(FALSE, promoBoard);
6406             DisplayMessage("Click in holdings to choose piece", "");
6407             return;
6408         }
6409         CopyBoard(promoBoard, boards[currentMove]);
6410         PromoPopUp(boards[currentMove][fromY][fromX]);
6411     } else {
6412         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6413         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6414         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6415         fromX = fromY = -1;
6416     }
6417     appData.animate = saveAnimate;
6418     if (appData.animate || appData.animateDragging) {
6419         /* Undo animation damage if needed */
6420         DrawPosition(FALSE, NULL);
6421     }
6422 }
6423
6424 int RightClick(ClickType action, int x, int y, int *xx, int *yy)
6425 {   // front-end-free part taken out of PieceMenuPopup
6426     int whichMenu; int xSqr, ySqr;
6427
6428     if(seekGraphUp) { // [HGM] seekgraph
6429         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6430         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6431         return -2;
6432     }
6433
6434     xSqr = EventToSquare(x, BOARD_WIDTH);
6435     ySqr = EventToSquare(y, BOARD_HEIGHT);
6436     if (flipView)
6437       xSqr = BOARD_WIDTH - 1 - xSqr;
6438     else
6439       ySqr = BOARD_HEIGHT - 1 - ySqr;
6440     if(promotionChoice == 3 && action == Release) {
6441         EditPositionMenuEvent(promoBoard[ySqr][xSqr], fromX, fromY);
6442         fromX = fromY = -1;
6443         promotionChoice = 0;
6444         return -1;
6445     }
6446     if (action == Release) UnLoadPV(); // [HGM] pv
6447     if (action != Press) return -2; // return code to be ignored
6448     switch (gameMode) {
6449       case IcsExamining:
6450         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
6451       case EditPosition:
6452         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
6453         if (xSqr < 0 || ySqr < 0) return -1;
6454         whichMenu = 0; // edit-position menu
6455         break;
6456       case IcsObserving:
6457         if(!appData.icsEngineAnalyze) return -1;
6458       case IcsPlayingWhite:
6459       case IcsPlayingBlack:
6460         if(!appData.zippyPlay) goto noZip;
6461       case AnalyzeMode:
6462       case AnalyzeFile:
6463       case MachinePlaysWhite:
6464       case MachinePlaysBlack:
6465       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6466         if (!appData.dropMenu) {
6467           LoadPV(x, y);
6468           return 2; // flag front-end to grab mouse events
6469         }
6470         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6471            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6472       case EditGame:
6473       noZip:
6474         if (xSqr < 0 || ySqr < 0) return -1;
6475         if (!appData.dropMenu || appData.testLegality &&
6476             gameInfo.variant != VariantBughouse &&
6477             gameInfo.variant != VariantCrazyhouse) return -1;
6478         whichMenu = 1; // drop menu
6479         break;
6480       default:
6481         return -1;
6482     }
6483
6484     if (((*xx = xSqr) < 0) ||
6485         ((*yy = ySqr) < 0)) {
6486         *xx = *yy = -1;
6487         return -1;
6488     }
6489
6490     fromX = *xx; fromY = *yy;
6491     if(whichMenu == 0) { PiecePopUp(x, y); return -1; } // suppress EditPosition menu
6492
6493     return whichMenu;
6494 }
6495
6496 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6497 {
6498 //    char * hint = lastHint;
6499     FrontEndProgramStats stats;
6500
6501     stats.which = cps == &first ? 0 : 1;
6502     stats.depth = cpstats->depth;
6503     stats.nodes = cpstats->nodes;
6504     stats.score = cpstats->score;
6505     stats.time = cpstats->time;
6506     stats.pv = cpstats->movelist;
6507     stats.hint = lastHint;
6508     stats.an_move_index = 0;
6509     stats.an_move_count = 0;
6510
6511     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6512         stats.hint = cpstats->move_name;
6513         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6514         stats.an_move_count = cpstats->nr_moves;
6515     }
6516
6517     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6518
6519     SetProgramStats( &stats );
6520 }
6521
6522 int
6523 Adjudicate(ChessProgramState *cps)
6524 {       // [HGM] some adjudications useful with buggy engines
6525         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6526         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6527         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6528         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6529         int k, count = 0; static int bare = 1;
6530         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6531         Boolean canAdjudicate = !appData.icsActive;
6532
6533         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6534         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6535             if( appData.testLegality )
6536             {   /* [HGM] Some more adjudications for obstinate engines */
6537                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6538                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6539                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6540                 static int moveCount = 6;
6541                 ChessMove result;
6542                 char *reason = NULL;
6543
6544
6545                 /* Count what is on board. */
6546                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6547                 {   ChessSquare p = boards[forwardMostMove][i][j];
6548                     int m=i;
6549
6550                     switch((int) p)
6551                     {   /* count B,N,R and other of each side */
6552                         case WhiteKing:
6553                         case BlackKing:
6554                              NrK++; break; // [HGM] atomic: count Kings
6555                         case WhiteKnight:
6556                              NrWN++; break;
6557                         case WhiteBishop:
6558                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6559                              bishopsColor |= 1 << ((i^j)&1);
6560                              NrWB++; break;
6561                         case BlackKnight:
6562                              NrBN++; break;
6563                         case BlackBishop:
6564                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6565                              bishopsColor |= 1 << ((i^j)&1);
6566                              NrBB++; break;
6567                         case WhiteRook:
6568                              NrWR++; break;
6569                         case BlackRook:
6570                              NrBR++; break;
6571                         case WhiteQueen:
6572                              NrWQ++; break;
6573                         case BlackQueen:
6574                              NrBQ++; break;
6575                         case EmptySquare: 
6576                              break;
6577                         case BlackPawn:
6578                              m = 7-i;
6579                         case WhitePawn:
6580                              PawnAdvance += m; NrPawns++;
6581                     }
6582                     NrPieces += (p != EmptySquare);
6583                     NrW += ((int)p < (int)BlackPawn);
6584                     if(gameInfo.variant == VariantXiangqi && 
6585                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6586                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6587                         NrW -= ((int)p < (int)BlackPawn);
6588                     }
6589                 }
6590
6591                 /* Some material-based adjudications that have to be made before stalemate test */
6592                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6593                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6594                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6595                      if(canAdjudicate && appData.checkMates) {
6596                          if(engineOpponent)
6597                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6598                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6599                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6600                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6601                          return 1;
6602                      }
6603                 }
6604
6605                 /* Bare King in Shatranj (loses) or Losers (wins) */
6606                 if( NrW == 1 || NrPieces - NrW == 1) {
6607                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6608                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6609                      if(canAdjudicate && appData.checkMates) {
6610                          if(engineOpponent)
6611                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6612                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6613                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6614                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6615                          return 1;
6616                      }
6617                   } else
6618                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6619                   {    /* bare King */
6620                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6621                         if(canAdjudicate && appData.checkMates) {
6622                             /* but only adjudicate if adjudication enabled */
6623                             if(engineOpponent)
6624                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6625                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6626                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6627                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6628                             return 1;
6629                         }
6630                   }
6631                 } else bare = 1;
6632
6633
6634             // don't wait for engine to announce game end if we can judge ourselves
6635             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6636               case MT_CHECK:
6637                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6638                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6639                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6640                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6641                             checkCnt++;
6642                         if(checkCnt >= 2) {
6643                             reason = "Xboard adjudication: 3rd check";
6644                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6645                             break;
6646                         }
6647                     }
6648                 }
6649               case MT_NONE:
6650               default:
6651                 break;
6652               case MT_STALEMATE:
6653               case MT_STAINMATE:
6654                 reason = "Xboard adjudication: Stalemate";
6655                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6656                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6657                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6658                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6659                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6660                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6661                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6662                                                                         EP_CHECKMATE : EP_WINS);
6663                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6664                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6665                 }
6666                 break;
6667               case MT_CHECKMATE:
6668                 reason = "Xboard adjudication: Checkmate";
6669                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6670                 break;
6671             }
6672
6673                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6674                     case EP_STALEMATE:
6675                         result = GameIsDrawn; break;
6676                     case EP_CHECKMATE:
6677                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6678                     case EP_WINS:
6679                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6680                     default:
6681                         result = (ChessMove) 0;
6682                 }
6683                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6684                     if(engineOpponent)
6685                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6686                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6687                     GameEnds( result, reason, GE_XBOARD );
6688                     return 1;
6689                 }
6690
6691                 /* Next absolutely insufficient mating material. */
6692                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6693                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6694                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6695                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6696                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6697
6698                      /* always flag draws, for judging claims */
6699                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6700
6701                      if(canAdjudicate && appData.materialDraws) {
6702                          /* but only adjudicate them if adjudication enabled */
6703                          if(engineOpponent) {
6704                            SendToProgram("force\n", engineOpponent); // suppress reply
6705                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6706                          }
6707                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6708                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6709                          return 1;
6710                      }
6711                 }
6712
6713                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6714                 if(NrPieces == 4 && 
6715                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6716                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6717                    || NrWN==2 || NrBN==2     /* KNNK */
6718                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6719                   ) ) {
6720                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6721                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6722                           if(engineOpponent) {
6723                             SendToProgram("force\n", engineOpponent); // suppress reply
6724                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6725                           }
6726                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6727                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6728                           return 1;
6729                      }
6730                 } else moveCount = 6;
6731             }
6732         }
6733           
6734         if (appData.debugMode) { int i;
6735             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6736                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6737                     appData.drawRepeats);
6738             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6739               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6740             
6741         }
6742
6743         // Repetition draws and 50-move rule can be applied independently of legality testing
6744
6745                 /* Check for rep-draws */
6746                 count = 0;
6747                 for(k = forwardMostMove-2;
6748                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6749                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6750                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6751                     k-=2)
6752                 {   int rights=0;
6753                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6754                         /* compare castling rights */
6755                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6756                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6757                                 rights++; /* King lost rights, while rook still had them */
6758                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6759                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6760                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6761                                    rights++; /* but at least one rook lost them */
6762                         }
6763                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6764                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6765                                 rights++; 
6766                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6767                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6768                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6769                                    rights++;
6770                         }
6771                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6772                             && appData.drawRepeats > 1) {
6773                              /* adjudicate after user-specified nr of repeats */
6774                              if(engineOpponent) {
6775                                SendToProgram("force\n", engineOpponent); // suppress reply
6776                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6777                              }
6778                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6779                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6780                                 // [HGM] xiangqi: check for forbidden perpetuals
6781                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6782                                 for(m=forwardMostMove; m>k; m-=2) {
6783                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6784                                         ourPerpetual = 0; // the current mover did not always check
6785                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6786                                         hisPerpetual = 0; // the opponent did not always check
6787                                 }
6788                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6789                                                                         ourPerpetual, hisPerpetual);
6790                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6791                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6792                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6793                                     return 1;
6794                                 }
6795                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6796                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6797                                 // Now check for perpetual chases
6798                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6799                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6800                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6801                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6802                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6803                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6804                                         return 1;
6805                                     }
6806                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6807                                         break; // Abort repetition-checking loop.
6808                                 }
6809                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6810                              }
6811                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6812                              return 1;
6813                         }
6814                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6815                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6816                     }
6817                 }
6818
6819                 /* Now we test for 50-move draws. Determine ply count */
6820                 count = forwardMostMove;
6821                 /* look for last irreversble move */
6822                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6823                     count--;
6824                 /* if we hit starting position, add initial plies */
6825                 if( count == backwardMostMove )
6826                     count -= initialRulePlies;
6827                 count = forwardMostMove - count; 
6828                 if( count >= 100)
6829                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6830                          /* this is used to judge if draw claims are legal */
6831                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6832                          if(engineOpponent) {
6833                            SendToProgram("force\n", engineOpponent); // suppress reply
6834                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6835                          }
6836                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6837                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6838                          return 1;
6839                 }
6840
6841                 /* if draw offer is pending, treat it as a draw claim
6842                  * when draw condition present, to allow engines a way to
6843                  * claim draws before making their move to avoid a race
6844                  * condition occurring after their move
6845                  */
6846                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6847                          char *p = NULL;
6848                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6849                              p = "Draw claim: 50-move rule";
6850                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6851                              p = "Draw claim: 3-fold repetition";
6852                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6853                              p = "Draw claim: insufficient mating material";
6854                          if( p != NULL && canAdjudicate) {
6855                              if(engineOpponent) {
6856                                SendToProgram("force\n", engineOpponent); // suppress reply
6857                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6858                              }
6859                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6860                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6861                              return 1;
6862                          }
6863                 }
6864
6865                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6866                     if(engineOpponent) {
6867                       SendToProgram("force\n", engineOpponent); // suppress reply
6868                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6869                     }
6870                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6871                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6872                     return 1;
6873                 }
6874         return 0;
6875 }
6876
6877 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6878 {   // [HGM] book: this routine intercepts moves to simulate book replies
6879     char *bookHit = NULL;
6880
6881     //first determine if the incoming move brings opponent into his book
6882     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6883         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6884     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6885     if(bookHit != NULL && !cps->bookSuspend) {
6886         // make sure opponent is not going to reply after receiving move to book position
6887         SendToProgram("force\n", cps);
6888         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6889     }
6890     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6891     // now arrange restart after book miss
6892     if(bookHit) {
6893         // after a book hit we never send 'go', and the code after the call to this routine
6894         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6895         char buf[MSG_SIZ];
6896         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6897         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6898         SendToProgram(buf, cps);
6899         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6900     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6901         SendToProgram("go\n", cps);
6902         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6903     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6904         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6905             SendToProgram("go\n", cps); 
6906         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6907     }
6908     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6909 }
6910
6911 char *savedMessage;
6912 ChessProgramState *savedState;
6913 void DeferredBookMove(void)
6914 {
6915         if(savedState->lastPing != savedState->lastPong)
6916                     ScheduleDelayedEvent(DeferredBookMove, 10);
6917         else
6918         HandleMachineMove(savedMessage, savedState);
6919 }
6920
6921 void
6922 HandleMachineMove(message, cps)
6923      char *message;
6924      ChessProgramState *cps;
6925 {
6926     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6927     char realname[MSG_SIZ];
6928     int fromX, fromY, toX, toY;
6929     ChessMove moveType;
6930     char promoChar;
6931     char *p;
6932     int machineWhite;
6933     char *bookHit;
6934
6935     cps->userError = 0;
6936
6937 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6938     /*
6939      * Kludge to ignore BEL characters
6940      */
6941     while (*message == '\007') message++;
6942
6943     /*
6944      * [HGM] engine debug message: ignore lines starting with '#' character
6945      */
6946     if(cps->debug && *message == '#') return;
6947
6948     /*
6949      * Look for book output
6950      */
6951     if (cps == &first && bookRequested) {
6952         if (message[0] == '\t' || message[0] == ' ') {
6953             /* Part of the book output is here; append it */
6954             strcat(bookOutput, message);
6955             strcat(bookOutput, "  \n");
6956             return;
6957         } else if (bookOutput[0] != NULLCHAR) {
6958             /* All of book output has arrived; display it */
6959             char *p = bookOutput;
6960             while (*p != NULLCHAR) {
6961                 if (*p == '\t') *p = ' ';
6962                 p++;
6963             }
6964             DisplayInformation(bookOutput);
6965             bookRequested = FALSE;
6966             /* Fall through to parse the current output */
6967         }
6968     }
6969
6970     /*
6971      * Look for machine move.
6972      */
6973     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6974         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6975     {
6976         /* This method is only useful on engines that support ping */
6977         if (cps->lastPing != cps->lastPong) {
6978           if (gameMode == BeginningOfGame) {
6979             /* Extra move from before last new; ignore */
6980             if (appData.debugMode) {
6981                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6982             }
6983           } else {
6984             if (appData.debugMode) {
6985                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6986                         cps->which, gameMode);
6987             }
6988
6989             SendToProgram("undo\n", cps);
6990           }
6991           return;
6992         }
6993
6994         switch (gameMode) {
6995           case BeginningOfGame:
6996             /* Extra move from before last reset; ignore */
6997             if (appData.debugMode) {
6998                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6999             }
7000             return;
7001
7002           case EndOfGame:
7003           case IcsIdle:
7004           default:
7005             /* Extra move after we tried to stop.  The mode test is
7006                not a reliable way of detecting this problem, but it's
7007                the best we can do on engines that don't support ping.
7008             */
7009             if (appData.debugMode) {
7010                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7011                         cps->which, gameMode);
7012             }
7013             SendToProgram("undo\n", cps);
7014             return;
7015
7016           case MachinePlaysWhite:
7017           case IcsPlayingWhite:
7018             machineWhite = TRUE;
7019             break;
7020
7021           case MachinePlaysBlack:
7022           case IcsPlayingBlack:
7023             machineWhite = FALSE;
7024             break;
7025
7026           case TwoMachinesPlay:
7027             machineWhite = (cps->twoMachinesColor[0] == 'w');
7028             break;
7029         }
7030         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7031             if (appData.debugMode) {
7032                 fprintf(debugFP,
7033                         "Ignoring move out of turn by %s, gameMode %d"
7034                         ", forwardMost %d\n",
7035                         cps->which, gameMode, forwardMostMove);
7036             }
7037             return;
7038         }
7039
7040     if (appData.debugMode) { int f = forwardMostMove;
7041         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7042                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7043                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7044     }
7045         if(cps->alphaRank) AlphaRank(machineMove, 4);
7046         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7047                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7048             /* Machine move could not be parsed; ignore it. */
7049             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7050                     machineMove, cps->which);
7051             DisplayError(buf1, 0);
7052             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7053                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7054             if (gameMode == TwoMachinesPlay) {
7055               GameEnds(machineWhite ? BlackWins : WhiteWins,
7056                        buf1, GE_XBOARD);
7057             }
7058             return;
7059         }
7060
7061         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7062         /* So we have to redo legality test with true e.p. status here,  */
7063         /* to make sure an illegal e.p. capture does not slip through,   */
7064         /* to cause a forfeit on a justified illegal-move complaint      */
7065         /* of the opponent.                                              */
7066         if( gameMode==TwoMachinesPlay && appData.testLegality
7067             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7068                                                               ) {
7069            ChessMove moveType;
7070            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7071                              fromY, fromX, toY, toX, promoChar);
7072             if (appData.debugMode) {
7073                 int i;
7074                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7075                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7076                 fprintf(debugFP, "castling rights\n");
7077             }
7078             if(moveType == IllegalMove) {
7079                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7080                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7081                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7082                            buf1, GE_XBOARD);
7083                 return;
7084            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7085            /* [HGM] Kludge to handle engines that send FRC-style castling
7086               when they shouldn't (like TSCP-Gothic) */
7087            switch(moveType) {
7088              case WhiteASideCastleFR:
7089              case BlackASideCastleFR:
7090                toX+=2;
7091                currentMoveString[2]++;
7092                break;
7093              case WhiteHSideCastleFR:
7094              case BlackHSideCastleFR:
7095                toX--;
7096                currentMoveString[2]--;
7097                break;
7098              default: ; // nothing to do, but suppresses warning of pedantic compilers
7099            }
7100         }
7101         hintRequested = FALSE;
7102         lastHint[0] = NULLCHAR;
7103         bookRequested = FALSE;
7104         /* Program may be pondering now */
7105         cps->maybeThinking = TRUE;
7106         if (cps->sendTime == 2) cps->sendTime = 1;
7107         if (cps->offeredDraw) cps->offeredDraw--;
7108
7109         /* currentMoveString is set as a side-effect of ParseOneMove */
7110         strcpy(machineMove, currentMoveString);
7111         strcat(machineMove, "\n");
7112         strcpy(moveList[forwardMostMove], machineMove);
7113
7114         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7115
7116         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7117         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7118             int count = 0;
7119
7120             while( count < adjudicateLossPlies ) {
7121                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7122
7123                 if( count & 1 ) {
7124                     score = -score; /* Flip score for winning side */
7125                 }
7126
7127                 if( score > adjudicateLossThreshold ) {
7128                     break;
7129                 }
7130
7131                 count++;
7132             }
7133
7134             if( count >= adjudicateLossPlies ) {
7135                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7136
7137                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7138                     "Xboard adjudication", 
7139                     GE_XBOARD );
7140
7141                 return;
7142             }
7143         }
7144
7145         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7146
7147 #if ZIPPY
7148         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7149             first.initDone) {
7150           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7151                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7152                 SendToICS("draw ");
7153                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7154           }
7155           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7156           ics_user_moved = 1;
7157           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7158                 char buf[3*MSG_SIZ];
7159
7160                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7161                         programStats.score / 100.,
7162                         programStats.depth,
7163                         programStats.time / 100.,
7164                         (unsigned int)programStats.nodes,
7165                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7166                         programStats.movelist);
7167                 SendToICS(buf);
7168 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7169           }
7170         }
7171 #endif
7172
7173         /* [AS] Save move info and clear stats for next move */
7174         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7175         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7176         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7177         ClearProgramStats();
7178         thinkOutput[0] = NULLCHAR;
7179         hiddenThinkOutputState = 0;
7180
7181         bookHit = NULL;
7182         if (gameMode == TwoMachinesPlay) {
7183             /* [HGM] relaying draw offers moved to after reception of move */
7184             /* and interpreting offer as claim if it brings draw condition */
7185             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7186                 SendToProgram("draw\n", cps->other);
7187             }
7188             if (cps->other->sendTime) {
7189                 SendTimeRemaining(cps->other,
7190                                   cps->other->twoMachinesColor[0] == 'w');
7191             }
7192             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7193             if (firstMove && !bookHit) {
7194                 firstMove = FALSE;
7195                 if (cps->other->useColors) {
7196                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7197                 }
7198                 SendToProgram("go\n", cps->other);
7199             }
7200             cps->other->maybeThinking = TRUE;
7201         }
7202
7203         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7204
7205         if (!pausing && appData.ringBellAfterMoves) {
7206             RingBell();
7207         }
7208
7209         /*
7210          * Reenable menu items that were disabled while
7211          * machine was thinking
7212          */
7213         if (gameMode != TwoMachinesPlay)
7214             SetUserThinkingEnables();
7215
7216         // [HGM] book: after book hit opponent has received move and is now in force mode
7217         // force the book reply into it, and then fake that it outputted this move by jumping
7218         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7219         if(bookHit) {
7220                 static char bookMove[MSG_SIZ]; // a bit generous?
7221
7222                 strcpy(bookMove, "move ");
7223                 strcat(bookMove, bookHit);
7224                 message = bookMove;
7225                 cps = cps->other;
7226                 programStats.nodes = programStats.depth = programStats.time =
7227                 programStats.score = programStats.got_only_move = 0;
7228                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7229
7230                 if(cps->lastPing != cps->lastPong) {
7231                     savedMessage = message; // args for deferred call
7232                     savedState = cps;
7233                     ScheduleDelayedEvent(DeferredBookMove, 10);
7234                     return;
7235                 }
7236                 goto FakeBookMove;
7237         }
7238
7239         return;
7240     }
7241
7242     /* Set special modes for chess engines.  Later something general
7243      *  could be added here; for now there is just one kludge feature,
7244      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7245      *  when "xboard" is given as an interactive command.
7246      */
7247     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7248         cps->useSigint = FALSE;
7249         cps->useSigterm = FALSE;
7250     }
7251     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7252       ParseFeatures(message+8, cps);
7253       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7254     }
7255
7256     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7257      * want this, I was asked to put it in, and obliged.
7258      */
7259     if (!strncmp(message, "setboard ", 9)) {
7260         Board initial_position;
7261
7262         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7263
7264         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7265             DisplayError(_("Bad FEN received from engine"), 0);
7266             return ;
7267         } else {
7268            Reset(TRUE, FALSE);
7269            CopyBoard(boards[0], initial_position);
7270            initialRulePlies = FENrulePlies;
7271            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7272            else gameMode = MachinePlaysBlack;
7273            DrawPosition(FALSE, boards[currentMove]);
7274         }
7275         return;
7276     }
7277
7278     /*
7279      * Look for communication commands
7280      */
7281     if (!strncmp(message, "telluser ", 9)) {
7282         DisplayNote(message + 9);
7283         return;
7284     }
7285     if (!strncmp(message, "tellusererror ", 14)) {
7286         cps->userError = 1;
7287         DisplayError(message + 14, 0);
7288         return;
7289     }
7290     if (!strncmp(message, "tellopponent ", 13)) {
7291       if (appData.icsActive) {
7292         if (loggedOn) {
7293           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7294           SendToICS(buf1);
7295         }
7296       } else {
7297         DisplayNote(message + 13);
7298       }
7299       return;
7300     }
7301     if (!strncmp(message, "tellothers ", 11)) {
7302       if (appData.icsActive) {
7303         if (loggedOn) {
7304           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7305           SendToICS(buf1);
7306         }
7307       }
7308       return;
7309     }
7310     if (!strncmp(message, "tellall ", 8)) {
7311       if (appData.icsActive) {
7312         if (loggedOn) {
7313           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7314           SendToICS(buf1);
7315         }
7316       } else {
7317         DisplayNote(message + 8);
7318       }
7319       return;
7320     }
7321     if (strncmp(message, "warning", 7) == 0) {
7322         /* Undocumented feature, use tellusererror in new code */
7323         DisplayError(message, 0);
7324         return;
7325     }
7326     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7327         strcpy(realname, cps->tidy);
7328         strcat(realname, " query");
7329         AskQuestion(realname, buf2, buf1, cps->pr);
7330         return;
7331     }
7332     /* Commands from the engine directly to ICS.  We don't allow these to be
7333      *  sent until we are logged on. Crafty kibitzes have been known to
7334      *  interfere with the login process.
7335      */
7336     if (loggedOn) {
7337         if (!strncmp(message, "tellics ", 8)) {
7338             SendToICS(message + 8);
7339             SendToICS("\n");
7340             return;
7341         }
7342         if (!strncmp(message, "tellicsnoalias ", 15)) {
7343             SendToICS(ics_prefix);
7344             SendToICS(message + 15);
7345             SendToICS("\n");
7346             return;
7347         }
7348         /* The following are for backward compatibility only */
7349         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7350             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7351             SendToICS(ics_prefix);
7352             SendToICS(message);
7353             SendToICS("\n");
7354             return;
7355         }
7356     }
7357     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7358         return;
7359     }
7360     /*
7361      * If the move is illegal, cancel it and redraw the board.
7362      * Also deal with other error cases.  Matching is rather loose
7363      * here to accommodate engines written before the spec.
7364      */
7365     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7366         strncmp(message, "Error", 5) == 0) {
7367         if (StrStr(message, "name") ||
7368             StrStr(message, "rating") || StrStr(message, "?") ||
7369             StrStr(message, "result") || StrStr(message, "board") ||
7370             StrStr(message, "bk") || StrStr(message, "computer") ||
7371             StrStr(message, "variant") || StrStr(message, "hint") ||
7372             StrStr(message, "random") || StrStr(message, "depth") ||
7373             StrStr(message, "accepted")) {
7374             return;
7375         }
7376         if (StrStr(message, "protover")) {
7377           /* Program is responding to input, so it's apparently done
7378              initializing, and this error message indicates it is
7379              protocol version 1.  So we don't need to wait any longer
7380              for it to initialize and send feature commands. */
7381           FeatureDone(cps, 1);
7382           cps->protocolVersion = 1;
7383           return;
7384         }
7385         cps->maybeThinking = FALSE;
7386
7387         if (StrStr(message, "draw")) {
7388             /* Program doesn't have "draw" command */
7389             cps->sendDrawOffers = 0;
7390             return;
7391         }
7392         if (cps->sendTime != 1 &&
7393             (StrStr(message, "time") || StrStr(message, "otim"))) {
7394           /* Program apparently doesn't have "time" or "otim" command */
7395           cps->sendTime = 0;
7396           return;
7397         }
7398         if (StrStr(message, "analyze")) {
7399             cps->analysisSupport = FALSE;
7400             cps->analyzing = FALSE;
7401             Reset(FALSE, TRUE);
7402             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7403             DisplayError(buf2, 0);
7404             return;
7405         }
7406         if (StrStr(message, "(no matching move)st")) {
7407           /* Special kludge for GNU Chess 4 only */
7408           cps->stKludge = TRUE;
7409           SendTimeControl(cps, movesPerSession, timeControl,
7410                           timeIncrement, appData.searchDepth,
7411                           searchTime);
7412           return;
7413         }
7414         if (StrStr(message, "(no matching move)sd")) {
7415           /* Special kludge for GNU Chess 4 only */
7416           cps->sdKludge = TRUE;
7417           SendTimeControl(cps, movesPerSession, timeControl,
7418                           timeIncrement, appData.searchDepth,
7419                           searchTime);
7420           return;
7421         }
7422         if (!StrStr(message, "llegal")) {
7423             return;
7424         }
7425         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7426             gameMode == IcsIdle) return;
7427         if (forwardMostMove <= backwardMostMove) return;
7428         if (pausing) PauseEvent();
7429       if(appData.forceIllegal) {
7430             // [HGM] illegal: machine refused move; force position after move into it
7431           SendToProgram("force\n", cps);
7432           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7433                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7434                 // when black is to move, while there might be nothing on a2 or black
7435                 // might already have the move. So send the board as if white has the move.
7436                 // But first we must change the stm of the engine, as it refused the last move
7437                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7438                 if(WhiteOnMove(forwardMostMove)) {
7439                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7440                     SendBoard(cps, forwardMostMove); // kludgeless board
7441                 } else {
7442                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7443                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7444                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7445                 }
7446           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7447             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7448                  gameMode == TwoMachinesPlay)
7449               SendToProgram("go\n", cps);
7450             return;
7451       } else
7452         if (gameMode == PlayFromGameFile) {
7453             /* Stop reading this game file */
7454             gameMode = EditGame;
7455             ModeHighlight();
7456         }
7457         currentMove = --forwardMostMove;
7458         DisplayMove(currentMove-1); /* before DisplayMoveError */
7459         SwitchClocks();
7460         DisplayBothClocks();
7461         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7462                 parseList[currentMove], cps->which);
7463         DisplayMoveError(buf1);
7464         DrawPosition(FALSE, boards[currentMove]);
7465
7466         /* [HGM] illegal-move claim should forfeit game when Xboard */
7467         /* only passes fully legal moves                            */
7468         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7469             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7470                                 "False illegal-move claim", GE_XBOARD );
7471         }
7472         return;
7473     }
7474     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7475         /* Program has a broken "time" command that
7476            outputs a string not ending in newline.
7477            Don't use it. */
7478         cps->sendTime = 0;
7479     }
7480
7481     /*
7482      * If chess program startup fails, exit with an error message.
7483      * Attempts to recover here are futile.
7484      */
7485     if ((StrStr(message, "unknown host") != NULL)
7486         || (StrStr(message, "No remote directory") != NULL)
7487         || (StrStr(message, "not found") != NULL)
7488         || (StrStr(message, "No such file") != NULL)
7489         || (StrStr(message, "can't alloc") != NULL)
7490         || (StrStr(message, "Permission denied") != NULL)) {
7491
7492         cps->maybeThinking = FALSE;
7493         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7494                 cps->which, cps->program, cps->host, message);
7495         RemoveInputSource(cps->isr);
7496         DisplayFatalError(buf1, 0, 1);
7497         return;
7498     }
7499
7500     /*
7501      * Look for hint output
7502      */
7503     if (sscanf(message, "Hint: %s", buf1) == 1) {
7504         if (cps == &first && hintRequested) {
7505             hintRequested = FALSE;
7506             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7507                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7508                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7509                                     PosFlags(forwardMostMove),
7510                                     fromY, fromX, toY, toX, promoChar, buf1);
7511                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7512                 DisplayInformation(buf2);
7513             } else {
7514                 /* Hint move could not be parsed!? */
7515               snprintf(buf2, sizeof(buf2),
7516                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7517                         buf1, cps->which);
7518                 DisplayError(buf2, 0);
7519             }
7520         } else {
7521             strcpy(lastHint, buf1);
7522         }
7523         return;
7524     }
7525
7526     /*
7527      * Ignore other messages if game is not in progress
7528      */
7529     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7530         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7531
7532     /*
7533      * look for win, lose, draw, or draw offer
7534      */
7535     if (strncmp(message, "1-0", 3) == 0) {
7536         char *p, *q, *r = "";
7537         p = strchr(message, '{');
7538         if (p) {
7539             q = strchr(p, '}');
7540             if (q) {
7541                 *q = NULLCHAR;
7542                 r = p + 1;
7543             }
7544         }
7545         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7546         return;
7547     } else if (strncmp(message, "0-1", 3) == 0) {
7548         char *p, *q, *r = "";
7549         p = strchr(message, '{');
7550         if (p) {
7551             q = strchr(p, '}');
7552             if (q) {
7553                 *q = NULLCHAR;
7554                 r = p + 1;
7555             }
7556         }
7557         /* Kludge for Arasan 4.1 bug */
7558         if (strcmp(r, "Black resigns") == 0) {
7559             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7560             return;
7561         }
7562         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7563         return;
7564     } else if (strncmp(message, "1/2", 3) == 0) {
7565         char *p, *q, *r = "";
7566         p = strchr(message, '{');
7567         if (p) {
7568             q = strchr(p, '}');
7569             if (q) {
7570                 *q = NULLCHAR;
7571                 r = p + 1;
7572             }
7573         }
7574
7575         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7576         return;
7577
7578     } else if (strncmp(message, "White resign", 12) == 0) {
7579         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7580         return;
7581     } else if (strncmp(message, "Black resign", 12) == 0) {
7582         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7583         return;
7584     } else if (strncmp(message, "White matches", 13) == 0 ||
7585                strncmp(message, "Black matches", 13) == 0   ) {
7586         /* [HGM] ignore GNUShogi noises */
7587         return;
7588     } else if (strncmp(message, "White", 5) == 0 &&
7589                message[5] != '(' &&
7590                StrStr(message, "Black") == NULL) {
7591         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7592         return;
7593     } else if (strncmp(message, "Black", 5) == 0 &&
7594                message[5] != '(') {
7595         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7596         return;
7597     } else if (strcmp(message, "resign") == 0 ||
7598                strcmp(message, "computer resigns") == 0) {
7599         switch (gameMode) {
7600           case MachinePlaysBlack:
7601           case IcsPlayingBlack:
7602             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7603             break;
7604           case MachinePlaysWhite:
7605           case IcsPlayingWhite:
7606             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7607             break;
7608           case TwoMachinesPlay:
7609             if (cps->twoMachinesColor[0] == 'w')
7610               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7611             else
7612               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7613             break;
7614           default:
7615             /* can't happen */
7616             break;
7617         }
7618         return;
7619     } else if (strncmp(message, "opponent mates", 14) == 0) {
7620         switch (gameMode) {
7621           case MachinePlaysBlack:
7622           case IcsPlayingBlack:
7623             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7624             break;
7625           case MachinePlaysWhite:
7626           case IcsPlayingWhite:
7627             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7628             break;
7629           case TwoMachinesPlay:
7630             if (cps->twoMachinesColor[0] == 'w')
7631               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7632             else
7633               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7634             break;
7635           default:
7636             /* can't happen */
7637             break;
7638         }
7639         return;
7640     } else if (strncmp(message, "computer mates", 14) == 0) {
7641         switch (gameMode) {
7642           case MachinePlaysBlack:
7643           case IcsPlayingBlack:
7644             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7645             break;
7646           case MachinePlaysWhite:
7647           case IcsPlayingWhite:
7648             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7649             break;
7650           case TwoMachinesPlay:
7651             if (cps->twoMachinesColor[0] == 'w')
7652               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7653             else
7654               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7655             break;
7656           default:
7657             /* can't happen */
7658             break;
7659         }
7660         return;
7661     } else if (strncmp(message, "checkmate", 9) == 0) {
7662         if (WhiteOnMove(forwardMostMove)) {
7663             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7664         } else {
7665             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7666         }
7667         return;
7668     } else if (strstr(message, "Draw") != NULL ||
7669                strstr(message, "game is a draw") != NULL) {
7670         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7671         return;
7672     } else if (strstr(message, "offer") != NULL &&
7673                strstr(message, "draw") != NULL) {
7674 #if ZIPPY
7675         if (appData.zippyPlay && first.initDone) {
7676             /* Relay offer to ICS */
7677             SendToICS(ics_prefix);
7678             SendToICS("draw\n");
7679         }
7680 #endif
7681         cps->offeredDraw = 2; /* valid until this engine moves twice */
7682         if (gameMode == TwoMachinesPlay) {
7683             if (cps->other->offeredDraw) {
7684                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7685             /* [HGM] in two-machine mode we delay relaying draw offer      */
7686             /* until after we also have move, to see if it is really claim */
7687             }
7688         } else if (gameMode == MachinePlaysWhite ||
7689                    gameMode == MachinePlaysBlack) {
7690           if (userOfferedDraw) {
7691             DisplayInformation(_("Machine accepts your draw offer"));
7692             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7693           } else {
7694             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7695           }
7696         }
7697     }
7698
7699
7700     /*
7701      * Look for thinking output
7702      */
7703     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7704           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7705                                 ) {
7706         int plylev, mvleft, mvtot, curscore, time;
7707         char mvname[MOVE_LEN];
7708         u64 nodes; // [DM]
7709         char plyext;
7710         int ignore = FALSE;
7711         int prefixHint = FALSE;
7712         mvname[0] = NULLCHAR;
7713
7714         switch (gameMode) {
7715           case MachinePlaysBlack:
7716           case IcsPlayingBlack:
7717             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7718             break;
7719           case MachinePlaysWhite:
7720           case IcsPlayingWhite:
7721             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7722             break;
7723           case AnalyzeMode:
7724           case AnalyzeFile:
7725             break;
7726           case IcsObserving: /* [DM] icsEngineAnalyze */
7727             if (!appData.icsEngineAnalyze) ignore = TRUE;
7728             break;
7729           case TwoMachinesPlay:
7730             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7731                 ignore = TRUE;
7732             }
7733             break;
7734           default:
7735             ignore = TRUE;
7736             break;
7737         }
7738
7739         if (!ignore) {
7740             buf1[0] = NULLCHAR;
7741             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7742                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7743
7744                 if (plyext != ' ' && plyext != '\t') {
7745                     time *= 100;
7746                 }
7747
7748                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7749                 if( cps->scoreIsAbsolute && 
7750                     ( gameMode == MachinePlaysBlack ||
7751                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7752                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7753                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7754                      !WhiteOnMove(currentMove)
7755                     ) )
7756                 {
7757                     curscore = -curscore;
7758                 }
7759
7760
7761                 programStats.depth = plylev;
7762                 programStats.nodes = nodes;
7763                 programStats.time = time;
7764                 programStats.score = curscore;
7765                 programStats.got_only_move = 0;
7766
7767                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7768                         int ticklen;
7769
7770                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7771                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7772                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7773                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7774                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7775                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7776                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7777                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7778                 }
7779
7780                 /* Buffer overflow protection */
7781                 if (buf1[0] != NULLCHAR) {
7782                     if (strlen(buf1) >= sizeof(programStats.movelist)
7783                         && appData.debugMode) {
7784                         fprintf(debugFP,
7785                                 "PV is too long; using the first %u bytes.\n",
7786                                 (unsigned) sizeof(programStats.movelist) - 1);
7787                     }
7788
7789                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7790                 } else {
7791                     sprintf(programStats.movelist, " no PV\n");
7792                 }
7793
7794                 if (programStats.seen_stat) {
7795                     programStats.ok_to_send = 1;
7796                 }
7797
7798                 if (strchr(programStats.movelist, '(') != NULL) {
7799                     programStats.line_is_book = 1;
7800                     programStats.nr_moves = 0;
7801                     programStats.moves_left = 0;
7802                 } else {
7803                     programStats.line_is_book = 0;
7804                 }
7805
7806                 SendProgramStatsToFrontend( cps, &programStats );
7807
7808                 /*
7809                     [AS] Protect the thinkOutput buffer from overflow... this
7810                     is only useful if buf1 hasn't overflowed first!
7811                 */
7812                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7813                         plylev,
7814                         (gameMode == TwoMachinesPlay ?
7815                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7816                         ((double) curscore) / 100.0,
7817                         prefixHint ? lastHint : "",
7818                         prefixHint ? " " : "" );
7819
7820                 if( buf1[0] != NULLCHAR ) {
7821                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7822
7823                     if( strlen(buf1) > max_len ) {
7824                         if( appData.debugMode) {
7825                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7826                         }
7827                         buf1[max_len+1] = '\0';
7828                     }
7829
7830                     strcat( thinkOutput, buf1 );
7831                 }
7832
7833                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7834                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7835                     DisplayMove(currentMove - 1);
7836                 }
7837                 return;
7838
7839             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7840                 /* crafty (9.25+) says "(only move) <move>"
7841                  * if there is only 1 legal move
7842                  */
7843                 sscanf(p, "(only move) %s", buf1);
7844                 sprintf(thinkOutput, "%s (only move)", buf1);
7845                 sprintf(programStats.movelist, "%s (only move)", buf1);
7846                 programStats.depth = 1;
7847                 programStats.nr_moves = 1;
7848                 programStats.moves_left = 1;
7849                 programStats.nodes = 1;
7850                 programStats.time = 1;
7851                 programStats.got_only_move = 1;
7852
7853                 /* Not really, but we also use this member to
7854                    mean "line isn't going to change" (Crafty
7855                    isn't searching, so stats won't change) */
7856                 programStats.line_is_book = 1;
7857
7858                 SendProgramStatsToFrontend( cps, &programStats );
7859
7860                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7861                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7862                     DisplayMove(currentMove - 1);
7863                 }
7864                 return;
7865             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7866                               &time, &nodes, &plylev, &mvleft,
7867                               &mvtot, mvname) >= 5) {
7868                 /* The stat01: line is from Crafty (9.29+) in response
7869                    to the "." command */
7870                 programStats.seen_stat = 1;
7871                 cps->maybeThinking = TRUE;
7872
7873                 if (programStats.got_only_move || !appData.periodicUpdates)
7874                   return;
7875
7876                 programStats.depth = plylev;
7877                 programStats.time = time;
7878                 programStats.nodes = nodes;
7879                 programStats.moves_left = mvleft;
7880                 programStats.nr_moves = mvtot;
7881                 strcpy(programStats.move_name, mvname);
7882                 programStats.ok_to_send = 1;
7883                 programStats.movelist[0] = '\0';
7884
7885                 SendProgramStatsToFrontend( cps, &programStats );
7886
7887                 return;
7888
7889             } else if (strncmp(message,"++",2) == 0) {
7890                 /* Crafty 9.29+ outputs this */
7891                 programStats.got_fail = 2;
7892                 return;
7893
7894             } else if (strncmp(message,"--",2) == 0) {
7895                 /* Crafty 9.29+ outputs this */
7896                 programStats.got_fail = 1;
7897                 return;
7898
7899             } else if (thinkOutput[0] != NULLCHAR &&
7900                        strncmp(message, "    ", 4) == 0) {
7901                 unsigned message_len;
7902
7903                 p = message;
7904                 while (*p && *p == ' ') p++;
7905
7906                 message_len = strlen( p );
7907
7908                 /* [AS] Avoid buffer overflow */
7909                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7910                     strcat(thinkOutput, " ");
7911                     strcat(thinkOutput, p);
7912                 }
7913
7914                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7915                     strcat(programStats.movelist, " ");
7916                     strcat(programStats.movelist, p);
7917                 }
7918
7919                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7920                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7921                     DisplayMove(currentMove - 1);
7922                 }
7923                 return;
7924             }
7925         }
7926         else {
7927             buf1[0] = NULLCHAR;
7928
7929             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7930                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7931             {
7932                 ChessProgramStats cpstats;
7933
7934                 if (plyext != ' ' && plyext != '\t') {
7935                     time *= 100;
7936                 }
7937
7938                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7939                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7940                     curscore = -curscore;
7941                 }
7942
7943                 cpstats.depth = plylev;
7944                 cpstats.nodes = nodes;
7945                 cpstats.time = time;
7946                 cpstats.score = curscore;
7947                 cpstats.got_only_move = 0;
7948                 cpstats.movelist[0] = '\0';
7949
7950                 if (buf1[0] != NULLCHAR) {
7951                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7952                 }
7953
7954                 cpstats.ok_to_send = 0;
7955                 cpstats.line_is_book = 0;
7956                 cpstats.nr_moves = 0;
7957                 cpstats.moves_left = 0;
7958
7959                 SendProgramStatsToFrontend( cps, &cpstats );
7960             }
7961         }
7962     }
7963 }
7964
7965
7966 /* Parse a game score from the character string "game", and
7967    record it as the history of the current game.  The game
7968    score is NOT assumed to start from the standard position.
7969    The display is not updated in any way.
7970    */
7971 void
7972 ParseGameHistory(game)
7973      char *game;
7974 {
7975     ChessMove moveType;
7976     int fromX, fromY, toX, toY, boardIndex;
7977     char promoChar;
7978     char *p, *q;
7979     char buf[MSG_SIZ];
7980
7981     if (appData.debugMode)
7982       fprintf(debugFP, "Parsing game history: %s\n", game);
7983
7984     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7985     gameInfo.site = StrSave(appData.icsHost);
7986     gameInfo.date = PGNDate();
7987     gameInfo.round = StrSave("-");
7988
7989     /* Parse out names of players */
7990     while (*game == ' ') game++;
7991     p = buf;
7992     while (*game != ' ') *p++ = *game++;
7993     *p = NULLCHAR;
7994     gameInfo.white = StrSave(buf);
7995     while (*game == ' ') game++;
7996     p = buf;
7997     while (*game != ' ' && *game != '\n') *p++ = *game++;
7998     *p = NULLCHAR;
7999     gameInfo.black = StrSave(buf);
8000
8001     /* Parse moves */
8002     boardIndex = blackPlaysFirst ? 1 : 0;
8003     yynewstr(game);
8004     for (;;) {
8005         yyboardindex = boardIndex;
8006         moveType = (ChessMove) yylex();
8007         switch (moveType) {
8008           case IllegalMove:             /* maybe suicide chess, etc. */
8009   if (appData.debugMode) {
8010     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8011     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8012     setbuf(debugFP, NULL);
8013   }
8014           case WhitePromotionChancellor:
8015           case BlackPromotionChancellor:
8016           case WhitePromotionArchbishop:
8017           case BlackPromotionArchbishop:
8018           case WhitePromotionQueen:
8019           case BlackPromotionQueen:
8020           case WhitePromotionRook:
8021           case BlackPromotionRook:
8022           case WhitePromotionBishop:
8023           case BlackPromotionBishop:
8024           case WhitePromotionKnight:
8025           case BlackPromotionKnight:
8026           case WhitePromotionKing:
8027           case BlackPromotionKing:
8028           case NormalMove:
8029           case WhiteCapturesEnPassant:
8030           case BlackCapturesEnPassant:
8031           case WhiteKingSideCastle:
8032           case WhiteQueenSideCastle:
8033           case BlackKingSideCastle:
8034           case BlackQueenSideCastle:
8035           case WhiteKingSideCastleWild:
8036           case WhiteQueenSideCastleWild:
8037           case BlackKingSideCastleWild:
8038           case BlackQueenSideCastleWild:
8039           /* PUSH Fabien */
8040           case WhiteHSideCastleFR:
8041           case WhiteASideCastleFR:
8042           case BlackHSideCastleFR:
8043           case BlackASideCastleFR:
8044           /* POP Fabien */
8045             fromX = currentMoveString[0] - AAA;
8046             fromY = currentMoveString[1] - ONE;
8047             toX = currentMoveString[2] - AAA;
8048             toY = currentMoveString[3] - ONE;
8049             promoChar = currentMoveString[4];
8050             break;
8051           case WhiteDrop:
8052           case BlackDrop:
8053             fromX = moveType == WhiteDrop ?
8054               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8055             (int) CharToPiece(ToLower(currentMoveString[0]));
8056             fromY = DROP_RANK;
8057             toX = currentMoveString[2] - AAA;
8058             toY = currentMoveString[3] - ONE;
8059             promoChar = NULLCHAR;
8060             break;
8061           case AmbiguousMove:
8062             /* bug? */
8063             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8064   if (appData.debugMode) {
8065     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8066     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8067     setbuf(debugFP, NULL);
8068   }
8069             DisplayError(buf, 0);
8070             return;
8071           case ImpossibleMove:
8072             /* bug? */
8073             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8074   if (appData.debugMode) {
8075     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8076     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8077     setbuf(debugFP, NULL);
8078   }
8079             DisplayError(buf, 0);
8080             return;
8081           case (ChessMove) 0:   /* end of file */
8082             if (boardIndex < backwardMostMove) {
8083                 /* Oops, gap.  How did that happen? */
8084                 DisplayError(_("Gap in move list"), 0);
8085                 return;
8086             }
8087             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8088             if (boardIndex > forwardMostMove) {
8089                 forwardMostMove = boardIndex;
8090             }
8091             return;
8092           case ElapsedTime:
8093             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8094                 strcat(parseList[boardIndex-1], " ");
8095                 strcat(parseList[boardIndex-1], yy_text);
8096             }
8097             continue;
8098           case Comment:
8099           case PGNTag:
8100           case NAG:
8101           default:
8102             /* ignore */
8103             continue;
8104           case WhiteWins:
8105           case BlackWins:
8106           case GameIsDrawn:
8107           case GameUnfinished:
8108             if (gameMode == IcsExamining) {
8109                 if (boardIndex < backwardMostMove) {
8110                     /* Oops, gap.  How did that happen? */
8111                     return;
8112                 }
8113                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8114                 return;
8115             }
8116             gameInfo.result = moveType;
8117             p = strchr(yy_text, '{');
8118             if (p == NULL) p = strchr(yy_text, '(');
8119             if (p == NULL) {
8120                 p = yy_text;
8121                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8122             } else {
8123                 q = strchr(p, *p == '{' ? '}' : ')');
8124                 if (q != NULL) *q = NULLCHAR;
8125                 p++;
8126             }
8127             gameInfo.resultDetails = StrSave(p);
8128             continue;
8129         }
8130         if (boardIndex >= forwardMostMove &&
8131             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8132             backwardMostMove = blackPlaysFirst ? 1 : 0;
8133             return;
8134         }
8135         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8136                                  fromY, fromX, toY, toX, promoChar,
8137                                  parseList[boardIndex]);
8138         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8139         /* currentMoveString is set as a side-effect of yylex */
8140         strcpy(moveList[boardIndex], currentMoveString);
8141         strcat(moveList[boardIndex], "\n");
8142         boardIndex++;
8143         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8144         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8145           case MT_NONE:
8146           case MT_STALEMATE:
8147           default:
8148             break;
8149           case MT_CHECK:
8150             if(gameInfo.variant != VariantShogi)
8151                 strcat(parseList[boardIndex - 1], "+");
8152             break;
8153           case MT_CHECKMATE:
8154           case MT_STAINMATE:
8155             strcat(parseList[boardIndex - 1], "#");
8156             break;
8157         }
8158     }
8159 }
8160
8161
8162 /* Apply a move to the given board  */
8163 void
8164 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8165      int fromX, fromY, toX, toY;
8166      int promoChar;
8167      Board board;
8168 {
8169   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8170   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8171
8172     /* [HGM] compute & store e.p. status and castling rights for new position */
8173     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8174     { int i;
8175
8176       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8177       oldEP = (signed char)board[EP_STATUS];
8178       board[EP_STATUS] = EP_NONE;
8179
8180       if( board[toY][toX] != EmptySquare ) 
8181            board[EP_STATUS] = EP_CAPTURE;  
8182
8183       if( board[fromY][fromX] == WhitePawn ) {
8184            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8185                board[EP_STATUS] = EP_PAWN_MOVE;
8186            if( toY-fromY==2) {
8187                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8188                         gameInfo.variant != VariantBerolina || toX < fromX)
8189                       board[EP_STATUS] = toX | berolina;
8190                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8191                   gameInfo.variant != VariantBerolina || toX > fromX) 
8192                  board[EP_STATUS] = toX;
8193            }
8194       } else
8195       if( board[fromY][fromX] == BlackPawn ) {
8196            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8197                board[EP_STATUS] = EP_PAWN_MOVE; 
8198            if( toY-fromY== -2) {
8199                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8200                         gameInfo.variant != VariantBerolina || toX < fromX)
8201                       board[EP_STATUS] = toX | berolina;
8202                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8203                         gameInfo.variant != VariantBerolina || toX > fromX) 
8204                       board[EP_STATUS] = toX;
8205            }
8206        }
8207
8208        for(i=0; i<nrCastlingRights; i++) {
8209            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8210               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8211              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8212        }
8213
8214     }
8215
8216   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8217   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8218        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8219
8220   if (fromX == toX && fromY == toY) return;
8221
8222   if (fromY == DROP_RANK) {
8223         /* must be first */
8224         piece = board[toY][toX] = (ChessSquare) fromX;
8225   } else {
8226      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8227      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8228      if(gameInfo.variant == VariantKnightmate)
8229          king += (int) WhiteUnicorn - (int) WhiteKing;
8230
8231     /* Code added by Tord: */
8232     /* FRC castling assumed when king captures friendly rook. */
8233     if (board[fromY][fromX] == WhiteKing &&
8234              board[toY][toX] == WhiteRook) {
8235       board[fromY][fromX] = EmptySquare;
8236       board[toY][toX] = EmptySquare;
8237       if(toX > fromX) {
8238         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8239       } else {
8240         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8241       }
8242     } else if (board[fromY][fromX] == BlackKing &&
8243                board[toY][toX] == BlackRook) {
8244       board[fromY][fromX] = EmptySquare;
8245       board[toY][toX] = EmptySquare;
8246       if(toX > fromX) {
8247         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8248       } else {
8249         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8250       }
8251     /* End of code added by Tord */
8252
8253     } else if (board[fromY][fromX] == king
8254         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8255         && toY == fromY && toX > fromX+1) {
8256         board[fromY][fromX] = EmptySquare;
8257         board[toY][toX] = king;
8258         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8259         board[fromY][BOARD_RGHT-1] = EmptySquare;
8260     } else if (board[fromY][fromX] == king
8261         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8262                && toY == fromY && toX < fromX-1) {
8263         board[fromY][fromX] = EmptySquare;
8264         board[toY][toX] = king;
8265         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8266         board[fromY][BOARD_LEFT] = EmptySquare;
8267     } else if (board[fromY][fromX] == WhitePawn
8268                && toY >= BOARD_HEIGHT-promoRank
8269                && gameInfo.variant != VariantXiangqi
8270                ) {
8271         /* white pawn promotion */
8272         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8273         if (board[toY][toX] == EmptySquare) {
8274             board[toY][toX] = WhiteQueen;
8275         }
8276         if(gameInfo.variant==VariantBughouse ||
8277            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8278             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8279         board[fromY][fromX] = EmptySquare;
8280     } else if ((fromY == BOARD_HEIGHT-4)
8281                && (toX != fromX)
8282                && gameInfo.variant != VariantXiangqi
8283                && gameInfo.variant != VariantBerolina
8284                && (board[fromY][fromX] == WhitePawn)
8285                && (board[toY][toX] == EmptySquare)) {
8286         board[fromY][fromX] = EmptySquare;
8287         board[toY][toX] = WhitePawn;
8288         captured = board[toY - 1][toX];
8289         board[toY - 1][toX] = EmptySquare;
8290     } else if ((fromY == BOARD_HEIGHT-4)
8291                && (toX == fromX)
8292                && gameInfo.variant == VariantBerolina
8293                && (board[fromY][fromX] == WhitePawn)
8294                && (board[toY][toX] == EmptySquare)) {
8295         board[fromY][fromX] = EmptySquare;
8296         board[toY][toX] = WhitePawn;
8297         if(oldEP & EP_BEROLIN_A) {
8298                 captured = board[fromY][fromX-1];
8299                 board[fromY][fromX-1] = EmptySquare;
8300         }else{  captured = board[fromY][fromX+1];
8301                 board[fromY][fromX+1] = EmptySquare;
8302         }
8303     } else if (board[fromY][fromX] == king
8304         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8305                && toY == fromY && toX > fromX+1) {
8306         board[fromY][fromX] = EmptySquare;
8307         board[toY][toX] = king;
8308         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8309         board[fromY][BOARD_RGHT-1] = EmptySquare;
8310     } else if (board[fromY][fromX] == king
8311         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8312                && toY == fromY && toX < fromX-1) {
8313         board[fromY][fromX] = EmptySquare;
8314         board[toY][toX] = king;
8315         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8316         board[fromY][BOARD_LEFT] = EmptySquare;
8317     } else if (fromY == 7 && fromX == 3
8318                && board[fromY][fromX] == BlackKing
8319                && toY == 7 && toX == 5) {
8320         board[fromY][fromX] = EmptySquare;
8321         board[toY][toX] = BlackKing;
8322         board[fromY][7] = EmptySquare;
8323         board[toY][4] = BlackRook;
8324     } else if (fromY == 7 && fromX == 3
8325                && board[fromY][fromX] == BlackKing
8326                && toY == 7 && toX == 1) {
8327         board[fromY][fromX] = EmptySquare;
8328         board[toY][toX] = BlackKing;
8329         board[fromY][0] = EmptySquare;
8330         board[toY][2] = BlackRook;
8331     } else if (board[fromY][fromX] == BlackPawn
8332                && toY < promoRank
8333                && gameInfo.variant != VariantXiangqi
8334                ) {
8335         /* black pawn promotion */
8336         board[toY][toX] = CharToPiece(ToLower(promoChar));
8337         if (board[toY][toX] == EmptySquare) {
8338             board[toY][toX] = BlackQueen;
8339         }
8340         if(gameInfo.variant==VariantBughouse ||
8341            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8342             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8343         board[fromY][fromX] = EmptySquare;
8344     } else if ((fromY == 3)
8345                && (toX != fromX)
8346                && gameInfo.variant != VariantXiangqi
8347                && gameInfo.variant != VariantBerolina
8348                && (board[fromY][fromX] == BlackPawn)
8349                && (board[toY][toX] == EmptySquare)) {
8350         board[fromY][fromX] = EmptySquare;
8351         board[toY][toX] = BlackPawn;
8352         captured = board[toY + 1][toX];
8353         board[toY + 1][toX] = EmptySquare;
8354     } else if ((fromY == 3)
8355                && (toX == fromX)
8356                && gameInfo.variant == VariantBerolina
8357                && (board[fromY][fromX] == BlackPawn)
8358                && (board[toY][toX] == EmptySquare)) {
8359         board[fromY][fromX] = EmptySquare;
8360         board[toY][toX] = BlackPawn;
8361         if(oldEP & EP_BEROLIN_A) {
8362                 captured = board[fromY][fromX-1];
8363                 board[fromY][fromX-1] = EmptySquare;
8364         }else{  captured = board[fromY][fromX+1];
8365                 board[fromY][fromX+1] = EmptySquare;
8366         }
8367     } else {
8368         board[toY][toX] = board[fromY][fromX];
8369         board[fromY][fromX] = EmptySquare;
8370     }
8371
8372     /* [HGM] now we promote for Shogi, if needed */
8373     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8374         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8375   }
8376
8377     if (gameInfo.holdingsWidth != 0) {
8378
8379       /* !!A lot more code needs to be written to support holdings  */
8380       /* [HGM] OK, so I have written it. Holdings are stored in the */
8381       /* penultimate board files, so they are automaticlly stored   */
8382       /* in the game history.                                       */
8383       if (fromY == DROP_RANK) {
8384         /* Delete from holdings, by decreasing count */
8385         /* and erasing image if necessary            */
8386         p = (int) fromX;
8387         if(p < (int) BlackPawn) { /* white drop */
8388              p -= (int)WhitePawn;
8389                  p = PieceToNumber((ChessSquare)p);
8390              if(p >= gameInfo.holdingsSize) p = 0;
8391              if(--board[p][BOARD_WIDTH-2] <= 0)
8392                   board[p][BOARD_WIDTH-1] = EmptySquare;
8393              if((int)board[p][BOARD_WIDTH-2] < 0)
8394                         board[p][BOARD_WIDTH-2] = 0;
8395         } else {                  /* black drop */
8396              p -= (int)BlackPawn;
8397                  p = PieceToNumber((ChessSquare)p);
8398              if(p >= gameInfo.holdingsSize) p = 0;
8399              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8400                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8401              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8402                         board[BOARD_HEIGHT-1-p][1] = 0;
8403         }
8404       }
8405       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8406           && gameInfo.variant != VariantBughouse        ) {
8407         /* [HGM] holdings: Add to holdings, if holdings exist */
8408         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8409                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8410                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8411         }
8412         p = (int) captured;
8413         if (p >= (int) BlackPawn) {
8414           p -= (int)BlackPawn;
8415           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8416                   /* in Shogi restore piece to its original  first */
8417                   captured = (ChessSquare) (DEMOTED captured);
8418                   p = DEMOTED p;
8419           }
8420           p = PieceToNumber((ChessSquare)p);
8421           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8422           board[p][BOARD_WIDTH-2]++;
8423           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8424         } else {
8425           p -= (int)WhitePawn;
8426           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8427                   captured = (ChessSquare) (DEMOTED captured);
8428                   p = DEMOTED p;
8429           }
8430           p = PieceToNumber((ChessSquare)p);
8431           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8432           board[BOARD_HEIGHT-1-p][1]++;
8433           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8434         }
8435       }
8436     } else if (gameInfo.variant == VariantAtomic) {
8437       if (captured != EmptySquare) {
8438         int y, x;
8439         for (y = toY-1; y <= toY+1; y++) {
8440           for (x = toX-1; x <= toX+1; x++) {
8441             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8442                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8443               board[y][x] = EmptySquare;
8444             }
8445           }
8446         }
8447         board[toY][toX] = EmptySquare;
8448       }
8449     }
8450     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8451         /* [HGM] Shogi promotions */
8452         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8453     }
8454
8455     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8456                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8457         // [HGM] superchess: take promotion piece out of holdings
8458         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8459         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8460             if(!--board[k][BOARD_WIDTH-2])
8461                 board[k][BOARD_WIDTH-1] = EmptySquare;
8462         } else {
8463             if(!--board[BOARD_HEIGHT-1-k][1])
8464                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8465         }
8466     }
8467
8468 }
8469
8470 /* Updates forwardMostMove */
8471 void
8472 MakeMove(fromX, fromY, toX, toY, promoChar)
8473      int fromX, fromY, toX, toY;
8474      int promoChar;
8475 {
8476 //    forwardMostMove++; // [HGM] bare: moved downstream
8477
8478     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8479         int timeLeft; static int lastLoadFlag=0; int king, piece;
8480         piece = boards[forwardMostMove][fromY][fromX];
8481         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8482         if(gameInfo.variant == VariantKnightmate)
8483             king += (int) WhiteUnicorn - (int) WhiteKing;
8484         if(forwardMostMove == 0) {
8485             if(blackPlaysFirst)
8486                 fprintf(serverMoves, "%s;", second.tidy);
8487             fprintf(serverMoves, "%s;", first.tidy);
8488             if(!blackPlaysFirst)
8489                 fprintf(serverMoves, "%s;", second.tidy);
8490         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8491         lastLoadFlag = loadFlag;
8492         // print base move
8493         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8494         // print castling suffix
8495         if( toY == fromY && piece == king ) {
8496             if(toX-fromX > 1)
8497                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8498             if(fromX-toX >1)
8499                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8500         }
8501         // e.p. suffix
8502         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8503              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8504              boards[forwardMostMove][toY][toX] == EmptySquare
8505              && fromX != toX )
8506                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8507         // promotion suffix
8508         if(promoChar != NULLCHAR)
8509                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8510         if(!loadFlag) {
8511             fprintf(serverMoves, "/%d/%d",
8512                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8513             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8514             else                      timeLeft = blackTimeRemaining/1000;
8515             fprintf(serverMoves, "/%d", timeLeft);
8516         }
8517         fflush(serverMoves);
8518     }
8519
8520     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8521       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8522                         0, 1);
8523       return;
8524     }
8525     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8526     if (commentList[forwardMostMove+1] != NULL) {
8527         free(commentList[forwardMostMove+1]);
8528         commentList[forwardMostMove+1] = NULL;
8529     }
8530     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8531     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8532     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8533     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8534     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8535     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8536     gameInfo.result = GameUnfinished;
8537     if (gameInfo.resultDetails != NULL) {
8538         free(gameInfo.resultDetails);
8539         gameInfo.resultDetails = NULL;
8540     }
8541     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8542                               moveList[forwardMostMove - 1]);
8543     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8544                              PosFlags(forwardMostMove - 1),
8545                              fromY, fromX, toY, toX, promoChar,
8546                              parseList[forwardMostMove - 1]);
8547     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8548       case MT_NONE:
8549       case MT_STALEMATE:
8550       default:
8551         break;
8552       case MT_CHECK:
8553         if(gameInfo.variant != VariantShogi)
8554             strcat(parseList[forwardMostMove - 1], "+");
8555         break;
8556       case MT_CHECKMATE:
8557       case MT_STAINMATE:
8558         strcat(parseList[forwardMostMove - 1], "#");
8559         break;
8560     }
8561     if (appData.debugMode) {
8562         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8563     }
8564
8565 }
8566
8567 /* Updates currentMove if not pausing */
8568 void
8569 ShowMove(fromX, fromY, toX, toY)
8570 {
8571     int instant = (gameMode == PlayFromGameFile) ?
8572         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8573
8574     if(appData.noGUI) return;
8575
8576     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
8577       {
8578         if (!instant)
8579           {
8580             if (forwardMostMove == currentMove + 1)
8581               {
8582 //TODO
8583 //              AnimateMove(boards[forwardMostMove - 1],
8584 //                          fromX, fromY, toX, toY);
8585               }
8586             if (appData.highlightLastMove)
8587               {
8588                 SetHighlights(fromX, fromY, toX, toY);
8589               }
8590           }
8591         currentMove = forwardMostMove;
8592     }
8593
8594     if (instant) return;
8595
8596     DisplayMove(currentMove - 1);
8597     DrawPosition(FALSE, boards[currentMove]);
8598     DisplayBothClocks();
8599     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8600
8601     return;
8602 }
8603
8604 void SendEgtPath(ChessProgramState *cps)
8605 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8606         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8607
8608         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8609
8610         while(*p) {
8611             char c, *q = name+1, *r, *s;
8612
8613             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8614             while(*p && *p != ',') *q++ = *p++;
8615             *q++ = ':'; *q = 0;
8616             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8617                 strcmp(name, ",nalimov:") == 0 ) {
8618                 // take nalimov path from the menu-changeable option first, if it is defined
8619                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8620                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8621             } else
8622             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8623                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8624                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8625                 s = r = StrStr(s, ":") + 1; // beginning of path info
8626                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8627                 c = *r; *r = 0;             // temporarily null-terminate path info
8628                     *--q = 0;               // strip of trailig ':' from name
8629                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8630                 *r = c;
8631                 SendToProgram(buf,cps);     // send egtbpath command for this format
8632             }
8633             if(*p == ',') p++; // read away comma to position for next format name
8634         }
8635 }
8636
8637 void
8638 InitChessProgram(cps, setup)
8639      ChessProgramState *cps;
8640      int setup; /* [HGM] needed to setup FRC opening position */
8641 {
8642     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8643     if (appData.noChessProgram) return;
8644     hintRequested = FALSE;
8645     bookRequested = FALSE;
8646
8647     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8648     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8649     if(cps->memSize) { /* [HGM] memory */
8650         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8651         SendToProgram(buf, cps);
8652     }
8653     SendEgtPath(cps); /* [HGM] EGT */
8654     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8655         sprintf(buf, "cores %d\n", appData.smpCores);
8656         SendToProgram(buf, cps);
8657     }
8658
8659     SendToProgram(cps->initString, cps);
8660     if (gameInfo.variant != VariantNormal &&
8661         gameInfo.variant != VariantLoadable
8662         /* [HGM] also send variant if board size non-standard */
8663         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8664                                             ) {
8665       char *v = VariantName(gameInfo.variant);
8666       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8667         /* [HGM] in protocol 1 we have to assume all variants valid */
8668         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8669         DisplayFatalError(buf, 0, 1);
8670         return;
8671       }
8672
8673       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8674       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8675       if( gameInfo.variant == VariantXiangqi )
8676            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8677       if( gameInfo.variant == VariantShogi )
8678            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8679       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8680            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8681       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8682                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8683            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8684       if( gameInfo.variant == VariantCourier )
8685            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8686       if( gameInfo.variant == VariantSuper )
8687            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8688       if( gameInfo.variant == VariantGreat )
8689            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8690
8691       if(overruled) {
8692            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8693                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8694            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8695            if(StrStr(cps->variants, b) == NULL) {
8696                // specific sized variant not known, check if general sizing allowed
8697                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8698                    if(StrStr(cps->variants, "boardsize") == NULL) {
8699                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8700                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8701                        DisplayFatalError(buf, 0, 1);
8702                        return;
8703                    }
8704                    /* [HGM] here we really should compare with the maximum supported board size */
8705                }
8706            }
8707       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8708       sprintf(buf, "variant %s\n", b);
8709       SendToProgram(buf, cps);
8710     }
8711     currentlyInitializedVariant = gameInfo.variant;
8712
8713     /* [HGM] send opening position in FRC to first engine */
8714     if(setup) {
8715           SendToProgram("force\n", cps);
8716           SendBoard(cps, 0);
8717           /* engine is now in force mode! Set flag to wake it up after first move. */
8718           setboardSpoiledMachineBlack = 1;
8719     }
8720
8721     if (cps->sendICS) {
8722       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8723       SendToProgram(buf, cps);
8724     }
8725     cps->maybeThinking = FALSE;
8726     cps->offeredDraw = 0;
8727     if (!appData.icsActive) {
8728         SendTimeControl(cps, movesPerSession, timeControl,
8729                         timeIncrement, appData.searchDepth,
8730                         searchTime);
8731     }
8732     if (appData.showThinking
8733         // [HGM] thinking: four options require thinking output to be sent
8734         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8735                                 ) {
8736         SendToProgram("post\n", cps);
8737     }
8738     SendToProgram("hard\n", cps);
8739     if (!appData.ponderNextMove) {
8740         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8741            it without being sure what state we are in first.  "hard"
8742            is not a toggle, so that one is OK.
8743          */
8744         SendToProgram("easy\n", cps);
8745     }
8746     if (cps->usePing) {
8747       sprintf(buf, "ping %d\n", ++cps->lastPing);
8748       SendToProgram(buf, cps);
8749     }
8750     cps->initDone = TRUE;
8751 }
8752
8753
8754 void
8755 StartChessProgram(cps)
8756      ChessProgramState *cps;
8757 {
8758     char buf[MSG_SIZ];
8759     int err;
8760
8761     if (appData.noChessProgram) return;
8762     cps->initDone = FALSE;
8763
8764     if (strcmp(cps->host, "localhost") == 0) {
8765         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8766     } else if (*appData.remoteShell == NULLCHAR) {
8767         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8768     } else {
8769         if (*appData.remoteUser == NULLCHAR) {
8770           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8771                     cps->program);
8772         } else {
8773           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8774                     cps->host, appData.remoteUser, cps->program);
8775         }
8776         err = StartChildProcess(buf, "", &cps->pr);
8777     }
8778
8779     if (err != 0) {
8780         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8781         DisplayFatalError(buf, err, 1);
8782         cps->pr = NoProc;
8783         cps->isr = NULL;
8784         return;
8785     }
8786
8787     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8788     if (cps->protocolVersion > 1) {
8789       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8790       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8791       cps->comboCnt = 0;  //                and values of combo boxes
8792       SendToProgram(buf, cps);
8793     } else {
8794       SendToProgram("xboard\n", cps);
8795     }
8796 }
8797
8798
8799 void
8800 TwoMachinesEventIfReady P((void))
8801 {
8802   if (first.lastPing != first.lastPong) {
8803     DisplayMessage("", _("Waiting for first chess program"));
8804     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8805     return;
8806   }
8807   if (second.lastPing != second.lastPong) {
8808     DisplayMessage("", _("Waiting for second chess program"));
8809     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8810     return;
8811   }
8812   ThawUI();
8813   TwoMachinesEvent();
8814 }
8815
8816 void
8817 NextMatchGame P((void))
8818 {
8819     int index; /* [HGM] autoinc: step load index during match */
8820     Reset(FALSE, TRUE);
8821     if (*appData.loadGameFile != NULLCHAR) {
8822         index = appData.loadGameIndex;
8823         if(index < 0) { // [HGM] autoinc
8824             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8825             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8826         }
8827         LoadGameFromFile(appData.loadGameFile,
8828                          index,
8829                          appData.loadGameFile, FALSE);
8830     } else if (*appData.loadPositionFile != NULLCHAR) {
8831         index = appData.loadPositionIndex;
8832         if(index < 0) { // [HGM] autoinc
8833             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8834             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8835         }
8836         LoadPositionFromFile(appData.loadPositionFile,
8837                              index,
8838                              appData.loadPositionFile);
8839     }
8840     TwoMachinesEventIfReady();
8841 }
8842
8843 void UserAdjudicationEvent( int result )
8844 {
8845     ChessMove gameResult = GameIsDrawn;
8846
8847     if( result > 0 ) {
8848         gameResult = WhiteWins;
8849     }
8850     else if( result < 0 ) {
8851         gameResult = BlackWins;
8852     }
8853
8854     if( gameMode == TwoMachinesPlay ) {
8855         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8856     }
8857 }
8858
8859
8860 // [HGM] save: calculate checksum of game to make games easily identifiable
8861 int StringCheckSum(char *s)
8862 {
8863         int i = 0;
8864         if(s==NULL) return 0;
8865         while(*s) i = i*259 + *s++;
8866         return i;
8867 }
8868
8869 int GameCheckSum()
8870 {
8871         int i, sum=0;
8872         for(i=backwardMostMove; i<forwardMostMove; i++) {
8873                 sum += pvInfoList[i].depth;
8874                 sum += StringCheckSum(parseList[i]);
8875                 sum += StringCheckSum(commentList[i]);
8876                 sum *= 261;
8877         }
8878         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8879         return sum + StringCheckSum(commentList[i]);
8880 } // end of save patch
8881
8882 void
8883 GameEnds(result, resultDetails, whosays)
8884      ChessMove result;
8885      char *resultDetails;
8886      int whosays;
8887 {
8888     GameMode nextGameMode;
8889     int isIcsGame;
8890     char buf[MSG_SIZ];
8891
8892     if(endingGame) return; /* [HGM] crash: forbid recursion */
8893     endingGame = 1;
8894
8895     if (appData.debugMode) {
8896       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8897               result, resultDetails ? resultDetails : "(null)", whosays);
8898     }
8899
8900     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8901         /* If we are playing on ICS, the server decides when the
8902            game is over, but the engine can offer to draw, claim
8903            a draw, or resign.
8904          */
8905 #if ZIPPY
8906         if (appData.zippyPlay && first.initDone) {
8907             if (result == GameIsDrawn) {
8908                 /* In case draw still needs to be claimed */
8909                 SendToICS(ics_prefix);
8910                 SendToICS("draw\n");
8911             } else if (StrCaseStr(resultDetails, "resign")) {
8912                 SendToICS(ics_prefix);
8913                 SendToICS("resign\n");
8914             }
8915         }
8916 #endif
8917         endingGame = 0; /* [HGM] crash */
8918         return;
8919     }
8920
8921     /* If we're loading the game from a file, stop */
8922     if (whosays == GE_FILE) {
8923       (void) StopLoadGameTimer();
8924       gameFileFP = NULL;
8925     }
8926
8927     /* Cancel draw offers */
8928     first.offeredDraw = second.offeredDraw = 0;
8929
8930     /* If this is an ICS game, only ICS can really say it's done;
8931        if not, anyone can. */
8932     isIcsGame = (gameMode == IcsPlayingWhite ||
8933                  gameMode == IcsPlayingBlack ||
8934                  gameMode == IcsObserving    ||
8935                  gameMode == IcsExamining);
8936
8937     if (!isIcsGame || whosays == GE_ICS) {
8938         /* OK -- not an ICS game, or ICS said it was done */
8939         StopClocks();
8940         if (!isIcsGame && !appData.noChessProgram)
8941           SetUserThinkingEnables();
8942
8943         /* [HGM] if a machine claims the game end we verify this claim */
8944         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8945             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8946                 char claimer;
8947                 ChessMove trueResult = (ChessMove) -1;
8948
8949                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8950                                             first.twoMachinesColor[0] :
8951                                             second.twoMachinesColor[0] ;
8952
8953                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8954                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8955                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8956                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8957                 } else
8958                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8959                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8960                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8961                 } else
8962                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8963                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8964                 }
8965
8966                 // now verify win claims, but not in drop games, as we don't understand those yet
8967                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8968                                                  || gameInfo.variant == VariantGreat) &&
8969                     (result == WhiteWins && claimer == 'w' ||
8970                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8971                       if (appData.debugMode) {
8972                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8973                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8974                       }
8975                       if(result != trueResult) {
8976                               sprintf(buf, "False win claim: '%s'", resultDetails);
8977                               result = claimer == 'w' ? BlackWins : WhiteWins;
8978                               resultDetails = buf;
8979                       }
8980                 } else
8981                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8982                     && (forwardMostMove <= backwardMostMove ||
8983                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8984                         (claimer=='b')==(forwardMostMove&1))
8985                                                                                   ) {
8986                       /* [HGM] verify: draws that were not flagged are false claims */
8987                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8988                       result = claimer == 'w' ? BlackWins : WhiteWins;
8989                       resultDetails = buf;
8990                 }
8991                 /* (Claiming a loss is accepted no questions asked!) */
8992             }
8993
8994             /* [HGM] bare: don't allow bare King to win */
8995             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8996                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8997                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8998                && result != GameIsDrawn)
8999             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9000                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9001                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9002                         if(p >= 0 && p <= (int)WhiteKing) k++;
9003                 }
9004                 if (appData.debugMode) {
9005                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9006                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9007                 }
9008                 if(k <= 1) {
9009                         result = GameIsDrawn;
9010                         sprintf(buf, "%s but bare king", resultDetails);
9011                         resultDetails = buf;
9012                 }
9013             }
9014         }
9015
9016         if(serverMoves != NULL && !loadFlag) { char c = '=';
9017             if(result==WhiteWins) c = '+';
9018             if(result==BlackWins) c = '-';
9019             if(resultDetails != NULL)
9020                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9021         }
9022         if (resultDetails != NULL) {
9023             gameInfo.result = result;
9024             gameInfo.resultDetails = StrSave(resultDetails);
9025
9026             /* display last move only if game was not loaded from file */
9027             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9028                 DisplayMove(currentMove - 1);
9029
9030             if (forwardMostMove != 0) {
9031                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9032                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9033                                                                 ) {
9034                     if (*appData.saveGameFile != NULLCHAR) {
9035                         SaveGameToFile(appData.saveGameFile, TRUE);
9036                     } else if (appData.autoSaveGames) {
9037                         AutoSaveGame();
9038                     }
9039                     if (*appData.savePositionFile != NULLCHAR) {
9040                         SavePositionToFile(appData.savePositionFile);
9041                     }
9042                 }
9043             }
9044
9045             /* Tell program how game ended in case it is learning */
9046             /* [HGM] Moved this to after saving the PGN, just in case */
9047             /* engine died and we got here through time loss. In that */
9048             /* case we will get a fatal error writing the pipe, which */
9049             /* would otherwise lose us the PGN.                       */
9050             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9051             /* output during GameEnds should never be fatal anymore   */
9052             if (gameMode == MachinePlaysWhite ||
9053                 gameMode == MachinePlaysBlack ||
9054                 gameMode == TwoMachinesPlay ||
9055                 gameMode == IcsPlayingWhite ||
9056                 gameMode == IcsPlayingBlack ||
9057                 gameMode == BeginningOfGame) {
9058                 char buf[MSG_SIZ];
9059                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9060                         resultDetails);
9061                 if (first.pr != NoProc) {
9062                     SendToProgram(buf, &first);
9063                 }
9064                 if (second.pr != NoProc &&
9065                     gameMode == TwoMachinesPlay) {
9066                     SendToProgram(buf, &second);
9067                 }
9068             }
9069         }
9070
9071         if (appData.icsActive) {
9072             if (appData.quietPlay &&
9073                 (gameMode == IcsPlayingWhite ||
9074                  gameMode == IcsPlayingBlack)) {
9075                 SendToICS(ics_prefix);
9076                 SendToICS("set shout 1\n");
9077             }
9078             nextGameMode = IcsIdle;
9079             ics_user_moved = FALSE;
9080             /* clean up premove.  It's ugly when the game has ended and the
9081              * premove highlights are still on the board.
9082              */
9083             if (gotPremove) {
9084               gotPremove = FALSE;
9085               ClearPremoveHighlights();
9086               DrawPosition(FALSE, boards[currentMove]);
9087             }
9088             if (whosays == GE_ICS) {
9089                 switch (result) {
9090                 case WhiteWins:
9091                     if (gameMode == IcsPlayingWhite)
9092                         PlayIcsWinSound();
9093                     else if(gameMode == IcsPlayingBlack)
9094                         PlayIcsLossSound();
9095                     break;
9096                 case BlackWins:
9097                     if (gameMode == IcsPlayingBlack)
9098                         PlayIcsWinSound();
9099                     else if(gameMode == IcsPlayingWhite)
9100                         PlayIcsLossSound();
9101                     break;
9102                 case GameIsDrawn:
9103                     PlayIcsDrawSound();
9104                     break;
9105                 default:
9106                     PlayIcsUnfinishedSound();
9107                 }
9108             }
9109         } else if (gameMode == EditGame ||
9110                    gameMode == PlayFromGameFile ||
9111                    gameMode == AnalyzeMode ||
9112                    gameMode == AnalyzeFile) {
9113             nextGameMode = gameMode;
9114         } else {
9115             nextGameMode = EndOfGame;
9116         }
9117         pausing = FALSE;
9118         ModeHighlight();
9119     } else {
9120         nextGameMode = gameMode;
9121     }
9122
9123     if (appData.noChessProgram) {
9124         gameMode = nextGameMode;
9125         ModeHighlight();
9126         endingGame = 0; /* [HGM] crash */
9127         return;
9128     }
9129
9130     if (first.reuse) {
9131         /* Put first chess program into idle state */
9132         if (first.pr != NoProc &&
9133             (gameMode == MachinePlaysWhite ||
9134              gameMode == MachinePlaysBlack ||
9135              gameMode == TwoMachinesPlay ||
9136              gameMode == IcsPlayingWhite ||
9137              gameMode == IcsPlayingBlack ||
9138              gameMode == BeginningOfGame)) {
9139             SendToProgram("force\n", &first);
9140             if (first.usePing) {
9141               char buf[MSG_SIZ];
9142               sprintf(buf, "ping %d\n", ++first.lastPing);
9143               SendToProgram(buf, &first);
9144             }
9145         }
9146     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9147         /* Kill off first chess program */
9148         if (first.isr != NULL)
9149           RemoveInputSource(first.isr);
9150         first.isr = NULL;
9151
9152         if (first.pr != NoProc) {
9153             ExitAnalyzeMode();
9154             DoSleep( appData.delayBeforeQuit );
9155             SendToProgram("quit\n", &first);
9156             DoSleep( appData.delayAfterQuit );
9157             DestroyChildProcess(first.pr, first.useSigterm);
9158         }
9159         first.pr = NoProc;
9160     }
9161     if (second.reuse) {
9162         /* Put second chess program into idle state */
9163         if (second.pr != NoProc &&
9164             gameMode == TwoMachinesPlay) {
9165             SendToProgram("force\n", &second);
9166             if (second.usePing) {
9167               char buf[MSG_SIZ];
9168               sprintf(buf, "ping %d\n", ++second.lastPing);
9169               SendToProgram(buf, &second);
9170             }
9171         }
9172     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9173         /* Kill off second chess program */
9174         if (second.isr != NULL)
9175           RemoveInputSource(second.isr);
9176         second.isr = NULL;
9177
9178         if (second.pr != NoProc) {
9179             DoSleep( appData.delayBeforeQuit );
9180             SendToProgram("quit\n", &second);
9181             DoSleep( appData.delayAfterQuit );
9182             DestroyChildProcess(second.pr, second.useSigterm);
9183         }
9184         second.pr = NoProc;
9185     }
9186
9187     if (matchMode && gameMode == TwoMachinesPlay) {
9188         switch (result) {
9189         case WhiteWins:
9190           if (first.twoMachinesColor[0] == 'w') {
9191             first.matchWins++;
9192           } else {
9193             second.matchWins++;
9194           }
9195           break;
9196         case BlackWins:
9197           if (first.twoMachinesColor[0] == 'b') {
9198             first.matchWins++;
9199           } else {
9200             second.matchWins++;
9201           }
9202           break;
9203         default:
9204           break;
9205         }
9206         if (matchGame < appData.matchGames) {
9207             char *tmp;
9208             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9209                 tmp = first.twoMachinesColor;
9210                 first.twoMachinesColor = second.twoMachinesColor;
9211                 second.twoMachinesColor = tmp;
9212             }
9213             gameMode = nextGameMode;
9214             matchGame++;
9215             if(appData.matchPause>10000 || appData.matchPause<10)
9216                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9217             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9218             endingGame = 0; /* [HGM] crash */
9219             return;
9220         } else {
9221             char buf[MSG_SIZ];
9222             gameMode = nextGameMode;
9223             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9224                     first.tidy, second.tidy,
9225                     first.matchWins, second.matchWins,
9226                     appData.matchGames - (first.matchWins + second.matchWins));
9227             DisplayFatalError(buf, 0, 0);
9228         }
9229     }
9230     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9231         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9232       ExitAnalyzeMode();
9233     gameMode = nextGameMode;
9234     ModeHighlight();
9235     endingGame = 0;  /* [HGM] crash */
9236 }
9237
9238 /* Assumes program was just initialized (initString sent).
9239    Leaves program in force mode. */
9240 void
9241 FeedMovesToProgram(cps, upto)
9242      ChessProgramState *cps;
9243      int upto;
9244 {
9245     int i;
9246
9247     if (appData.debugMode)
9248       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9249               startedFromSetupPosition ? "position and " : "",
9250               backwardMostMove, upto, cps->which);
9251     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9252         // [HGM] variantswitch: make engine aware of new variant
9253         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9254                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9255         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9256         SendToProgram(buf, cps);
9257         currentlyInitializedVariant = gameInfo.variant;
9258     }
9259     SendToProgram("force\n", cps);
9260     if (startedFromSetupPosition) {
9261         SendBoard(cps, backwardMostMove);
9262     if (appData.debugMode) {
9263         fprintf(debugFP, "feedMoves\n");
9264     }
9265     }
9266     for (i = backwardMostMove; i < upto; i++) {
9267         SendMoveToProgram(i, cps);
9268     }
9269 }
9270
9271
9272 void
9273 ResurrectChessProgram()
9274 {
9275      /* The chess program may have exited.
9276         If so, restart it and feed it all the moves made so far. */
9277
9278     if (appData.noChessProgram || first.pr != NoProc) return;
9279
9280     StartChessProgram(&first);
9281     InitChessProgram(&first, FALSE);
9282     FeedMovesToProgram(&first, currentMove);
9283
9284     if (!first.sendTime) {
9285         /* can't tell gnuchess what its clock should read,
9286            so we bow to its notion. */
9287         ResetClocks();
9288         timeRemaining[0][currentMove] = whiteTimeRemaining;
9289         timeRemaining[1][currentMove] = blackTimeRemaining;
9290     }
9291
9292     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9293                 appData.icsEngineAnalyze) && first.analysisSupport) {
9294       SendToProgram("analyze\n", &first);
9295       first.analyzing = TRUE;
9296     }
9297 }
9298
9299 /*
9300  * Button procedures
9301  */
9302 void
9303 Reset(redraw, init)
9304      int redraw, init;
9305 {
9306     int i;
9307
9308     if (appData.debugMode) {
9309         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9310                 redraw, init, gameMode);
9311     }
9312     CleanupTail(); // [HGM] vari: delete any stored variations
9313     pausing = pauseExamInvalid = FALSE;
9314     startedFromSetupPosition = blackPlaysFirst = FALSE;
9315     firstMove = TRUE;
9316     whiteFlag = blackFlag = FALSE;
9317     userOfferedDraw = FALSE;
9318     hintRequested = bookRequested = FALSE;
9319     first.maybeThinking = FALSE;
9320     second.maybeThinking = FALSE;
9321     first.bookSuspend = FALSE; // [HGM] book
9322     second.bookSuspend = FALSE;
9323     thinkOutput[0] = NULLCHAR;
9324     lastHint[0] = NULLCHAR;
9325     ClearGameInfo(&gameInfo);
9326     gameInfo.variant = StringToVariant(appData.variant);
9327     ics_user_moved = ics_clock_paused = FALSE;
9328     ics_getting_history = H_FALSE;
9329     ics_gamenum = -1;
9330     white_holding[0] = black_holding[0] = NULLCHAR;
9331     ClearProgramStats();
9332     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9333
9334     ResetFrontEnd();
9335     ClearHighlights();
9336     flipView = appData.flipView;
9337     ClearPremoveHighlights();
9338     gotPremove = FALSE;
9339     alarmSounded = FALSE;
9340
9341     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9342     if(appData.serverMovesName != NULL) {
9343         /* [HGM] prepare to make moves file for broadcasting */
9344         clock_t t = clock();
9345         if(serverMoves != NULL) fclose(serverMoves);
9346         serverMoves = fopen(appData.serverMovesName, "r");
9347         if(serverMoves != NULL) {
9348             fclose(serverMoves);
9349             /* delay 15 sec before overwriting, so all clients can see end */
9350             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9351         }
9352         serverMoves = fopen(appData.serverMovesName, "w");
9353     }
9354
9355     ExitAnalyzeMode();
9356     gameMode = BeginningOfGame;
9357     ModeHighlight();
9358
9359     if(appData.icsActive) gameInfo.variant = VariantNormal;
9360     currentMove = forwardMostMove = backwardMostMove = 0;
9361     InitPosition(redraw);
9362     for (i = 0; i < MAX_MOVES; i++) {
9363         if (commentList[i] != NULL) {
9364             free(commentList[i]);
9365             commentList[i] = NULL;
9366         }
9367     }
9368
9369     ResetClocks();
9370     timeRemaining[0][0] = whiteTimeRemaining;
9371     timeRemaining[1][0] = blackTimeRemaining;
9372     if (first.pr == NULL) {
9373         StartChessProgram(&first);
9374     }
9375     if (init) {
9376             InitChessProgram(&first, startedFromSetupPosition);
9377     }
9378
9379     DisplayTitle("");
9380     DisplayMessage("", "");
9381     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9382     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9383     return;
9384 }
9385
9386 void
9387 AutoPlayGameLoop()
9388 {
9389     for (;;) {
9390         if (!AutoPlayOneMove())
9391           return;
9392         if (matchMode || appData.timeDelay == 0)
9393           continue;
9394         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9395           return;
9396         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9397         break;
9398     }
9399 }
9400
9401
9402 int
9403 AutoPlayOneMove()
9404 {
9405     int fromX, fromY, toX, toY;
9406
9407     if (appData.debugMode) {
9408       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9409     }
9410
9411     if (gameMode != PlayFromGameFile)
9412       return FALSE;
9413
9414     if (currentMove >= forwardMostMove) {
9415       gameMode = EditGame;
9416       ModeHighlight();
9417
9418       /* [AS] Clear current move marker at the end of a game */
9419       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9420
9421       return FALSE;
9422     }
9423
9424     toX = moveList[currentMove][2] - AAA;
9425     toY = moveList[currentMove][3] - ONE;
9426
9427     if (moveList[currentMove][1] == '@') {
9428         if (appData.highlightLastMove) {
9429             SetHighlights(-1, -1, toX, toY);
9430         }
9431     } else {
9432         fromX = moveList[currentMove][0] - AAA;
9433         fromY = moveList[currentMove][1] - ONE;
9434
9435         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9436
9437         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9438
9439         if (appData.highlightLastMove) {
9440             SetHighlights(fromX, fromY, toX, toY);
9441         }
9442     }
9443     DisplayMove(currentMove);
9444     SendMoveToProgram(currentMove++, &first);
9445     DisplayBothClocks();
9446     DrawPosition(FALSE, boards[currentMove]);
9447     // [HGM] PV info: always display, routine tests if empty
9448     DisplayComment(currentMove - 1, commentList[currentMove]);
9449     return TRUE;
9450 }
9451
9452
9453 int
9454 LoadGameOneMove(readAhead)
9455      ChessMove readAhead;
9456 {
9457     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9458     char promoChar = NULLCHAR;
9459     ChessMove moveType;
9460     char move[MSG_SIZ];
9461     char *p, *q;
9462
9463     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9464         gameMode != AnalyzeMode && gameMode != Training) {
9465         gameFileFP = NULL;
9466         return FALSE;
9467     }
9468
9469     yyboardindex = forwardMostMove;
9470     if (readAhead != (ChessMove)0) {
9471       moveType = readAhead;
9472     } else {
9473       if (gameFileFP == NULL)
9474           return FALSE;
9475       moveType = (ChessMove) yylex();
9476     }
9477
9478     done = FALSE;
9479     switch (moveType) {
9480       case Comment:
9481         if (appData.debugMode)
9482           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9483         p = yy_text;
9484
9485         /* append the comment but don't display it */
9486         AppendComment(currentMove, p, FALSE);
9487         return TRUE;
9488
9489       case WhiteCapturesEnPassant:
9490       case BlackCapturesEnPassant:
9491       case WhitePromotionChancellor:
9492       case BlackPromotionChancellor:
9493       case WhitePromotionArchbishop:
9494       case BlackPromotionArchbishop:
9495       case WhitePromotionCentaur:
9496       case BlackPromotionCentaur:
9497       case WhitePromotionQueen:
9498       case BlackPromotionQueen:
9499       case WhitePromotionRook:
9500       case BlackPromotionRook:
9501       case WhitePromotionBishop:
9502       case BlackPromotionBishop:
9503       case WhitePromotionKnight:
9504       case BlackPromotionKnight:
9505       case WhitePromotionKing:
9506       case BlackPromotionKing:
9507       case NormalMove:
9508       case WhiteKingSideCastle:
9509       case WhiteQueenSideCastle:
9510       case BlackKingSideCastle:
9511       case BlackQueenSideCastle:
9512       case WhiteKingSideCastleWild:
9513       case WhiteQueenSideCastleWild:
9514       case BlackKingSideCastleWild:
9515       case BlackQueenSideCastleWild:
9516       /* PUSH Fabien */
9517       case WhiteHSideCastleFR:
9518       case WhiteASideCastleFR:
9519       case BlackHSideCastleFR:
9520       case BlackASideCastleFR:
9521       /* POP Fabien */
9522         if (appData.debugMode)
9523           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9524         fromX = currentMoveString[0] - AAA;
9525         fromY = currentMoveString[1] - ONE;
9526         toX = currentMoveString[2] - AAA;
9527         toY = currentMoveString[3] - ONE;
9528         promoChar = currentMoveString[4];
9529         break;
9530
9531       case WhiteDrop:
9532       case BlackDrop:
9533         if (appData.debugMode)
9534           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9535         fromX = moveType == WhiteDrop ?
9536           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9537         (int) CharToPiece(ToLower(currentMoveString[0]));
9538         fromY = DROP_RANK;
9539         toX = currentMoveString[2] - AAA;
9540         toY = currentMoveString[3] - ONE;
9541         break;
9542
9543       case WhiteWins:
9544       case BlackWins:
9545       case GameIsDrawn:
9546       case GameUnfinished:
9547         if (appData.debugMode)
9548           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9549         p = strchr(yy_text, '{');
9550         if (p == NULL) p = strchr(yy_text, '(');
9551         if (p == NULL) {
9552             p = yy_text;
9553             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9554         } else {
9555             q = strchr(p, *p == '{' ? '}' : ')');
9556             if (q != NULL) *q = NULLCHAR;
9557             p++;
9558         }
9559         GameEnds(moveType, p, GE_FILE);
9560         done = TRUE;
9561         if (cmailMsgLoaded) {
9562             ClearHighlights();
9563             flipView = WhiteOnMove(currentMove);
9564             if (moveType == GameUnfinished) flipView = !flipView;
9565             if (appData.debugMode)
9566               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9567         }
9568         break;
9569
9570       case (ChessMove) 0:       /* end of file */
9571         if (appData.debugMode)
9572           fprintf(debugFP, "Parser hit end of file\n");
9573         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9574           case MT_NONE:
9575           case MT_CHECK:
9576             break;
9577           case MT_CHECKMATE:
9578           case MT_STAINMATE:
9579             if (WhiteOnMove(currentMove)) {
9580                 GameEnds(BlackWins, "Black mates", GE_FILE);
9581             } else {
9582                 GameEnds(WhiteWins, "White mates", GE_FILE);
9583             }
9584             break;
9585           case MT_STALEMATE:
9586             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9587             break;
9588         }
9589         done = TRUE;
9590         break;
9591
9592       case MoveNumberOne:
9593         if (lastLoadGameStart == GNUChessGame) {
9594             /* GNUChessGames have numbers, but they aren't move numbers */
9595             if (appData.debugMode)
9596               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9597                       yy_text, (int) moveType);
9598             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9599         }
9600         /* else fall thru */
9601
9602       case XBoardGame:
9603       case GNUChessGame:
9604       case PGNTag:
9605         /* Reached start of next game in file */
9606         if (appData.debugMode)
9607           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9608         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9609           case MT_NONE:
9610           case MT_CHECK:
9611             break;
9612           case MT_CHECKMATE:
9613           case MT_STAINMATE:
9614             if (WhiteOnMove(currentMove)) {
9615                 GameEnds(BlackWins, "Black mates", GE_FILE);
9616             } else {
9617                 GameEnds(WhiteWins, "White mates", GE_FILE);
9618             }
9619             break;
9620           case MT_STALEMATE:
9621             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9622             break;
9623         }
9624         done = TRUE;
9625         break;
9626
9627       case PositionDiagram:     /* should not happen; ignore */
9628       case ElapsedTime:         /* ignore */
9629       case NAG:                 /* ignore */
9630         if (appData.debugMode)
9631           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9632                   yy_text, (int) moveType);
9633         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9634
9635       case IllegalMove:
9636         if (appData.testLegality) {
9637             if (appData.debugMode)
9638               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9639             sprintf(move, _("Illegal move: %d.%s%s"),
9640                     (forwardMostMove / 2) + 1,
9641                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9642             DisplayError(move, 0);
9643             done = TRUE;
9644         } else {
9645             if (appData.debugMode)
9646               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9647                       yy_text, currentMoveString);
9648             fromX = currentMoveString[0] - AAA;
9649             fromY = currentMoveString[1] - ONE;
9650             toX = currentMoveString[2] - AAA;
9651             toY = currentMoveString[3] - ONE;
9652             promoChar = currentMoveString[4];
9653         }
9654         break;
9655
9656       case AmbiguousMove:
9657         if (appData.debugMode)
9658           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9659         sprintf(move, _("Ambiguous move: %d.%s%s"),
9660                 (forwardMostMove / 2) + 1,
9661                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9662         DisplayError(move, 0);
9663         done = TRUE;
9664         break;
9665
9666       default:
9667       case ImpossibleMove:
9668         if (appData.debugMode)
9669           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9670         sprintf(move, _("Illegal move: %d.%s%s"),
9671                 (forwardMostMove / 2) + 1,
9672                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9673         DisplayError(move, 0);
9674         done = TRUE;
9675         break;
9676     }
9677
9678     if (done) {
9679         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9680             DrawPosition(FALSE, boards[currentMove]);
9681             DisplayBothClocks();
9682             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9683               DisplayComment(currentMove - 1, commentList[currentMove]);
9684         }
9685         (void) StopLoadGameTimer();
9686         gameFileFP = NULL;
9687         cmailOldMove = forwardMostMove;
9688         return FALSE;
9689     } else {
9690         /* currentMoveString is set as a side-effect of yylex */
9691         strcat(currentMoveString, "\n");
9692         strcpy(moveList[forwardMostMove], currentMoveString);
9693
9694         thinkOutput[0] = NULLCHAR;
9695         MakeMove(fromX, fromY, toX, toY, promoChar);
9696         currentMove = forwardMostMove;
9697         return TRUE;
9698     }
9699 }
9700
9701 /* Load the nth game from the given file */
9702 int
9703 LoadGameFromFile(filename, n, title, useList)
9704      char *filename;
9705      int n;
9706      char *title;
9707      /*Boolean*/ int useList;
9708 {
9709     FILE *f;
9710     char buf[MSG_SIZ];
9711
9712     if (strcmp(filename, "-") == 0) {
9713         f = stdin;
9714         title = "stdin";
9715     } else {
9716         f = fopen(filename, "rb");
9717         if (f == NULL) {
9718           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9719             DisplayError(buf, errno);
9720             return FALSE;
9721         }
9722     }
9723     if (fseek(f, 0, 0) == -1) {
9724         /* f is not seekable; probably a pipe */
9725         useList = FALSE;
9726     }
9727     if (useList && n == 0) {
9728         int error = GameListBuild(f);
9729         if (error) {
9730             DisplayError(_("Cannot build game list"), error);
9731         } else if (!ListEmpty(&gameList) &&
9732                    ((ListGame *) gameList.tailPred)->number > 1) {
9733           // TODO convert to GTK
9734           //        GameListPopUp(f, title);
9735             return TRUE;
9736         }
9737         GameListDestroy();
9738         n = 1;
9739     }
9740     if (n == 0) n = 1;
9741     return LoadGame(f, n, title, FALSE);
9742 }
9743
9744
9745 void
9746 MakeRegisteredMove()
9747 {
9748     int fromX, fromY, toX, toY;
9749     char promoChar;
9750     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9751         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9752           case CMAIL_MOVE:
9753           case CMAIL_DRAW:
9754             if (appData.debugMode)
9755               fprintf(debugFP, "Restoring %s for game %d\n",
9756                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9757
9758             thinkOutput[0] = NULLCHAR;
9759             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9760             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9761             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9762             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9763             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9764             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9765             MakeMove(fromX, fromY, toX, toY, promoChar);
9766             ShowMove(fromX, fromY, toX, toY);
9767             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9768               case MT_NONE:
9769               case MT_CHECK:
9770                 break;
9771
9772               case MT_CHECKMATE:
9773               case MT_STAINMATE:
9774                 if (WhiteOnMove(currentMove)) {
9775                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9776                 } else {
9777                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9778                 }
9779                 break;
9780
9781               case MT_STALEMATE:
9782                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9783                 break;
9784             }
9785
9786             break;
9787
9788           case CMAIL_RESIGN:
9789             if (WhiteOnMove(currentMove)) {
9790                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9791             } else {
9792                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9793             }
9794             break;
9795
9796           case CMAIL_ACCEPT:
9797             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9798             break;
9799
9800           default:
9801             break;
9802         }
9803     }
9804
9805     return;
9806 }
9807
9808 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9809 int
9810 CmailLoadGame(f, gameNumber, title, useList)
9811      FILE *f;
9812      int gameNumber;
9813      char *title;
9814      int useList;
9815 {
9816     int retVal;
9817
9818     if (gameNumber > nCmailGames) {
9819         DisplayError(_("No more games in this message"), 0);
9820         return FALSE;
9821     }
9822     if (f == lastLoadGameFP) {
9823         int offset = gameNumber - lastLoadGameNumber;
9824         if (offset == 0) {
9825             cmailMsg[0] = NULLCHAR;
9826             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9827                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9828                 nCmailMovesRegistered--;
9829             }
9830             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9831             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9832                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9833             }
9834         } else {
9835             if (! RegisterMove()) return FALSE;
9836         }
9837     }
9838
9839     retVal = LoadGame(f, gameNumber, title, useList);
9840
9841     /* Make move registered during previous look at this game, if any */
9842     MakeRegisteredMove();
9843
9844     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9845         commentList[currentMove]
9846           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9847         DisplayComment(currentMove - 1, commentList[currentMove]);
9848     }
9849
9850     return retVal;
9851 }
9852
9853 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9854 int
9855 ReloadGame(offset)
9856      int offset;
9857 {
9858     int gameNumber = lastLoadGameNumber + offset;
9859     if (lastLoadGameFP == NULL) {
9860         DisplayError(_("No game has been loaded yet"), 0);
9861         return FALSE;
9862     }
9863     if (gameNumber <= 0) {
9864         DisplayError(_("Can't back up any further"), 0);
9865         return FALSE;
9866     }
9867     if (cmailMsgLoaded) {
9868         return CmailLoadGame(lastLoadGameFP, gameNumber,
9869                              lastLoadGameTitle, lastLoadGameUseList);
9870     } else {
9871         return LoadGame(lastLoadGameFP, gameNumber,
9872                         lastLoadGameTitle, lastLoadGameUseList);
9873     }
9874 }
9875
9876
9877
9878 /* Load the nth game from open file f */
9879 int
9880 LoadGame(f, gameNumber, title, useList)
9881      FILE *f;
9882      int gameNumber;
9883      char *title;
9884      int useList;
9885 {
9886     ChessMove cm;
9887     char buf[MSG_SIZ];
9888     int gn = gameNumber;
9889     ListGame *lg = NULL;
9890     int numPGNTags = 0;
9891     int err;
9892     GameMode oldGameMode;
9893     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9894
9895     if (appData.debugMode)
9896         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9897
9898     if (gameMode == Training )
9899         SetTrainingModeOff();
9900
9901     oldGameMode = gameMode;
9902     if (gameMode != BeginningOfGame) 
9903       {
9904         Reset(FALSE, TRUE);
9905       };
9906
9907     gameFileFP = f;
9908     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9909       {
9910         fclose(lastLoadGameFP);
9911       };
9912
9913     if (useList) 
9914       {
9915         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9916         
9917         if (lg) 
9918           {
9919             fseek(f, lg->offset, 0);
9920             GameListHighlight(gameNumber);
9921             gn = 1;
9922           }
9923         else 
9924           {
9925             DisplayError(_("Game number out of range"), 0);
9926             return FALSE;
9927           };
9928       } 
9929     else 
9930       {
9931         GameListDestroy();
9932         if (fseek(f, 0, 0) == -1) 
9933           {
9934             if (f == lastLoadGameFP ?
9935                 gameNumber == lastLoadGameNumber + 1 :
9936                 gameNumber == 1) 
9937               {
9938                 gn = 1;
9939               } 
9940             else 
9941               {
9942                 DisplayError(_("Can't seek on game file"), 0);
9943                 return FALSE;
9944               };
9945           };
9946       };
9947
9948     lastLoadGameFP      = f;
9949     lastLoadGameNumber  = gameNumber;
9950     strcpy(lastLoadGameTitle, title);
9951     lastLoadGameUseList = useList;
9952
9953     yynewfile(f);
9954
9955     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9956       {
9957         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9958                  lg->gameInfo.black);
9959         DisplayTitle(buf);
9960       } 
9961     else if (*title != NULLCHAR) 
9962       {
9963         if (gameNumber > 1) 
9964           {
9965             sprintf(buf, "%s %d", title, gameNumber);
9966             DisplayTitle(buf);
9967           } 
9968         else 
9969           {
9970             DisplayTitle(title);
9971           };
9972       };
9973
9974     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9975       {
9976         gameMode = PlayFromGameFile;
9977         ModeHighlight();
9978       };
9979
9980     currentMove = forwardMostMove = backwardMostMove = 0;
9981     CopyBoard(boards[0], initialPosition);
9982     StopClocks();
9983
9984     /*
9985      * Skip the first gn-1 games in the file.
9986      * Also skip over anything that precedes an identifiable
9987      * start of game marker, to avoid being confused by
9988      * garbage at the start of the file.  Currently
9989      * recognized start of game markers are the move number "1",
9990      * the pattern "gnuchess .* game", the pattern
9991      * "^[#;%] [^ ]* game file", and a PGN tag block.
9992      * A game that starts with one of the latter two patterns
9993      * will also have a move number 1, possibly
9994      * following a position diagram.
9995      * 5-4-02: Let's try being more lenient and allowing a game to
9996      * start with an unnumbered move.  Does that break anything?
9997      */
9998     cm = lastLoadGameStart = (ChessMove) 0;
9999     while (gn > 0) {
10000         yyboardindex = forwardMostMove;
10001         cm = (ChessMove) yylex();
10002         switch (cm) {
10003           case (ChessMove) 0:
10004             if (cmailMsgLoaded) {
10005                 nCmailGames = CMAIL_MAX_GAMES - gn;
10006             } else {
10007                 Reset(TRUE, TRUE);
10008                 DisplayError(_("Game not found in file"), 0);
10009             }
10010             return FALSE;
10011
10012           case GNUChessGame:
10013           case XBoardGame:
10014             gn--;
10015             lastLoadGameStart = cm;
10016             break;
10017
10018           case MoveNumberOne:
10019             switch (lastLoadGameStart) {
10020               case GNUChessGame:
10021               case XBoardGame:
10022               case PGNTag:
10023                 break;
10024               case MoveNumberOne:
10025               case (ChessMove) 0:
10026                 gn--;           /* count this game */
10027                 lastLoadGameStart = cm;
10028                 break;
10029               default:
10030                 /* impossible */
10031                 break;
10032             }
10033             break;
10034
10035           case PGNTag:
10036             switch (lastLoadGameStart) {
10037               case GNUChessGame:
10038               case PGNTag:
10039               case MoveNumberOne:
10040               case (ChessMove) 0:
10041                 gn--;           /* count this game */
10042                 lastLoadGameStart = cm;
10043                 break;
10044               case XBoardGame:
10045                 lastLoadGameStart = cm; /* game counted already */
10046                 break;
10047               default:
10048                 /* impossible */
10049                 break;
10050             }
10051             if (gn > 0) {
10052                 do {
10053                     yyboardindex = forwardMostMove;
10054                     cm = (ChessMove) yylex();
10055                 } while (cm == PGNTag || cm == Comment);
10056             }
10057             break;
10058
10059           case WhiteWins:
10060           case BlackWins:
10061           case GameIsDrawn:
10062             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10063                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10064                     != CMAIL_OLD_RESULT) {
10065                     nCmailResults ++ ;
10066                     cmailResult[  CMAIL_MAX_GAMES
10067                                 - gn - 1] = CMAIL_OLD_RESULT;
10068                 }
10069             }
10070             break;
10071
10072           case NormalMove:
10073             /* Only a NormalMove can be at the start of a game
10074              * without a position diagram. */
10075             if (lastLoadGameStart == (ChessMove) 0) {
10076               gn--;
10077               lastLoadGameStart = MoveNumberOne;
10078             }
10079             break;
10080
10081           default:
10082             break;
10083         }
10084     }
10085
10086     if (appData.debugMode)
10087       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10088
10089     if (cm == XBoardGame) {
10090         /* Skip any header junk before position diagram and/or move 1 */
10091         for (;;) {
10092             yyboardindex = forwardMostMove;
10093             cm = (ChessMove) yylex();
10094
10095             if (cm == (ChessMove) 0 ||
10096                 cm == GNUChessGame || cm == XBoardGame) {
10097                 /* Empty game; pretend end-of-file and handle later */
10098                 cm = (ChessMove) 0;
10099                 break;
10100             }
10101
10102             if (cm == MoveNumberOne || cm == PositionDiagram ||
10103                 cm == PGNTag || cm == Comment)
10104               break;
10105         }
10106     } else if (cm == GNUChessGame) {
10107         if (gameInfo.event != NULL) {
10108             free(gameInfo.event);
10109         }
10110         gameInfo.event = StrSave(yy_text);
10111     }
10112
10113     startedFromSetupPosition = FALSE;
10114     while (cm == PGNTag) {
10115         if (appData.debugMode)
10116           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10117         err = ParsePGNTag(yy_text, &gameInfo);
10118         if (!err) numPGNTags++;
10119
10120         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10121         if(gameInfo.variant != oldVariant) {
10122             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10123             InitPosition(TRUE);
10124             oldVariant = gameInfo.variant;
10125             if (appData.debugMode)
10126               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10127         }
10128
10129
10130         if (gameInfo.fen != NULL) {
10131           Board initial_position;
10132           startedFromSetupPosition = TRUE;
10133           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10134             Reset(TRUE, TRUE);
10135             DisplayError(_("Bad FEN position in file"), 0);
10136             return FALSE;
10137           }
10138           CopyBoard(boards[0], initial_position);
10139           if (blackPlaysFirst) {
10140             currentMove = forwardMostMove = backwardMostMove = 1;
10141             CopyBoard(boards[1], initial_position);
10142             strcpy(moveList[0], "");
10143             strcpy(parseList[0], "");
10144             timeRemaining[0][1] = whiteTimeRemaining;
10145             timeRemaining[1][1] = blackTimeRemaining;
10146             if (commentList[0] != NULL) {
10147               commentList[1] = commentList[0];
10148               commentList[0] = NULL;
10149             }
10150           } else {
10151             currentMove = forwardMostMove = backwardMostMove = 0;
10152           }
10153           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10154           {   int i;
10155               initialRulePlies = FENrulePlies;
10156               for( i=0; i< nrCastlingRights; i++ )
10157                   initialRights[i] = initial_position[CASTLING][i];
10158           }
10159           yyboardindex = forwardMostMove;
10160           free(gameInfo.fen);
10161           gameInfo.fen = NULL;
10162         }
10163
10164         yyboardindex = forwardMostMove;
10165         cm = (ChessMove) yylex();
10166
10167         /* Handle comments interspersed among the tags */
10168         while (cm == Comment) {
10169             char *p;
10170             if (appData.debugMode)
10171               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10172             p = yy_text;
10173             AppendComment(currentMove, p, FALSE);
10174             yyboardindex = forwardMostMove;
10175             cm = (ChessMove) yylex();
10176         }
10177     }
10178
10179     /* don't rely on existence of Event tag since if game was
10180      * pasted from clipboard the Event tag may not exist
10181      */
10182     if (numPGNTags > 0){
10183         char *tags;
10184         if (gameInfo.variant == VariantNormal) {
10185           gameInfo.variant = StringToVariant(gameInfo.event);
10186         }
10187         if (!matchMode) {
10188           if( appData.autoDisplayTags ) {
10189             tags = PGNTags(&gameInfo);
10190             TagsPopUp(tags, CmailMsg());
10191             free(tags);
10192           }
10193         }
10194     } else {
10195         /* Make something up, but don't display it now */
10196         SetGameInfo();
10197         TagsPopDown();
10198     }
10199
10200     if (cm == PositionDiagram) {
10201         int i, j;
10202         char *p;
10203         Board initial_position;
10204
10205         if (appData.debugMode)
10206           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10207
10208         if (!startedFromSetupPosition) {
10209             p = yy_text;
10210             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10211               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10212                 switch (*p) {
10213                   case '[':
10214                   case '-':
10215                   case ' ':
10216                   case '\t':
10217                   case '\n':
10218                   case '\r':
10219                     break;
10220                   default:
10221                     initial_position[i][j++] = CharToPiece(*p);
10222                     break;
10223                 }
10224             while (*p == ' ' || *p == '\t' ||
10225                    *p == '\n' || *p == '\r') p++;
10226
10227             if (strncmp(p, "black", strlen("black"))==0)
10228               blackPlaysFirst = TRUE;
10229             else
10230               blackPlaysFirst = FALSE;
10231             startedFromSetupPosition = TRUE;
10232
10233             CopyBoard(boards[0], initial_position);
10234             if (blackPlaysFirst) {
10235                 currentMove = forwardMostMove = backwardMostMove = 1;
10236                 CopyBoard(boards[1], initial_position);
10237                 strcpy(moveList[0], "");
10238                 strcpy(parseList[0], "");
10239                 timeRemaining[0][1] = whiteTimeRemaining;
10240                 timeRemaining[1][1] = blackTimeRemaining;
10241                 if (commentList[0] != NULL) {
10242                     commentList[1] = commentList[0];
10243                     commentList[0] = NULL;
10244                 }
10245             } else {
10246                 currentMove = forwardMostMove = backwardMostMove = 0;
10247             }
10248         }
10249         yyboardindex = forwardMostMove;
10250         cm = (ChessMove) yylex();
10251     }
10252
10253     if (first.pr == NoProc) {
10254         StartChessProgram(&first);
10255     }
10256     InitChessProgram(&first, FALSE);
10257     SendToProgram("force\n", &first);
10258     if (startedFromSetupPosition) {
10259         SendBoard(&first, forwardMostMove);
10260     if (appData.debugMode) {
10261         fprintf(debugFP, "Load Game\n");
10262     }
10263         DisplayBothClocks();
10264     }
10265
10266     /* [HGM] server: flag to write setup moves in broadcast file as one */
10267     loadFlag = appData.suppressLoadMoves;
10268
10269     while (cm == Comment) {
10270         char *p;
10271         if (appData.debugMode)
10272           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10273         p = yy_text;
10274         AppendComment(currentMove, p, FALSE);
10275         yyboardindex = forwardMostMove;
10276         cm = (ChessMove) yylex();
10277     }
10278
10279     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10280         cm == WhiteWins || cm == BlackWins ||
10281         cm == GameIsDrawn || cm == GameUnfinished) {
10282         DisplayMessage("", _("No moves in game"));
10283         if (cmailMsgLoaded) {
10284             if (appData.debugMode)
10285               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10286             ClearHighlights();
10287             flipView = FALSE;
10288         }
10289         DrawPosition(FALSE, boards[currentMove]);
10290         DisplayBothClocks();
10291         gameMode = EditGame;
10292         ModeHighlight();
10293         gameFileFP = NULL;
10294         cmailOldMove = 0;
10295         return TRUE;
10296     }
10297
10298     // [HGM] PV info: routine tests if comment empty
10299     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10300         DisplayComment(currentMove - 1, commentList[currentMove]);
10301     }
10302     if (!matchMode && appData.timeDelay != 0)
10303       DrawPosition(FALSE, boards[currentMove]);
10304
10305     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10306       programStats.ok_to_send = 1;
10307     }
10308
10309     /* if the first token after the PGN tags is a move
10310      * and not move number 1, retrieve it from the parser
10311      */
10312     if (cm != MoveNumberOne)
10313         LoadGameOneMove(cm);
10314
10315     /* load the remaining moves from the file */
10316     while (LoadGameOneMove((ChessMove)0)) {
10317       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10318       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10319     }
10320
10321     /* rewind to the start of the game */
10322     currentMove = backwardMostMove;
10323
10324     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10325
10326     if (oldGameMode == AnalyzeFile ||
10327         oldGameMode == AnalyzeMode) {
10328       AnalyzeFileEvent();
10329     }
10330
10331     if (matchMode || appData.timeDelay == 0) {
10332       ToEndEvent();
10333       gameMode = EditGame;
10334       ModeHighlight();
10335     } else if (appData.timeDelay > 0) {
10336       AutoPlayGameLoop();
10337     }
10338
10339     if (appData.debugMode)
10340         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10341
10342     loadFlag = 0; /* [HGM] true game starts */
10343     return TRUE;
10344 }
10345
10346 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10347 int
10348 ReloadPosition(offset)
10349      int offset;
10350 {
10351     int positionNumber = lastLoadPositionNumber + offset;
10352     if (lastLoadPositionFP == NULL) {
10353         DisplayError(_("No position has been loaded yet"), 0);
10354         return FALSE;
10355     }
10356     if (positionNumber <= 0) {
10357         DisplayError(_("Can't back up any further"), 0);
10358         return FALSE;
10359     }
10360     return LoadPosition(lastLoadPositionFP, positionNumber,
10361                         lastLoadPositionTitle);
10362 }
10363
10364 /* Load the nth position from the given file */
10365 int
10366 LoadPositionFromFile(filename, n, title)
10367      char *filename;
10368      int n;
10369      char *title;
10370 {
10371     FILE *f;
10372     char buf[MSG_SIZ];
10373
10374     if (strcmp(filename, "-") == 0) {
10375         return LoadPosition(stdin, n, "stdin");
10376     } else {
10377         f = fopen(filename, "rb");
10378         if (f == NULL) {
10379             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10380             DisplayError(buf, errno);
10381             return FALSE;
10382         } else {
10383             return LoadPosition(f, n, title);
10384         }
10385     }
10386 }
10387
10388 /* Load the nth position from the given open file, and close it */
10389 int
10390 LoadPosition(f, positionNumber, title)
10391      FILE *f;
10392      int positionNumber;
10393      char *title;
10394 {
10395     char *p, line[MSG_SIZ];
10396     Board initial_position;
10397     int i, j, fenMode, pn;
10398
10399     if (gameMode == Training )
10400         SetTrainingModeOff();
10401
10402     if (gameMode != BeginningOfGame) {
10403         Reset(FALSE, TRUE);
10404     }
10405     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10406         fclose(lastLoadPositionFP);
10407     }
10408     if (positionNumber == 0) positionNumber = 1;
10409     lastLoadPositionFP = f;
10410     lastLoadPositionNumber = positionNumber;
10411     strcpy(lastLoadPositionTitle, title);
10412     if (first.pr == NoProc) {
10413       StartChessProgram(&first);
10414       InitChessProgram(&first, FALSE);
10415     }
10416     pn = positionNumber;
10417     if (positionNumber < 0) {
10418         /* Negative position number means to seek to that byte offset */
10419         if (fseek(f, -positionNumber, 0) == -1) {
10420             DisplayError(_("Can't seek on position file"), 0);
10421             return FALSE;
10422         };
10423         pn = 1;
10424     } else {
10425         if (fseek(f, 0, 0) == -1) {
10426             if (f == lastLoadPositionFP ?
10427                 positionNumber == lastLoadPositionNumber + 1 :
10428                 positionNumber == 1) {
10429                 pn = 1;
10430             } else {
10431                 DisplayError(_("Can't seek on position file"), 0);
10432                 return FALSE;
10433             }
10434         }
10435     }
10436     /* See if this file is FEN or old-style xboard */
10437     if (fgets(line, MSG_SIZ, f) == NULL) {
10438         DisplayError(_("Position not found in file"), 0);
10439         return FALSE;
10440     }
10441     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10442     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10443
10444     if (pn >= 2) {
10445         if (fenMode || line[0] == '#') pn--;
10446         while (pn > 0) {
10447             /* skip positions before number pn */
10448             if (fgets(line, MSG_SIZ, f) == NULL) {
10449                 Reset(TRUE, TRUE);
10450                 DisplayError(_("Position not found in file"), 0);
10451                 return FALSE;
10452             }
10453             if (fenMode || line[0] == '#') pn--;
10454         }
10455     }
10456
10457     if (fenMode) {
10458         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10459             DisplayError(_("Bad FEN position in file"), 0);
10460             return FALSE;
10461         }
10462     } else {
10463         (void) fgets(line, MSG_SIZ, f);
10464         (void) fgets(line, MSG_SIZ, f);
10465
10466         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10467             (void) fgets(line, MSG_SIZ, f);
10468             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10469                 if (*p == ' ')
10470                   continue;
10471                 initial_position[i][j++] = CharToPiece(*p);
10472             }
10473         }
10474
10475         blackPlaysFirst = FALSE;
10476         if (!feof(f)) {
10477             (void) fgets(line, MSG_SIZ, f);
10478             if (strncmp(line, "black", strlen("black"))==0)
10479               blackPlaysFirst = TRUE;
10480         }
10481     }
10482     startedFromSetupPosition = TRUE;
10483
10484     SendToProgram("force\n", &first);
10485     CopyBoard(boards[0], initial_position);
10486     if (blackPlaysFirst) {
10487         currentMove = forwardMostMove = backwardMostMove = 1;
10488         strcpy(moveList[0], "");
10489         strcpy(parseList[0], "");
10490         CopyBoard(boards[1], initial_position);
10491         DisplayMessage("", _("Black to play"));
10492     } else {
10493         currentMove = forwardMostMove = backwardMostMove = 0;
10494         DisplayMessage("", _("White to play"));
10495     }
10496     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10497     SendBoard(&first, forwardMostMove);
10498     if (appData.debugMode) {
10499 int i, j;
10500   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10501   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10502         fprintf(debugFP, "Load Position\n");
10503     }
10504
10505     if (positionNumber > 1) {
10506         sprintf(line, "%s %d", title, positionNumber);
10507         DisplayTitle(line);
10508     } else {
10509         DisplayTitle(title);
10510     }
10511     gameMode = EditGame;
10512     ModeHighlight();
10513     ResetClocks();
10514     timeRemaining[0][1] = whiteTimeRemaining;
10515     timeRemaining[1][1] = blackTimeRemaining;
10516     DrawPosition(FALSE, boards[currentMove]);
10517
10518     return TRUE;
10519 }
10520
10521
10522 void
10523 CopyPlayerNameIntoFileName(dest, src)
10524      char **dest, *src;
10525 {
10526     while (*src != NULLCHAR && *src != ',') {
10527         if (*src == ' ') {
10528             *(*dest)++ = '_';
10529             src++;
10530         } else {
10531             *(*dest)++ = *src++;
10532         }
10533     }
10534 }
10535
10536 char *DefaultFileName(ext)
10537      char *ext;
10538 {
10539     static char def[MSG_SIZ];
10540     char *p;
10541
10542     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10543         p = def;
10544         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10545         *p++ = '-';
10546         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10547         *p++ = '.';
10548         strcpy(p, ext);
10549     } else {
10550         def[0] = NULLCHAR;
10551     }
10552     return def;
10553 }
10554
10555 /* Save the current game to the given file */
10556 int
10557 SaveGameToFile(filename, append)
10558      char *filename;
10559      int append;
10560 {
10561     FILE *f;
10562     char buf[MSG_SIZ];
10563
10564     if (strcmp(filename, "-") == 0) {
10565         return SaveGame(stdout, 0, NULL);
10566     } else {
10567         f = fopen(filename, append ? "a" : "w");
10568         if (f == NULL) {
10569             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10570             DisplayError(buf, errno);
10571             return FALSE;
10572         } else {
10573             return SaveGame(f, 0, NULL);
10574         }
10575     }
10576 }
10577
10578 char *
10579 SavePart(str)
10580      char *str;
10581 {
10582     static char buf[MSG_SIZ];
10583     char *p;
10584
10585     p = strchr(str, ' ');
10586     if (p == NULL) return str;
10587     strncpy(buf, str, p - str);
10588     buf[p - str] = NULLCHAR;
10589     return buf;
10590 }
10591
10592 #define PGN_MAX_LINE 75
10593
10594 #define PGN_SIDE_WHITE  0
10595 #define PGN_SIDE_BLACK  1
10596
10597 /* [AS] */
10598 static int FindFirstMoveOutOfBook( int side )
10599 {
10600     int result = -1;
10601
10602     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10603         int index = backwardMostMove;
10604         int has_book_hit = 0;
10605
10606         if( (index % 2) != side ) {
10607             index++;
10608         }
10609
10610         while( index < forwardMostMove ) {
10611             /* Check to see if engine is in book */
10612             int depth = pvInfoList[index].depth;
10613             int score = pvInfoList[index].score;
10614             int in_book = 0;
10615
10616             if( depth <= 2 ) {
10617                 in_book = 1;
10618             }
10619             else if( score == 0 && depth == 63 ) {
10620                 in_book = 1; /* Zappa */
10621             }
10622             else if( score == 2 && depth == 99 ) {
10623                 in_book = 1; /* Abrok */
10624             }
10625
10626             has_book_hit += in_book;
10627
10628             if( ! in_book ) {
10629                 result = index;
10630
10631                 break;
10632             }
10633
10634             index += 2;
10635         }
10636     }
10637
10638     return result;
10639 }
10640
10641 /* [AS] */
10642 void GetOutOfBookInfo( char * buf )
10643 {
10644     int oob[2];
10645     int i;
10646     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10647
10648     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10649     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10650
10651     *buf = '\0';
10652
10653     if( oob[0] >= 0 || oob[1] >= 0 ) {
10654         for( i=0; i<2; i++ ) {
10655             int idx = oob[i];
10656
10657             if( idx >= 0 ) {
10658                 if( i > 0 && oob[0] >= 0 ) {
10659                     strcat( buf, "   " );
10660                 }
10661
10662                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10663                 sprintf( buf+strlen(buf), "%s%.2f",
10664                     pvInfoList[idx].score >= 0 ? "+" : "",
10665                     pvInfoList[idx].score / 100.0 );
10666             }
10667         }
10668     }
10669 }
10670
10671 /* Save game in PGN style and close the file */
10672 int
10673 SaveGamePGN(f)
10674      FILE *f;
10675 {
10676     int i, offset, linelen, newblock;
10677     time_t tm;
10678 //    char *movetext;
10679     char numtext[32];
10680     int movelen, numlen, blank;
10681     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10682
10683     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10684
10685     tm = time((time_t *) NULL);
10686
10687     PrintPGNTags(f, &gameInfo);
10688
10689     if (backwardMostMove > 0 || startedFromSetupPosition) {
10690         char *fen = PositionToFEN(backwardMostMove, NULL);
10691         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10692         fprintf(f, "\n{--------------\n");
10693         PrintPosition(f, backwardMostMove);
10694         fprintf(f, "--------------}\n");
10695         free(fen);
10696     }
10697     else {
10698         /* [AS] Out of book annotation */
10699         if( appData.saveOutOfBookInfo ) {
10700             char buf[64];
10701
10702             GetOutOfBookInfo( buf );
10703
10704             if( buf[0] != '\0' ) {
10705                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10706             }
10707         }
10708
10709         fprintf(f, "\n");
10710     }
10711
10712     i = backwardMostMove;
10713     linelen = 0;
10714     newblock = TRUE;
10715
10716     while (i < forwardMostMove) {
10717         /* Print comments preceding this move */
10718         if (commentList[i] != NULL) {
10719             if (linelen > 0) fprintf(f, "\n");
10720             fprintf(f, "%s", commentList[i]);
10721             linelen = 0;
10722             newblock = TRUE;
10723         }
10724
10725         /* Format move number */
10726         if ((i % 2) == 0) {
10727             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10728         } else {
10729             if (newblock) {
10730                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10731             } else {
10732                 numtext[0] = NULLCHAR;
10733             }
10734         }
10735         numlen = strlen(numtext);
10736         newblock = FALSE;
10737
10738         /* Print move number */
10739         blank = linelen > 0 && numlen > 0;
10740         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10741             fprintf(f, "\n");
10742             linelen = 0;
10743             blank = 0;
10744         }
10745         if (blank) {
10746             fprintf(f, " ");
10747             linelen++;
10748         }
10749         fprintf(f, "%s", numtext);
10750         linelen += numlen;
10751
10752         /* Get move */
10753         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10754         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10755
10756         /* Print move */
10757         blank = linelen > 0 && movelen > 0;
10758         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10759             fprintf(f, "\n");
10760             linelen = 0;
10761             blank = 0;
10762         }
10763         if (blank) {
10764             fprintf(f, " ");
10765             linelen++;
10766         }
10767         fprintf(f, "%s", move_buffer);
10768         linelen += movelen;
10769
10770         /* [AS] Add PV info if present */
10771         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10772             /* [HGM] add time */
10773             char buf[MSG_SIZ]; int seconds;
10774
10775             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10776
10777             if( seconds <= 0) buf[0] = 0; else
10778             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10779                 seconds = (seconds + 4)/10; // round to full seconds
10780                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10781                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10782             }
10783
10784             sprintf( move_buffer, "{%s%.2f/%d%s}",
10785                 pvInfoList[i].score >= 0 ? "+" : "",
10786                 pvInfoList[i].score / 100.0,
10787                 pvInfoList[i].depth,
10788                 buf );
10789
10790             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10791
10792             /* Print score/depth */
10793             blank = linelen > 0 && movelen > 0;
10794             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10795                 fprintf(f, "\n");
10796                 linelen = 0;
10797                 blank = 0;
10798             }
10799             if (blank) {
10800                 fprintf(f, " ");
10801                 linelen++;
10802             }
10803             fprintf(f, "%s", move_buffer);
10804             linelen += movelen;
10805         }
10806
10807         i++;
10808     }
10809
10810     /* Start a new line */
10811     if (linelen > 0) fprintf(f, "\n");
10812
10813     /* Print comments after last move */
10814     if (commentList[i] != NULL) {
10815         fprintf(f, "%s\n", commentList[i]);
10816     }
10817
10818     /* Print result */
10819     if (gameInfo.resultDetails != NULL &&
10820         gameInfo.resultDetails[0] != NULLCHAR) {
10821         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10822                 PGNResult(gameInfo.result));
10823     } else {
10824         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10825     }
10826
10827     fclose(f);
10828     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10829     return TRUE;
10830 }
10831
10832 /* Save game in old style and close the file */
10833 int
10834 SaveGameOldStyle(f)
10835      FILE *f;
10836 {
10837     int i, offset;
10838     time_t tm;
10839
10840     tm = time((time_t *) NULL);
10841
10842     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10843     PrintOpponents(f);
10844
10845     if (backwardMostMove > 0 || startedFromSetupPosition) {
10846         fprintf(f, "\n[--------------\n");
10847         PrintPosition(f, backwardMostMove);
10848         fprintf(f, "--------------]\n");
10849     } else {
10850         fprintf(f, "\n");
10851     }
10852
10853     i = backwardMostMove;
10854     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10855
10856     while (i < forwardMostMove) {
10857         if (commentList[i] != NULL) {
10858             fprintf(f, "[%s]\n", commentList[i]);
10859         }
10860
10861         if ((i % 2) == 1) {
10862             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10863             i++;
10864         } else {
10865             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10866             i++;
10867             if (commentList[i] != NULL) {
10868                 fprintf(f, "\n");
10869                 continue;
10870             }
10871             if (i >= forwardMostMove) {
10872                 fprintf(f, "\n");
10873                 break;
10874             }
10875             fprintf(f, "%s\n", parseList[i]);
10876             i++;
10877         }
10878     }
10879
10880     if (commentList[i] != NULL) {
10881         fprintf(f, "[%s]\n", commentList[i]);
10882     }
10883
10884     /* This isn't really the old style, but it's close enough */
10885     if (gameInfo.resultDetails != NULL &&
10886         gameInfo.resultDetails[0] != NULLCHAR) {
10887         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10888                 gameInfo.resultDetails);
10889     } else {
10890         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10891     }
10892
10893     fclose(f);
10894     return TRUE;
10895 }
10896
10897 /* Save the current game to open file f and close the file */
10898 int
10899 SaveGame(f, dummy, dummy2)
10900      FILE *f;
10901      int dummy;
10902      char *dummy2;
10903 {
10904     if (gameMode == EditPosition) EditPositionDone(TRUE);
10905     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10906     if (appData.oldSaveStyle)
10907       return SaveGameOldStyle(f);
10908     else
10909       return SaveGamePGN(f);
10910 }
10911
10912 /* Save the current position to the given file */
10913 int
10914 SavePositionToFile(filename)
10915      char *filename;
10916 {
10917     FILE *f;
10918     char buf[MSG_SIZ];
10919
10920     if (strcmp(filename, "-") == 0) {
10921         return SavePosition(stdout, 0, NULL);
10922     } else {
10923         f = fopen(filename, "a");
10924         if (f == NULL) {
10925             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10926             DisplayError(buf, errno);
10927             return FALSE;
10928         } else {
10929             SavePosition(f, 0, NULL);
10930             return TRUE;
10931         }
10932     }
10933 }
10934
10935 /* Save the current position to the given open file and close the file */
10936 int
10937 SavePosition(f, dummy, dummy2)
10938      FILE *f;
10939      int dummy;
10940      char *dummy2;
10941 {
10942     time_t tm;
10943     char *fen;
10944     if (gameMode == EditPosition) EditPositionDone(TRUE);
10945     if (appData.oldSaveStyle) {
10946         tm = time((time_t *) NULL);
10947
10948         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10949         PrintOpponents(f);
10950         fprintf(f, "[--------------\n");
10951         PrintPosition(f, currentMove);
10952         fprintf(f, "--------------]\n");
10953     } else {
10954         fen = PositionToFEN(currentMove, NULL);
10955         fprintf(f, "%s\n", fen);
10956         free(fen);
10957     }
10958     fclose(f);
10959     return TRUE;
10960 }
10961
10962 void
10963 ReloadCmailMsgEvent(unregister)
10964      int unregister;
10965 {
10966 #if !WIN32
10967     static char *inFilename = NULL;
10968     static char *outFilename;
10969     int i;
10970     struct stat inbuf, outbuf;
10971     int status;
10972
10973     /* Any registered moves are unregistered if unregister is set, */
10974     /* i.e. invoked by the signal handler */
10975     if (unregister) {
10976         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10977             cmailMoveRegistered[i] = FALSE;
10978             if (cmailCommentList[i] != NULL) {
10979                 free(cmailCommentList[i]);
10980                 cmailCommentList[i] = NULL;
10981             }
10982         }
10983         nCmailMovesRegistered = 0;
10984     }
10985
10986     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10987         cmailResult[i] = CMAIL_NOT_RESULT;
10988     }
10989     nCmailResults = 0;
10990
10991     if (inFilename == NULL) {
10992         /* Because the filenames are static they only get malloced once  */
10993         /* and they never get freed                                      */
10994         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10995         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10996
10997         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10998         sprintf(outFilename, "%s.out", appData.cmailGameName);
10999     }
11000
11001     status = stat(outFilename, &outbuf);
11002     if (status < 0) {
11003         cmailMailedMove = FALSE;
11004     } else {
11005         status = stat(inFilename, &inbuf);
11006         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11007     }
11008
11009     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11010        counts the games, notes how each one terminated, etc.
11011
11012        It would be nice to remove this kludge and instead gather all
11013        the information while building the game list.  (And to keep it
11014        in the game list nodes instead of having a bunch of fixed-size
11015        parallel arrays.)  Note this will require getting each game's
11016        termination from the PGN tags, as the game list builder does
11017        not process the game moves.  --mann
11018        */
11019     cmailMsgLoaded = TRUE;
11020     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11021
11022     /* Load first game in the file or popup game menu */
11023     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11024
11025 #endif /* !WIN32 */
11026     return;
11027 }
11028
11029 int
11030 RegisterMove()
11031 {
11032     FILE *f;
11033     char string[MSG_SIZ];
11034
11035     if (   cmailMailedMove
11036         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11037         return TRUE;            /* Allow free viewing  */
11038     }
11039
11040     /* Unregister move to ensure that we don't leave RegisterMove        */
11041     /* with the move registered when the conditions for registering no   */
11042     /* longer hold                                                       */
11043     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11044         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11045         nCmailMovesRegistered --;
11046
11047         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11048           {
11049               free(cmailCommentList[lastLoadGameNumber - 1]);
11050               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11051           }
11052     }
11053
11054     if (cmailOldMove == -1) {
11055         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11056         return FALSE;
11057     }
11058
11059     if (currentMove > cmailOldMove + 1) {
11060         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11061         return FALSE;
11062     }
11063
11064     if (currentMove < cmailOldMove) {
11065         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11066         return FALSE;
11067     }
11068
11069     if (forwardMostMove > currentMove) {
11070         /* Silently truncate extra moves */
11071         TruncateGame();
11072     }
11073
11074     if (   (currentMove == cmailOldMove + 1)
11075         || (   (currentMove == cmailOldMove)
11076             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11077                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11078         if (gameInfo.result != GameUnfinished) {
11079             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11080         }
11081
11082         if (commentList[currentMove] != NULL) {
11083             cmailCommentList[lastLoadGameNumber - 1]
11084               = StrSave(commentList[currentMove]);
11085         }
11086         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11087
11088         if (appData.debugMode)
11089           fprintf(debugFP, "Saving %s for game %d\n",
11090                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11091
11092         sprintf(string,
11093                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11094
11095         f = fopen(string, "w");
11096         if (appData.oldSaveStyle) {
11097             SaveGameOldStyle(f); /* also closes the file */
11098
11099             sprintf(string, "%s.pos.out", appData.cmailGameName);
11100             f = fopen(string, "w");
11101             SavePosition(f, 0, NULL); /* also closes the file */
11102         } else {
11103             fprintf(f, "{--------------\n");
11104             PrintPosition(f, currentMove);
11105             fprintf(f, "--------------}\n\n");
11106
11107             SaveGame(f, 0, NULL); /* also closes the file*/
11108         }
11109
11110         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11111         nCmailMovesRegistered ++;
11112     } else if (nCmailGames == 1) {
11113         DisplayError(_("You have not made a move yet"), 0);
11114         return FALSE;
11115     }
11116
11117     return TRUE;
11118 }
11119
11120 void
11121 MailMoveEvent()
11122 {
11123 #if !WIN32
11124     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11125     FILE *commandOutput;
11126     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11127     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11128     int nBuffers;
11129     int i;
11130     int archived;
11131     char *arcDir;
11132
11133     if (! cmailMsgLoaded) {
11134         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11135         return;
11136     }
11137
11138     if (nCmailGames == nCmailResults) {
11139         DisplayError(_("No unfinished games"), 0);
11140         return;
11141     }
11142
11143 #if CMAIL_PROHIBIT_REMAIL
11144     if (cmailMailedMove) {
11145         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);
11146         DisplayError(msg, 0);
11147         return;
11148     }
11149 #endif
11150
11151     if (! (cmailMailedMove || RegisterMove())) return;
11152
11153     if (   cmailMailedMove
11154         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11155         sprintf(string, partCommandString,
11156                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11157         commandOutput = popen(string, "r");
11158
11159         if (commandOutput == NULL) {
11160             DisplayError(_("Failed to invoke cmail"), 0);
11161         } else {
11162             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11163                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11164             }
11165             if (nBuffers > 1) {
11166                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11167                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11168                 nBytes = MSG_SIZ - 1;
11169             } else {
11170                 (void) memcpy(msg, buffer, nBytes);
11171             }
11172             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11173
11174             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11175                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11176
11177                 archived = TRUE;
11178                 for (i = 0; i < nCmailGames; i ++) {
11179                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11180                         archived = FALSE;
11181                     }
11182                 }
11183                 if (   archived
11184                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11185                         != NULL)) {
11186                     sprintf(buffer, "%s/%s.%s.archive",
11187                             arcDir,
11188                             appData.cmailGameName,
11189                             gameInfo.date);
11190                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11191                     cmailMsgLoaded = FALSE;
11192                 }
11193             }
11194
11195             DisplayInformation(msg);
11196             pclose(commandOutput);
11197         }
11198     } else {
11199         if ((*cmailMsg) != '\0') {
11200             DisplayInformation(cmailMsg);
11201         }
11202     }
11203
11204     return;
11205 #endif /* !WIN32 */
11206 }
11207
11208 char *
11209 CmailMsg()
11210 {
11211 #if WIN32
11212     return NULL;
11213 #else
11214     int  prependComma = 0;
11215     char number[5];
11216     char string[MSG_SIZ];       /* Space for game-list */
11217     int  i;
11218
11219     if (!cmailMsgLoaded) return "";
11220
11221     if (cmailMailedMove) {
11222         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11223     } else {
11224         /* Create a list of games left */
11225         sprintf(string, "[");
11226         for (i = 0; i < nCmailGames; i ++) {
11227             if (! (   cmailMoveRegistered[i]
11228                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11229                 if (prependComma) {
11230                     sprintf(number, ",%d", i + 1);
11231                 } else {
11232                     sprintf(number, "%d", i + 1);
11233                     prependComma = 1;
11234                 }
11235
11236                 strcat(string, number);
11237             }
11238         }
11239         strcat(string, "]");
11240
11241         if (nCmailMovesRegistered + nCmailResults == 0) {
11242             switch (nCmailGames) {
11243               case 1:
11244                 sprintf(cmailMsg,
11245                         _("Still need to make move for game\n"));
11246                 break;
11247
11248               case 2:
11249                 sprintf(cmailMsg,
11250                         _("Still need to make moves for both games\n"));
11251                 break;
11252
11253               default:
11254                 sprintf(cmailMsg,
11255                         _("Still need to make moves for all %d games\n"),
11256                         nCmailGames);
11257                 break;
11258             }
11259         } else {
11260             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11261               case 1:
11262                 sprintf(cmailMsg,
11263                         _("Still need to make a move for game %s\n"),
11264                         string);
11265                 break;
11266
11267               case 0:
11268                 if (nCmailResults == nCmailGames) {
11269                     sprintf(cmailMsg, _("No unfinished games\n"));
11270                 } else {
11271                     sprintf(cmailMsg, _("Ready to send mail\n"));
11272                 }
11273                 break;
11274
11275               default:
11276                 sprintf(cmailMsg,
11277                         _("Still need to make moves for games %s\n"),
11278                         string);
11279             }
11280         }
11281     }
11282     return cmailMsg;
11283 #endif /* WIN32 */
11284 }
11285
11286 void
11287 ResetGameEvent()
11288 {
11289     if (gameMode == Training)
11290       SetTrainingModeOff();
11291
11292     Reset(TRUE, TRUE);
11293     cmailMsgLoaded = FALSE;
11294     if (appData.icsActive) {
11295       SendToICS(ics_prefix);
11296       SendToICS("refresh\n");
11297     }
11298 }
11299
11300 void
11301 ExitEvent(status)
11302      int status;
11303 {
11304     exiting++;
11305     if (exiting > 2) {
11306       /* Give up on clean exit */
11307       exit(status);
11308     }
11309     if (exiting > 1) {
11310       /* Keep trying for clean exit */
11311       return;
11312     }
11313
11314     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11315
11316     if (telnetISR != NULL) {
11317       RemoveInputSource(telnetISR);
11318     }
11319     if (icsPR != NoProc) {
11320       DestroyChildProcess(icsPR, TRUE);
11321     }
11322
11323     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11324     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11325
11326     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11327     /* make sure this other one finishes before killing it!                  */
11328     if(endingGame) { int count = 0;
11329         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11330         while(endingGame && count++ < 10) DoSleep(1);
11331         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11332     }
11333
11334     /* Kill off chess programs */
11335     if (first.pr != NoProc) {
11336         ExitAnalyzeMode();
11337
11338         DoSleep( appData.delayBeforeQuit );
11339         SendToProgram("quit\n", &first);
11340         DoSleep( appData.delayAfterQuit );
11341         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11342     }
11343     if (second.pr != NoProc) {
11344         DoSleep( appData.delayBeforeQuit );
11345         SendToProgram("quit\n", &second);
11346         DoSleep( appData.delayAfterQuit );
11347         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11348     }
11349     if (first.isr != NULL) {
11350         RemoveInputSource(first.isr);
11351     }
11352     if (second.isr != NULL) {
11353         RemoveInputSource(second.isr);
11354     }
11355
11356     ShutDownFrontEnd();
11357     exit(status);
11358 }
11359
11360 void
11361 PauseEvent()
11362 {
11363     if (appData.debugMode)
11364         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11365     if (pausing) {
11366         pausing = FALSE;
11367         ModeHighlight();
11368         if (gameMode == MachinePlaysWhite ||
11369             gameMode == MachinePlaysBlack) {
11370             StartClocks();
11371         } else {
11372             DisplayBothClocks();
11373         }
11374         if (gameMode == PlayFromGameFile) {
11375             if (appData.timeDelay >= 0)
11376                 AutoPlayGameLoop();
11377         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11378             Reset(FALSE, TRUE);
11379             SendToICS(ics_prefix);
11380             SendToICS("refresh\n");
11381         } else if (currentMove < forwardMostMove) {
11382             ForwardInner(forwardMostMove);
11383         }
11384         pauseExamInvalid = FALSE;
11385     } else {
11386         switch (gameMode) {
11387           default:
11388             return;
11389           case IcsExamining:
11390             pauseExamForwardMostMove = forwardMostMove;
11391             pauseExamInvalid = FALSE;
11392             /* fall through */
11393           case IcsObserving:
11394           case IcsPlayingWhite:
11395           case IcsPlayingBlack:
11396             pausing = TRUE;
11397             ModeHighlight();
11398             return;
11399           case PlayFromGameFile:
11400             (void) StopLoadGameTimer();
11401             pausing = TRUE;
11402             ModeHighlight();
11403             break;
11404           case BeginningOfGame:
11405             if (appData.icsActive) return;
11406             /* else fall through */
11407           case MachinePlaysWhite:
11408           case MachinePlaysBlack:
11409           case TwoMachinesPlay:
11410             if (forwardMostMove == 0)
11411               return;           /* don't pause if no one has moved */
11412             if ((gameMode == MachinePlaysWhite &&
11413                  !WhiteOnMove(forwardMostMove)) ||
11414                 (gameMode == MachinePlaysBlack &&
11415                  WhiteOnMove(forwardMostMove))) {
11416                 StopClocks();
11417             }
11418             pausing = TRUE;
11419             ModeHighlight();
11420             break;
11421         }
11422     }
11423 }
11424
11425 void
11426 EditCommentEvent()
11427 {
11428     char title[MSG_SIZ];
11429
11430     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11431         strcpy(title, _("Edit comment"));
11432     } else {
11433         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11434                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11435                 parseList[currentMove - 1]);
11436     }
11437
11438     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11439 }
11440
11441
11442 void
11443 EditTagsEvent()
11444 {
11445     char *tags = PGNTags(&gameInfo);
11446     EditTagsPopUp(tags);
11447     free(tags);
11448 }
11449
11450 void
11451 AnalyzeModeEvent()
11452 {
11453     if (appData.noChessProgram || gameMode == AnalyzeMode)
11454       return;
11455
11456     if (gameMode != AnalyzeFile) {
11457         if (!appData.icsEngineAnalyze) {
11458                EditGameEvent();
11459                if (gameMode != EditGame) return;
11460         }
11461         ResurrectChessProgram();
11462         SendToProgram("analyze\n", &first);
11463         first.analyzing = TRUE;
11464         /*first.maybeThinking = TRUE;*/
11465         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11466         EngineOutputPopUp();
11467     }
11468     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11469     pausing = FALSE;
11470     ModeHighlight();
11471     SetGameInfo();
11472
11473     StartAnalysisClock();
11474     GetTimeMark(&lastNodeCountTime);
11475     lastNodeCount = 0;
11476 }
11477
11478 void
11479 AnalyzeFileEvent()
11480 {
11481     if (appData.noChessProgram || gameMode == AnalyzeFile)
11482       return;
11483
11484     if (gameMode != AnalyzeMode) {
11485         EditGameEvent();
11486         if (gameMode != EditGame) return;
11487         ResurrectChessProgram();
11488         SendToProgram("analyze\n", &first);
11489         first.analyzing = TRUE;
11490         /*first.maybeThinking = TRUE;*/
11491         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11492         EngineOutputPopUp();
11493     }
11494     gameMode = AnalyzeFile;
11495     pausing = FALSE;
11496     ModeHighlight();
11497     SetGameInfo();
11498
11499     StartAnalysisClock();
11500     GetTimeMark(&lastNodeCountTime);
11501     lastNodeCount = 0;
11502 }
11503
11504 void
11505 MachineWhiteEvent()
11506 {
11507     char buf[MSG_SIZ];
11508     char *bookHit = NULL;
11509
11510     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11511       return;
11512
11513
11514     if (gameMode == PlayFromGameFile ||
11515         gameMode == TwoMachinesPlay  ||
11516         gameMode == Training         ||
11517         gameMode == AnalyzeMode      ||
11518         gameMode == EndOfGame)
11519         EditGameEvent();
11520
11521     if (gameMode == EditPosition) 
11522         EditPositionDone(TRUE);
11523
11524     if (!WhiteOnMove(currentMove)) {
11525         DisplayError(_("It is not White'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     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11538         gameMode = MachinePlaysWhite;
11539         ResetClocks();
11540     } else
11541     gameMode = MachinePlaysWhite;
11542     pausing = FALSE;
11543     ModeHighlight();
11544     SetGameInfo();
11545     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11546     DisplayTitle(buf);
11547     if (first.sendName) {
11548       sprintf(buf, "name %s\n", gameInfo.black);
11549       SendToProgram(buf, &first);
11550     }
11551     if (first.sendTime) {
11552       if (first.useColors) {
11553         SendToProgram("black\n", &first); /*gnu kludge*/
11554       }
11555       SendTimeRemaining(&first, TRUE);
11556     }
11557     if (first.useColors) {
11558       SendToProgram("white\n", &first); // [HGM] book: send 'go' 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     firstMove = FALSE;
11565
11566     if (appData.autoFlipView && !flipView) {
11567       flipView = !flipView;
11568       DrawPosition(FALSE, NULL);
11569       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11570     }
11571
11572     if(bookHit) { // [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 }
11584
11585 void
11586 MachineBlackEvent()
11587 {
11588   char buf[MSG_SIZ];
11589   char *bookHit = NULL;
11590   
11591   if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11592     return;
11593   
11594   
11595   if (gameMode == PlayFromGameFile 
11596       || gameMode == TwoMachinesPlay  
11597       || gameMode == Training     
11598       || gameMode == AnalyzeMode
11599       || gameMode == EndOfGame)
11600     EditGameEvent();
11601   
11602   if (gameMode == EditPosition) 
11603     EditPositionDone(TRUE);
11604   
11605   if (WhiteOnMove(currentMove)) 
11606     {
11607       DisplayError(_("It is not Black's turn"), 0);
11608       return;
11609     }
11610   
11611   if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11612     ExitAnalyzeMode();
11613   
11614   if (gameMode == EditGame || gameMode == AnalyzeMode 
11615       || gameMode == AnalyzeFile)
11616     TruncateGame();
11617   
11618   ResurrectChessProgram();      /* in case it isn't running */
11619   gameMode = MachinePlaysBlack;
11620   pausing  = FALSE;
11621   ModeHighlight();
11622   SetGameInfo();
11623   sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11624   DisplayTitle(buf);
11625   if (first.sendName) 
11626     {
11627       sprintf(buf, "name %s\n", gameInfo.white);
11628       SendToProgram(buf, &first);
11629     }
11630   if (first.sendTime) 
11631     {
11632       if (first.useColors) 
11633         {
11634           SendToProgram("white\n", &first); /*gnu kludge*/
11635         }
11636       SendTimeRemaining(&first, FALSE);
11637     }
11638   if (first.useColors) 
11639     {
11640       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11641     }
11642   bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11643   SetMachineThinkingEnables();
11644   first.maybeThinking = TRUE;
11645   StartClocks();
11646   
11647   if (appData.autoFlipView && flipView) 
11648     {
11649       flipView = !flipView;
11650       DrawPosition(FALSE, NULL);
11651       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11652     }
11653   if(bookHit) 
11654     { // [HGM] book: simulate book reply
11655       static char bookMove[MSG_SIZ]; // a bit generous?
11656       
11657       programStats.nodes = programStats.depth = programStats.time 
11658         = programStats.score = programStats.got_only_move = 0;
11659       sprintf(programStats.movelist, "%s (xbook)", bookHit);
11660       
11661       strcpy(bookMove, "move ");
11662       strcat(bookMove, bookHit);
11663       HandleMachineMove(bookMove, &first);
11664     }
11665   return;
11666 }
11667
11668
11669 void
11670 DisplayTwoMachinesTitle()
11671 {
11672     char buf[MSG_SIZ];
11673     if (appData.matchGames > 0) {
11674         if (first.twoMachinesColor[0] == 'w') {
11675             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11676                     gameInfo.white, gameInfo.black,
11677                     first.matchWins, second.matchWins,
11678                     matchGame - 1 - (first.matchWins + second.matchWins));
11679         } else {
11680             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11681                     gameInfo.white, gameInfo.black,
11682                     second.matchWins, first.matchWins,
11683                     matchGame - 1 - (first.matchWins + second.matchWins));
11684         }
11685     } else {
11686         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11687     }
11688     DisplayTitle(buf);
11689 }
11690
11691 void
11692 TwoMachinesEvent P((void))
11693 {
11694     int i;
11695     char buf[MSG_SIZ];
11696     ChessProgramState *onmove;
11697     char *bookHit = NULL;
11698
11699     if (appData.noChessProgram) return;
11700
11701     switch (gameMode) {
11702       case TwoMachinesPlay:
11703         return;
11704       case MachinePlaysWhite:
11705       case MachinePlaysBlack:
11706         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11707             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11708             return;
11709         }
11710         /* fall through */
11711       case BeginningOfGame:
11712       case PlayFromGameFile:
11713       case EndOfGame:
11714         EditGameEvent();
11715         if (gameMode != EditGame) return;
11716         break;
11717       case EditPosition:
11718         EditPositionDone(TRUE);
11719         break;
11720       case AnalyzeMode:
11721       case AnalyzeFile:
11722         ExitAnalyzeMode();
11723         break;
11724       case EditGame:
11725       default:
11726         break;
11727     }
11728
11729 //    forwardMostMove = currentMove;
11730     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11731     ResurrectChessProgram();    /* in case first program isn't running */
11732
11733     if (second.pr == NULL) {
11734         StartChessProgram(&second);
11735         if (second.protocolVersion == 1) {
11736           TwoMachinesEventIfReady();
11737         } else {
11738           /* kludge: allow timeout for initial "feature" command */
11739           FreezeUI();
11740           DisplayMessage("", _("Starting second chess program"));
11741           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11742         }
11743         return;
11744     }
11745     DisplayMessage("", "");
11746     InitChessProgram(&second, FALSE);
11747     SendToProgram("force\n", &second);
11748     if (startedFromSetupPosition) {
11749         SendBoard(&second, backwardMostMove);
11750     if (appData.debugMode) {
11751         fprintf(debugFP, "Two Machines\n");
11752     }
11753     }
11754     for (i = backwardMostMove; i < forwardMostMove; i++) {
11755         SendMoveToProgram(i, &second);
11756     }
11757
11758     gameMode = TwoMachinesPlay;
11759     pausing = FALSE;
11760     ModeHighlight();
11761     SetGameInfo();
11762     DisplayTwoMachinesTitle();
11763     firstMove = TRUE;
11764     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11765         onmove = &first;
11766     } else {
11767         onmove = &second;
11768     }
11769
11770     SendToProgram(first.computerString, &first);
11771     if (first.sendName) {
11772       sprintf(buf, "name %s\n", second.tidy);
11773       SendToProgram(buf, &first);
11774     }
11775     SendToProgram(second.computerString, &second);
11776     if (second.sendName) {
11777       sprintf(buf, "name %s\n", first.tidy);
11778       SendToProgram(buf, &second);
11779     }
11780
11781     ResetClocks();
11782     if (!first.sendTime || !second.sendTime) {
11783         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11784         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11785     }
11786     if (onmove->sendTime) {
11787       if (onmove->useColors) {
11788         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11789       }
11790       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11791     }
11792     if (onmove->useColors) {
11793       SendToProgram(onmove->twoMachinesColor, onmove);
11794     }
11795     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11796 //    SendToProgram("go\n", onmove);
11797     onmove->maybeThinking = TRUE;
11798     SetMachineThinkingEnables();
11799
11800     StartClocks();
11801
11802     if(bookHit) { // [HGM] book: simulate book reply
11803         static char bookMove[MSG_SIZ]; // a bit generous?
11804
11805         programStats.nodes = programStats.depth = programStats.time =
11806         programStats.score = programStats.got_only_move = 0;
11807         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11808
11809         strcpy(bookMove, "move ");
11810         strcat(bookMove, bookHit);
11811         savedMessage = bookMove; // args for deferred call
11812         savedState = onmove;
11813         ScheduleDelayedEvent(DeferredBookMove, 1);
11814     }
11815 }
11816
11817 void
11818 TrainingEvent()
11819 {
11820     if (gameMode == Training) {
11821       SetTrainingModeOff();
11822       gameMode = PlayFromGameFile;
11823       DisplayMessage("", _("Training mode off"));
11824     } else {
11825       gameMode = Training;
11826       animateTraining = appData.animate;
11827
11828       /* make sure we are not already at the end of the game */
11829       if (currentMove < forwardMostMove) {
11830         SetTrainingModeOn();
11831         DisplayMessage("", _("Training mode on"));
11832       } else {
11833         gameMode = PlayFromGameFile;
11834         DisplayError(_("Already at end of game"), 0);
11835       }
11836     }
11837     ModeHighlight();
11838 }
11839
11840 void
11841 IcsClientEvent()
11842 {
11843     if (!appData.icsActive) return;
11844     switch (gameMode) {
11845       case IcsPlayingWhite:
11846       case IcsPlayingBlack:
11847       case IcsObserving:
11848       case IcsIdle:
11849       case BeginningOfGame:
11850       case IcsExamining:
11851         return;
11852
11853       case EditGame:
11854         break;
11855
11856       case EditPosition:
11857         EditPositionDone(TRUE);
11858         break;
11859
11860       case AnalyzeMode:
11861       case AnalyzeFile:
11862         ExitAnalyzeMode();
11863         break;
11864
11865       default:
11866         EditGameEvent();
11867         break;
11868     }
11869
11870     gameMode = IcsIdle;
11871     ModeHighlight();
11872     return;
11873 }
11874
11875
11876 void
11877 EditGameEvent()
11878 {
11879     int i;
11880
11881     switch (gameMode) {
11882       case Training:
11883         SetTrainingModeOff();
11884         break;
11885       case MachinePlaysWhite:
11886       case MachinePlaysBlack:
11887       case BeginningOfGame:
11888         SendToProgram("force\n", &first);
11889         SetUserThinkingEnables();
11890         break;
11891       case PlayFromGameFile:
11892         (void) StopLoadGameTimer();
11893         if (gameFileFP != NULL) {
11894             gameFileFP = NULL;
11895         }
11896         break;
11897       case EditPosition:
11898         EditPositionDone(TRUE);
11899         break;
11900       case AnalyzeMode:
11901       case AnalyzeFile:
11902         ExitAnalyzeMode();
11903         SendToProgram("force\n", &first);
11904         break;
11905       case TwoMachinesPlay:
11906         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11907         ResurrectChessProgram();
11908         SetUserThinkingEnables();
11909         break;
11910       case EndOfGame:
11911         ResurrectChessProgram();
11912         break;
11913       case IcsPlayingBlack:
11914       case IcsPlayingWhite:
11915         DisplayError(_("Warning: You are still playing a game"), 0);
11916         break;
11917       case IcsObserving:
11918         DisplayError(_("Warning: You are still observing a game"), 0);
11919         break;
11920       case IcsExamining:
11921         DisplayError(_("Warning: You are still examining a game"), 0);
11922         break;
11923       case IcsIdle:
11924         break;
11925       case EditGame:
11926       default:
11927         return;
11928     }
11929
11930     pausing = FALSE;
11931     StopClocks();
11932     first.offeredDraw = second.offeredDraw = 0;
11933
11934     if (gameMode == PlayFromGameFile) {
11935         whiteTimeRemaining = timeRemaining[0][currentMove];
11936         blackTimeRemaining = timeRemaining[1][currentMove];
11937         DisplayTitle("");
11938     }
11939
11940     if (gameMode == MachinePlaysWhite ||
11941         gameMode == MachinePlaysBlack ||
11942         gameMode == TwoMachinesPlay ||
11943         gameMode == EndOfGame) {
11944         i = forwardMostMove;
11945         while (i > currentMove) {
11946             SendToProgram("undo\n", &first);
11947             i--;
11948         }
11949         whiteTimeRemaining = timeRemaining[0][currentMove];
11950         blackTimeRemaining = timeRemaining[1][currentMove];
11951         DisplayBothClocks();
11952         if (whiteFlag || blackFlag) {
11953             whiteFlag = blackFlag = 0;
11954         }
11955         DisplayTitle("");
11956     }
11957
11958     gameMode = EditGame;
11959     ModeHighlight();
11960     SetGameInfo();
11961 }
11962
11963
11964 void
11965 EditPositionEvent()
11966 {
11967     if (gameMode == EditPosition) {
11968         EditGameEvent();
11969         return;
11970     }
11971
11972     EditGameEvent();
11973     if (gameMode != EditGame) return;
11974
11975     gameMode = EditPosition;
11976     ModeHighlight();
11977     SetGameInfo();
11978     if (currentMove > 0)
11979       CopyBoard(boards[0], boards[currentMove]);
11980
11981     blackPlaysFirst = !WhiteOnMove(currentMove);
11982     ResetClocks();
11983     currentMove = forwardMostMove = backwardMostMove = 0;
11984     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11985     DisplayMove(-1);
11986 }
11987
11988 void
11989 ExitAnalyzeMode()
11990 {
11991     /* [DM] icsEngineAnalyze - possible call from other functions */
11992     if (appData.icsEngineAnalyze) {
11993         appData.icsEngineAnalyze = FALSE;
11994
11995         DisplayMessage("",_("Close ICS engine analyze..."));
11996     }
11997     if (first.analysisSupport && first.analyzing) {
11998       SendToProgram("exit\n", &first);
11999       first.analyzing = FALSE;
12000     }
12001     thinkOutput[0] = NULLCHAR;
12002 }
12003
12004 void
12005 EditPositionDone(Boolean fakeRights)
12006 {
12007     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12008
12009     startedFromSetupPosition = TRUE;
12010     InitChessProgram(&first, FALSE);
12011     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12012       boards[0][EP_STATUS] = EP_NONE;
12013       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12014     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12015         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12016         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12017       } else boards[0][CASTLING][2] = NoRights;
12018     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12019         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12020         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12021       } else boards[0][CASTLING][5] = NoRights;
12022     }
12023     SendToProgram("force\n", &first);
12024     if (blackPlaysFirst) {
12025         strcpy(moveList[0], "");
12026         strcpy(parseList[0], "");
12027         currentMove = forwardMostMove = backwardMostMove = 1;
12028         CopyBoard(boards[1], boards[0]);
12029     } else {
12030         currentMove = forwardMostMove = backwardMostMove = 0;
12031     }
12032     SendBoard(&first, forwardMostMove);
12033     if (appData.debugMode) {
12034         fprintf(debugFP, "EditPosDone\n");
12035     }
12036     DisplayTitle("");
12037     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12038     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12039     gameMode = EditGame;
12040     ModeHighlight();
12041     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12042     ClearHighlights(); /* [AS] */
12043 }
12044
12045 /* Pause for `ms' milliseconds */
12046 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12047 void
12048 TimeDelay(ms)
12049      long ms;
12050 {
12051     TimeMark m1, m2;
12052
12053     GetTimeMark(&m1);
12054     do {
12055         GetTimeMark(&m2);
12056     } while (SubtractTimeMarks(&m2, &m1) < ms);
12057 }
12058
12059 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12060 void
12061 SendMultiLineToICS(buf)
12062      char *buf;
12063 {
12064     char temp[MSG_SIZ+1], *p;
12065     int len;
12066
12067     len = strlen(buf);
12068     if (len > MSG_SIZ)
12069       len = MSG_SIZ;
12070
12071     strncpy(temp, buf, len);
12072     temp[len] = 0;
12073
12074     p = temp;
12075     while (*p) {
12076         if (*p == '\n' || *p == '\r')
12077           *p = ' ';
12078         ++p;
12079     }
12080
12081     strcat(temp, "\n");
12082     SendToICS(temp);
12083     SendToPlayer(temp, strlen(temp));
12084 }
12085
12086 void
12087 SetWhiteToPlayEvent()
12088 {
12089     if (gameMode == EditPosition) {
12090         blackPlaysFirst = FALSE;
12091         DisplayBothClocks();    /* works because currentMove is 0 */
12092     } else if (gameMode == IcsExamining) {
12093         SendToICS(ics_prefix);
12094         SendToICS("tomove white\n");
12095     }
12096 }
12097
12098 void
12099 SetBlackToPlayEvent()
12100 {
12101     if (gameMode == EditPosition) {
12102         blackPlaysFirst = TRUE;
12103         currentMove = 1;        /* kludge */
12104         DisplayBothClocks();
12105         currentMove = 0;
12106     } else if (gameMode == IcsExamining) {
12107         SendToICS(ics_prefix);
12108         SendToICS("tomove black\n");
12109     }
12110 }
12111
12112 void
12113 EditPositionMenuEvent(selection, x, y)
12114      ChessSquare selection;
12115      int x, y;
12116 {
12117     char buf[MSG_SIZ];
12118     ChessSquare piece = boards[0][y][x];
12119
12120     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12121
12122     switch (selection) {
12123       case ClearBoard:
12124         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12125             SendToICS(ics_prefix);
12126             SendToICS("bsetup clear\n");
12127         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12128             SendToICS(ics_prefix);
12129             SendToICS("clearboard\n");
12130         } else {
12131             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12132                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12133                 for (y = 0; y < BOARD_HEIGHT; y++) {
12134                     if (gameMode == IcsExamining) {
12135                         if (boards[currentMove][y][x] != EmptySquare) {
12136                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12137                                     AAA + x, ONE + y);
12138                             SendToICS(buf);
12139                         }
12140                     } else {
12141                         boards[0][y][x] = p;
12142                     }
12143                 }
12144             }
12145         }
12146         if (gameMode == EditPosition) {
12147             DrawPosition(FALSE, boards[0]);
12148         }
12149         break;
12150
12151       case WhitePlay:
12152         SetWhiteToPlayEvent();
12153         break;
12154
12155       case BlackPlay:
12156         SetBlackToPlayEvent();
12157         break;
12158
12159       case EmptySquare:
12160         if (gameMode == IcsExamining) {
12161             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12162             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12163             SendToICS(buf);
12164         } else {
12165             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12166                 if(x == BOARD_LEFT-2) {
12167                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12168                     boards[0][y][1] = 0;
12169                 } else
12170                 if(x == BOARD_RGHT+1) {
12171                     if(y >= gameInfo.holdingsSize) break;
12172                     boards[0][y][BOARD_WIDTH-2] = 0;
12173                 } else break;
12174             }
12175             boards[0][y][x] = EmptySquare;
12176             DrawPosition(FALSE, boards[0]);
12177         }
12178         break;
12179
12180       case PromotePiece:
12181         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12182            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12183             selection = (ChessSquare) (PROMOTED piece);
12184         } else if(piece == EmptySquare) selection = WhiteSilver;
12185         else selection = (ChessSquare)((int)piece - 1);
12186         goto defaultlabel;
12187
12188       case DemotePiece:
12189         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12190            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12191             selection = (ChessSquare) (DEMOTED piece);
12192         } else if(piece == EmptySquare) selection = BlackSilver;
12193         else selection = (ChessSquare)((int)piece + 1);
12194         goto defaultlabel;
12195
12196       case WhiteQueen:
12197       case BlackQueen:
12198         if(gameInfo.variant == VariantShatranj ||
12199            gameInfo.variant == VariantXiangqi  ||
12200            gameInfo.variant == VariantCourier  ||
12201            gameInfo.variant == VariantMakruk     )
12202             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12203         goto defaultlabel;
12204
12205       case WhiteKing:
12206       case BlackKing:
12207         if(gameInfo.variant == VariantXiangqi)
12208             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12209         if(gameInfo.variant == VariantKnightmate)
12210             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12211       default:
12212         defaultlabel:
12213         if (gameMode == IcsExamining) {
12214             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12215             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12216                     PieceToChar(selection), AAA + x, ONE + y);
12217             SendToICS(buf);
12218         } else {
12219             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12220                 int n;
12221                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12222                     n = PieceToNumber(selection - BlackPawn);
12223                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12224                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12225                     boards[0][BOARD_HEIGHT-1-n][1]++;
12226                 } else
12227                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12228                     n = PieceToNumber(selection);
12229                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12230                     boards[0][n][BOARD_WIDTH-1] = selection;
12231                     boards[0][n][BOARD_WIDTH-2]++;
12232                 }
12233             } else
12234             boards[0][y][x] = selection;
12235             DrawPosition(TRUE, boards[0]);
12236         }
12237         break;
12238     }
12239 }
12240
12241
12242 void
12243 DropMenuEvent(selection, x, y)
12244      ChessSquare selection;
12245      int x, y;
12246 {
12247     ChessMove moveType;
12248
12249     switch (gameMode) {
12250       case IcsPlayingWhite:
12251       case MachinePlaysBlack:
12252         if (!WhiteOnMove(currentMove)) {
12253             DisplayMoveError(_("It is Black's turn"));
12254             return;
12255         }
12256         moveType = WhiteDrop;
12257         break;
12258       case IcsPlayingBlack:
12259       case MachinePlaysWhite:
12260         if (WhiteOnMove(currentMove)) {
12261             DisplayMoveError(_("It is White's turn"));
12262             return;
12263         }
12264         moveType = BlackDrop;
12265         break;
12266       case EditGame:
12267         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12268         break;
12269       default:
12270         return;
12271     }
12272
12273     if (moveType == BlackDrop && selection < BlackPawn) {
12274       selection = (ChessSquare) ((int) selection
12275                                  + (int) BlackPawn - (int) WhitePawn);
12276     }
12277     if (boards[currentMove][y][x] != EmptySquare) {
12278         DisplayMoveError(_("That square is occupied"));
12279         return;
12280     }
12281
12282     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12283 }
12284
12285 void
12286 AcceptEvent()
12287 {
12288     /* Accept a pending offer of any kind from opponent */
12289
12290     if (appData.icsActive) {
12291         SendToICS(ics_prefix);
12292         SendToICS("accept\n");
12293     } else if (cmailMsgLoaded) {
12294         if (currentMove == cmailOldMove &&
12295             commentList[cmailOldMove] != NULL &&
12296             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12297                    "Black offers a draw" : "White offers a draw")) {
12298             TruncateGame();
12299             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12300             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12301         } else {
12302             DisplayError(_("There is no pending offer on this move"), 0);
12303             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12304         }
12305     } else {
12306         /* Not used for offers from chess program */
12307     }
12308 }
12309
12310 void
12311 DeclineEvent()
12312 {
12313     /* Decline a pending offer of any kind from opponent */
12314
12315     if (appData.icsActive) {
12316         SendToICS(ics_prefix);
12317         SendToICS("decline\n");
12318     } else if (cmailMsgLoaded) {
12319         if (currentMove == cmailOldMove &&
12320             commentList[cmailOldMove] != NULL &&
12321             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12322                    "Black offers a draw" : "White offers a draw")) {
12323 #ifdef NOTDEF
12324             AppendComment(cmailOldMove, "Draw declined", TRUE);
12325             DisplayComment(cmailOldMove - 1, "Draw declined");
12326 #endif /*NOTDEF*/
12327         } else {
12328             DisplayError(_("There is no pending offer on this move"), 0);
12329         }
12330     } else {
12331         /* Not used for offers from chess program */
12332     }
12333 }
12334
12335 void
12336 RematchEvent()
12337 {
12338     /* Issue ICS rematch command */
12339     if (appData.icsActive) {
12340         SendToICS(ics_prefix);
12341         SendToICS("rematch\n");
12342     }
12343 }
12344
12345 void
12346 CallFlagEvent()
12347 {
12348     /* Call your opponent's flag (claim a win on time) */
12349     if (appData.icsActive) {
12350         SendToICS(ics_prefix);
12351         SendToICS("flag\n");
12352     } else {
12353         switch (gameMode) {
12354           default:
12355             return;
12356           case MachinePlaysWhite:
12357             if (whiteFlag) {
12358                 if (blackFlag)
12359                   GameEnds(GameIsDrawn, "Both players ran out of time",
12360                            GE_PLAYER);
12361                 else
12362                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12363             } else {
12364                 DisplayError(_("Your opponent is not out of time"), 0);
12365             }
12366             break;
12367           case MachinePlaysBlack:
12368             if (blackFlag) {
12369                 if (whiteFlag)
12370                   GameEnds(GameIsDrawn, "Both players ran out of time",
12371                            GE_PLAYER);
12372                 else
12373                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12374             } else {
12375                 DisplayError(_("Your opponent is not out of time"), 0);
12376             }
12377             break;
12378         }
12379     }
12380 }
12381
12382 void
12383 DrawEvent()
12384 {
12385     /* Offer draw or accept pending draw offer from opponent */
12386
12387     if (appData.icsActive) {
12388         /* Note: tournament rules require draw offers to be
12389            made after you make your move but before you punch
12390            your clock.  Currently ICS doesn't let you do that;
12391            instead, you immediately punch your clock after making
12392            a move, but you can offer a draw at any time. */
12393
12394         SendToICS(ics_prefix);
12395         SendToICS("draw\n");
12396         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12397     } else if (cmailMsgLoaded) {
12398         if (currentMove == cmailOldMove &&
12399             commentList[cmailOldMove] != NULL &&
12400             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12401                    "Black offers a draw" : "White offers a draw")) {
12402             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12403             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12404         } else if (currentMove == cmailOldMove + 1) {
12405             char *offer = WhiteOnMove(cmailOldMove) ?
12406               "White offers a draw" : "Black offers a draw";
12407             AppendComment(currentMove, offer, TRUE);
12408             DisplayComment(currentMove - 1, offer);
12409             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12410         } else {
12411             DisplayError(_("You must make your move before offering a draw"), 0);
12412             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12413         }
12414     } else if (first.offeredDraw) {
12415         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12416     } else {
12417         if (first.sendDrawOffers) {
12418             SendToProgram("draw\n", &first);
12419             userOfferedDraw = TRUE;
12420         }
12421     }
12422 }
12423
12424 void
12425 AdjournEvent()
12426 {
12427     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12428
12429     if (appData.icsActive) {
12430         SendToICS(ics_prefix);
12431         SendToICS("adjourn\n");
12432     } else {
12433         /* Currently GNU Chess doesn't offer or accept Adjourns */
12434     }
12435 }
12436
12437
12438 void
12439 AbortEvent()
12440 {
12441     /* Offer Abort or accept pending Abort offer from opponent */
12442
12443     if (appData.icsActive) {
12444         SendToICS(ics_prefix);
12445         SendToICS("abort\n");
12446     } else {
12447         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12448     }
12449 }
12450
12451 void
12452 ResignEvent()
12453 {
12454     /* Resign.  You can do this even if it's not your turn. */
12455
12456     if (appData.icsActive) {
12457         SendToICS(ics_prefix);
12458         SendToICS("resign\n");
12459     } else {
12460         switch (gameMode) {
12461           case MachinePlaysWhite:
12462             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12463             break;
12464           case MachinePlaysBlack:
12465             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12466             break;
12467           case EditGame:
12468             if (cmailMsgLoaded) {
12469                 TruncateGame();
12470                 if (WhiteOnMove(cmailOldMove)) {
12471                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12472                 } else {
12473                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12474                 }
12475                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12476             }
12477             break;
12478           default:
12479             break;
12480         }
12481     }
12482 }
12483
12484
12485 void
12486 StopObservingEvent()
12487 {
12488     /* Stop observing current games */
12489     SendToICS(ics_prefix);
12490     SendToICS("unobserve\n");
12491 }
12492
12493 void
12494 StopExaminingEvent()
12495 {
12496     /* Stop observing current game */
12497     SendToICS(ics_prefix);
12498     SendToICS("unexamine\n");
12499 }
12500
12501 void
12502 ForwardInner(target)
12503      int target;
12504 {
12505     int limit;
12506
12507     if (appData.debugMode)
12508         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12509                 target, currentMove, forwardMostMove);
12510
12511     if (gameMode == EditPosition)
12512       return;
12513
12514     if (gameMode == PlayFromGameFile && !pausing)
12515       PauseEvent();
12516
12517     if (gameMode == IcsExamining && pausing)
12518       limit = pauseExamForwardMostMove;
12519     else
12520       limit = forwardMostMove;
12521
12522     if (target > limit) target = limit;
12523
12524     if (target > 0 && moveList[target - 1][0]) {
12525         int fromX, fromY, toX, toY;
12526         toX = moveList[target - 1][2] - AAA;
12527         toY = moveList[target - 1][3] - ONE;
12528         if (moveList[target - 1][1] == '@') {
12529             if (appData.highlightLastMove) {
12530                 SetHighlights(-1, -1, toX, toY);
12531             }
12532         } else {
12533             fromX = moveList[target - 1][0] - AAA;
12534             fromY = moveList[target - 1][1] - ONE;
12535             if (target == currentMove + 1) {
12536                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12537             }
12538             if (appData.highlightLastMove) {
12539                 SetHighlights(fromX, fromY, toX, toY);
12540             }
12541         }
12542     }
12543     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12544         gameMode == Training || gameMode == PlayFromGameFile ||
12545         gameMode == AnalyzeFile) {
12546         while (currentMove < target) {
12547             SendMoveToProgram(currentMove++, &first);
12548         }
12549     } else {
12550         currentMove = target;
12551     }
12552
12553     if (gameMode == EditGame || gameMode == EndOfGame) {
12554         whiteTimeRemaining = timeRemaining[0][currentMove];
12555         blackTimeRemaining = timeRemaining[1][currentMove];
12556     }
12557     DisplayBothClocks();
12558     DisplayMove(currentMove - 1);
12559     DrawPosition(FALSE, boards[currentMove]);
12560     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12561     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12562         DisplayComment(currentMove - 1, commentList[currentMove]);
12563     }
12564 }
12565
12566
12567 void
12568 ForwardEvent()
12569 {
12570     if (gameMode == IcsExamining && !pausing) {
12571         SendToICS(ics_prefix);
12572         SendToICS("forward\n");
12573     } else {
12574         ForwardInner(currentMove + 1);
12575     }
12576 }
12577
12578 void
12579 ToEndEvent()
12580 {
12581     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12582         /* to optimze, we temporarily turn off analysis mode while we feed
12583          * the remaining moves to the engine. Otherwise we get analysis output
12584          * after each move.
12585          */
12586         if (first.analysisSupport) {
12587           SendToProgram("exit\nforce\n", &first);
12588           first.analyzing = FALSE;
12589         }
12590     }
12591
12592     if (gameMode == IcsExamining && !pausing) {
12593         SendToICS(ics_prefix);
12594         SendToICS("forward 999999\n");
12595     } else {
12596         ForwardInner(forwardMostMove);
12597     }
12598
12599     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12600         /* we have fed all the moves, so reactivate analysis mode */
12601         SendToProgram("analyze\n", &first);
12602         first.analyzing = TRUE;
12603         /*first.maybeThinking = TRUE;*/
12604         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12605     }
12606 }
12607
12608 void
12609 BackwardInner(target)
12610      int target;
12611 {
12612     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12613
12614     if (appData.debugMode)
12615         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12616                 target, currentMove, forwardMostMove);
12617
12618     if (gameMode == EditPosition) return;
12619     if (currentMove <= backwardMostMove) {
12620         ClearHighlights();
12621         DrawPosition(full_redraw, boards[currentMove]);
12622         return;
12623     }
12624     if (gameMode == PlayFromGameFile && !pausing)
12625       PauseEvent();
12626
12627     if (moveList[target][0]) {
12628         int fromX, fromY, toX, toY;
12629         toX = moveList[target][2] - AAA;
12630         toY = moveList[target][3] - ONE;
12631         if (moveList[target][1] == '@') {
12632             if (appData.highlightLastMove) {
12633                 SetHighlights(-1, -1, toX, toY);
12634             }
12635         } else {
12636             fromX = moveList[target][0] - AAA;
12637             fromY = moveList[target][1] - ONE;
12638             if (target == currentMove - 1) {
12639                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12640             }
12641             if (appData.highlightLastMove) {
12642                 SetHighlights(fromX, fromY, toX, toY);
12643             }
12644         }
12645     }
12646     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12647         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12648         while (currentMove > target) {
12649             SendToProgram("undo\n", &first);
12650             currentMove--;
12651         }
12652     } else {
12653         currentMove = target;
12654     }
12655
12656     if (gameMode == EditGame || gameMode == EndOfGame) {
12657         whiteTimeRemaining = timeRemaining[0][currentMove];
12658         blackTimeRemaining = timeRemaining[1][currentMove];
12659     }
12660     DisplayBothClocks();
12661     DisplayMove(currentMove - 1);
12662     DrawPosition(full_redraw, boards[currentMove]);
12663     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12664     // [HGM] PV info: routine tests if comment empty
12665     DisplayComment(currentMove - 1, commentList[currentMove]);
12666 }
12667
12668 void
12669 BackwardEvent()
12670 {
12671     if (gameMode == IcsExamining && !pausing) {
12672         SendToICS(ics_prefix);
12673         SendToICS("backward\n");
12674     } else {
12675         BackwardInner(currentMove - 1);
12676     }
12677 }
12678
12679 void
12680 ToStartEvent()
12681 {
12682     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12683         /* to optimize, we temporarily turn off analysis mode while we undo
12684          * all the moves. Otherwise we get analysis output after each undo.
12685          */
12686         if (first.analysisSupport) {
12687           SendToProgram("exit\nforce\n", &first);
12688           first.analyzing = FALSE;
12689         }
12690     }
12691
12692     if (gameMode == IcsExamining && !pausing) {
12693         SendToICS(ics_prefix);
12694         SendToICS("backward 999999\n");
12695     } else {
12696         BackwardInner(backwardMostMove);
12697     }
12698
12699     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12700         /* we have fed all the moves, so reactivate analysis mode */
12701         SendToProgram("analyze\n", &first);
12702         first.analyzing = TRUE;
12703         /*first.maybeThinking = TRUE;*/
12704         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12705     }
12706 }
12707
12708 void
12709 ToNrEvent(int to)
12710 {
12711   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12712   if (to >= forwardMostMove) to = forwardMostMove;
12713   if (to <= backwardMostMove) to = backwardMostMove;
12714   if (to < currentMove) {
12715     BackwardInner(to);
12716   } else {
12717     ForwardInner(to);
12718   }
12719 }
12720
12721 void
12722 RevertEvent()
12723 {
12724     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12725         return;
12726     }
12727     if (gameMode != IcsExamining) {
12728         DisplayError(_("You are not examining a game"), 0);
12729         return;
12730     }
12731     if (pausing) {
12732         DisplayError(_("You can't revert while pausing"), 0);
12733         return;
12734     }
12735     SendToICS(ics_prefix);
12736     SendToICS("revert\n");
12737 }
12738
12739 void
12740 RetractMoveEvent()
12741 {
12742     switch (gameMode) {
12743       case MachinePlaysWhite:
12744       case MachinePlaysBlack:
12745         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12746             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12747             return;
12748         }
12749         if (forwardMostMove < 2) return;
12750         currentMove = forwardMostMove = forwardMostMove - 2;
12751         whiteTimeRemaining = timeRemaining[0][currentMove];
12752         blackTimeRemaining = timeRemaining[1][currentMove];
12753         DisplayBothClocks();
12754         DisplayMove(currentMove - 1);
12755         ClearHighlights();/*!! could figure this out*/
12756         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12757         SendToProgram("remove\n", &first);
12758         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12759         break;
12760
12761       case BeginningOfGame:
12762       default:
12763         break;
12764
12765       case IcsPlayingWhite:
12766       case IcsPlayingBlack:
12767         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12768             SendToICS(ics_prefix);
12769             SendToICS("takeback 2\n");
12770         } else {
12771             SendToICS(ics_prefix);
12772             SendToICS("takeback 1\n");
12773         }
12774         break;
12775     }
12776 }
12777
12778 void
12779 MoveNowEvent()
12780 {
12781     ChessProgramState *cps;
12782
12783     switch (gameMode) {
12784       case MachinePlaysWhite:
12785         if (!WhiteOnMove(forwardMostMove)) {
12786             DisplayError(_("It is your turn"), 0);
12787             return;
12788         }
12789         cps = &first;
12790         break;
12791       case MachinePlaysBlack:
12792         if (WhiteOnMove(forwardMostMove)) {
12793             DisplayError(_("It is your turn"), 0);
12794             return;
12795         }
12796         cps = &first;
12797         break;
12798       case TwoMachinesPlay:
12799         if (WhiteOnMove(forwardMostMove) ==
12800             (first.twoMachinesColor[0] == 'w')) {
12801             cps = &first;
12802         } else {
12803             cps = &second;
12804         }
12805         break;
12806       case BeginningOfGame:
12807       default:
12808         return;
12809     }
12810     SendToProgram("?\n", cps);
12811 }
12812
12813 void
12814 TruncateGameEvent()
12815 {
12816     EditGameEvent();
12817     if (gameMode != EditGame) return;
12818     TruncateGame();
12819 }
12820
12821 void
12822 TruncateGame()
12823 {
12824     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12825     if (forwardMostMove > currentMove) {
12826         if (gameInfo.resultDetails != NULL) {
12827             free(gameInfo.resultDetails);
12828             gameInfo.resultDetails = NULL;
12829             gameInfo.result = GameUnfinished;
12830         }
12831         forwardMostMove = currentMove;
12832         HistorySet(parseList, backwardMostMove, forwardMostMove,
12833                    currentMove-1);
12834     }
12835 }
12836
12837 void
12838 HintEvent()
12839 {
12840     if (appData.noChessProgram) return;
12841     switch (gameMode) {
12842       case MachinePlaysWhite:
12843         if (WhiteOnMove(forwardMostMove)) {
12844             DisplayError(_("Wait until your turn"), 0);
12845             return;
12846         }
12847         break;
12848       case BeginningOfGame:
12849       case MachinePlaysBlack:
12850         if (!WhiteOnMove(forwardMostMove)) {
12851             DisplayError(_("Wait until your turn"), 0);
12852             return;
12853         }
12854         break;
12855       default:
12856         DisplayError(_("No hint available"), 0);
12857         return;
12858     }
12859     SendToProgram("hint\n", &first);
12860     hintRequested = TRUE;
12861 }
12862
12863 void
12864 BookEvent()
12865 {
12866     if (appData.noChessProgram) return;
12867     switch (gameMode) {
12868       case MachinePlaysWhite:
12869         if (WhiteOnMove(forwardMostMove)) {
12870             DisplayError(_("Wait until your turn"), 0);
12871             return;
12872         }
12873         break;
12874       case BeginningOfGame:
12875       case MachinePlaysBlack:
12876         if (!WhiteOnMove(forwardMostMove)) {
12877             DisplayError(_("Wait until your turn"), 0);
12878             return;
12879         }
12880         break;
12881       case EditPosition:
12882         EditPositionDone(TRUE);
12883         break;
12884       case TwoMachinesPlay:
12885         return;
12886       default:
12887         break;
12888     }
12889     SendToProgram("bk\n", &first);
12890     bookOutput[0] = NULLCHAR;
12891     bookRequested = TRUE;
12892 }
12893
12894 void
12895 AboutGameEvent()
12896 {
12897     char *tags = PGNTags(&gameInfo);
12898     TagsPopUp(tags, CmailMsg());
12899     free(tags);
12900 }
12901
12902 /* end button procedures */
12903
12904 void
12905 PrintPosition(fp, move)
12906      FILE *fp;
12907      int move;
12908 {
12909     int i, j;
12910
12911     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12912         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12913             char c = PieceToChar(boards[move][i][j]);
12914             fputc(c == 'x' ? '.' : c, fp);
12915             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12916         }
12917     }
12918     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12919       fprintf(fp, "white to play\n");
12920     else
12921       fprintf(fp, "black to play\n");
12922 }
12923
12924 void
12925 PrintOpponents(fp)
12926      FILE *fp;
12927 {
12928     if (gameInfo.white != NULL) {
12929         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12930     } else {
12931         fprintf(fp, "\n");
12932     }
12933 }
12934
12935 /* Find last component of program's own name, using some heuristics */
12936 void
12937 TidyProgramName(prog, host, buf)
12938      char *prog, *host, buf[MSG_SIZ];
12939 {
12940     char *p, *q;
12941     int local = (strcmp(host, "localhost") == 0);
12942     while (!local && (p = strchr(prog, ';')) != NULL) {
12943         p++;
12944         while (*p == ' ') p++;
12945         prog = p;
12946     }
12947     if (*prog == '"' || *prog == '\'') {
12948         q = strchr(prog + 1, *prog);
12949     } else {
12950         q = strchr(prog, ' ');
12951     }
12952     if (q == NULL) q = prog + strlen(prog);
12953     p = q;
12954     while (p >= prog && *p != '/' && *p != '\\') p--;
12955     p++;
12956     if(p == prog && *p == '"') p++;
12957     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12958     memcpy(buf, p, q - p);
12959     buf[q - p] = NULLCHAR;
12960     if (!local) {
12961         strcat(buf, "@");
12962         strcat(buf, host);
12963     }
12964 }
12965
12966 char *
12967 TimeControlTagValue()
12968 {
12969     char buf[MSG_SIZ];
12970     if (!appData.clockMode) {
12971         strcpy(buf, "-");
12972     } else if (movesPerSession > 0) {
12973         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12974     } else if (timeIncrement == 0) {
12975         sprintf(buf, "%ld", timeControl/1000);
12976     } else {
12977         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12978     }
12979     return StrSave(buf);
12980 }
12981
12982 void
12983 SetGameInfo()
12984 {
12985     /* This routine is used only for certain modes */
12986     VariantClass v = gameInfo.variant;
12987     ChessMove r = GameUnfinished;
12988     char *p = NULL;
12989
12990     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12991         r = gameInfo.result; 
12992         p = gameInfo.resultDetails; 
12993         gameInfo.resultDetails = NULL;
12994     }
12995     ClearGameInfo(&gameInfo);
12996     gameInfo.variant = v;
12997
12998     switch (gameMode) {
12999       case MachinePlaysWhite:
13000         gameInfo.event = StrSave( appData.pgnEventHeader );
13001         gameInfo.site = StrSave(HostName());
13002         gameInfo.date = PGNDate();
13003         gameInfo.round = StrSave("-");
13004         gameInfo.white = StrSave(first.tidy);
13005         gameInfo.black = StrSave(UserName());
13006         gameInfo.timeControl = TimeControlTagValue();
13007         break;
13008
13009       case MachinePlaysBlack:
13010         gameInfo.event = StrSave( appData.pgnEventHeader );
13011         gameInfo.site = StrSave(HostName());
13012         gameInfo.date = PGNDate();
13013         gameInfo.round = StrSave("-");
13014         gameInfo.white = StrSave(UserName());
13015         gameInfo.black = StrSave(first.tidy);
13016         gameInfo.timeControl = TimeControlTagValue();
13017         break;
13018
13019       case TwoMachinesPlay:
13020         gameInfo.event = StrSave( appData.pgnEventHeader );
13021         gameInfo.site = StrSave(HostName());
13022         gameInfo.date = PGNDate();
13023         if (matchGame > 0) {
13024             char buf[MSG_SIZ];
13025             sprintf(buf, "%d", matchGame);
13026             gameInfo.round = StrSave(buf);
13027         } else {
13028             gameInfo.round = StrSave("-");
13029         }
13030         if (first.twoMachinesColor[0] == 'w') {
13031             gameInfo.white = StrSave(first.tidy);
13032             gameInfo.black = StrSave(second.tidy);
13033         } else {
13034             gameInfo.white = StrSave(second.tidy);
13035             gameInfo.black = StrSave(first.tidy);
13036         }
13037         gameInfo.timeControl = TimeControlTagValue();
13038         break;
13039
13040       case EditGame:
13041         gameInfo.event = StrSave("Edited game");
13042         gameInfo.site = StrSave(HostName());
13043         gameInfo.date = PGNDate();
13044         gameInfo.round = StrSave("-");
13045         gameInfo.white = StrSave("-");
13046         gameInfo.black = StrSave("-");
13047         gameInfo.result = r;
13048         gameInfo.resultDetails = p;
13049         break;
13050
13051       case EditPosition:
13052         gameInfo.event = StrSave("Edited position");
13053         gameInfo.site = StrSave(HostName());
13054         gameInfo.date = PGNDate();
13055         gameInfo.round = StrSave("-");
13056         gameInfo.white = StrSave("-");
13057         gameInfo.black = StrSave("-");
13058         break;
13059
13060       case IcsPlayingWhite:
13061       case IcsPlayingBlack:
13062       case IcsObserving:
13063       case IcsExamining:
13064         break;
13065
13066       case PlayFromGameFile:
13067         gameInfo.event = StrSave("Game from non-PGN file");
13068         gameInfo.site = StrSave(HostName());
13069         gameInfo.date = PGNDate();
13070         gameInfo.round = StrSave("-");
13071         gameInfo.white = StrSave("?");
13072         gameInfo.black = StrSave("?");
13073         break;
13074
13075       default:
13076         break;
13077     }
13078 }
13079
13080 void
13081 ReplaceComment(index, text)
13082      int index;
13083      char *text;
13084 {
13085     int len;
13086
13087     while (*text == '\n') text++;
13088     len = strlen(text);
13089     while (len > 0 && text[len - 1] == '\n') len--;
13090
13091     if (commentList[index] != NULL)
13092       free(commentList[index]);
13093
13094     if (len == 0) {
13095         commentList[index] = NULL;
13096         return;
13097     }
13098   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13099       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13100       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13101     commentList[index] = (char *) malloc(len + 2);
13102     strncpy(commentList[index], text, len);
13103     commentList[index][len] = '\n';
13104     commentList[index][len + 1] = NULLCHAR;
13105   } else { 
13106     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13107     char *p;
13108     commentList[index] = (char *) malloc(len + 6);
13109     strcpy(commentList[index], "{\n");
13110     strncpy(commentList[index]+2, text, len);
13111     commentList[index][len+2] = NULLCHAR;
13112     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13113     strcat(commentList[index], "\n}\n");
13114   }
13115 }
13116
13117 void
13118 CrushCRs(text)
13119      char *text;
13120 {
13121   char *p = text;
13122   char *q = text;
13123   char ch;
13124
13125   do {
13126     ch = *p++;
13127     if (ch == '\r') continue;
13128     *q++ = ch;
13129   } while (ch != '\0');
13130 }
13131
13132 void
13133 AppendComment(index, text, addBraces)
13134      int index;
13135      char *text;
13136      Boolean addBraces; // [HGM] braces: tells if we should add {}
13137 {
13138     int oldlen, len;
13139     char *old;
13140
13141 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13142     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13143
13144     CrushCRs(text);
13145     while (*text == '\n') text++;
13146     len = strlen(text);
13147     while (len > 0 && text[len - 1] == '\n') len--;
13148
13149     if (len == 0) return;
13150
13151     if (commentList[index] != NULL) {
13152         old = commentList[index];
13153         oldlen = strlen(old);
13154         while(commentList[index][oldlen-1] ==  '\n')
13155           commentList[index][--oldlen] = NULLCHAR;
13156         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13157         strcpy(commentList[index], old);
13158         free(old);
13159         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13160         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13161           if(addBraces) addBraces = FALSE; else { text++; len--; }
13162           while (*text == '\n') { text++; len--; }
13163           commentList[index][--oldlen] = NULLCHAR;
13164       }
13165         if(addBraces) strcat(commentList[index], "\n{\n");
13166         else          strcat(commentList[index], "\n");
13167         strcat(commentList[index], text);
13168         if(addBraces) strcat(commentList[index], "\n}\n");
13169         else          strcat(commentList[index], "\n");
13170     } else {
13171         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13172         if(addBraces)
13173              strcpy(commentList[index], "{\n");
13174         else commentList[index][0] = NULLCHAR;
13175         strcat(commentList[index], text);
13176         strcat(commentList[index], "\n");
13177         if(addBraces) strcat(commentList[index], "}\n");
13178     }
13179 }
13180
13181 static char * FindStr( char * text, char * sub_text )
13182 {
13183     char * result = strstr( text, sub_text );
13184
13185     if( result != NULL ) {
13186         result += strlen( sub_text );
13187     }
13188
13189     return result;
13190 }
13191
13192 /* [AS] Try to extract PV info from PGN comment */
13193 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13194 char *GetInfoFromComment( int index, char * text )
13195 {
13196     char * sep = text;
13197
13198     if( text != NULL && index > 0 ) {
13199         int score = 0;
13200         int depth = 0;
13201         int time = -1, sec = 0, deci;
13202         char * s_eval = FindStr( text, "[%eval " );
13203         char * s_emt = FindStr( text, "[%emt " );
13204
13205         if( s_eval != NULL || s_emt != NULL ) {
13206             /* New style */
13207             char delim;
13208
13209             if( s_eval != NULL ) {
13210                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13211                     return text;
13212                 }
13213
13214                 if( delim != ']' ) {
13215                     return text;
13216                 }
13217             }
13218
13219             if( s_emt != NULL ) {
13220             }
13221                 return text;
13222         }
13223         else {
13224             /* We expect something like: [+|-]nnn.nn/dd */
13225             int score_lo = 0;
13226
13227             if(*text != '{') return text; // [HGM] braces: must be normal comment
13228
13229             sep = strchr( text, '/' );
13230             if( sep == NULL || sep < (text+4) ) {
13231                 return text;
13232             }
13233
13234             time = -1; sec = -1; deci = -1;
13235             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13236                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13237                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13238                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13239                 return text;
13240             }
13241
13242             if( score_lo < 0 || score_lo >= 100 ) {
13243                 return text;
13244             }
13245
13246             if(sec >= 0) time = 600*time + 10*sec; else
13247             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13248
13249             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13250
13251             /* [HGM] PV time: now locate end of PV info */
13252             while( *++sep >= '0' && *sep <= '9'); // strip depth
13253             if(time >= 0)
13254             while( *++sep >= '0' && *sep <= '9'); // strip time
13255             if(sec >= 0)
13256             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13257             if(deci >= 0)
13258             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13259             while(*sep == ' ') sep++;
13260         }
13261
13262         if( depth <= 0 ) {
13263             return text;
13264         }
13265
13266         if( time < 0 ) {
13267             time = -1;
13268         }
13269
13270         pvInfoList[index-1].depth = depth;
13271         pvInfoList[index-1].score = score;
13272         pvInfoList[index-1].time  = 10*time; // centi-sec
13273         if(*sep == '}') *sep = 0; else *--sep = '{';
13274     }
13275     return sep;
13276 }
13277
13278 void
13279 SendToProgram(message, cps)
13280      char *message;
13281      ChessProgramState *cps;
13282 {
13283     int count, outCount, error;
13284     char buf[MSG_SIZ];
13285
13286     if (cps->pr == NULL) return;
13287     Attention(cps);
13288
13289     if (appData.debugMode) {
13290         TimeMark now;
13291         GetTimeMark(&now);
13292         fprintf(debugFP, "%ld >%-6s: %s",
13293                 SubtractTimeMarks(&now, &programStartTime),
13294                 cps->which, message);
13295     }
13296
13297     count = strlen(message);
13298     outCount = OutputToProcess(cps->pr, message, count, &error);
13299     if (outCount < count && !exiting
13300                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13301         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13302         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13303             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13304                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13305                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13306             } else {
13307                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13308             }
13309             gameInfo.resultDetails = StrSave(buf);
13310         }
13311         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13312     }
13313 }
13314
13315 void
13316 ReceiveFromProgram(isr, closure, message, count, error)
13317      InputSourceRef isr;
13318      VOIDSTAR closure;
13319      char *message;
13320      int count;
13321      int error;
13322 {
13323     char *end_str;
13324     char buf[MSG_SIZ];
13325     ChessProgramState *cps = (ChessProgramState *)closure;
13326
13327     if (isr != cps->isr) return; /* Killed intentionally */
13328     if (count <= 0) {
13329         if (count == 0) {
13330             sprintf(buf,
13331                     _("Error: %s chess program (%s) exited unexpectedly"),
13332                     cps->which, cps->program);
13333         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13334                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13335                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13336                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13337                 } else {
13338                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13339                 }
13340                 gameInfo.resultDetails = StrSave(buf);
13341             }
13342             RemoveInputSource(cps->isr);
13343             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13344         } else {
13345             sprintf(buf,
13346                     _("Error reading from %s chess program (%s)"),
13347                     cps->which, cps->program);
13348             RemoveInputSource(cps->isr);
13349
13350             /* [AS] Program is misbehaving badly... kill it */
13351             if( count == -2 ) {
13352                 DestroyChildProcess( cps->pr, 9 );
13353                 cps->pr = NoProc;
13354             }
13355
13356             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13357         }
13358         return;
13359     }
13360
13361     if ((end_str = strchr(message, '\r')) != NULL)
13362       *end_str = NULLCHAR;
13363     if ((end_str = strchr(message, '\n')) != NULL)
13364       *end_str = NULLCHAR;
13365
13366     if (appData.debugMode) {
13367         TimeMark now; int print = 1;
13368         char *quote = ""; char c; int i;
13369
13370         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13371                 char start = message[0];
13372                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13373                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13374                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13375                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13376                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13377                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13378                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13379                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13380                         { quote = "# "; print = (appData.engineComments == 2); }
13381                 message[0] = start; // restore original message
13382         }
13383         if(print) {
13384                 GetTimeMark(&now);
13385                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13386                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13387                         quote,
13388                         message);
13389         }
13390     }
13391
13392     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13393     if (appData.icsEngineAnalyze) {
13394         if (strstr(message, "whisper") != NULL ||
13395              strstr(message, "kibitz") != NULL ||
13396             strstr(message, "tellics") != NULL) return;
13397     }
13398
13399     HandleMachineMove(message, cps);
13400 }
13401
13402
13403 void
13404 SendTimeControl(cps, mps, tc, inc, sd, st)
13405      ChessProgramState *cps;
13406      int mps, inc, sd, st;
13407      long tc;
13408 {
13409     char buf[MSG_SIZ];
13410     int seconds;
13411
13412     if( timeControl_2 > 0 ) {
13413         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13414             tc = timeControl_2;
13415         }
13416     }
13417     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13418     inc /= cps->timeOdds;
13419     st  /= cps->timeOdds;
13420
13421     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13422
13423     if (st > 0) {
13424       /* Set exact time per move, normally using st command */
13425       if (cps->stKludge) {
13426         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13427         seconds = st % 60;
13428         if (seconds == 0) {
13429           sprintf(buf, "level 1 %d\n", st/60);
13430         } else {
13431           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13432         }
13433       } else {
13434         sprintf(buf, "st %d\n", st);
13435       }
13436     } else {
13437       /* Set conventional or incremental time control, using level command */
13438       if (seconds == 0) {
13439         /* Note old gnuchess bug -- minutes:seconds used to not work.
13440            Fixed in later versions, but still avoid :seconds
13441            when seconds is 0. */
13442         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13443       } else {
13444         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13445                 seconds, inc/1000);
13446       }
13447     }
13448     SendToProgram(buf, cps);
13449
13450     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13451     /* Orthogonally, limit search to given depth */
13452     if (sd > 0) {
13453       if (cps->sdKludge) {
13454         sprintf(buf, "depth\n%d\n", sd);
13455       } else {
13456         sprintf(buf, "sd %d\n", sd);
13457       }
13458       SendToProgram(buf, cps);
13459     }
13460
13461     if(cps->nps > 0) { /* [HGM] nps */
13462         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13463         else {
13464                 sprintf(buf, "nps %d\n", cps->nps);
13465               SendToProgram(buf, cps);
13466         }
13467     }
13468 }
13469
13470 ChessProgramState *WhitePlayer()
13471 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13472 {
13473     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13474        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13475         return &second;
13476     return &first;
13477 }
13478
13479 void
13480 SendTimeRemaining(cps, machineWhite)
13481      ChessProgramState *cps;
13482      int /*boolean*/ machineWhite;
13483 {
13484     char message[MSG_SIZ];
13485     long time, otime;
13486
13487     /* Note: this routine must be called when the clocks are stopped
13488        or when they have *just* been set or switched; otherwise
13489        it will be off by the time since the current tick started.
13490     */
13491     if (machineWhite) {
13492         time = whiteTimeRemaining / 10;
13493         otime = blackTimeRemaining / 10;
13494     } else {
13495         time = blackTimeRemaining / 10;
13496         otime = whiteTimeRemaining / 10;
13497     }
13498     /* [HGM] translate opponent's time by time-odds factor */
13499     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13500     if (appData.debugMode) {
13501         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13502     }
13503
13504     if (time <= 0) time = 1;
13505     if (otime <= 0) otime = 1;
13506
13507     sprintf(message, "time %ld\n", time);
13508     SendToProgram(message, cps);
13509
13510     sprintf(message, "otim %ld\n", otime);
13511     SendToProgram(message, cps);
13512 }
13513
13514 int
13515 BoolFeature(p, name, loc, cps)
13516      char **p;
13517      char *name;
13518      int *loc;
13519      ChessProgramState *cps;
13520 {
13521   char buf[MSG_SIZ];
13522   int len = strlen(name);
13523   int val;
13524   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13525     (*p) += len + 1;
13526     sscanf(*p, "%d", &val);
13527     *loc = (val != 0);
13528     while (**p && **p != ' ') (*p)++;
13529     sprintf(buf, "accepted %s\n", name);
13530     SendToProgram(buf, cps);
13531     return TRUE;
13532   }
13533   return FALSE;
13534 }
13535
13536 int
13537 IntFeature(p, name, loc, cps)
13538      char **p;
13539      char *name;
13540      int *loc;
13541      ChessProgramState *cps;
13542 {
13543   char buf[MSG_SIZ];
13544   int len = strlen(name);
13545   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13546     (*p) += len + 1;
13547     sscanf(*p, "%d", loc);
13548     while (**p && **p != ' ') (*p)++;
13549     sprintf(buf, "accepted %s\n", name);
13550     SendToProgram(buf, cps);
13551     return TRUE;
13552   }
13553   return FALSE;
13554 }
13555
13556 int
13557 StringFeature(p, name, loc, cps)
13558      char **p;
13559      char *name;
13560      char loc[];
13561      ChessProgramState *cps;
13562 {
13563   char buf[MSG_SIZ];
13564   int len = strlen(name);
13565   if (strncmp((*p), name, len) == 0
13566       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13567     (*p) += len + 2;
13568     sscanf(*p, "%[^\"]", loc);
13569     while (**p && **p != '\"') (*p)++;
13570     if (**p == '\"') (*p)++;
13571     sprintf(buf, "accepted %s\n", name);
13572     SendToProgram(buf, cps);
13573     return TRUE;
13574   }
13575   return FALSE;
13576 }
13577
13578 int
13579 ParseOption(Option *opt, ChessProgramState *cps)
13580 // [HGM] options: process the string that defines an engine option, and determine
13581 // name, type, default value, and allowed value range
13582 {
13583         char *p, *q, buf[MSG_SIZ];
13584         int n, min = (-1)<<31, max = 1<<31, def;
13585
13586         if(p = strstr(opt->name, " -spin ")) {
13587             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13588             if(max < min) max = min; // enforce consistency
13589             if(def < min) def = min;
13590             if(def > max) def = max;
13591             opt->value = def;
13592             opt->min = min;
13593             opt->max = max;
13594             opt->type = Spin;
13595         } else if((p = strstr(opt->name, " -slider "))) {
13596             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13597             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13598             if(max < min) max = min; // enforce consistency
13599             if(def < min) def = min;
13600             if(def > max) def = max;
13601             opt->value = def;
13602             opt->min = min;
13603             opt->max = max;
13604             opt->type = Spin; // Slider;
13605         } else if((p = strstr(opt->name, " -string "))) {
13606             opt->textValue = p+9;
13607             opt->type = TextBox;
13608         } else if((p = strstr(opt->name, " -file "))) {
13609             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13610             opt->textValue = p+7;
13611             opt->type = TextBox; // FileName;
13612         } else if((p = strstr(opt->name, " -path "))) {
13613             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13614             opt->textValue = p+7;
13615             opt->type = TextBox; // PathName;
13616         } else if(p = strstr(opt->name, " -check ")) {
13617             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13618             opt->value = (def != 0);
13619             opt->type = CheckBox;
13620         } else if(p = strstr(opt->name, " -combo ")) {
13621             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13622             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13623             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13624             opt->value = n = 0;
13625             while(q = StrStr(q, " /// ")) {
13626                 n++; *q = 0;    // count choices, and null-terminate each of them
13627                 q += 5;
13628                 if(*q == '*') { // remember default, which is marked with * prefix
13629                     q++;
13630                     opt->value = n;
13631                 }
13632                 cps->comboList[cps->comboCnt++] = q;
13633             }
13634             cps->comboList[cps->comboCnt++] = NULL;
13635             opt->max = n + 1;
13636             opt->type = ComboBox;
13637         } else if(p = strstr(opt->name, " -button")) {
13638             opt->type = Button;
13639         } else if(p = strstr(opt->name, " -save")) {
13640             opt->type = SaveButton;
13641         } else return FALSE;
13642         *p = 0; // terminate option name
13643         // now look if the command-line options define a setting for this engine option.
13644         if(cps->optionSettings && cps->optionSettings[0])
13645             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13646         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13647                 sprintf(buf, "option %s", p);
13648                 if(p = strstr(buf, ",")) *p = 0;
13649                 strcat(buf, "\n");
13650                 SendToProgram(buf, cps);
13651         }
13652         return TRUE;
13653 }
13654
13655 void
13656 FeatureDone(cps, val)
13657      ChessProgramState* cps;
13658      int val;
13659 {
13660   DelayedEventCallback cb = GetDelayedEvent();
13661   if ((cb == InitBackEnd3 && cps == &first) ||
13662       (cb == TwoMachinesEventIfReady && cps == &second)) {
13663     CancelDelayedEvent();
13664     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13665   }
13666   cps->initDone = val;
13667 }
13668
13669 /* Parse feature command from engine */
13670 void
13671 ParseFeatures(args, cps)
13672      char* args;
13673      ChessProgramState *cps;
13674 {
13675   char *p = args;
13676   char *q;
13677   int val;
13678   char buf[MSG_SIZ];
13679
13680   for (;;) {
13681     while (*p == ' ') p++;
13682     if (*p == NULLCHAR) return;
13683
13684     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13685     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13686     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13687     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13688     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13689     if (BoolFeature(&p, "reuse", &val, cps)) {
13690       /* Engine can disable reuse, but can't enable it if user said no */
13691       if (!val) cps->reuse = FALSE;
13692       continue;
13693     }
13694     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13695     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13696       if (gameMode == TwoMachinesPlay) {
13697         DisplayTwoMachinesTitle();
13698       } else {
13699         DisplayTitle("");
13700       }
13701       continue;
13702     }
13703     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13704     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13705     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13706     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13707     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13708     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13709     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13710     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13711     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13712     if (IntFeature(&p, "done", &val, cps)) {
13713       FeatureDone(cps, val);
13714       continue;
13715     }
13716     /* Added by Tord: */
13717     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13718     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13719     /* End of additions by Tord */
13720
13721     /* [HGM] added features: */
13722     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13723     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13724     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13725     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13726     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13727     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13728     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13729         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13730             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13731             SendToProgram(buf, cps);
13732             continue;
13733         }
13734         if(cps->nrOptions >= MAX_OPTIONS) {
13735             cps->nrOptions--;
13736             sprintf(buf, "%s engine has too many options\n", cps->which);
13737             DisplayError(buf, 0);
13738         }
13739         continue;
13740     }
13741     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13742     /* End of additions by HGM */
13743
13744     /* unknown feature: complain and skip */
13745     q = p;
13746     while (*q && *q != '=') q++;
13747     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13748     SendToProgram(buf, cps);
13749     p = q;
13750     if (*p == '=') {
13751       p++;
13752       if (*p == '\"') {
13753         p++;
13754         while (*p && *p != '\"') p++;
13755         if (*p == '\"') p++;
13756       } else {
13757         while (*p && *p != ' ') p++;
13758       }
13759     }
13760   }
13761
13762 }
13763
13764 void
13765 PeriodicUpdatesEvent(newState)
13766      int newState;
13767 {
13768     if (newState == appData.periodicUpdates)
13769       return;
13770
13771     appData.periodicUpdates=newState;
13772
13773     /* Display type changes, so update it now */
13774 //    DisplayAnalysis();
13775
13776     /* Get the ball rolling again... */
13777     if (newState) {
13778         AnalysisPeriodicEvent(1);
13779         StartAnalysisClock();
13780     }
13781 }
13782
13783 void
13784 PonderNextMoveEvent(newState)
13785      int newState;
13786 {
13787     if (newState == appData.ponderNextMove) return;
13788     if (gameMode == EditPosition) EditPositionDone(TRUE);
13789     if (newState) {
13790         SendToProgram("hard\n", &first);
13791         if (gameMode == TwoMachinesPlay) {
13792             SendToProgram("hard\n", &second);
13793         }
13794     } else {
13795         SendToProgram("easy\n", &first);
13796         thinkOutput[0] = NULLCHAR;
13797         if (gameMode == TwoMachinesPlay) {
13798             SendToProgram("easy\n", &second);
13799         }
13800     }
13801     appData.ponderNextMove = newState;
13802 }
13803
13804 void
13805 NewSettingEvent(option, command, value)
13806      char *command;
13807      int option, value;
13808 {
13809     char buf[MSG_SIZ];
13810
13811     if (gameMode == EditPosition) EditPositionDone(TRUE);
13812     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13813     SendToProgram(buf, &first);
13814     if (gameMode == TwoMachinesPlay) {
13815         SendToProgram(buf, &second);
13816     }
13817 }
13818
13819 void
13820 ShowThinkingEvent()
13821 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13822 {
13823     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13824     int newState = appData.showThinking
13825         // [HGM] thinking: other features now need thinking output as well
13826         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13827
13828     if (oldState == newState) return;
13829     oldState = newState;
13830     if (gameMode == EditPosition) EditPositionDone(TRUE);
13831     if (oldState) {
13832         SendToProgram("post\n", &first);
13833         if (gameMode == TwoMachinesPlay) {
13834             SendToProgram("post\n", &second);
13835         }
13836     } else {
13837         SendToProgram("nopost\n", &first);
13838         thinkOutput[0] = NULLCHAR;
13839         if (gameMode == TwoMachinesPlay) {
13840             SendToProgram("nopost\n", &second);
13841         }
13842     }
13843 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13844 }
13845
13846 void
13847 AskQuestionEvent(title, question, replyPrefix, which)
13848      char *title; char *question; char *replyPrefix; char *which;
13849 {
13850   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13851   if (pr == NoProc) return;
13852   AskQuestion(title, question, replyPrefix, pr);
13853 }
13854
13855 void
13856 DisplayMove(moveNumber)
13857      int moveNumber;
13858 {
13859     char message[MSG_SIZ];
13860     char res[MSG_SIZ];
13861     char cpThinkOutput[MSG_SIZ];
13862
13863     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13864
13865     if (moveNumber == forwardMostMove - 1 ||
13866         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13867
13868         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13869
13870         if (strchr(cpThinkOutput, '\n')) {
13871             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13872         }
13873     } else {
13874         *cpThinkOutput = NULLCHAR;
13875     }
13876
13877     /* [AS] Hide thinking from human user */
13878     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13879         *cpThinkOutput = NULLCHAR;
13880         if( thinkOutput[0] != NULLCHAR ) {
13881             int i;
13882
13883             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13884                 cpThinkOutput[i] = '.';
13885             }
13886             cpThinkOutput[i] = NULLCHAR;
13887             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13888         }
13889     }
13890
13891     if (moveNumber == forwardMostMove - 1 &&
13892         gameInfo.resultDetails != NULL) {
13893         if (gameInfo.resultDetails[0] == NULLCHAR) {
13894             sprintf(res, " %s", PGNResult(gameInfo.result));
13895         } else {
13896             sprintf(res, " {%s} %s",
13897                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13898         }
13899     } else {
13900         res[0] = NULLCHAR;
13901     }
13902
13903     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13904         DisplayMessage(res, cpThinkOutput);
13905     } else {
13906         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13907                 WhiteOnMove(moveNumber) ? " " : ".. ",
13908                 parseList[moveNumber], res);
13909         DisplayMessage(message, cpThinkOutput);
13910     }
13911 }
13912
13913 void
13914 DisplayComment(moveNumber, text)
13915      int moveNumber;
13916      char *text;
13917 {
13918     char title[MSG_SIZ];
13919     char buf[8000]; // comment can be long!
13920     int score, depth;
13921     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13922       strcpy(title, "Comment");
13923     } else {
13924       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13925               WhiteOnMove(moveNumber) ? " " : ".. ",
13926               parseList[moveNumber]);
13927     }
13928     // [HGM] PV info: display PV info together with (or as) comment
13929     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13930       if(text == NULL) text = "";                                           
13931       score = pvInfoList[moveNumber].score;
13932       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13933               depth, (pvInfoList[moveNumber].time+50)/100, text);
13934       text = buf;
13935     }
13936     if (text != NULL && (appData.autoDisplayComment || commentUp))
13937       CommentPopUp(title, text);
13938 }
13939
13940 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13941  * might be busy thinking or pondering.  It can be omitted if your
13942  * gnuchess is configured to stop thinking immediately on any user
13943  * input.  However, that gnuchess feature depends on the FIONREAD
13944  * ioctl, which does not work properly on some flavors of Unix.
13945  */
13946 void
13947 Attention(cps)
13948      ChessProgramState *cps;
13949 {
13950 #if ATTENTION
13951     if (!cps->useSigint) return;
13952     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13953     switch (gameMode) {
13954       case MachinePlaysWhite:
13955       case MachinePlaysBlack:
13956       case TwoMachinesPlay:
13957       case IcsPlayingWhite:
13958       case IcsPlayingBlack:
13959       case AnalyzeMode:
13960       case AnalyzeFile:
13961         /* Skip if we know it isn't thinking */
13962         if (!cps->maybeThinking) return;
13963         if (appData.debugMode)
13964           fprintf(debugFP, "Interrupting %s\n", cps->which);
13965         InterruptChildProcess(cps->pr);
13966         cps->maybeThinking = FALSE;
13967         break;
13968       default:
13969         break;
13970     }
13971 #endif /*ATTENTION*/
13972 }
13973
13974 int
13975 CheckFlags()
13976 {
13977     if (whiteTimeRemaining <= 0) {
13978         if (!whiteFlag) {
13979             whiteFlag = TRUE;
13980             if (appData.icsActive) {
13981                 if (appData.autoCallFlag &&
13982                     gameMode == IcsPlayingBlack && !blackFlag) {
13983                   SendToICS(ics_prefix);
13984                   SendToICS("flag\n");
13985                 }
13986             } else {
13987                 if (blackFlag) {
13988                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13989                 } else {
13990                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13991                     if (appData.autoCallFlag) {
13992                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13993                         return TRUE;
13994                     }
13995                 }
13996             }
13997         }
13998     }
13999     if (blackTimeRemaining <= 0) {
14000         if (!blackFlag) {
14001             blackFlag = TRUE;
14002             if (appData.icsActive) {
14003                 if (appData.autoCallFlag &&
14004                     gameMode == IcsPlayingWhite && !whiteFlag) {
14005                   SendToICS(ics_prefix);
14006                   SendToICS("flag\n");
14007                 }
14008             } else {
14009                 if (whiteFlag) {
14010                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14011                 } else {
14012                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14013                     if (appData.autoCallFlag) {
14014                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14015                         return TRUE;
14016                     }
14017                 }
14018             }
14019         }
14020     }
14021     return FALSE;
14022 }
14023
14024 void
14025 CheckTimeControl()
14026 {
14027     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14028         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14029
14030     /*
14031      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14032      */
14033     if ( !WhiteOnMove(forwardMostMove) )
14034         /* White made time control */
14035         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14036         /* [HGM] time odds: correct new time quota for time odds! */
14037                                             / WhitePlayer()->timeOdds;
14038       else
14039         /* Black made time control */
14040         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14041                                             / WhitePlayer()->other->timeOdds;
14042 }
14043
14044 void
14045 DisplayBothClocks()
14046 {
14047     int wom = gameMode == EditPosition ?
14048       !blackPlaysFirst : WhiteOnMove(currentMove);
14049     DisplayWhiteClock(whiteTimeRemaining, wom);
14050     DisplayBlackClock(blackTimeRemaining, !wom);
14051 }
14052
14053
14054 /* Timekeeping seems to be a portability nightmare.  I think everyone
14055    has ftime(), but I'm really not sure, so I'm including some ifdefs
14056    to use other calls if you don't.  Clocks will be less accurate if
14057    you have neither ftime nor gettimeofday.
14058 */
14059
14060 /* VS 2008 requires the #include outside of the function */
14061 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14062 #include <sys/timeb.h>
14063 #endif
14064
14065 /* Get the current time as a TimeMark */
14066 void
14067 GetTimeMark(tm)
14068      TimeMark *tm;
14069 {
14070 #if HAVE_GETTIMEOFDAY
14071
14072     struct timeval timeVal;
14073     struct timezone timeZone;
14074
14075     gettimeofday(&timeVal, &timeZone);
14076     tm->sec = (long) timeVal.tv_sec;
14077     tm->ms = (int) (timeVal.tv_usec / 1000L);
14078
14079 #else /*!HAVE_GETTIMEOFDAY*/
14080 #if HAVE_FTIME
14081
14082 // include <sys/timeb.h> / moved to just above start of function
14083     struct timeb timeB;
14084
14085     ftime(&timeB);
14086     tm->sec = (long) timeB.time;
14087     tm->ms = (int) timeB.millitm;
14088
14089 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14090     tm->sec = (long) time(NULL);
14091     tm->ms = 0;
14092 #endif
14093 #endif
14094 }
14095
14096 /* Return the difference in milliseconds between two
14097    time marks.  We assume the difference will fit in a long!
14098 */
14099 long
14100 SubtractTimeMarks(tm2, tm1)
14101      TimeMark *tm2, *tm1;
14102 {
14103     return 1000L*(tm2->sec - tm1->sec) +
14104            (long) (tm2->ms - tm1->ms);
14105 }
14106
14107
14108 /*
14109  * Code to manage the game clocks.
14110  *
14111  * In tournament play, black starts the clock and then white makes a move.
14112  * We give the human user a slight advantage if he is playing white---the
14113  * clocks don't run until he makes his first move, so it takes zero time.
14114  * Also, we don't account for network lag, so we could get out of sync
14115  * with GNU Chess's clock -- but then, referees are always right.
14116  */
14117
14118 static TimeMark tickStartTM;
14119 static long intendedTickLength;
14120
14121 long
14122 NextTickLength(timeRemaining)
14123      long timeRemaining;
14124 {
14125     long nominalTickLength, nextTickLength;
14126
14127     if (timeRemaining > 0L && timeRemaining <= 10000L)
14128       nominalTickLength = 100L;
14129     else
14130       nominalTickLength = 1000L;
14131     nextTickLength = timeRemaining % nominalTickLength;
14132     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14133
14134     return nextTickLength;
14135 }
14136
14137 /* Adjust clock one minute up or down */
14138 void
14139 AdjustClock(Boolean which, int dir)
14140 {
14141     if(which) blackTimeRemaining += 60000*dir;
14142     else      whiteTimeRemaining += 60000*dir;
14143     DisplayBothClocks();
14144 }
14145
14146 /* Stop clocks and reset to a fresh time control */
14147 void
14148 ResetClocks()
14149 {
14150     (void) StopClockTimer();
14151     if (appData.icsActive) {
14152         whiteTimeRemaining = blackTimeRemaining = 0;
14153     } else if (searchTime) {
14154         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14155         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14156     } else { /* [HGM] correct new time quote for time odds */
14157         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14158         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14159     }
14160     if (whiteFlag || blackFlag) {
14161         DisplayTitle("");
14162         whiteFlag = blackFlag = FALSE;
14163     }
14164     DisplayBothClocks();
14165 }
14166
14167 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14168
14169 /* Decrement running clock by amount of time that has passed */
14170 void
14171 DecrementClocks()
14172 {
14173     long timeRemaining;
14174     long lastTickLength, fudge;
14175     TimeMark now;
14176
14177     if (!appData.clockMode) return;
14178     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14179
14180     GetTimeMark(&now);
14181
14182     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14183
14184     /* Fudge if we woke up a little too soon */
14185     fudge = intendedTickLength - lastTickLength;
14186     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14187
14188     if (WhiteOnMove(forwardMostMove)) {
14189         if(whiteNPS >= 0) lastTickLength = 0;
14190         timeRemaining = whiteTimeRemaining -= lastTickLength;
14191         DisplayWhiteClock(whiteTimeRemaining - fudge,
14192                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14193     } else {
14194         if(blackNPS >= 0) lastTickLength = 0;
14195         timeRemaining = blackTimeRemaining -= lastTickLength;
14196         DisplayBlackClock(blackTimeRemaining - fudge,
14197                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14198     }
14199
14200     if (CheckFlags()) return;
14201
14202     tickStartTM = now;
14203     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14204     StartClockTimer(intendedTickLength);
14205
14206     /* if the time remaining has fallen below the alarm threshold, sound the
14207      * alarm. if the alarm has sounded and (due to a takeback or time control
14208      * with increment) the time remaining has increased to a level above the
14209      * threshold, reset the alarm so it can sound again.
14210      */
14211
14212     if (appData.icsActive && appData.icsAlarm) {
14213
14214         /* make sure we are dealing with the user's clock */
14215         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14216                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14217            )) return;
14218
14219         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14220             alarmSounded = FALSE;
14221         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14222             PlayAlarmSound();
14223             alarmSounded = TRUE;
14224         }
14225     }
14226 }
14227
14228
14229 /* A player has just moved, so stop the previously running
14230    clock and (if in clock mode) start the other one.
14231    We redisplay both clocks in case we're in ICS mode, because
14232    ICS gives us an update to both clocks after every move.
14233    Note that this routine is called *after* forwardMostMove
14234    is updated, so the last fractional tick must be subtracted
14235    from the color that is *not* on move now.
14236 */
14237 void
14238 SwitchClocks()
14239 {
14240     long lastTickLength;
14241     TimeMark now;
14242     int flagged = FALSE;
14243
14244     GetTimeMark(&now);
14245
14246     if (StopClockTimer() && appData.clockMode) {
14247         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14248         if (WhiteOnMove(forwardMostMove)) {
14249             if(blackNPS >= 0) lastTickLength = 0;
14250             blackTimeRemaining -= lastTickLength;
14251            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14252 //         if(pvInfoList[forwardMostMove-1].time == -1)
14253                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14254                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14255         } else {
14256            if(whiteNPS >= 0) lastTickLength = 0;
14257            whiteTimeRemaining -= lastTickLength;
14258            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14259 //         if(pvInfoList[forwardMostMove-1].time == -1)
14260                  pvInfoList[forwardMostMove-1].time =
14261                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14262         }
14263         flagged = CheckFlags();
14264     }
14265     CheckTimeControl();
14266
14267     if (flagged || !appData.clockMode) return;
14268
14269     switch (gameMode) {
14270       case MachinePlaysBlack:
14271       case MachinePlaysWhite:
14272       case BeginningOfGame:
14273         if (pausing) return;
14274         break;
14275
14276       case EditGame:
14277       case PlayFromGameFile:
14278       case IcsExamining:
14279         return;
14280
14281       default:
14282         break;
14283     }
14284
14285     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14286         if(WhiteOnMove(forwardMostMove))
14287              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14288         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14289     }
14290
14291     tickStartTM = now;
14292     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14293       whiteTimeRemaining : blackTimeRemaining);
14294     StartClockTimer(intendedTickLength);
14295 }
14296
14297
14298 /* Stop both clocks */
14299 void
14300 StopClocks()
14301 {
14302     long lastTickLength;
14303     TimeMark now;
14304
14305     if (!StopClockTimer()) return;
14306     if (!appData.clockMode) return;
14307
14308     GetTimeMark(&now);
14309
14310     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14311     if (WhiteOnMove(forwardMostMove)) {
14312         if(whiteNPS >= 0) lastTickLength = 0;
14313         whiteTimeRemaining -= lastTickLength;
14314         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14315     } else {
14316         if(blackNPS >= 0) lastTickLength = 0;
14317         blackTimeRemaining -= lastTickLength;
14318         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14319     }
14320     CheckFlags();
14321 }
14322
14323 /* Start clock of player on move.  Time may have been reset, so
14324    if clock is already running, stop and restart it. */
14325 void
14326 StartClocks()
14327 {
14328     (void) StopClockTimer(); /* in case it was running already */
14329     DisplayBothClocks();
14330     if (CheckFlags()) return;
14331
14332     if (!appData.clockMode) return;
14333     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14334
14335     GetTimeMark(&tickStartTM);
14336     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14337       whiteTimeRemaining : blackTimeRemaining);
14338
14339    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14340     whiteNPS = blackNPS = -1;
14341     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14342        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14343         whiteNPS = first.nps;
14344     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14345        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14346         blackNPS = first.nps;
14347     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14348         whiteNPS = second.nps;
14349     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14350         blackNPS = second.nps;
14351     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14352
14353     StartClockTimer(intendedTickLength);
14354 }
14355
14356 char *
14357 TimeString(ms)
14358      long ms;
14359 {
14360     long second, minute, hour, day;
14361     char *sign = "";
14362     static char buf[32];
14363
14364     if (ms > 0 && ms <= 9900) {
14365       /* convert milliseconds to tenths, rounding up */
14366       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14367
14368       sprintf(buf, " %03.1f ", tenths/10.0);
14369       return buf;
14370     }
14371
14372     /* convert milliseconds to seconds, rounding up */
14373     /* use floating point to avoid strangeness of integer division
14374        with negative dividends on many machines */
14375     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14376
14377     if (second < 0) {
14378         sign = "-";
14379         second = -second;
14380     }
14381
14382     day = second / (60 * 60 * 24);
14383     second = second % (60 * 60 * 24);
14384     hour = second / (60 * 60);
14385     second = second % (60 * 60);
14386     minute = second / 60;
14387     second = second % 60;
14388
14389     if (day > 0)
14390       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14391               sign, day, hour, minute, second);
14392     else if (hour > 0)
14393       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14394     else
14395       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14396
14397     return buf;
14398 }
14399
14400
14401 /*
14402  * This is necessary because some C libraries aren't ANSI C compliant yet.
14403  */
14404 char *
14405 StrStr(string, match)
14406      char *string, *match;
14407 {
14408     int i, length;
14409
14410     length = strlen(match);
14411
14412     for (i = strlen(string) - length; i >= 0; i--, string++)
14413       if (!strncmp(match, string, length))
14414         return string;
14415
14416     return NULL;
14417 }
14418
14419 char *
14420 StrCaseStr(string, match)
14421      char *string, *match;
14422 {
14423     int i, j, length;
14424
14425     length = strlen(match);
14426
14427     for (i = strlen(string) - length; i >= 0; i--, string++) {
14428         for (j = 0; j < length; j++) {
14429             if (ToLower(match[j]) != ToLower(string[j]))
14430               break;
14431         }
14432         if (j == length) return string;
14433     }
14434
14435     return NULL;
14436 }
14437
14438 #ifndef _amigados
14439 int
14440 StrCaseCmp(s1, s2)
14441      char *s1, *s2;
14442 {
14443     char c1, c2;
14444
14445     for (;;) {
14446         c1 = ToLower(*s1++);
14447         c2 = ToLower(*s2++);
14448         if (c1 > c2) return 1;
14449         if (c1 < c2) return -1;
14450         if (c1 == NULLCHAR) return 0;
14451     }
14452 }
14453
14454
14455 int
14456 ToLower(c)
14457      int c;
14458 {
14459     return isupper(c) ? tolower(c) : c;
14460 }
14461
14462
14463 int
14464 ToUpper(c)
14465      int c;
14466 {
14467     return islower(c) ? toupper(c) : c;
14468 }
14469 #endif /* !_amigados    */
14470
14471 char *
14472 StrSave(s)
14473      char *s;
14474 {
14475     char *ret;
14476
14477     if ((ret = (char *) malloc(strlen(s) + 1))) {
14478         strcpy(ret, s);
14479     }
14480     return ret;
14481 }
14482
14483 char *
14484 StrSavePtr(s, savePtr)
14485      char *s, **savePtr;
14486 {
14487     if (*savePtr) {
14488         free(*savePtr);
14489     }
14490     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14491         strcpy(*savePtr, s);
14492     }
14493     return(*savePtr);
14494 }
14495
14496 char *
14497 PGNDate()
14498 {
14499     time_t clock;
14500     struct tm *tm;
14501     char buf[MSG_SIZ];
14502
14503     clock = time((time_t *)NULL);
14504     tm = localtime(&clock);
14505     sprintf(buf, "%04d.%02d.%02d",
14506             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14507     return StrSave(buf);
14508 }
14509
14510
14511 char *
14512 PositionToFEN(move, overrideCastling)
14513      int move;
14514      char *overrideCastling;
14515 {
14516     int i, j, fromX, fromY, toX, toY;
14517     int whiteToPlay;
14518     char buf[128];
14519     char *p, *q;
14520     int emptycount;
14521     ChessSquare piece;
14522
14523     whiteToPlay = (gameMode == EditPosition) ?
14524       !blackPlaysFirst : (move % 2 == 0);
14525     p = buf;
14526
14527     /* Piece placement data */
14528     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14529         emptycount = 0;
14530         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14531             if (boards[move][i][j] == EmptySquare) {
14532                 emptycount++;
14533             } else { ChessSquare piece = boards[move][i][j];
14534                 if (emptycount > 0) {
14535                     if(emptycount<10) /* [HGM] can be >= 10 */
14536                         *p++ = '0' + emptycount;
14537                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14538                     emptycount = 0;
14539                 }
14540                 if(PieceToChar(piece) == '+') {
14541                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14542                     *p++ = '+';
14543                     piece = (ChessSquare)(DEMOTED piece);
14544                 }
14545                 *p++ = PieceToChar(piece);
14546                 if(p[-1] == '~') {
14547                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14548                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14549                     *p++ = '~';
14550                 }
14551             }
14552         }
14553         if (emptycount > 0) {
14554             if(emptycount<10) /* [HGM] can be >= 10 */
14555                 *p++ = '0' + emptycount;
14556             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14557             emptycount = 0;
14558         }
14559         *p++ = '/';
14560     }
14561     *(p - 1) = ' ';
14562
14563     /* [HGM] print Crazyhouse or Shogi holdings */
14564     if( gameInfo.holdingsWidth ) {
14565         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14566         q = p;
14567         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14568             piece = boards[move][i][BOARD_WIDTH-1];
14569             if( piece != EmptySquare )
14570               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14571                   *p++ = PieceToChar(piece);
14572         }
14573         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14574             piece = boards[move][BOARD_HEIGHT-i-1][0];
14575             if( piece != EmptySquare )
14576               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14577                   *p++ = PieceToChar(piece);
14578         }
14579
14580         if( q == p ) *p++ = '-';
14581         *p++ = ']';
14582         *p++ = ' ';
14583     }
14584
14585     /* Active color */
14586     *p++ = whiteToPlay ? 'w' : 'b';
14587     *p++ = ' ';
14588
14589   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14590     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14591   } else {
14592   if(nrCastlingRights) {
14593      q = p;
14594      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14595        /* [HGM] write directly from rights */
14596            if(boards[move][CASTLING][2] != NoRights &&
14597               boards[move][CASTLING][0] != NoRights   )
14598                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14599            if(boards[move][CASTLING][2] != NoRights &&
14600               boards[move][CASTLING][1] != NoRights   )
14601                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14602            if(boards[move][CASTLING][5] != NoRights &&
14603               boards[move][CASTLING][3] != NoRights   )
14604                 *p++ = boards[move][CASTLING][3] + AAA;
14605            if(boards[move][CASTLING][5] != NoRights &&
14606               boards[move][CASTLING][4] != NoRights   )
14607                 *p++ = boards[move][CASTLING][4] + AAA;
14608      } else {
14609
14610         /* [HGM] write true castling rights */
14611         if( nrCastlingRights == 6 ) {
14612             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14613                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14614             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14615                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14616             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14617                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14618             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14619                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14620         }
14621      }
14622      if (q == p) *p++ = '-'; /* No castling rights */
14623      *p++ = ' ';
14624   }
14625
14626   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14627      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14628     /* En passant target square */
14629     if (move > backwardMostMove) {
14630         fromX = moveList[move - 1][0] - AAA;
14631         fromY = moveList[move - 1][1] - ONE;
14632         toX = moveList[move - 1][2] - AAA;
14633         toY = moveList[move - 1][3] - ONE;
14634         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14635             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14636             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14637             fromX == toX) {
14638             /* 2-square pawn move just happened */
14639             *p++ = toX + AAA;
14640             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14641         } else {
14642             *p++ = '-';
14643         }
14644     } else if(move == backwardMostMove) {
14645         // [HGM] perhaps we should always do it like this, and forget the above?
14646         if((signed char)boards[move][EP_STATUS] >= 0) {
14647             *p++ = boards[move][EP_STATUS] + AAA;
14648             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14649         } else {
14650             *p++ = '-';
14651         }
14652     } else {
14653         *p++ = '-';
14654     }
14655     *p++ = ' ';
14656   }
14657   }
14658
14659     /* [HGM] find reversible plies */
14660     {   int i = 0, j=move;
14661
14662         if (appData.debugMode) { int k;
14663             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14664             for(k=backwardMostMove; k<=forwardMostMove; k++)
14665                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14666
14667         }
14668
14669         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14670         if( j == backwardMostMove ) i += initialRulePlies;
14671         sprintf(p, "%d ", i);
14672         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14673     }
14674     /* Fullmove number */
14675     sprintf(p, "%d", (move / 2) + 1);
14676
14677     return StrSave(buf);
14678 }
14679
14680 Boolean
14681 ParseFEN(board, blackPlaysFirst, fen)
14682     Board board;
14683      int *blackPlaysFirst;
14684      char *fen;
14685 {
14686     int i, j;
14687     char *p;
14688     int emptycount;
14689     ChessSquare piece;
14690
14691     p = fen;
14692
14693     /* [HGM] by default clear Crazyhouse holdings, if present */
14694     if(gameInfo.holdingsWidth) {
14695        for(i=0; i<BOARD_HEIGHT; i++) {
14696            board[i][0]             = EmptySquare; /* black holdings */
14697            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14698            board[i][1]             = (ChessSquare) 0; /* black counts */
14699            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14700        }
14701     }
14702
14703     /* Piece placement data */
14704     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14705         j = 0;
14706         for (;;) {
14707             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14708                 if (*p == '/') p++;
14709                 emptycount = gameInfo.boardWidth - j;
14710                 while (emptycount--)
14711                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14712                 break;
14713 #if(BOARD_FILES >= 10)
14714             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14715                 p++; emptycount=10;
14716                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14717                 while (emptycount--)
14718                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14719 #endif
14720             } else if (isdigit(*p)) {
14721                 emptycount = *p++ - '0';
14722                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14723                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14724                 while (emptycount--)
14725                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14726             } else if (*p == '+' || isalpha(*p)) {
14727                 if (j >= gameInfo.boardWidth) return FALSE;
14728                 if(*p=='+') {
14729                     piece = CharToPiece(*++p);
14730                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14731                     piece = (ChessSquare) (PROMOTED piece ); p++;
14732                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14733                 } else piece = CharToPiece(*p++);
14734
14735                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14736                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14737                     piece = (ChessSquare) (PROMOTED piece);
14738                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14739                     p++;
14740                 }
14741                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14742             } else {
14743                 return FALSE;
14744             }
14745         }
14746     }
14747     while (*p == '/' || *p == ' ') p++;
14748
14749     /* [HGM] look for Crazyhouse holdings here */
14750     while(*p==' ') p++;
14751     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14752         if(*p == '[') p++;
14753         if(*p == '-' ) *p++; /* empty holdings */ else {
14754             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14755             /* if we would allow FEN reading to set board size, we would   */
14756             /* have to add holdings and shift the board read so far here   */
14757             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14758                 *p++;
14759                 if((int) piece >= (int) BlackPawn ) {
14760                     i = (int)piece - (int)BlackPawn;
14761                     i = PieceToNumber((ChessSquare)i);
14762                     if( i >= gameInfo.holdingsSize ) return FALSE;
14763                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14764                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14765                 } else {
14766                     i = (int)piece - (int)WhitePawn;
14767                     i = PieceToNumber((ChessSquare)i);
14768                     if( i >= gameInfo.holdingsSize ) return FALSE;
14769                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14770                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14771                 }
14772             }
14773         }
14774         if(*p == ']') *p++;
14775     }
14776
14777     while(*p == ' ') p++;
14778
14779     /* Active color */
14780     switch (*p++) {
14781       case 'w':
14782         *blackPlaysFirst = FALSE;
14783         break;
14784       case 'b':
14785         *blackPlaysFirst = TRUE;
14786         break;
14787       default:
14788         return FALSE;
14789     }
14790
14791     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14792     /* return the extra info in global variiables             */
14793
14794     /* set defaults in case FEN is incomplete */
14795     board[EP_STATUS] = EP_UNKNOWN;
14796     for(i=0; i<nrCastlingRights; i++ ) {
14797         board[CASTLING][i] =
14798             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14799     }   /* assume possible unless obviously impossible */
14800     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14801     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14802     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14803                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14804     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14805     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14806     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14807                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14808     FENrulePlies = 0;
14809
14810     while(*p==' ') p++;
14811     if(nrCastlingRights) {
14812       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14813           /* castling indicator present, so default becomes no castlings */
14814           for(i=0; i<nrCastlingRights; i++ ) {
14815                  board[CASTLING][i] = NoRights;
14816           }
14817       }
14818       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14819              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14820              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14821              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14822         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14823
14824         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14825             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14826             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14827         }
14828         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14829             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14830         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14831                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14832         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14833                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14834         switch(c) {
14835           case'K':
14836               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14837               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14838               board[CASTLING][2] = whiteKingFile;
14839               break;
14840           case'Q':
14841               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14842               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14843               board[CASTLING][2] = whiteKingFile;
14844               break;
14845           case'k':
14846               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14847               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14848               board[CASTLING][5] = blackKingFile;
14849               break;
14850           case'q':
14851               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14852               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14853               board[CASTLING][5] = blackKingFile;
14854           case '-':
14855               break;
14856           default: /* FRC castlings */
14857               if(c >= 'a') { /* black rights */
14858                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14859                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14860                   if(i == BOARD_RGHT) break;
14861                   board[CASTLING][5] = i;
14862                   c -= AAA;
14863                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14864                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14865                   if(c > i)
14866                       board[CASTLING][3] = c;
14867                   else
14868                       board[CASTLING][4] = c;
14869               } else { /* white rights */
14870                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14871                     if(board[0][i] == WhiteKing) break;
14872                   if(i == BOARD_RGHT) break;
14873                   board[CASTLING][2] = i;
14874                   c -= AAA - 'a' + 'A';
14875                   if(board[0][c] >= WhiteKing) break;
14876                   if(c > i)
14877                       board[CASTLING][0] = c;
14878                   else
14879                       board[CASTLING][1] = c;
14880               }
14881         }
14882       }
14883       for(i=0; i<nrCastlingRights; i++)
14884         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14885     if (appData.debugMode) {
14886         fprintf(debugFP, "FEN castling rights:");
14887         for(i=0; i<nrCastlingRights; i++)
14888         fprintf(debugFP, " %d", board[CASTLING][i]);
14889         fprintf(debugFP, "\n");
14890     }
14891
14892       while(*p==' ') p++;
14893     }
14894
14895     /* read e.p. field in games that know e.p. capture */
14896     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14897        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14898       if(*p=='-') {
14899         p++; board[EP_STATUS] = EP_NONE;
14900       } else {
14901          char c = *p++ - AAA;
14902
14903          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14904          if(*p >= '0' && *p <='9') *p++;
14905          board[EP_STATUS] = c;
14906       }
14907     }
14908
14909
14910     if(sscanf(p, "%d", &i) == 1) {
14911         FENrulePlies = i; /* 50-move ply counter */
14912         /* (The move number is still ignored)    */
14913     }
14914
14915     return TRUE;
14916 }
14917
14918 void
14919 EditPositionPasteFEN(char *fen)
14920 {
14921   if (fen != NULL) {
14922     Board initial_position;
14923
14924     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14925       DisplayError(_("Bad FEN position in clipboard"), 0);
14926       return ;
14927     } else {
14928       int savedBlackPlaysFirst = blackPlaysFirst;
14929       EditPositionEvent();
14930       blackPlaysFirst = savedBlackPlaysFirst;
14931       CopyBoard(boards[0], initial_position);
14932       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14933       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14934       DisplayBothClocks();
14935       DrawPosition(FALSE, boards[currentMove]);
14936     }
14937   }
14938 }
14939
14940 static char cseq[12] = "\\   ";
14941
14942 Boolean set_cont_sequence(char *new_seq)
14943 {
14944     int len;
14945     Boolean ret;
14946
14947     // handle bad attempts to set the sequence
14948         if (!new_seq)
14949                 return 0; // acceptable error - no debug
14950
14951     len = strlen(new_seq);
14952     ret = (len > 0) && (len < sizeof(cseq));
14953     if (ret)
14954         strcpy(cseq, new_seq);
14955     else if (appData.debugMode)
14956         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14957     return ret;
14958 }
14959
14960 /*
14961     reformat a source message so words don't cross the width boundary.  internal
14962     newlines are not removed.  returns the wrapped size (no null character unless
14963     included in source message).  If dest is NULL, only calculate the size required
14964     for the dest buffer.  lp argument indicats line position upon entry, and it's
14965     passed back upon exit.
14966 */
14967 int wrap(char *dest, char *src, int count, int width, int *lp)
14968 {
14969     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14970
14971     cseq_len = strlen(cseq);
14972     old_line = line = *lp;
14973     ansi = len = clen = 0;
14974
14975     for (i=0; i < count; i++)
14976     {
14977         if (src[i] == '\033')
14978             ansi = 1;
14979
14980         // if we hit the width, back up
14981         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14982         {
14983             // store i & len in case the word is too long
14984             old_i = i, old_len = len;
14985
14986             // find the end of the last word
14987             while (i && src[i] != ' ' && src[i] != '\n')
14988             {
14989                 i--;
14990                 len--;
14991             }
14992
14993             // word too long?  restore i & len before splitting it
14994             if ((old_i-i+clen) >= width)
14995             {
14996                 i = old_i;
14997                 len = old_len;
14998             }
14999
15000             // extra space?
15001             if (i && src[i-1] == ' ')
15002                 len--;
15003
15004             if (src[i] != ' ' && src[i] != '\n')
15005             {
15006                 i--;
15007                 if (len)
15008                     len--;
15009             }
15010
15011             // now append the newline and continuation sequence
15012             if (dest)
15013                 dest[len] = '\n';
15014             len++;
15015             if (dest)
15016                 strncpy(dest+len, cseq, cseq_len);
15017             len += cseq_len;
15018             line = cseq_len;
15019             clen = cseq_len;
15020             continue;
15021         }
15022
15023         if (dest)
15024             dest[len] = src[i];
15025         len++;
15026         if (!ansi)
15027             line++;
15028         if (src[i] == '\n')
15029             line = 0;
15030         if (src[i] == 'm')
15031             ansi = 0;
15032     }
15033     if (dest && appData.debugMode)
15034     {
15035         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15036             count, width, line, len, *lp);
15037         show_bytes(debugFP, src, count);
15038         fprintf(debugFP, "\ndest: ");
15039         show_bytes(debugFP, dest, len);
15040         fprintf(debugFP, "\n");
15041     }
15042     *lp = dest ? line : old_line;
15043
15044     return len;
15045 }
15046
15047 // [HGM] vari: routines for shelving variations
15048
15049 void 
15050 PushTail(int firstMove, int lastMove)
15051 {
15052         int i, j, nrMoves = lastMove - firstMove;
15053
15054         if(appData.icsActive) { // only in local mode
15055                 forwardMostMove = currentMove; // mimic old ICS behavior
15056                 return;
15057         }
15058         if(storedGames >= MAX_VARIATIONS-1) return;
15059
15060         // push current tail of game on stack
15061         savedResult[storedGames] = gameInfo.result;
15062         savedDetails[storedGames] = gameInfo.resultDetails;
15063         gameInfo.resultDetails = NULL;
15064         savedFirst[storedGames] = firstMove;
15065         savedLast [storedGames] = lastMove;
15066         savedFramePtr[storedGames] = framePtr;
15067         framePtr -= nrMoves; // reserve space for the boards
15068         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15069             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15070             for(j=0; j<MOVE_LEN; j++)
15071                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15072             for(j=0; j<2*MOVE_LEN; j++)
15073                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15074             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15075             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15076             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15077             pvInfoList[firstMove+i-1].depth = 0;
15078             commentList[framePtr+i] = commentList[firstMove+i];
15079             commentList[firstMove+i] = NULL;
15080         }
15081
15082         storedGames++;
15083         forwardMostMove = currentMove; // truncte game so we can start variation
15084         if(storedGames == 1) GreyRevert(FALSE);
15085 }
15086
15087 Boolean
15088 PopTail(Boolean annotate)
15089 {
15090         int i, j, nrMoves;
15091         char buf[8000], moveBuf[20];
15092
15093         if(appData.icsActive) return FALSE; // only in local mode
15094         if(!storedGames) return FALSE; // sanity
15095
15096         storedGames--;
15097         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15098         nrMoves = savedLast[storedGames] - currentMove;
15099         if(annotate) {
15100                 int cnt = 10;
15101                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15102                 else strcpy(buf, "(");
15103                 for(i=currentMove; i<forwardMostMove; i++) {
15104                         if(WhiteOnMove(i))
15105                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15106                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15107                         strcat(buf, moveBuf);
15108                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15109                 }
15110                 strcat(buf, ")");
15111         }
15112         for(i=1; i<nrMoves; i++) { // copy last variation back
15113             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15114             for(j=0; j<MOVE_LEN; j++)
15115                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15116             for(j=0; j<2*MOVE_LEN; j++)
15117                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15118             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15119             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15120             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15121             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15122             commentList[currentMove+i] = commentList[framePtr+i];
15123             commentList[framePtr+i] = NULL;
15124         }
15125         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15126         framePtr = savedFramePtr[storedGames];
15127         gameInfo.result = savedResult[storedGames];
15128         if(gameInfo.resultDetails != NULL) {
15129             free(gameInfo.resultDetails);
15130       }
15131         gameInfo.resultDetails = savedDetails[storedGames];
15132         forwardMostMove = currentMove + nrMoves;
15133         if(storedGames == 0) GreyRevert(TRUE);
15134         return TRUE;
15135 }
15136
15137 void 
15138 CleanupTail()
15139 {       // remove all shelved variations
15140         int i;
15141         for(i=0; i<storedGames; i++) {
15142             if(savedDetails[i])
15143                 free(savedDetails[i]);
15144             savedDetails[i] = NULL;
15145         }
15146         for(i=framePtr; i<MAX_MOVES; i++) {
15147                 if(commentList[i]) free(commentList[i]);
15148                 commentList[i] = NULL;
15149         }
15150         framePtr = MAX_MOVES-1;
15151         storedGames = 0;
15152 }