Merge branch 'master' into gtk
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 #else
135 # define _(s) (s)
136 # define N_(s) s
137 #endif
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
254 int chattingPartner;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
256
257 /* States for ics_getting_history */
258 #define H_FALSE 0
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
264
265 /* whosays values for GameEnds */
266 #define GE_ICS 0
267 #define GE_ENGINE 1
268 #define GE_PLAYER 2
269 #define GE_FILE 3
270 #define GE_XBOARD 4
271 #define GE_ENGINE1 5
272 #define GE_ENGINE2 6
273
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
276
277 /* Different types of move when calling RegisterMove */
278 #define CMAIL_MOVE   0
279 #define CMAIL_RESIGN 1
280 #define CMAIL_DRAW   2
281 #define CMAIL_ACCEPT 3
282
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
287
288 /* Telnet protocol constants */
289 #define TN_WILL 0373
290 #define TN_WONT 0374
291 #define TN_DO   0375
292 #define TN_DONT 0376
293 #define TN_IAC  0377
294 #define TN_ECHO 0001
295 #define TN_SGA  0003
296 #define TN_PORT 23
297
298 /* [AS] */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
300 {
301     assert( dst != NULL );
302     assert( src != NULL );
303     assert( count > 0 );
304
305     strncpy( dst, src, count );
306     dst[ count-1 ] = '\0';
307     return dst;
308 }
309
310 /* Some compiler can't cast u64 to double
311  * This function do the job for us:
312
313  * We use the highest bit for cast, this only
314  * works if the highest bit is not
315  * in use (This should not happen)
316  *
317  * We used this for all compiler
318  */
319 double
320 u64ToDouble(u64 value)
321 {
322   double r;
323   u64 tmp = value & u64Const(0x7fffffffffffffff);
324   r = (double)(s64)tmp;
325   if (value & u64Const(0x8000000000000000))
326        r +=  9.2233720368547758080e18; /* 2^63 */
327  return r;
328 }
329
330 /* Fake up flags for now, as we aren't keeping track of castling
331    availability yet. [HGM] Change of logic: the flag now only
332    indicates the type of castlings allowed by the rule of the game.
333    The actual rights themselves are maintained in the array
334    castlingRights, as part of the game history, and are not probed
335    by this function.
336  */
337 int
338 PosFlags(index)
339 {
340   int flags = F_ALL_CASTLE_OK;
341   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342   switch (gameInfo.variant) {
343   case VariantSuicide:
344     flags &= ~F_ALL_CASTLE_OK;
345   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346     flags |= F_IGNORE_CHECK;
347   case VariantLosers:
348     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
349     break;
350   case VariantAtomic:
351     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
352     break;
353   case VariantKriegspiel:
354     flags |= F_KRIEGSPIEL_CAPTURE;
355     break;
356   case VariantCapaRandom:
357   case VariantFischeRandom:
358     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359   case VariantNoCastle:
360   case VariantShatranj:
361   case VariantCourier:
362   case VariantMakruk:
363     flags &= ~F_ALL_CASTLE_OK;
364     break;
365   default:
366     break;
367   }
368   return flags;
369 }
370
371 FILE *gameFileFP, *debugFP;
372
373 /*
374     [AS] Note: sometimes, the sscanf() function is used to parse the input
375     into a fixed-size buffer. Because of this, we must be prepared to
376     receive strings as long as the size of the input buffer, which is currently
377     set to 4K for Windows and 8K for the rest.
378     So, we must either allocate sufficiently large buffers here, or
379     reduce the size of the input buffer in the input reading part.
380 */
381
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
385
386 ChessProgramState first, second;
387
388 /* premove variables */
389 int premoveToX = 0;
390 int premoveToY = 0;
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
394 int gotPremove = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
397
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
400
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
428 int movesPerSession;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
433 int matchGame = 0;
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
437
438 /* animateTraining preserves the state of appData.animate
439  * when Training mode is activated. This allows the
440  * response to be animated when appData.animate == TRUE and
441  * appData.animateDragging == TRUE.
442  */
443 Boolean animateTraining;
444
445 GameInfo gameInfo;
446
447 AppData appData;
448
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char  initialRights[BOARD_FILES];
453 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int   initialRulePlies, FENrulePlies;
455 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
456 int loadFlag = 0;
457 int shuffleOpenings;
458 int mute; // mute all sounds
459
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
463 int storedGames = 0;
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
469
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
473
474 ChessSquare  FIDEArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackKing, BlackKnight, BlackRook }
486 };
487
488 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
489     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491     { BlackRook, BlackMan, BlackBishop, BlackQueen,
492         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
493 };
494
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
500 };
501
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackMan, BlackFerz,
513         BlackKing, BlackMan, BlackKnight, BlackRook }
514 };
515
516
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
523 };
524
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
530 };
531
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
534         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
536         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
537 };
538
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
541         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
543         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
544 };
545
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
548         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
550         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
551 };
552
553 #ifdef GOTHIC
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
556         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
558         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !GOTHIC
561 #define GothicArray CapablancaArray
562 #endif // !GOTHIC
563
564 #ifdef FALCON
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
567         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
569         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
570 };
571 #else // !FALCON
572 #define FalconArray CapablancaArray
573 #endif // !FALCON
574
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
581
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
588 };
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
592
593
594 Board initialPosition;
595
596
597 /* Convert str to a rating. Checks for special cases of "----",
598
599    "++++", etc. Also strips ()'s */
600 int
601 string_to_rating(str)
602   char *str;
603 {
604   while(*str && !isdigit(*str)) ++str;
605   if (!*str)
606     return 0;   /* One of the special "no rating" cases */
607   else
608     return atoi(str);
609 }
610
611 void
612 ClearProgramStats()
613 {
614     /* Init programStats */
615     programStats.movelist[0] = 0;
616     programStats.depth = 0;
617     programStats.nr_moves = 0;
618     programStats.moves_left = 0;
619     programStats.nodes = 0;
620     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
621     programStats.score = 0;
622     programStats.got_only_move = 0;
623     programStats.got_fail = 0;
624     programStats.line_is_book = 0;
625 }
626
627 void
628 InitBackEnd1()
629 {
630     int matched, min, sec;
631
632     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
633
634     GetTimeMark(&programStartTime);
635     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
636
637     ClearProgramStats();
638     programStats.ok_to_send = 1;
639     programStats.seen_stat = 0;
640
641     /*
642      * Initialize game list
643      */
644     ListNew(&gameList);
645
646
647     /*
648      * Internet chess server status
649      */
650     if (appData.icsActive) {
651         appData.matchMode = FALSE;
652         appData.matchGames = 0;
653 #if ZIPPY
654         appData.noChessProgram = !appData.zippyPlay;
655 #else
656         appData.zippyPlay = FALSE;
657         appData.zippyTalk = FALSE;
658         appData.noChessProgram = TRUE;
659 #endif
660         if (*appData.icsHelper != NULLCHAR) {
661             appData.useTelnet = TRUE;
662             appData.telnetProgram = appData.icsHelper;
663         }
664     } else {
665         appData.zippyTalk = appData.zippyPlay = FALSE;
666     }
667
668     /* [AS] Initialize pv info list [HGM] and game state */
669     {
670         int i, j;
671
672         for( i=0; i<=framePtr; i++ ) {
673             pvInfoList[i].depth = -1;
674             boards[i][EP_STATUS] = EP_NONE;
675             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
676         }
677     }
678
679     /*
680      * Parse timeControl resource
681      */
682     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683                           appData.movesPerSession)) {
684         char buf[MSG_SIZ];
685         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686         DisplayFatalError(buf, 0, 2);
687     }
688
689     /*
690      * Parse searchTime resource
691      */
692     if (*appData.searchTime != NULLCHAR) {
693         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
694         if (matched == 1) {
695             searchTime = min * 60;
696         } else if (matched == 2) {
697             searchTime = min * 60 + sec;
698         } else {
699             char buf[MSG_SIZ];
700             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701             DisplayFatalError(buf, 0, 2);
702         }
703     }
704
705     /* [AS] Adjudication threshold */
706     adjudicateLossThreshold = appData.adjudicateLossThreshold;
707
708     first.which = "first";
709     second.which = "second";
710     first.maybeThinking = second.maybeThinking = FALSE;
711     first.pr = second.pr = NoProc;
712     first.isr = second.isr = NULL;
713     first.sendTime = second.sendTime = 2;
714     first.sendDrawOffers = 1;
715     if (appData.firstPlaysBlack) {
716         first.twoMachinesColor = "black\n";
717         second.twoMachinesColor = "white\n";
718     } else {
719         first.twoMachinesColor = "white\n";
720         second.twoMachinesColor = "black\n";
721     }
722     first.program = appData.firstChessProgram;
723     second.program = appData.secondChessProgram;
724     first.host = appData.firstHost;
725     second.host = appData.secondHost;
726     first.dir = appData.firstDirectory;
727     second.dir = appData.secondDirectory;
728     first.other = &second;
729     second.other = &first;
730     first.initString = appData.initString;
731     second.initString = appData.secondInitString;
732     first.computerString = appData.firstComputerString;
733     second.computerString = appData.secondComputerString;
734     first.useSigint = second.useSigint = TRUE;
735     first.useSigterm = second.useSigterm = TRUE;
736     first.reuse = appData.reuseFirst;
737     second.reuse = appData.reuseSecond;
738     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
739     second.nps = appData.secondNPS;
740     first.useSetboard = second.useSetboard = FALSE;
741     first.useSAN = second.useSAN = FALSE;
742     first.usePing = second.usePing = FALSE;
743     first.lastPing = second.lastPing = 0;
744     first.lastPong = second.lastPong = 0;
745     first.usePlayother = second.usePlayother = FALSE;
746     first.useColors = second.useColors = TRUE;
747     first.useUsermove = second.useUsermove = FALSE;
748     first.sendICS = second.sendICS = FALSE;
749     first.sendName = second.sendName = appData.icsActive;
750     first.sdKludge = second.sdKludge = FALSE;
751     first.stKludge = second.stKludge = FALSE;
752     TidyProgramName(first.program, first.host, first.tidy);
753     TidyProgramName(second.program, second.host, second.tidy);
754     first.matchWins = second.matchWins = 0;
755     strcpy(first.variants, appData.variant);
756     strcpy(second.variants, appData.variant);
757     first.analysisSupport = second.analysisSupport = 2; /* detect */
758     first.analyzing = second.analyzing = FALSE;
759     first.initDone = second.initDone = FALSE;
760
761     /* New features added by Tord: */
762     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764     /* End of new features added by Tord. */
765     first.fenOverride  = appData.fenOverride1;
766     second.fenOverride = appData.fenOverride2;
767
768     /* [HGM] time odds: set factor for each machine */
769     first.timeOdds  = appData.firstTimeOdds;
770     second.timeOdds = appData.secondTimeOdds;
771     { float norm = 1;
772         if(appData.timeOddsMode) {
773             norm = first.timeOdds;
774             if(norm > second.timeOdds) norm = second.timeOdds;
775         }
776         first.timeOdds /= norm;
777         second.timeOdds /= norm;
778     }
779
780     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781     first.accumulateTC = appData.firstAccumulateTC;
782     second.accumulateTC = appData.secondAccumulateTC;
783     first.maxNrOfSessions = second.maxNrOfSessions = 1;
784
785     /* [HGM] debug */
786     first.debug = second.debug = FALSE;
787     first.supportsNPS = second.supportsNPS = UNKNOWN;
788
789     /* [HGM] options */
790     first.optionSettings  = appData.firstOptions;
791     second.optionSettings = appData.secondOptions;
792
793     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795     first.isUCI = appData.firstIsUCI; /* [AS] */
796     second.isUCI = appData.secondIsUCI; /* [AS] */
797     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
799
800     if (appData.firstProtocolVersion > PROTOVER ||
801         appData.firstProtocolVersion < 1) {
802       char buf[MSG_SIZ];
803       sprintf(buf, _("protocol version %d not supported"),
804               appData.firstProtocolVersion);
805       DisplayFatalError(buf, 0, 2);
806     } else {
807       first.protocolVersion = appData.firstProtocolVersion;
808     }
809
810     if (appData.secondProtocolVersion > PROTOVER ||
811         appData.secondProtocolVersion < 1) {
812       char buf[MSG_SIZ];
813       sprintf(buf, _("protocol version %d not supported"),
814               appData.secondProtocolVersion);
815       DisplayFatalError(buf, 0, 2);
816     } else {
817       second.protocolVersion = appData.secondProtocolVersion;
818     }
819
820     if (appData.icsActive) {
821         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
822 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824         appData.clockMode = FALSE;
825         first.sendTime = second.sendTime = 0;
826     }
827
828 #if ZIPPY
829     /* Override some settings from environment variables, for backward
830        compatibility.  Unfortunately it's not feasible to have the env
831        vars just set defaults, at least in xboard.  Ugh.
832     */
833     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
834       ZippyInit();
835     }
836 #endif
837
838     if (appData.noChessProgram) {
839         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840         sprintf(programVersion, "%s", PACKAGE_STRING);
841     } else {
842       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
845     }
846
847     if (!appData.icsActive) {
848       char buf[MSG_SIZ];
849       /* Check for variants that are supported only in ICS mode,
850          or not at all.  Some that are accepted here nevertheless
851          have bugs; see comments below.
852       */
853       VariantClass variant = StringToVariant(appData.variant);
854       switch (variant) {
855       case VariantBughouse:     /* need four players and two boards */
856       case VariantKriegspiel:   /* need to hide pieces and move details */
857       /* case VariantFischeRandom: (Fabien: moved below) */
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859         DisplayFatalError(buf, 0, 2);
860         return;
861
862       case VariantUnknown:
863       case VariantLoadable:
864       case Variant29:
865       case Variant30:
866       case Variant31:
867       case Variant32:
868       case Variant33:
869       case Variant34:
870       case Variant35:
871       case Variant36:
872       default:
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);
874         DisplayFatalError(buf, 0, 2);
875         return;
876
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */
879       case VariantGothic:     /* [HGM] should work */
880       case VariantCapablanca: /* [HGM] should work */
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */
882       case VariantShogi:      /* [HGM] drops not tested for legality */
883       case VariantKnightmate: /* [HGM] should work */
884       case VariantCylinder:   /* [HGM] untested */
885       case VariantFalcon:     /* [HGM] untested */
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887                                  offboard interposition not understood */
888       case VariantNormal:     /* definitely works! */
889       case VariantWildCastle: /* pieces not automatically shuffled */
890       case VariantNoCastle:   /* pieces not automatically shuffled */
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892       case VariantLosers:     /* should work except for win condition,
893                                  and doesn't know captures are mandatory */
894       case VariantSuicide:    /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantGiveaway:   /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantTwoKings:   /* should work */
899       case VariantAtomic:     /* should work except for win condition */
900       case Variant3Check:     /* should work except for win condition */
901       case VariantShatranj:   /* should work except for all win conditions */
902       case VariantMakruk:     /* should work except for daw countdown */
903       case VariantBerolina:   /* might work if TestLegality is off */
904       case VariantCapaRandom: /* should work */
905       case VariantJanus:      /* should work */
906       case VariantSuper:      /* experimental */
907       case VariantGreat:      /* experimental, requires legality testing to be off */
908         break;
909       }
910     }
911
912     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
913     InitEngineUCI( installDir, &second );
914 }
915
916 int NextIntegerFromString( char ** str, long * value )
917 {
918     int result = -1;
919     char * s = *str;
920
921     while( *s == ' ' || *s == '\t' ) {
922         s++;
923     }
924
925     *value = 0;
926
927     if( *s >= '0' && *s <= '9' ) {
928         while( *s >= '0' && *s <= '9' ) {
929             *value = *value * 10 + (*s - '0');
930             s++;
931         }
932
933         result = 0;
934     }
935
936     *str = s;
937
938     return result;
939 }
940
941 int NextTimeControlFromString( char ** str, long * value )
942 {
943     long temp;
944     int result = NextIntegerFromString( str, &temp );
945
946     if( result == 0 ) {
947         *value = temp * 60; /* Minutes */
948         if( **str == ':' ) {
949             (*str)++;
950             result = NextIntegerFromString( str, &temp );
951             *value += temp; /* Seconds */
952         }
953     }
954
955     return result;
956 }
957
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
960     int result = -1; long temp, temp2;
961
962     if(**str != '+') return -1; // old params remain in force!
963     (*str)++;
964     if( NextTimeControlFromString( str, &temp ) ) return -1;
965
966     if(**str != '/') {
967         /* time only: incremental or sudden-death time control */
968         if(**str == '+') { /* increment follows; read it */
969             (*str)++;
970             if(result = NextIntegerFromString( str, &temp2)) return -1;
971             *inc = temp2 * 1000;
972         } else *inc = 0;
973         *moves = 0; *tc = temp * 1000;
974         return 0;
975     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
976
977     (*str)++; /* classical time control */
978     result = NextTimeControlFromString( str, &temp2);
979     if(result == 0) {
980         *moves = temp/60;
981         *tc    = temp2 * 1000;
982         *inc   = 0;
983     }
984     return result;
985 }
986
987 int GetTimeQuota(int movenr)
988 {   /* [HGM] get time to add from the multi-session time-control string */
989     int moves=1; /* kludge to force reading of first session */
990     long time, increment;
991     char *s = fullTimeControlString;
992
993     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994     do {
995         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997         if(movenr == -1) return time;    /* last move before new session     */
998         if(!moves) return increment;     /* current session is incremental   */
999         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000     } while(movenr >= -1);               /* try again for next session       */
1001
1002     return 0; // no new time quota on this move
1003 }
1004
1005 int
1006 ParseTimeControl(tc, ti, mps)
1007      char *tc;
1008      int ti;
1009      int mps;
1010 {
1011   long tc1;
1012   long tc2;
1013   char buf[MSG_SIZ];
1014   
1015   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1016   if(ti > 0) {
1017     if(mps)
1018       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019     else sprintf(buf, "+%s+%d", tc, ti);
1020   } else {
1021     if(mps)
1022              sprintf(buf, "+%d/%s", mps, tc);
1023     else sprintf(buf, "+%s", tc);
1024   }
1025   fullTimeControlString = StrSave(buf);
1026   
1027   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1028     return FALSE;
1029   }
1030   
1031   if( *tc == '/' ) {
1032     /* Parse second time control */
1033     tc++;
1034     
1035     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1036       return FALSE;
1037     }
1038     
1039     if( tc2 == 0 ) {
1040       return FALSE;
1041     }
1042     
1043     timeControl_2 = tc2 * 1000;
1044   }
1045   else {
1046     timeControl_2 = 0;
1047   }
1048   
1049   if( tc1 == 0 ) {
1050     return FALSE;
1051   }
1052   
1053   timeControl = tc1 * 1000;
1054   
1055   if (ti >= 0) {
1056     timeIncrement = ti * 1000;  /* convert to ms */
1057     movesPerSession = 0;
1058   } else {
1059     timeIncrement = 0;
1060     movesPerSession = mps;
1061   }
1062   return TRUE;
1063 }
1064
1065 void
1066 InitBackEnd2()
1067 {
1068   if (appData.debugMode) {
1069     fprintf(debugFP, "%s\n", programVersion);
1070   }
1071
1072   set_cont_sequence(appData.wrapContSeq);
1073   if (appData.matchGames > 0) {
1074     appData.matchMode = TRUE;
1075   } else if (appData.matchMode) {
1076     appData.matchGames = 1;
1077   }
1078   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079     appData.matchGames = appData.sameColorGames;
1080   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1083   }
1084   Reset(TRUE, FALSE);
1085   if (appData.noChessProgram || first.protocolVersion == 1) {
1086     InitBackEnd3();
1087     } else {
1088     /* kludge: allow timeout for initial "feature" commands */
1089     FreezeUI();
1090     DisplayMessage("", _("Starting chess program"));
1091     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1092   }
1093 }
1094
1095 void
1096 InitBackEnd3 P((void))
1097 {
1098   GameMode initialMode;
1099   char buf[MSG_SIZ];
1100   int err;
1101   
1102   InitChessProgram(&first, startedFromSetupPosition);
1103   
1104   
1105   if (appData.icsActive) 
1106     {
1107       err = establish();
1108       if (err != 0) 
1109         {
1110           if (*appData.icsCommPort != NULLCHAR) 
1111             {
1112               sprintf(buf, _("Could not open comm port %s"),
1113                       appData.icsCommPort);
1114             }
1115           else 
1116             {
1117               snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1118                        appData.icsHost, appData.icsPort);
1119             }
1120           DisplayFatalError(buf, err, 1);
1121           return;
1122         }
1123
1124         SetICSMode();
1125         telnetISR =
1126           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1127         fromUserISR =
1128           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1131     }
1132   else if (appData.noChessProgram) 
1133     {
1134       SetNCPMode();
1135     } 
1136   else 
1137     {
1138       SetGNUMode();
1139     }
1140   
1141   if (*appData.cmailGameName != NULLCHAR) 
1142     {
1143       SetCmailMode();
1144       OpenLoopback(&cmailPR);
1145       cmailISR =
1146         AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1147     }
1148   
1149   ThawUI();
1150   DisplayMessage("", "");
1151   if (StrCaseCmp(appData.initialMode, "") == 0) 
1152     {
1153       initialMode = BeginningOfGame;
1154     } 
1155   else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) 
1156     {
1157       initialMode = TwoMachinesPlay;
1158     } 
1159   else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) 
1160     {
1161       initialMode = AnalyzeFile;
1162     } 
1163   else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) 
1164     {
1165       initialMode = AnalyzeMode;
1166     } 
1167   else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) 
1168     {
1169       initialMode = MachinePlaysWhite;
1170     } 
1171   else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) 
1172     {
1173       initialMode = MachinePlaysBlack;
1174     } 
1175   else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) 
1176     {
1177       initialMode = EditGame;
1178     } 
1179   else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) 
1180     {
1181       initialMode = EditPosition;
1182     } 
1183   else if (StrCaseCmp(appData.initialMode, "Training") == 0) 
1184     {
1185       initialMode = Training;
1186     } 
1187   else 
1188     {
1189       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1190       DisplayFatalError(buf, 0, 2);
1191       return;
1192     }
1193   
1194   if (appData.matchMode) 
1195     {
1196       /* Set up machine vs. machine match */
1197       if (appData.noChessProgram) 
1198         {
1199           DisplayFatalError(_("Can't have a match with no chess programs"),
1200                             0, 2);
1201           return;
1202         }
1203       matchMode = TRUE;
1204       matchGame = 1;
1205       if (*appData.loadGameFile != NULLCHAR) 
1206         {
1207           int index = appData.loadGameIndex; // [HGM] autoinc
1208           if(index<0) lastIndex = index = 1;
1209           if (!LoadGameFromFile(appData.loadGameFile,
1210                                 index,
1211                                 appData.loadGameFile, FALSE)) 
1212             {
1213               DisplayFatalError(_("Bad game file"), 0, 1);
1214               return;
1215             }
1216         } 
1217       else if (*appData.loadPositionFile != NULLCHAR) 
1218         {
1219           int index = appData.loadPositionIndex; // [HGM] autoinc
1220           if(index<0) lastIndex = index = 1;
1221           if (!LoadPositionFromFile(appData.loadPositionFile,
1222                                     index,
1223                                     appData.loadPositionFile)) 
1224             {
1225               DisplayFatalError(_("Bad position file"), 0, 1);
1226               return;
1227             }
1228         }
1229       TwoMachinesEvent();
1230     } 
1231   else if (*appData.cmailGameName != NULLCHAR) 
1232     {
1233       /* Set up cmail mode */
1234       ReloadCmailMsgEvent(TRUE);
1235     } 
1236   else 
1237     {
1238       /* Set up other modes */
1239       if (initialMode == AnalyzeFile) 
1240         {
1241           if (*appData.loadGameFile == NULLCHAR) 
1242             {
1243               DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1244               return;
1245             }
1246         }
1247       if (*appData.loadGameFile != NULLCHAR) 
1248         {
1249           (void) LoadGameFromFile(appData.loadGameFile,
1250                                   appData.loadGameIndex,
1251                                   appData.loadGameFile, TRUE);
1252         } 
1253       else if (*appData.loadPositionFile != NULLCHAR) 
1254         {
1255           (void) LoadPositionFromFile(appData.loadPositionFile,
1256                                       appData.loadPositionIndex,
1257                                       appData.loadPositionFile);
1258           /* [HGM] try to make self-starting even after FEN load */
1259           /* to allow automatic setup of fairy variants with wtm */
1260           if(initialMode == BeginningOfGame && !blackPlaysFirst) 
1261             {
1262               gameMode = BeginningOfGame;
1263               setboardSpoiledMachineBlack = 1;
1264             }
1265           /* [HGM] loadPos: make that every new game uses the setup */
1266           /* from file as long as we do not switch variant          */
1267           if(!blackPlaysFirst) 
1268             {
1269               startedFromPositionFile = TRUE;
1270               CopyBoard(filePosition, boards[0]);
1271             }
1272         }
1273       if (initialMode == AnalyzeMode) 
1274         {
1275           if (appData.noChessProgram) 
1276             {
1277               DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1278               return;
1279             }
1280           if (appData.icsActive) 
1281             {
1282               DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1283               return;
1284             }
1285           AnalyzeModeEvent();
1286         } 
1287       else if (initialMode == AnalyzeFile) 
1288         {
1289           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1290           ShowThinkingEvent();
1291           AnalyzeFileEvent();
1292           AnalysisPeriodicEvent(1);
1293         } 
1294       else if (initialMode == MachinePlaysWhite) 
1295         {
1296           if (appData.noChessProgram) 
1297             {
1298               DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1299                                 0, 2);
1300               return;
1301             }
1302           if (appData.icsActive) 
1303             {
1304               DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1305                                 0, 2);
1306               return;
1307             }
1308           MachineWhiteEvent();
1309         } 
1310       else if (initialMode == MachinePlaysBlack) 
1311         {
1312           if (appData.noChessProgram) 
1313             {
1314               DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1315                                 0, 2);
1316               return;
1317             }
1318           if (appData.icsActive) 
1319             {
1320               DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1321                                 0, 2);
1322               return;
1323             }
1324           MachineBlackEvent();
1325         } 
1326       else if (initialMode == TwoMachinesPlay) 
1327         {
1328           if (appData.noChessProgram) 
1329             {
1330               DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1331                                 0, 2);
1332               return;
1333             }
1334           if (appData.icsActive) 
1335             {
1336               DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1337                                 0, 2);
1338               return;
1339             }
1340           TwoMachinesEvent();
1341         } 
1342       else if (initialMode == EditGame) 
1343         {
1344           EditGameEvent();
1345         } 
1346       else if (initialMode == EditPosition) 
1347         {
1348           EditPositionEvent();
1349         } 
1350       else if (initialMode == Training) 
1351         {
1352           if (*appData.loadGameFile == NULLCHAR) 
1353             {
1354               DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1355               return;
1356             }
1357           TrainingEvent();
1358         }
1359     }
1360   
1361   return;
1362 }
1363
1364 /*
1365  * Establish will establish a contact to a remote host.port.
1366  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1367  *  used to talk to the host.
1368  * Returns 0 if okay, error code if not.
1369  */
1370 int
1371 establish()
1372 {
1373     char buf[MSG_SIZ];
1374
1375     if (*appData.icsCommPort != NULLCHAR) {
1376         /* Talk to the host through a serial comm port */
1377         return OpenCommPort(appData.icsCommPort, &icsPR);
1378
1379     } else if (*appData.gateway != NULLCHAR) {
1380         if (*appData.remoteShell == NULLCHAR) {
1381             /* Use the rcmd protocol to run telnet program on a gateway host */
1382             snprintf(buf, sizeof(buf), "%s %s %s",
1383                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1384             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1385
1386         } else {
1387             /* Use the rsh program to run telnet program on a gateway host */
1388             if (*appData.remoteUser == NULLCHAR) {
1389                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1390                         appData.gateway, appData.telnetProgram,
1391                         appData.icsHost, appData.icsPort);
1392             } else {
1393                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1394                         appData.remoteShell, appData.gateway,
1395                         appData.remoteUser, appData.telnetProgram,
1396                         appData.icsHost, appData.icsPort);
1397             }
1398             return StartChildProcess(buf, "", &icsPR);
1399
1400         }
1401     } else if (appData.useTelnet) {
1402         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1403
1404     } else {
1405         /* TCP socket interface differs somewhat between
1406            Unix and NT; handle details in the front end.
1407            */
1408         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1409     }
1410 }
1411
1412 void
1413 show_bytes(fp, buf, count)
1414      FILE *fp;
1415      char *buf;
1416      int count;
1417 {
1418     while (count--) {
1419         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1420             fprintf(fp, "\\%03o", *buf & 0xff);
1421         } else {
1422             putc(*buf, fp);
1423         }
1424         buf++;
1425     }
1426     fflush(fp);
1427 }
1428
1429 /* Returns an errno value */
1430 int
1431 OutputMaybeTelnet(pr, message, count, outError)
1432      ProcRef pr;
1433      char *message;
1434      int count;
1435      int *outError;
1436 {
1437     char buf[8192], *p, *q, *buflim;
1438     int left, newcount, outcount;
1439
1440     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1441         *appData.gateway != NULLCHAR) {
1442         if (appData.debugMode) {
1443             fprintf(debugFP, ">ICS: ");
1444             show_bytes(debugFP, message, count);
1445             fprintf(debugFP, "\n");
1446         }
1447         return OutputToProcess(pr, message, count, outError);
1448     }
1449
1450     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1451     p = message;
1452     q = buf;
1453     left = count;
1454     newcount = 0;
1455     while (left) {
1456         if (q >= buflim) {
1457             if (appData.debugMode) {
1458                 fprintf(debugFP, ">ICS: ");
1459                 show_bytes(debugFP, buf, newcount);
1460                 fprintf(debugFP, "\n");
1461             }
1462             outcount = OutputToProcess(pr, buf, newcount, outError);
1463             if (outcount < newcount) return -1; /* to be sure */
1464             q = buf;
1465             newcount = 0;
1466         }
1467         if (*p == '\n') {
1468             *q++ = '\r';
1469             newcount++;
1470         } else if (((unsigned char) *p) == TN_IAC) {
1471             *q++ = (char) TN_IAC;
1472             newcount ++;
1473         }
1474         *q++ = *p++;
1475         newcount++;
1476         left--;
1477     }
1478     if (appData.debugMode) {
1479         fprintf(debugFP, ">ICS: ");
1480         show_bytes(debugFP, buf, newcount);
1481         fprintf(debugFP, "\n");
1482     }
1483     outcount = OutputToProcess(pr, buf, newcount, outError);
1484     if (outcount < newcount) return -1; /* to be sure */
1485     return count;
1486 }
1487
1488 void
1489 read_from_player(isr, closure, message, count, error)
1490      InputSourceRef isr;
1491      VOIDSTAR closure;
1492      char *message;
1493      int count;
1494      int error;
1495 {
1496     int outError, outCount;
1497     static int gotEof = 0;
1498
1499     /* Pass data read from player on to ICS */
1500     if (count > 0) {
1501         gotEof = 0;
1502         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1503         if (outCount < count) {
1504             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1505         }
1506     } else if (count < 0) {
1507         RemoveInputSource(isr);
1508         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1509     } else if (gotEof++ > 0) {
1510         RemoveInputSource(isr);
1511         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1512     }
1513 }
1514
1515 void
1516 KeepAlive()
1517 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1518     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1519     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1520     SendToICS("date\n");
1521     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1522 }
1523
1524 /* added routine for printf style output to ics */
1525 void ics_printf(char *format, ...)
1526 {
1527     char buffer[MSG_SIZ];
1528     va_list args;
1529
1530     va_start(args, format);
1531     vsnprintf(buffer, sizeof(buffer), format, args);
1532     buffer[sizeof(buffer)-1] = '\0';
1533     SendToICS(buffer);
1534     va_end(args);
1535 }
1536
1537 void
1538 SendToICS(s)
1539      char *s;
1540 {
1541     int count, outCount, outError;
1542
1543     if (icsPR == NULL) return;
1544
1545     count = strlen(s);
1546     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1547     if (outCount < count) {
1548         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1549     }
1550 }
1551
1552 /* This is used for sending logon scripts to the ICS. Sending
1553    without a delay causes problems when using timestamp on ICC
1554    (at least on my machine). */
1555 void
1556 SendToICSDelayed(s,msdelay)
1557      char *s;
1558      long msdelay;
1559 {
1560     int count, outCount, outError;
1561
1562     if (icsPR == NULL) return;
1563
1564     count = strlen(s);
1565     if (appData.debugMode) {
1566         fprintf(debugFP, ">ICS: ");
1567         show_bytes(debugFP, s, count);
1568         fprintf(debugFP, "\n");
1569     }
1570     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1571                                       msdelay);
1572     if (outCount < count) {
1573         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1574     }
1575 }
1576
1577
1578 /* Remove all highlighting escape sequences in s
1579    Also deletes any suffix starting with '('
1580    */
1581 char *
1582 StripHighlightAndTitle(s)
1583      char *s;
1584 {
1585     static char retbuf[MSG_SIZ];
1586     char *p = retbuf;
1587
1588     while (*s != NULLCHAR) {
1589         while (*s == '\033') {
1590             while (*s != NULLCHAR && !isalpha(*s)) s++;
1591             if (*s != NULLCHAR) s++;
1592         }
1593         while (*s != NULLCHAR && *s != '\033') {
1594             if (*s == '(' || *s == '[') {
1595                 *p = NULLCHAR;
1596                 return retbuf;
1597             }
1598             *p++ = *s++;
1599         }
1600     }
1601     *p = NULLCHAR;
1602     return retbuf;
1603 }
1604
1605 /* Remove all highlighting escape sequences in s */
1606 char *
1607 StripHighlight(s)
1608      char *s;
1609 {
1610     static char retbuf[MSG_SIZ];
1611     char *p = retbuf;
1612
1613     while (*s != NULLCHAR) {
1614         while (*s == '\033') {
1615             while (*s != NULLCHAR && !isalpha(*s)) s++;
1616             if (*s != NULLCHAR) s++;
1617         }
1618         while (*s != NULLCHAR && *s != '\033') {
1619             *p++ = *s++;
1620         }
1621     }
1622     *p = NULLCHAR;
1623     return retbuf;
1624 }
1625
1626 char *variantNames[] = VARIANT_NAMES;
1627 char *
1628 VariantName(v)
1629      VariantClass v;
1630 {
1631     return variantNames[v];
1632 }
1633
1634
1635 /* Identify a variant from the strings the chess servers use or the
1636    PGN Variant tag names we use. */
1637 VariantClass
1638 StringToVariant(e)
1639      char *e;
1640 {
1641     char *p;
1642     int wnum = -1;
1643     VariantClass v = VariantNormal;
1644     int i, found = FALSE;
1645     char buf[MSG_SIZ];
1646
1647     if (!e) return v;
1648
1649     /* [HGM] skip over optional board-size prefixes */
1650     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1651         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1652         while( *e++ != '_');
1653     }
1654
1655     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1656         v = VariantNormal;
1657         found = TRUE;
1658     } else
1659     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1660       if (StrCaseStr(e, variantNames[i])) {
1661         v = (VariantClass) i;
1662         found = TRUE;
1663         break;
1664       }
1665     }
1666
1667     if (!found) {
1668       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1669           || StrCaseStr(e, "wild/fr")
1670           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1671         v = VariantFischeRandom;
1672       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1673                  (i = 1, p = StrCaseStr(e, "w"))) {
1674         p += i;
1675         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1676         if (isdigit(*p)) {
1677           wnum = atoi(p);
1678         } else {
1679           wnum = -1;
1680         }
1681         switch (wnum) {
1682         case 0: /* FICS only, actually */
1683         case 1:
1684           /* Castling legal even if K starts on d-file */
1685           v = VariantWildCastle;
1686           break;
1687         case 2:
1688         case 3:
1689         case 4:
1690           /* Castling illegal even if K & R happen to start in
1691              normal positions. */
1692           v = VariantNoCastle;
1693           break;
1694         case 5:
1695         case 7:
1696         case 8:
1697         case 10:
1698         case 11:
1699         case 12:
1700         case 13:
1701         case 14:
1702         case 15:
1703         case 18:
1704         case 19:
1705           /* Castling legal iff K & R start in normal positions */
1706           v = VariantNormal;
1707           break;
1708         case 6:
1709         case 20:
1710         case 21:
1711           /* Special wilds for position setup; unclear what to do here */
1712           v = VariantLoadable;
1713           break;
1714         case 9:
1715           /* Bizarre ICC game */
1716           v = VariantTwoKings;
1717           break;
1718         case 16:
1719           v = VariantKriegspiel;
1720           break;
1721         case 17:
1722           v = VariantLosers;
1723           break;
1724         case 22:
1725           v = VariantFischeRandom;
1726           break;
1727         case 23:
1728           v = VariantCrazyhouse;
1729           break;
1730         case 24:
1731           v = VariantBughouse;
1732           break;
1733         case 25:
1734           v = Variant3Check;
1735           break;
1736         case 26:
1737           /* Not quite the same as FICS suicide! */
1738           v = VariantGiveaway;
1739           break;
1740         case 27:
1741           v = VariantAtomic;
1742           break;
1743         case 28:
1744           v = VariantShatranj;
1745           break;
1746
1747         /* Temporary names for future ICC types.  The name *will* change in
1748            the next xboard/WinBoard release after ICC defines it. */
1749         case 29:
1750           v = Variant29;
1751           break;
1752         case 30:
1753           v = Variant30;
1754           break;
1755         case 31:
1756           v = Variant31;
1757           break;
1758         case 32:
1759           v = Variant32;
1760           break;
1761         case 33:
1762           v = Variant33;
1763           break;
1764         case 34:
1765           v = Variant34;
1766           break;
1767         case 35:
1768           v = Variant35;
1769           break;
1770         case 36:
1771           v = Variant36;
1772           break;
1773         case 37:
1774           v = VariantShogi;
1775           break;
1776         case 38:
1777           v = VariantXiangqi;
1778           break;
1779         case 39:
1780           v = VariantCourier;
1781           break;
1782         case 40:
1783           v = VariantGothic;
1784           break;
1785         case 41:
1786           v = VariantCapablanca;
1787           break;
1788         case 42:
1789           v = VariantKnightmate;
1790           break;
1791         case 43:
1792           v = VariantFairy;
1793           break;
1794         case 44:
1795           v = VariantCylinder;
1796           break;
1797         case 45:
1798           v = VariantFalcon;
1799           break;
1800         case 46:
1801           v = VariantCapaRandom;
1802           break;
1803         case 47:
1804           v = VariantBerolina;
1805           break;
1806         case 48:
1807           v = VariantJanus;
1808           break;
1809         case 49:
1810           v = VariantSuper;
1811           break;
1812         case 50:
1813           v = VariantGreat;
1814           break;
1815         case -1:
1816           /* Found "wild" or "w" in the string but no number;
1817              must assume it's normal chess. */
1818           v = VariantNormal;
1819           break;
1820         default:
1821           sprintf(buf, _("Unknown wild type %d"), wnum);
1822           DisplayError(buf, 0);
1823           v = VariantUnknown;
1824           break;
1825         }
1826       }
1827     }
1828     if (appData.debugMode) {
1829       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1830               e, wnum, VariantName(v));
1831     }
1832     return v;
1833 }
1834
1835 static int leftover_start = 0, leftover_len = 0;
1836 char star_match[STAR_MATCH_N][MSG_SIZ];
1837
1838 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1839    advance *index beyond it, and set leftover_start to the new value of
1840    *index; else return FALSE.  If pattern contains the character '*', it
1841    matches any sequence of characters not containing '\r', '\n', or the
1842    character following the '*' (if any), and the matched sequence(s) are
1843    copied into star_match.
1844    */
1845 int
1846 looking_at(buf, index, pattern)
1847      char *buf;
1848      int *index;
1849      char *pattern;
1850 {
1851     char *bufp = &buf[*index], *patternp = pattern;
1852     int star_count = 0;
1853     char *matchp = star_match[0];
1854
1855     for (;;) {
1856         if (*patternp == NULLCHAR) {
1857             *index = leftover_start = bufp - buf;
1858             *matchp = NULLCHAR;
1859             return TRUE;
1860         }
1861         if (*bufp == NULLCHAR) return FALSE;
1862         if (*patternp == '*') {
1863             if (*bufp == *(patternp + 1)) {
1864                 *matchp = NULLCHAR;
1865                 matchp = star_match[++star_count];
1866                 patternp += 2;
1867                 bufp++;
1868                 continue;
1869             } else if (*bufp == '\n' || *bufp == '\r') {
1870                 patternp++;
1871                 if (*patternp == NULLCHAR)
1872                   continue;
1873                 else
1874                   return FALSE;
1875             } else {
1876                 *matchp++ = *bufp++;
1877                 continue;
1878             }
1879         }
1880         if (*patternp != *bufp) return FALSE;
1881         patternp++;
1882         bufp++;
1883     }
1884 }
1885
1886 void
1887 SendToPlayer(data, length)
1888      char *data;
1889      int length;
1890 {
1891     int error, outCount;
1892     outCount = OutputToProcess(NoProc, data, length, &error);
1893     if (outCount < length) {
1894         DisplayFatalError(_("Error writing to display"), error, 1);
1895     }
1896 }
1897
1898 void
1899 PackHolding(packed, holding)
1900      char packed[];
1901      char *holding;
1902 {
1903     char *p = holding;
1904     char *q = packed;
1905     int runlength = 0;
1906     int curr = 9999;
1907     do {
1908         if (*p == curr) {
1909             runlength++;
1910         } else {
1911             switch (runlength) {
1912               case 0:
1913                 break;
1914               case 1:
1915                 *q++ = curr;
1916                 break;
1917               case 2:
1918                 *q++ = curr;
1919                 *q++ = curr;
1920                 break;
1921               default:
1922                 sprintf(q, "%d", runlength);
1923                 while (*q) q++;
1924                 *q++ = curr;
1925                 break;
1926             }
1927             runlength = 1;
1928             curr = *p;
1929         }
1930     } while (*p++);
1931     *q = NULLCHAR;
1932 }
1933
1934 /* Telnet protocol requests from the front end */
1935 void
1936 TelnetRequest(ddww, option)
1937      unsigned char ddww, option;
1938 {
1939     unsigned char msg[3];
1940     int outCount, outError;
1941
1942     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1943
1944     if (appData.debugMode) {
1945         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1946         switch (ddww) {
1947           case TN_DO:
1948             ddwwStr = "DO";
1949             break;
1950           case TN_DONT:
1951             ddwwStr = "DONT";
1952             break;
1953           case TN_WILL:
1954             ddwwStr = "WILL";
1955             break;
1956           case TN_WONT:
1957             ddwwStr = "WONT";
1958             break;
1959           default:
1960             ddwwStr = buf1;
1961             sprintf(buf1, "%d", ddww);
1962             break;
1963         }
1964         switch (option) {
1965           case TN_ECHO:
1966             optionStr = "ECHO";
1967             break;
1968           default:
1969             optionStr = buf2;
1970             sprintf(buf2, "%d", option);
1971             break;
1972         }
1973         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1974     }
1975     msg[0] = TN_IAC;
1976     msg[1] = ddww;
1977     msg[2] = option;
1978     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1979     if (outCount < 3) {
1980         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1981     }
1982 }
1983
1984 void
1985 DoEcho()
1986 {
1987     if (!appData.icsActive) return;
1988     TelnetRequest(TN_DO, TN_ECHO);
1989 }
1990
1991 void
1992 DontEcho()
1993 {
1994     if (!appData.icsActive) return;
1995     TelnetRequest(TN_DONT, TN_ECHO);
1996 }
1997
1998 void
1999 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2000 {
2001     /* put the holdings sent to us by the server on the board holdings area */
2002     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2003     char p;
2004     ChessSquare piece;
2005
2006     if(gameInfo.holdingsWidth < 2)  return;
2007     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2008         return; // prevent overwriting by pre-board holdings
2009
2010     if( (int)lowestPiece >= BlackPawn ) {
2011         holdingsColumn = 0;
2012         countsColumn = 1;
2013         holdingsStartRow = BOARD_HEIGHT-1;
2014         direction = -1;
2015     } else {
2016         holdingsColumn = BOARD_WIDTH-1;
2017         countsColumn = BOARD_WIDTH-2;
2018         holdingsStartRow = 0;
2019         direction = 1;
2020     }
2021
2022     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2023         board[i][holdingsColumn] = EmptySquare;
2024         board[i][countsColumn]   = (ChessSquare) 0;
2025     }
2026     while( (p=*holdings++) != NULLCHAR ) {
2027         piece = CharToPiece( ToUpper(p) );
2028         if(piece == EmptySquare) continue;
2029         /*j = (int) piece - (int) WhitePawn;*/
2030         j = PieceToNumber(piece);
2031         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2032         if(j < 0) continue;               /* should not happen */
2033         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2034         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2035         board[holdingsStartRow+j*direction][countsColumn]++;
2036     }
2037 }
2038
2039
2040 void
2041 VariantSwitch(Board board, VariantClass newVariant)
2042 {
2043    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2044    Board oldBoard;
2045
2046    startedFromPositionFile = FALSE;
2047    if(gameInfo.variant == newVariant) return;
2048
2049    /* [HGM] This routine is called each time an assignment is made to
2050     * gameInfo.variant during a game, to make sure the board sizes
2051     * are set to match the new variant. If that means adding or deleting
2052     * holdings, we shift the playing board accordingly
2053     * This kludge is needed because in ICS observe mode, we get boards
2054     * of an ongoing game without knowing the variant, and learn about the
2055     * latter only later. This can be because of the move list we requested,
2056     * in which case the game history is refilled from the beginning anyway,
2057     * but also when receiving holdings of a crazyhouse game. In the latter
2058     * case we want to add those holdings to the already received position.
2059     */
2060    
2061    if (appData.debugMode) {
2062      fprintf(debugFP, "Switch board from %s to %s\n",
2063              VariantName(gameInfo.variant), VariantName(newVariant));
2064      setbuf(debugFP, NULL);
2065    }
2066    shuffleOpenings = 0;       /* [HGM] shuffle */
2067    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2068    switch(newVariant) 
2069      {
2070      case VariantShogi:
2071        newWidth = 9;  newHeight = 9;
2072        gameInfo.holdingsSize = 7;
2073      case VariantBughouse:
2074      case VariantCrazyhouse:
2075        newHoldingsWidth = 2; break;
2076      case VariantGreat:
2077        newWidth = 10;
2078      case VariantSuper:
2079        newHoldingsWidth = 2;
2080        gameInfo.holdingsSize = 8;
2081        break;
2082      case VariantGothic:
2083      case VariantCapablanca:
2084      case VariantCapaRandom:
2085        newWidth = 10;
2086      default:
2087        newHoldingsWidth = gameInfo.holdingsSize = 0;
2088      };
2089    
2090    if(newWidth  != gameInfo.boardWidth  ||
2091       newHeight != gameInfo.boardHeight ||
2092       newHoldingsWidth != gameInfo.holdingsWidth ) {
2093      
2094      /* shift position to new playing area, if needed */
2095      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2096        for(i=0; i<BOARD_HEIGHT; i++) 
2097          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2098            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2099              board[i][j];
2100        for(i=0; i<newHeight; i++) {
2101          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2102          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2103        }
2104      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2105        for(i=0; i<BOARD_HEIGHT; i++)
2106          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2107            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2108              board[i][j];
2109      }
2110      gameInfo.boardWidth  = newWidth;
2111      gameInfo.boardHeight = newHeight;
2112      gameInfo.holdingsWidth = newHoldingsWidth;
2113      gameInfo.variant = newVariant;
2114      InitDrawingSizes(-2, 0);
2115    } else gameInfo.variant = newVariant;
2116    CopyBoard(oldBoard, board);   // remember correctly formatted board
2117      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2118    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2119 }
2120
2121 static int loggedOn = FALSE;
2122
2123 /*-- Game start info cache: --*/
2124 int gs_gamenum;
2125 char gs_kind[MSG_SIZ];
2126 static char player1Name[128] = "";
2127 static char player2Name[128] = "";
2128 static char cont_seq[] = "\n\\   ";
2129 static int player1Rating = -1;
2130 static int player2Rating = -1;
2131 /*----------------------------*/
2132
2133 ColorClass curColor = ColorNormal;
2134 int suppressKibitz = 0;
2135
2136 // [HGM] seekgraph
2137 Boolean soughtPending = FALSE;
2138 Boolean seekGraphUp;
2139 #define MAX_SEEK_ADS 200
2140 #define SQUARE 0x80
2141 char *seekAdList[MAX_SEEK_ADS];
2142 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2143 float tcList[MAX_SEEK_ADS];
2144 char colorList[MAX_SEEK_ADS];
2145 int nrOfSeekAds = 0;
2146 int minRating = 1010, maxRating = 2800;
2147 int hMargin = 10, vMargin = 20, h, w;
2148 extern int squareSize, lineGap;
2149
2150 void
2151 PlotSeekAd(int i)
2152 {
2153         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2154         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2155         if(r < minRating+100 && r >=0 ) r = minRating+100;
2156         if(r > maxRating) r = maxRating;
2157         if(tc < 1.) tc = 1.;
2158         if(tc > 95.) tc = 95.;
2159         x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2160         y = ((double)r - minRating)/(maxRating - minRating)
2161             * (h-vMargin-squareSize/8-1) + vMargin;
2162         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2163         if(strstr(seekAdList[i], " u ")) color = 1;
2164         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2165            !strstr(seekAdList[i], "bullet") &&
2166            !strstr(seekAdList[i], "blitz") &&
2167            !strstr(seekAdList[i], "standard") ) color = 2;
2168         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2169         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2170 }
2171
2172 void
2173 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2174 {
2175         char buf[MSG_SIZ], *ext = "";
2176         VariantClass v = StringToVariant(type);
2177         if(strstr(type, "wild")) {
2178             ext = type + 4; // append wild number
2179             if(v == VariantFischeRandom) type = "chess960"; else
2180             if(v == VariantLoadable) type = "setup"; else
2181             type = VariantName(v);
2182         }
2183         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2184         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2185             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2186             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2187             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2188             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2189             seekNrList[nrOfSeekAds] = nr;
2190             zList[nrOfSeekAds] = 0;
2191             seekAdList[nrOfSeekAds++] = StrSave(buf);
2192             if(plot) PlotSeekAd(nrOfSeekAds-1);
2193         }
2194 }
2195
2196 void
2197 EraseSeekDot(int i)
2198 {
2199     int x = xList[i], y = yList[i], d=squareSize/4, k;
2200     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2201     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2202     // now replot every dot that overlapped
2203     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2204         int xx = xList[k], yy = yList[k];
2205         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2206             DrawSeekDot(xx, yy, colorList[k]);
2207     }
2208 }
2209
2210 void
2211 RemoveSeekAd(int nr)
2212 {
2213         int i;
2214         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2215             EraseSeekDot(i);
2216             if(seekAdList[i]) free(seekAdList[i]);
2217             seekAdList[i] = seekAdList[--nrOfSeekAds];
2218             seekNrList[i] = seekNrList[nrOfSeekAds];
2219             ratingList[i] = ratingList[nrOfSeekAds];
2220             colorList[i]  = colorList[nrOfSeekAds];
2221             tcList[i] = tcList[nrOfSeekAds];
2222             xList[i]  = xList[nrOfSeekAds];
2223             yList[i]  = yList[nrOfSeekAds];
2224             zList[i]  = zList[nrOfSeekAds];
2225             seekAdList[nrOfSeekAds] = NULL;
2226             break;
2227         }
2228 }
2229
2230 Boolean
2231 MatchSoughtLine(char *line)
2232 {
2233     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2234     int nr, base, inc, u=0; char dummy;
2235
2236     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2237        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2238        (u=1) &&
2239        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2240         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2241         // match: compact and save the line
2242         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2243         return TRUE;
2244     }
2245     return FALSE;
2246 }
2247
2248 int
2249 DrawSeekGraph()
2250 {
2251     if(!seekGraphUp) return FALSE;
2252     int i;
2253     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2254     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2255
2256     DrawSeekBackground(0, 0, w, h);
2257     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2258     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2259     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2260         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2261         yy = h-1-yy;
2262         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2263         if(i%500 == 0) {
2264             char buf[MSG_SIZ];
2265             sprintf(buf, "%d", i);
2266             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2267         }
2268     }
2269     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2270     for(i=1; i<100; i+=(i<10?1:5)) {
2271         int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2272         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2273         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2274             char buf[MSG_SIZ];
2275             sprintf(buf, "%d", i);
2276             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2277         }
2278     }
2279     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2280     return TRUE;
2281 }
2282
2283 int SeekGraphClick(ClickType click, int x, int y, int moving)
2284 {
2285     static int lastDown = 0, displayed = 0, lastSecond;
2286     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2287         if(click == Release || moving) return FALSE;
2288         nrOfSeekAds = 0;
2289         soughtPending = TRUE;
2290         SendToICS(ics_prefix);
2291         SendToICS("sought\n"); // should this be "sought all"?
2292     } else { // issue challenge based on clicked ad
2293         int dist = 10000; int i, closest = 0, second = 0;
2294         for(i=0; i<nrOfSeekAds; i++) {
2295             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2296             if(d < dist) { dist = d; closest = i; }
2297             second += (d - zList[i] < 120); // count in-range ads
2298             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2299         }
2300         if(dist < 120) {
2301             char buf[MSG_SIZ];
2302             second = (second > 1);
2303             if(displayed != closest || second != lastSecond) {
2304                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2305                 lastSecond = second; displayed = closest;
2306             }
2307             sprintf(buf, "play %d\n", seekNrList[closest]);
2308             if(click == Press) {
2309                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2310                 lastDown = closest;
2311                 return TRUE;
2312             } // on press 'hit', only show info
2313             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2314             SendToICS(ics_prefix);
2315             SendToICS(buf); // should this be "sought all"?
2316         } else if(click == Release) { // release 'miss' is ignored
2317             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2318             if(moving == 2) { // right up-click
2319                 nrOfSeekAds = 0; // refresh graph
2320                 soughtPending = TRUE;
2321                 SendToICS(ics_prefix);
2322                 SendToICS("sought\n"); // should this be "sought all"?
2323             }
2324             return TRUE;
2325         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2326         // press miss or release hit 'pop down' seek graph
2327         seekGraphUp = FALSE;
2328         DrawPosition(TRUE, NULL);
2329     }
2330     return TRUE;
2331 }
2332
2333 void
2334 read_from_ics(isr, closure, data, count, error)
2335      InputSourceRef isr;
2336      VOIDSTAR closure;
2337      char *data;
2338      int count;
2339      int error;
2340 {
2341 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2342 #define STARTED_NONE 0
2343 #define STARTED_MOVES 1
2344 #define STARTED_BOARD 2
2345 #define STARTED_OBSERVE 3
2346 #define STARTED_HOLDINGS 4
2347 #define STARTED_CHATTER 5
2348 #define STARTED_COMMENT 6
2349 #define STARTED_MOVES_NOHIDE 7
2350
2351     static int started = STARTED_NONE;
2352     static char parse[20000];
2353     static int parse_pos = 0;
2354     static char buf[BUF_SIZE + 1];
2355     static int firstTime = TRUE, intfSet = FALSE;
2356     static ColorClass prevColor = ColorNormal;
2357     static int savingComment = FALSE;
2358     static int cmatch = 0; // continuation sequence match
2359     char *bp;
2360     char str[500];
2361     int i, oldi;
2362     int buf_len;
2363     int next_out;
2364     int tkind;
2365     int backup;    /* [DM] For zippy color lines */
2366     char *p;
2367     char talker[MSG_SIZ]; // [HGM] chat
2368     int channel;
2369
2370     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2371
2372     if (appData.debugMode) {
2373       if (!error) {
2374         fprintf(debugFP, "<ICS: ");
2375         show_bytes(debugFP, data, count);
2376         fprintf(debugFP, "\n");
2377       }
2378     }
2379
2380     if (appData.debugMode) { int f = forwardMostMove;
2381         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2382                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2383                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2384     }
2385     if (count > 0) {
2386         /* If last read ended with a partial line that we couldn't parse,
2387            prepend it to the new read and try again. */
2388         if (leftover_len > 0) {
2389             for (i=0; i<leftover_len; i++)
2390               buf[i] = buf[leftover_start + i];
2391         }
2392
2393     /* copy new characters into the buffer */
2394     bp = buf + leftover_len;
2395     buf_len=leftover_len;
2396     for (i=0; i<count; i++)
2397     {
2398         // ignore these
2399         if (data[i] == '\r')
2400             continue;
2401
2402         // join lines split by ICS?
2403         if (!appData.noJoin)
2404         {
2405             /*
2406                 Joining just consists of finding matches against the
2407                 continuation sequence, and discarding that sequence
2408                 if found instead of copying it.  So, until a match
2409                 fails, there's nothing to do since it might be the
2410                 complete sequence, and thus, something we don't want
2411                 copied.
2412             */
2413             if (data[i] == cont_seq[cmatch])
2414             {
2415                 cmatch++;
2416                 if (cmatch == strlen(cont_seq))
2417                 {
2418                     cmatch = 0; // complete match.  just reset the counter
2419
2420                     /*
2421                         it's possible for the ICS to not include the space
2422                         at the end of the last word, making our [correct]
2423                         join operation fuse two separate words.  the server
2424                         does this when the space occurs at the width setting.
2425                     */
2426                     if (!buf_len || buf[buf_len-1] != ' ')
2427                     {
2428                         *bp++ = ' ';
2429                         buf_len++;
2430                     }
2431                 }
2432                 continue;
2433             }
2434             else if (cmatch)
2435             {
2436                 /*
2437                     match failed, so we have to copy what matched before
2438                     falling through and copying this character.  In reality,
2439                     this will only ever be just the newline character, but
2440                     it doesn't hurt to be precise.
2441                 */
2442                 strncpy(bp, cont_seq, cmatch);
2443                 bp += cmatch;
2444                 buf_len += cmatch;
2445                 cmatch = 0;
2446             }
2447         }
2448
2449         // copy this char
2450         *bp++ = data[i];
2451         buf_len++;
2452     }
2453
2454         buf[buf_len] = NULLCHAR;
2455 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2456         next_out = 0;
2457         leftover_start = 0;
2458
2459         i = 0;
2460         while (i < buf_len) {
2461             /* Deal with part of the TELNET option negotiation
2462                protocol.  We refuse to do anything beyond the
2463                defaults, except that we allow the WILL ECHO option,
2464                which ICS uses to turn off password echoing when we are
2465                directly connected to it.  We reject this option
2466                if localLineEditing mode is on (always on in xboard)
2467                and we are talking to port 23, which might be a real
2468                telnet server that will try to keep WILL ECHO on permanently.
2469              */
2470             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2471                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2472                 unsigned char option;
2473                 oldi = i;
2474                 switch ((unsigned char) buf[++i]) {
2475                   case TN_WILL:
2476                     if (appData.debugMode)
2477                       fprintf(debugFP, "\n<WILL ");
2478                     switch (option = (unsigned char) buf[++i]) {
2479                       case TN_ECHO:
2480                         if (appData.debugMode)
2481                           fprintf(debugFP, "ECHO ");
2482                         /* Reply only if this is a change, according
2483                            to the protocol rules. */
2484                         if (remoteEchoOption) break;
2485                         if (appData.localLineEditing &&
2486                             atoi(appData.icsPort) == TN_PORT) {
2487                             TelnetRequest(TN_DONT, TN_ECHO);
2488                         } else {
2489                             EchoOff();
2490                             TelnetRequest(TN_DO, TN_ECHO);
2491                             remoteEchoOption = TRUE;
2492                         }
2493                         break;
2494                       default:
2495                         if (appData.debugMode)
2496                           fprintf(debugFP, "%d ", option);
2497                         /* Whatever this is, we don't want it. */
2498                         TelnetRequest(TN_DONT, option);
2499                         break;
2500                     }
2501                     break;
2502                   case TN_WONT:
2503                     if (appData.debugMode)
2504                       fprintf(debugFP, "\n<WONT ");
2505                     switch (option = (unsigned char) buf[++i]) {
2506                       case TN_ECHO:
2507                         if (appData.debugMode)
2508                           fprintf(debugFP, "ECHO ");
2509                         /* Reply only if this is a change, according
2510                            to the protocol rules. */
2511                         if (!remoteEchoOption) break;
2512                         EchoOn();
2513                         TelnetRequest(TN_DONT, TN_ECHO);
2514                         remoteEchoOption = FALSE;
2515                         break;
2516                       default:
2517                         if (appData.debugMode)
2518                           fprintf(debugFP, "%d ", (unsigned char) option);
2519                         /* Whatever this is, it must already be turned
2520                            off, because we never agree to turn on
2521                            anything non-default, so according to the
2522                            protocol rules, we don't reply. */
2523                         break;
2524                     }
2525                     break;
2526                   case TN_DO:
2527                     if (appData.debugMode)
2528                       fprintf(debugFP, "\n<DO ");
2529                     switch (option = (unsigned char) buf[++i]) {
2530                       default:
2531                         /* Whatever this is, we refuse to do it. */
2532                         if (appData.debugMode)
2533                           fprintf(debugFP, "%d ", option);
2534                         TelnetRequest(TN_WONT, option);
2535                         break;
2536                     }
2537                     break;
2538                   case TN_DONT:
2539                     if (appData.debugMode)
2540                       fprintf(debugFP, "\n<DONT ");
2541                     switch (option = (unsigned char) buf[++i]) {
2542                       default:
2543                         if (appData.debugMode)
2544                           fprintf(debugFP, "%d ", option);
2545                         /* Whatever this is, we are already not doing
2546                            it, because we never agree to do anything
2547                            non-default, so according to the protocol
2548                            rules, we don't reply. */
2549                         break;
2550                     }
2551                     break;
2552                   case TN_IAC:
2553                     if (appData.debugMode)
2554                       fprintf(debugFP, "\n<IAC ");
2555                     /* Doubled IAC; pass it through */
2556                     i--;
2557                     break;
2558                   default:
2559                     if (appData.debugMode)
2560                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2561                     /* Drop all other telnet commands on the floor */
2562                     break;
2563                 }
2564                 if (oldi > next_out)
2565                   SendToPlayer(&buf[next_out], oldi - next_out);
2566                 if (++i > next_out)
2567                   next_out = i;
2568                 continue;
2569             }
2570
2571             /* OK, this at least will *usually* work */
2572             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2573                 loggedOn = TRUE;
2574             }
2575
2576             if (loggedOn && !intfSet) {
2577                 if (ics_type == ICS_ICC) {
2578                   sprintf(str,
2579                           "/set-quietly interface %s\n/set-quietly style 12\n",
2580                           programVersion);
2581                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2582                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2583                 } else if (ics_type == ICS_CHESSNET) {
2584                   sprintf(str, "/style 12\n");
2585                 } else {
2586                   strcpy(str, "alias $ @\n$set interface ");
2587                   strcat(str, programVersion);
2588                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2589                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2590                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2591 #ifdef WIN32
2592                   strcat(str, "$iset nohighlight 1\n");
2593 #endif
2594                   strcat(str, "$iset lock 1\n$style 12\n");
2595                 }
2596                 SendToICS(str);
2597                 NotifyFrontendLogin();
2598                 intfSet = TRUE;
2599             }
2600
2601             if (started == STARTED_COMMENT) {
2602                 /* Accumulate characters in comment */
2603                 parse[parse_pos++] = buf[i];
2604                 if (buf[i] == '\n') {
2605                     parse[parse_pos] = NULLCHAR;
2606                     if(chattingPartner>=0) {
2607                         char mess[MSG_SIZ];
2608                         sprintf(mess, "%s%s", talker, parse);
2609                         OutputChatMessage(chattingPartner, mess);
2610                         chattingPartner = -1;
2611                     } else
2612                     if(!suppressKibitz) // [HGM] kibitz
2613                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2614                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2615                         int nrDigit = 0, nrAlph = 0, j;
2616                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2617                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2618                         parse[parse_pos] = NULLCHAR;
2619                         // try to be smart: if it does not look like search info, it should go to
2620                         // ICS interaction window after all, not to engine-output window.
2621                         for(j=0; j<parse_pos; j++) { // count letters and digits
2622                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2623                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2624                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2625                         }
2626                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2627                             int depth=0; float score;
2628                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2629                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2630                                 pvInfoList[forwardMostMove-1].depth = depth;
2631                                 pvInfoList[forwardMostMove-1].score = 100*score;
2632                             }
2633                             OutputKibitz(suppressKibitz, parse);
2634                             next_out = i+1; // [HGM] suppress printing in ICS window
2635                         } else {
2636                             char tmp[MSG_SIZ];
2637                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2638                             SendToPlayer(tmp, strlen(tmp));
2639                         }
2640                     }
2641                     started = STARTED_NONE;
2642                 } else {
2643                     /* Don't match patterns against characters in comment */
2644                     i++;
2645                     continue;
2646                 }
2647             }
2648             if (started == STARTED_CHATTER) {
2649                 if (buf[i] != '\n') {
2650                     /* Don't match patterns against characters in chatter */
2651                     i++;
2652                     continue;
2653                 }
2654                 started = STARTED_NONE;
2655             }
2656
2657             /* Kludge to deal with rcmd protocol */
2658             if (firstTime && looking_at(buf, &i, "\001*")) {
2659                 DisplayFatalError(&buf[1], 0, 1);
2660                 continue;
2661             } else {
2662                 firstTime = FALSE;
2663             }
2664
2665             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2666                 ics_type = ICS_ICC;
2667                 ics_prefix = "/";
2668                 if (appData.debugMode)
2669                   fprintf(debugFP, "ics_type %d\n", ics_type);
2670                 continue;
2671             }
2672             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2673                 ics_type = ICS_FICS;
2674                 ics_prefix = "$";
2675                 if (appData.debugMode)
2676                   fprintf(debugFP, "ics_type %d\n", ics_type);
2677                 continue;
2678             }
2679             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2680                 ics_type = ICS_CHESSNET;
2681                 ics_prefix = "/";
2682                 if (appData.debugMode)
2683                   fprintf(debugFP, "ics_type %d\n", ics_type);
2684                 continue;
2685             }
2686
2687             if (!loggedOn &&
2688                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2689                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2690                  looking_at(buf, &i, "will be \"*\""))) {
2691               strcpy(ics_handle, star_match[0]);
2692               continue;
2693             }
2694
2695             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2696               char buf[MSG_SIZ];
2697               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2698               DisplayIcsInteractionTitle(buf);
2699               have_set_title = TRUE;
2700             }
2701
2702             /* skip finger notes */
2703             if (started == STARTED_NONE &&
2704                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2705                  (buf[i] == '1' && buf[i+1] == '0')) &&
2706                 buf[i+2] == ':' && buf[i+3] == ' ') {
2707               started = STARTED_CHATTER;
2708               i += 3;
2709               continue;
2710             }
2711
2712             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2713             if(appData.seekGraph) {
2714                 if(soughtPending && MatchSoughtLine(buf+i)) {
2715                     i = strstr(buf+i, "rated") - buf;
2716                     next_out = leftover_start = i;
2717                     started = STARTED_CHATTER;
2718                     suppressKibitz = TRUE;
2719                     continue;
2720                 }
2721                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2722                         && looking_at(buf, &i, "* ads displayed")) {
2723                     soughtPending = FALSE;
2724                     seekGraphUp = TRUE;
2725                     DrawSeekGraph();
2726                     continue;
2727                 }
2728                 if(appData.autoRefresh) {
2729                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2730                         int s = (ics_type == ICS_ICC); // ICC format differs
2731                         if(seekGraphUp)
2732                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2733                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2734                         looking_at(buf, &i, "*% "); // eat prompt
2735                         next_out = i; // suppress
2736                         continue;
2737                     }
2738                     if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2739                         char *p = star_match[0];
2740                         while(*p) {
2741                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2742                             while(*p && *p++ != ' '); // next
2743                         }
2744                         looking_at(buf, &i, "*% "); // eat prompt
2745                         next_out = i;
2746                         continue;
2747                     }
2748                 }
2749             }
2750
2751             /* skip formula vars */
2752             if (started == STARTED_NONE &&
2753                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2754               started = STARTED_CHATTER;
2755               i += 3;
2756               continue;
2757             }
2758
2759             oldi = i;
2760             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2761             if (appData.autoKibitz && started == STARTED_NONE &&
2762                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2763                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2764                 if(looking_at(buf, &i, "* kibitzes: ") &&
2765                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2766                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2767                         suppressKibitz = TRUE;
2768                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2769                                 && (gameMode == IcsPlayingWhite)) ||
2770                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2771                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2772                             started = STARTED_CHATTER; // own kibitz we simply discard
2773                         else {
2774                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2775                             parse_pos = 0; parse[0] = NULLCHAR;
2776                             savingComment = TRUE;
2777                             suppressKibitz = gameMode != IcsObserving ? 2 :
2778                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2779                         }
2780                         continue;
2781                 } else
2782                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2783                     // suppress the acknowledgements of our own autoKibitz
2784                     char *p;
2785                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2786                     SendToPlayer(star_match[0], strlen(star_match[0]));
2787                     looking_at(buf, &i, "*% "); // eat prompt
2788                     next_out = i;
2789                 }
2790             } // [HGM] kibitz: end of patch
2791
2792 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2793
2794             // [HGM] chat: intercept tells by users for which we have an open chat window
2795             channel = -1;
2796             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2797                                            looking_at(buf, &i, "* whispers:") ||
2798                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2799                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2800                 int p;
2801                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2802                 chattingPartner = -1;
2803
2804                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2805                 for(p=0; p<MAX_CHAT; p++) {
2806                     if(channel == atoi(chatPartner[p])) {
2807                     talker[0] = '['; strcat(talker, "] ");
2808                     chattingPartner = p; break;
2809                     }
2810                 } else
2811                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2812                 for(p=0; p<MAX_CHAT; p++) {
2813                     if(!strcmp("WHISPER", chatPartner[p])) {
2814                         talker[0] = '['; strcat(talker, "] ");
2815                         chattingPartner = p; break;
2816                     }
2817                 }
2818                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2819                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2820                     talker[0] = 0;
2821                     chattingPartner = p; break;
2822                 }
2823                 if(chattingPartner<0) i = oldi; else {
2824                     started = STARTED_COMMENT;
2825                     parse_pos = 0; parse[0] = NULLCHAR;
2826                     savingComment = 3 + chattingPartner; // counts as TRUE
2827                     suppressKibitz = TRUE;
2828                 }
2829             } // [HGM] chat: end of patch
2830
2831             if (appData.zippyTalk || appData.zippyPlay) {
2832                 /* [DM] Backup address for color zippy lines */
2833                 backup = i;
2834 #if ZIPPY
2835        #ifdef WIN32
2836                if (loggedOn == TRUE)
2837                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2838                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2839        #else
2840                 if (ZippyControl(buf, &i) ||
2841                     ZippyConverse(buf, &i) ||
2842                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2843                       loggedOn = TRUE;
2844                       if (!appData.colorize) continue;
2845                 }
2846        #endif
2847 #endif
2848             } // [DM] 'else { ' deleted
2849                 if (
2850                     /* Regular tells and says */
2851                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2852                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2853                     looking_at(buf, &i, "* says: ") ||
2854                     /* Don't color "message" or "messages" output */
2855                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2856                     looking_at(buf, &i, "*. * at *:*: ") ||
2857                     looking_at(buf, &i, "--* (*:*): ") ||
2858                     /* Message notifications (same color as tells) */
2859                     looking_at(buf, &i, "* has left a message ") ||
2860                     looking_at(buf, &i, "* just sent you a message:\n") ||
2861                     /* Whispers and kibitzes */
2862                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2863                     looking_at(buf, &i, "* kibitzes: ") ||
2864                     /* Channel tells */
2865                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2866
2867                   if (tkind == 1 && strchr(star_match[0], ':')) {
2868                       /* Avoid "tells you:" spoofs in channels */
2869                      tkind = 3;
2870                   }
2871                   if (star_match[0][0] == NULLCHAR ||
2872                       strchr(star_match[0], ' ') ||
2873                       (tkind == 3 && strchr(star_match[1], ' '))) {
2874                     /* Reject bogus matches */
2875                     i = oldi;
2876                   } else {
2877                     if (appData.colorize) {
2878                       if (oldi > next_out) {
2879                         SendToPlayer(&buf[next_out], oldi - next_out);
2880                         next_out = oldi;
2881                       }
2882                       switch (tkind) {
2883                       case 1:
2884                         Colorize(ColorTell, FALSE);
2885                         curColor = ColorTell;
2886                         break;
2887                       case 2:
2888                         Colorize(ColorKibitz, FALSE);
2889                         curColor = ColorKibitz;
2890                         break;
2891                       case 3:
2892                         p = strrchr(star_match[1], '(');
2893                         if (p == NULL) {
2894                           p = star_match[1];
2895                         } else {
2896                           p++;
2897                         }
2898                         if (atoi(p) == 1) {
2899                           Colorize(ColorChannel1, FALSE);
2900                           curColor = ColorChannel1;
2901                         } else {
2902                           Colorize(ColorChannel, FALSE);
2903                           curColor = ColorChannel;
2904                         }
2905                         break;
2906                       case 5:
2907                         curColor = ColorNormal;
2908                         break;
2909                       }
2910                     }
2911                     if (started == STARTED_NONE && appData.autoComment &&
2912                         (gameMode == IcsObserving ||
2913                          gameMode == IcsPlayingWhite ||
2914                          gameMode == IcsPlayingBlack)) {
2915                       parse_pos = i - oldi;
2916                       memcpy(parse, &buf[oldi], parse_pos);
2917                       parse[parse_pos] = NULLCHAR;
2918                       started = STARTED_COMMENT;
2919                       savingComment = TRUE;
2920                     } else {
2921                       started = STARTED_CHATTER;
2922                       savingComment = FALSE;
2923                     }
2924                     loggedOn = TRUE;
2925                     continue;
2926                   }
2927                 }
2928
2929                 if (looking_at(buf, &i, "* s-shouts: ") ||
2930                     looking_at(buf, &i, "* c-shouts: ")) {
2931                     if (appData.colorize) {
2932                         if (oldi > next_out) {
2933                             SendToPlayer(&buf[next_out], oldi - next_out);
2934                             next_out = oldi;
2935                         }
2936                         Colorize(ColorSShout, FALSE);
2937                         curColor = ColorSShout;
2938                     }
2939                     loggedOn = TRUE;
2940                     started = STARTED_CHATTER;
2941                     continue;
2942                 }
2943
2944                 if (looking_at(buf, &i, "--->")) {
2945                     loggedOn = TRUE;
2946                     continue;
2947                 }
2948
2949                 if (looking_at(buf, &i, "* shouts: ") ||
2950                     looking_at(buf, &i, "--> ")) {
2951                     if (appData.colorize) {
2952                         if (oldi > next_out) {
2953                             SendToPlayer(&buf[next_out], oldi - next_out);
2954                             next_out = oldi;
2955                         }
2956                         Colorize(ColorShout, FALSE);
2957                         curColor = ColorShout;
2958                     }
2959                     loggedOn = TRUE;
2960                     started = STARTED_CHATTER;
2961                     continue;
2962                 }
2963
2964                 if (looking_at( buf, &i, "Challenge:")) {
2965                     if (appData.colorize) {
2966                         if (oldi > next_out) {
2967                             SendToPlayer(&buf[next_out], oldi - next_out);
2968                             next_out = oldi;
2969                         }
2970                         Colorize(ColorChallenge, FALSE);
2971                         curColor = ColorChallenge;
2972                     }
2973                     loggedOn = TRUE;
2974                     continue;
2975                 }
2976
2977                 if (looking_at(buf, &i, "* offers you") ||
2978                     looking_at(buf, &i, "* offers to be") ||
2979                     looking_at(buf, &i, "* would like to") ||
2980                     looking_at(buf, &i, "* requests to") ||
2981                     looking_at(buf, &i, "Your opponent offers") ||
2982                     looking_at(buf, &i, "Your opponent requests")) {
2983
2984                     if (appData.colorize) {
2985                         if (oldi > next_out) {
2986                             SendToPlayer(&buf[next_out], oldi - next_out);
2987                             next_out = oldi;
2988                         }
2989                         Colorize(ColorRequest, FALSE);
2990                         curColor = ColorRequest;
2991                     }
2992                     continue;
2993                 }
2994
2995                 if (looking_at(buf, &i, "* (*) seeking")) {
2996                     if (appData.colorize) {
2997                         if (oldi > next_out) {
2998                             SendToPlayer(&buf[next_out], oldi - next_out);
2999                             next_out = oldi;
3000                         }
3001                         Colorize(ColorSeek, FALSE);
3002                         curColor = ColorSeek;
3003                     }
3004                     continue;
3005             }
3006
3007             if (looking_at(buf, &i, "\\   ")) {
3008                 if (prevColor != ColorNormal) {
3009                     if (oldi > next_out) {
3010                         SendToPlayer(&buf[next_out], oldi - next_out);
3011                         next_out = oldi;
3012                     }
3013                     Colorize(prevColor, TRUE);
3014                     curColor = prevColor;
3015                 }
3016                 if (savingComment) {
3017                     parse_pos = i - oldi;
3018                     memcpy(parse, &buf[oldi], parse_pos);
3019                     parse[parse_pos] = NULLCHAR;
3020                     started = STARTED_COMMENT;
3021                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3022                         chattingPartner = savingComment - 3; // kludge to remember the box
3023                 } else {
3024                     started = STARTED_CHATTER;
3025                 }
3026                 continue;
3027             }
3028
3029             if (looking_at(buf, &i, "Black Strength :") ||
3030                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3031                 looking_at(buf, &i, "<10>") ||
3032                 looking_at(buf, &i, "#@#")) {
3033                 /* Wrong board style */
3034                 loggedOn = TRUE;
3035                 SendToICS(ics_prefix);
3036                 SendToICS("set style 12\n");
3037                 SendToICS(ics_prefix);
3038                 SendToICS("refresh\n");
3039                 continue;
3040             }
3041
3042             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3043                 ICSInitScript();
3044                 have_sent_ICS_logon = 1;
3045                 continue;
3046             }
3047
3048             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3049                 (looking_at(buf, &i, "\n<12> ") ||
3050                  looking_at(buf, &i, "<12> "))) {
3051                 loggedOn = TRUE;
3052                 if (oldi > next_out) {
3053                     SendToPlayer(&buf[next_out], oldi - next_out);
3054                 }
3055                 next_out = i;
3056                 started = STARTED_BOARD;
3057                 parse_pos = 0;
3058                 continue;
3059             }
3060
3061             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3062                 looking_at(buf, &i, "<b1> ")) {
3063                 if (oldi > next_out) {
3064                     SendToPlayer(&buf[next_out], oldi - next_out);
3065                 }
3066                 next_out = i;
3067                 started = STARTED_HOLDINGS;
3068                 parse_pos = 0;
3069                 continue;
3070             }
3071
3072             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3073                 loggedOn = TRUE;
3074                 /* Header for a move list -- first line */
3075
3076                 switch (ics_getting_history) {
3077                   case H_FALSE:
3078                     switch (gameMode) {
3079                       case IcsIdle:
3080                       case BeginningOfGame:
3081                         /* User typed "moves" or "oldmoves" while we
3082                            were idle.  Pretend we asked for these
3083                            moves and soak them up so user can step
3084                            through them and/or save them.
3085                            */
3086                         Reset(FALSE, TRUE);
3087                         gameMode = IcsObserving;
3088                         ModeHighlight();
3089                         ics_gamenum = -1;
3090                         ics_getting_history = H_GOT_UNREQ_HEADER;
3091                         break;
3092                       case EditGame: /*?*/
3093                       case EditPosition: /*?*/
3094                         /* Should above feature work in these modes too? */
3095                         /* For now it doesn't */
3096                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3097                         break;
3098                       default:
3099                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3100                         break;
3101                     }
3102                     break;
3103                   case H_REQUESTED:
3104                     /* Is this the right one? */
3105                     if (gameInfo.white && gameInfo.black &&
3106                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3107                         strcmp(gameInfo.black, star_match[2]) == 0) {
3108                         /* All is well */
3109                         ics_getting_history = H_GOT_REQ_HEADER;
3110                     }
3111                     break;
3112                   case H_GOT_REQ_HEADER:
3113                   case H_GOT_UNREQ_HEADER:
3114                   case H_GOT_UNWANTED_HEADER:
3115                   case H_GETTING_MOVES:
3116                     /* Should not happen */
3117                     DisplayError(_("Error gathering move list: two headers"), 0);
3118                     ics_getting_history = H_FALSE;
3119                     break;
3120                 }
3121
3122                 /* Save player ratings into gameInfo if needed */
3123                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3124                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3125                     (gameInfo.whiteRating == -1 ||
3126                      gameInfo.blackRating == -1)) {
3127
3128                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3129                     gameInfo.blackRating = string_to_rating(star_match[3]);
3130                     if (appData.debugMode)
3131                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3132                               gameInfo.whiteRating, gameInfo.blackRating);
3133                 }
3134                 continue;
3135             }
3136
3137             if (looking_at(buf, &i,
3138               "* * match, initial time: * minute*, increment: * second")) {
3139                 /* Header for a move list -- second line */
3140                 /* Initial board will follow if this is a wild game */
3141                 if (gameInfo.event != NULL) free(gameInfo.event);
3142                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3143                 gameInfo.event = StrSave(str);
3144                 /* [HGM] we switched variant. Translate boards if needed. */
3145                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3146                 continue;
3147             }
3148
3149             if (looking_at(buf, &i, "Move  ")) {
3150                 /* Beginning of a move list */
3151                 switch (ics_getting_history) {
3152                   case H_FALSE:
3153                     /* Normally should not happen */
3154                     /* Maybe user hit reset while we were parsing */
3155                     break;
3156                   case H_REQUESTED:
3157                     /* Happens if we are ignoring a move list that is not
3158                      * the one we just requested.  Common if the user
3159                      * tries to observe two games without turning off
3160                      * getMoveList */
3161                     break;
3162                   case H_GETTING_MOVES:
3163                     /* Should not happen */
3164                     DisplayError(_("Error gathering move list: nested"), 0);
3165                     ics_getting_history = H_FALSE;
3166                     break;
3167                   case H_GOT_REQ_HEADER:
3168                     ics_getting_history = H_GETTING_MOVES;
3169                     started = STARTED_MOVES;
3170                     parse_pos = 0;
3171                     if (oldi > next_out) {
3172                         SendToPlayer(&buf[next_out], oldi - next_out);
3173                     }
3174                     break;
3175                   case H_GOT_UNREQ_HEADER:
3176                     ics_getting_history = H_GETTING_MOVES;
3177                     started = STARTED_MOVES_NOHIDE;
3178                     parse_pos = 0;
3179                     break;
3180                   case H_GOT_UNWANTED_HEADER:
3181                     ics_getting_history = H_FALSE;
3182                     break;
3183                 }
3184                 continue;
3185             }
3186
3187             if (looking_at(buf, &i, "% ") ||
3188                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3189                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3190                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3191                     soughtPending = FALSE;
3192                     seekGraphUp = TRUE;
3193                     DrawSeekGraph();
3194                 }
3195                 if(suppressKibitz) next_out = i;
3196                 savingComment = FALSE;
3197                 suppressKibitz = 0;
3198                 switch (started) {
3199                   case STARTED_MOVES:
3200                   case STARTED_MOVES_NOHIDE:
3201                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3202                     parse[parse_pos + i - oldi] = NULLCHAR;
3203                     ParseGameHistory(parse);
3204 #if ZIPPY
3205                     if (appData.zippyPlay && first.initDone) {
3206                         FeedMovesToProgram(&first, forwardMostMove);
3207                         if (gameMode == IcsPlayingWhite) {
3208                             if (WhiteOnMove(forwardMostMove)) {
3209                                 if (first.sendTime) {
3210                                   if (first.useColors) {
3211                                     SendToProgram("black\n", &first);
3212                                   }
3213                                   SendTimeRemaining(&first, TRUE);
3214                                 }
3215                                 if (first.useColors) {
3216                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3217                                 }
3218                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3219                                 first.maybeThinking = TRUE;
3220                             } else {
3221                                 if (first.usePlayother) {
3222                                   if (first.sendTime) {
3223                                     SendTimeRemaining(&first, TRUE);
3224                                   }
3225                                   SendToProgram("playother\n", &first);
3226                                   firstMove = FALSE;
3227                                 } else {
3228                                   firstMove = TRUE;
3229                                 }
3230                             }
3231                         } else if (gameMode == IcsPlayingBlack) {
3232                             if (!WhiteOnMove(forwardMostMove)) {
3233                                 if (first.sendTime) {
3234                                   if (first.useColors) {
3235                                     SendToProgram("white\n", &first);
3236                                   }
3237                                   SendTimeRemaining(&first, FALSE);
3238                                 }
3239                                 if (first.useColors) {
3240                                   SendToProgram("black\n", &first);
3241                                 }
3242                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3243                                 first.maybeThinking = TRUE;
3244                             } else {
3245                                 if (first.usePlayother) {
3246                                   if (first.sendTime) {
3247                                     SendTimeRemaining(&first, FALSE);
3248                                   }
3249                                   SendToProgram("playother\n", &first);
3250                                   firstMove = FALSE;
3251                                 } else {
3252                                   firstMove = TRUE;
3253                                 }
3254                             }
3255                         }
3256                     }
3257 #endif
3258                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3259                         /* Moves came from oldmoves or moves command
3260                            while we weren't doing anything else.
3261                            */
3262                         currentMove = forwardMostMove;
3263                         ClearHighlights();/*!!could figure this out*/
3264                         flipView = appData.flipView;
3265                         DrawPosition(TRUE, boards[currentMove]);
3266                         DisplayBothClocks();
3267                         sprintf(str, "%s vs. %s",
3268                                 gameInfo.white, gameInfo.black);
3269                         DisplayTitle(str);
3270                         gameMode = IcsIdle;
3271                     } else {
3272                         /* Moves were history of an active game */
3273                         if (gameInfo.resultDetails != NULL) {
3274                             free(gameInfo.resultDetails);
3275                             gameInfo.resultDetails = NULL;
3276                         }
3277                     }
3278                     HistorySet(parseList, backwardMostMove,
3279                                forwardMostMove, currentMove-1);
3280                     DisplayMove(currentMove - 1);
3281                     if (started == STARTED_MOVES) next_out = i;
3282                     started = STARTED_NONE;
3283                     ics_getting_history = H_FALSE;
3284                     break;
3285
3286                   case STARTED_OBSERVE:
3287                     started = STARTED_NONE;
3288                     SendToICS(ics_prefix);
3289                     SendToICS("refresh\n");
3290                     break;
3291
3292                   default:
3293                     break;
3294                 }
3295                 if(bookHit) { // [HGM] book: simulate book reply
3296                     static char bookMove[MSG_SIZ]; // a bit generous?
3297
3298                     programStats.nodes = programStats.depth = programStats.time =
3299                     programStats.score = programStats.got_only_move = 0;
3300                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3301
3302                     strcpy(bookMove, "move ");
3303                     strcat(bookMove, bookHit);
3304                     HandleMachineMove(bookMove, &first);
3305                 }
3306                 continue;
3307             }
3308
3309             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3310                  started == STARTED_HOLDINGS ||
3311                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3312                 /* Accumulate characters in move list or board */
3313                 parse[parse_pos++] = buf[i];
3314             }
3315
3316             /* Start of game messages.  Mostly we detect start of game
3317                when the first board image arrives.  On some versions
3318                of the ICS, though, we need to do a "refresh" after starting
3319                to observe in order to get the current board right away. */
3320             if (looking_at(buf, &i, "Adding game * to observation list")) {
3321                 started = STARTED_OBSERVE;
3322                 continue;
3323             }
3324
3325             /* Handle auto-observe */
3326             if (appData.autoObserve &&
3327                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3328                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3329                 char *player;
3330                 /* Choose the player that was highlighted, if any. */
3331                 if (star_match[0][0] == '\033' ||
3332                     star_match[1][0] != '\033') {
3333                     player = star_match[0];
3334                 } else {
3335                     player = star_match[2];
3336                 }
3337                 sprintf(str, "%sobserve %s\n",
3338                         ics_prefix, StripHighlightAndTitle(player));
3339                 SendToICS(str);
3340
3341                 /* Save ratings from notify string */
3342                 strcpy(player1Name, star_match[0]);
3343                 player1Rating = string_to_rating(star_match[1]);
3344                 strcpy(player2Name, star_match[2]);
3345                 player2Rating = string_to_rating(star_match[3]);
3346
3347                 if (appData.debugMode)
3348                   fprintf(debugFP,
3349                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3350                           player1Name, player1Rating,
3351                           player2Name, player2Rating);
3352
3353                 continue;
3354             }
3355
3356             /* Deal with automatic examine mode after a game,
3357                and with IcsObserving -> IcsExamining transition */
3358             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3359                 looking_at(buf, &i, "has made you an examiner of game *")) {
3360
3361                 int gamenum = atoi(star_match[0]);
3362                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3363                     gamenum == ics_gamenum) {
3364                     /* We were already playing or observing this game;
3365                        no need to refetch history */
3366                     gameMode = IcsExamining;
3367                     if (pausing) {
3368                         pauseExamForwardMostMove = forwardMostMove;
3369                     } else if (currentMove < forwardMostMove) {
3370                         ForwardInner(forwardMostMove);
3371                     }
3372                 } else {
3373                     /* I don't think this case really can happen */
3374                     SendToICS(ics_prefix);
3375                     SendToICS("refresh\n");
3376                 }
3377                 continue;
3378             }
3379
3380             /* Error messages */
3381 //          if (ics_user_moved) {
3382             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3383                 if (looking_at(buf, &i, "Illegal move") ||
3384                     looking_at(buf, &i, "Not a legal move") ||
3385                     looking_at(buf, &i, "Your king is in check") ||
3386                     looking_at(buf, &i, "It isn't your turn") ||
3387                     looking_at(buf, &i, "It is not your move")) {
3388                     /* Illegal move */
3389                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3390                         currentMove = --forwardMostMove;
3391                         DisplayMove(currentMove - 1); /* before DMError */
3392                         DrawPosition(FALSE, boards[currentMove]);
3393                         SwitchClocks();
3394                         DisplayBothClocks();
3395                     }
3396                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3397                     ics_user_moved = 0;
3398                     continue;
3399                 }
3400             }
3401
3402             if (looking_at(buf, &i, "still have time") ||
3403                 looking_at(buf, &i, "not out of time") ||
3404                 looking_at(buf, &i, "either player is out of time") ||
3405                 looking_at(buf, &i, "has timeseal; checking")) {
3406                 /* We must have called his flag a little too soon */
3407                 whiteFlag = blackFlag = FALSE;
3408                 continue;
3409             }
3410
3411             if (looking_at(buf, &i, "added * seconds to") ||
3412                 looking_at(buf, &i, "seconds were added to")) {
3413                 /* Update the clocks */
3414                 SendToICS(ics_prefix);
3415                 SendToICS("refresh\n");
3416                 continue;
3417             }
3418
3419             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3420                 ics_clock_paused = TRUE;
3421                 StopClocks();
3422                 continue;
3423             }
3424
3425             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3426                 ics_clock_paused = FALSE;
3427                 StartClocks();
3428                 continue;
3429             }
3430
3431             /* Grab player ratings from the Creating: message.
3432                Note we have to check for the special case when
3433                the ICS inserts things like [white] or [black]. */
3434             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3435                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3436                 /* star_matches:
3437                    0    player 1 name (not necessarily white)
3438                    1    player 1 rating
3439                    2    empty, white, or black (IGNORED)
3440                    3    player 2 name (not necessarily black)
3441                    4    player 2 rating
3442
3443                    The names/ratings are sorted out when the game
3444                    actually starts (below).
3445                 */
3446                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3447                 player1Rating = string_to_rating(star_match[1]);
3448                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3449                 player2Rating = string_to_rating(star_match[4]);
3450
3451                 if (appData.debugMode)
3452                   fprintf(debugFP,
3453                           "Ratings from 'Creating:' %s %d, %s %d\n",
3454                           player1Name, player1Rating,
3455                           player2Name, player2Rating);
3456
3457                 continue;
3458             }
3459
3460             /* Improved generic start/end-of-game messages */
3461             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3462                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3463                 /* If tkind == 0: */
3464                 /* star_match[0] is the game number */
3465                 /*           [1] is the white player's name */
3466                 /*           [2] is the black player's name */
3467                 /* For end-of-game: */
3468                 /*           [3] is the reason for the game end */
3469                 /*           [4] is a PGN end game-token, preceded by " " */
3470                 /* For start-of-game: */
3471                 /*           [3] begins with "Creating" or "Continuing" */
3472                 /*           [4] is " *" or empty (don't care). */
3473                 int gamenum = atoi(star_match[0]);
3474                 char *whitename, *blackname, *why, *endtoken;
3475                 ChessMove endtype = (ChessMove) 0;
3476
3477                 if (tkind == 0) {
3478                   whitename = star_match[1];
3479                   blackname = star_match[2];
3480                   why = star_match[3];
3481                   endtoken = star_match[4];
3482                 } else {
3483                   whitename = star_match[1];
3484                   blackname = star_match[3];
3485                   why = star_match[5];
3486                   endtoken = star_match[6];
3487                 }
3488
3489                 /* Game start messages */
3490                 if (strncmp(why, "Creating ", 9) == 0 ||
3491                     strncmp(why, "Continuing ", 11) == 0) {
3492                     gs_gamenum = gamenum;
3493                     strcpy(gs_kind, strchr(why, ' ') + 1);
3494                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3495 #if ZIPPY
3496                     if (appData.zippyPlay) {
3497                         ZippyGameStart(whitename, blackname);
3498                     }
3499 #endif /*ZIPPY*/
3500                     continue;
3501                 }
3502
3503                 /* Game end messages */
3504                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3505                     ics_gamenum != gamenum) {
3506                     continue;
3507                 }
3508                 while (endtoken[0] == ' ') endtoken++;
3509                 switch (endtoken[0]) {
3510                   case '*':
3511                   default:
3512                     endtype = GameUnfinished;
3513                     break;
3514                   case '0':
3515                     endtype = BlackWins;
3516                     break;
3517                   case '1':
3518                     if (endtoken[1] == '/')
3519                       endtype = GameIsDrawn;
3520                     else
3521                       endtype = WhiteWins;
3522                     break;
3523                 }
3524                 GameEnds(endtype, why, GE_ICS);
3525 #if ZIPPY
3526                 if (appData.zippyPlay && first.initDone) {
3527                     ZippyGameEnd(endtype, why);
3528                     if (first.pr == NULL) {
3529                       /* Start the next process early so that we'll
3530                          be ready for the next challenge */
3531                       StartChessProgram(&first);
3532                     }
3533                     /* Send "new" early, in case this command takes
3534                        a long time to finish, so that we'll be ready
3535                        for the next challenge. */
3536                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3537                     Reset(TRUE, TRUE);
3538                 }
3539 #endif /*ZIPPY*/
3540                 continue;
3541             }
3542
3543             if (looking_at(buf, &i, "Removing game * from observation") ||
3544                 looking_at(buf, &i, "no longer observing game *") ||
3545                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3546                 if (gameMode == IcsObserving &&
3547                     atoi(star_match[0]) == ics_gamenum)
3548                   {
3549                       /* icsEngineAnalyze */
3550                       if (appData.icsEngineAnalyze) {
3551                             ExitAnalyzeMode();
3552                             ModeHighlight();
3553                       }
3554                       StopClocks();
3555                       gameMode = IcsIdle;
3556                       ics_gamenum = -1;
3557                       ics_user_moved = FALSE;
3558                   }
3559                 continue;
3560             }
3561
3562             if (looking_at(buf, &i, "no longer examining game *")) {
3563                 if (gameMode == IcsExamining &&
3564                     atoi(star_match[0]) == ics_gamenum)
3565                   {
3566                       gameMode = IcsIdle;
3567                       ics_gamenum = -1;
3568                       ics_user_moved = FALSE;
3569                   }
3570                 continue;
3571             }
3572
3573             /* Advance leftover_start past any newlines we find,
3574                so only partial lines can get reparsed */
3575             if (looking_at(buf, &i, "\n")) {
3576                 prevColor = curColor;
3577                 if (curColor != ColorNormal) {
3578                     if (oldi > next_out) {
3579                         SendToPlayer(&buf[next_out], oldi - next_out);
3580                         next_out = oldi;
3581                     }
3582                     Colorize(ColorNormal, FALSE);
3583                     curColor = ColorNormal;
3584                 }
3585                 if (started == STARTED_BOARD) {
3586                     started = STARTED_NONE;
3587                     parse[parse_pos] = NULLCHAR;
3588                     ParseBoard12(parse);
3589                     ics_user_moved = 0;
3590
3591                     /* Send premove here */
3592                     if (appData.premove) {
3593                       char str[MSG_SIZ];
3594                       if (currentMove == 0 &&
3595                           gameMode == IcsPlayingWhite &&
3596                           appData.premoveWhite) {
3597                         sprintf(str, "%s\n", appData.premoveWhiteText);
3598                         if (appData.debugMode)
3599                           fprintf(debugFP, "Sending premove:\n");
3600                         SendToICS(str);
3601                       } else if (currentMove == 1 &&
3602                                  gameMode == IcsPlayingBlack &&
3603                                  appData.premoveBlack) {
3604                         sprintf(str, "%s\n", appData.premoveBlackText);
3605                         if (appData.debugMode)
3606                           fprintf(debugFP, "Sending premove:\n");
3607                         SendToICS(str);
3608                       } else if (gotPremove) {
3609                         gotPremove = 0;
3610                         ClearPremoveHighlights();
3611                         if (appData.debugMode)
3612                           fprintf(debugFP, "Sending premove:\n");
3613                           UserMoveEvent(premoveFromX, premoveFromY,
3614                                         premoveToX, premoveToY,
3615                                         premovePromoChar);
3616                       }
3617                     }
3618
3619                     /* Usually suppress following prompt */
3620                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3621                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3622                         if (looking_at(buf, &i, "*% ")) {
3623                             savingComment = FALSE;
3624                             suppressKibitz = 0;
3625                         }
3626                     }
3627                     next_out = i;
3628                 } else if (started == STARTED_HOLDINGS) {
3629                     int gamenum;
3630                     char new_piece[MSG_SIZ];
3631                     started = STARTED_NONE;
3632                     parse[parse_pos] = NULLCHAR;
3633                     if (appData.debugMode)
3634                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3635                                                         parse, currentMove);
3636                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3637                         gamenum == ics_gamenum) {
3638                         if (gameInfo.variant == VariantNormal) {
3639                           /* [HGM] We seem to switch variant during a game!
3640                            * Presumably no holdings were displayed, so we have
3641                            * to move the position two files to the right to
3642                            * create room for them!
3643                            */
3644                           VariantClass newVariant;
3645                           switch(gameInfo.boardWidth) { // base guess on board width
3646                                 case 9:  newVariant = VariantShogi; break;
3647                                 case 10: newVariant = VariantGreat; break;
3648                                 default: newVariant = VariantCrazyhouse; break;
3649                           }
3650                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3651                           /* Get a move list just to see the header, which
3652                              will tell us whether this is really bug or zh */
3653                           if (ics_getting_history == H_FALSE) {
3654                             ics_getting_history = H_REQUESTED;
3655                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3656                             SendToICS(str);
3657                           }
3658                         }
3659                         new_piece[0] = NULLCHAR;
3660                         sscanf(parse, "game %d white [%s black [%s <- %s",
3661                                &gamenum, white_holding, black_holding,
3662                                new_piece);
3663                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3664                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3665                         /* [HGM] copy holdings to board holdings area */
3666                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3667                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3668                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3669 #if ZIPPY
3670                         if (appData.zippyPlay && first.initDone) {
3671                             ZippyHoldings(white_holding, black_holding,
3672                                           new_piece);
3673                         }
3674 #endif /*ZIPPY*/
3675                         if (tinyLayout || smallLayout) {
3676                             char wh[16], bh[16];
3677                             PackHolding(wh, white_holding);
3678                             PackHolding(bh, black_holding);
3679                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3680                                     gameInfo.white, gameInfo.black);
3681                         } else {
3682                             sprintf(str, "%s [%s] vs. %s [%s]",
3683                                     gameInfo.white, white_holding,
3684                                     gameInfo.black, black_holding);
3685                         }
3686
3687                         DrawPosition(FALSE, boards[currentMove]);
3688                         DisplayTitle(str);
3689                     }
3690                     /* Suppress following prompt */
3691                     if (looking_at(buf, &i, "*% ")) {
3692                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3693                         savingComment = FALSE;
3694                         suppressKibitz = 0;
3695                     }
3696                     next_out = i;
3697                 }
3698                 continue;
3699             }
3700
3701             i++;                /* skip unparsed character and loop back */
3702         }
3703         
3704         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3705 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3706 //          SendToPlayer(&buf[next_out], i - next_out);
3707             started != STARTED_HOLDINGS && leftover_start > next_out) {
3708             SendToPlayer(&buf[next_out], leftover_start - next_out);
3709             next_out = i;
3710         }
3711
3712         leftover_len = buf_len - leftover_start;
3713         /* if buffer ends with something we couldn't parse,
3714            reparse it after appending the next read */
3715
3716     } else if (count == 0) {
3717         RemoveInputSource(isr);
3718         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3719     } else {
3720         DisplayFatalError(_("Error reading from ICS"), error, 1);
3721     }
3722 }
3723
3724
3725 /* Board style 12 looks like this:
3726
3727    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3728
3729  * The "<12> " is stripped before it gets to this routine.  The two
3730  * trailing 0's (flip state and clock ticking) are later addition, and
3731  * some chess servers may not have them, or may have only the first.
3732  * Additional trailing fields may be added in the future.
3733  */
3734
3735 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3736
3737 #define RELATION_OBSERVING_PLAYED    0
3738 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3739 #define RELATION_PLAYING_MYMOVE      1
3740 #define RELATION_PLAYING_NOTMYMOVE  -1
3741 #define RELATION_EXAMINING           2
3742 #define RELATION_ISOLATED_BOARD     -3
3743 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3744
3745 void
3746 ParseBoard12(string)
3747      char *string;
3748 {
3749     GameMode newGameMode;
3750     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3751     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3752     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3753     char to_play, board_chars[200];
3754     char move_str[500], str[500], elapsed_time[500];
3755     char black[32], white[32];
3756     Board board;
3757     int prevMove = currentMove;
3758     int ticking = 2;
3759     ChessMove moveType;
3760     int fromX, fromY, toX, toY;
3761     char promoChar;
3762     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3763     char *bookHit = NULL; // [HGM] book
3764     Boolean weird = FALSE, reqFlag = FALSE;
3765
3766     fromX = fromY = toX = toY = -1;
3767
3768     newGame = FALSE;
3769
3770     if (appData.debugMode)
3771       fprintf(debugFP, _("Parsing board: %s\n"), string);
3772
3773     move_str[0] = NULLCHAR;
3774     elapsed_time[0] = NULLCHAR;
3775     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3776         int  i = 0, j;
3777         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3778             if(string[i] == ' ') { ranks++; files = 0; }
3779             else files++;
3780             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3781             i++;
3782         }
3783         for(j = 0; j <i; j++) board_chars[j] = string[j];
3784         board_chars[i] = '\0';
3785         string += i + 1;
3786     }
3787     n = sscanf(string, PATTERN, &to_play, &double_push,
3788                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3789                &gamenum, white, black, &relation, &basetime, &increment,
3790                &white_stren, &black_stren, &white_time, &black_time,
3791                &moveNum, str, elapsed_time, move_str, &ics_flip,
3792                &ticking);
3793
3794     if (n < 21) {
3795         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3796         DisplayError(str, 0);
3797         return;
3798     }
3799
3800     /* Convert the move number to internal form */
3801     moveNum = (moveNum - 1) * 2;
3802     if (to_play == 'B') moveNum++;
3803     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3804       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3805                         0, 1);
3806       return;
3807     }
3808
3809     switch (relation) {
3810       case RELATION_OBSERVING_PLAYED:
3811       case RELATION_OBSERVING_STATIC:
3812         if (gamenum == -1) {
3813             /* Old ICC buglet */
3814             relation = RELATION_OBSERVING_STATIC;
3815         }
3816         newGameMode = IcsObserving;
3817         break;
3818       case RELATION_PLAYING_MYMOVE:
3819       case RELATION_PLAYING_NOTMYMOVE:
3820         newGameMode =
3821           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3822             IcsPlayingWhite : IcsPlayingBlack;
3823         break;
3824       case RELATION_EXAMINING:
3825         newGameMode = IcsExamining;
3826         break;
3827       case RELATION_ISOLATED_BOARD:
3828       default:
3829         /* Just display this board.  If user was doing something else,
3830            we will forget about it until the next board comes. */
3831         newGameMode = IcsIdle;
3832         break;
3833       case RELATION_STARTING_POSITION:
3834         newGameMode = gameMode;
3835         break;
3836     }
3837
3838     /* Modify behavior for initial board display on move listing
3839        of wild games.
3840        */
3841     switch (ics_getting_history) {
3842       case H_FALSE:
3843       case H_REQUESTED:
3844         break;
3845       case H_GOT_REQ_HEADER:
3846       case H_GOT_UNREQ_HEADER:
3847         /* This is the initial position of the current game */
3848         gamenum = ics_gamenum;
3849         moveNum = 0;            /* old ICS bug workaround */
3850         if (to_play == 'B') {
3851           startedFromSetupPosition = TRUE;
3852           blackPlaysFirst = TRUE;
3853           moveNum = 1;
3854           if (forwardMostMove == 0) forwardMostMove = 1;
3855           if (backwardMostMove == 0) backwardMostMove = 1;
3856           if (currentMove == 0) currentMove = 1;
3857         }
3858         newGameMode = gameMode;
3859         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3860         break;
3861       case H_GOT_UNWANTED_HEADER:
3862         /* This is an initial board that we don't want */
3863         return;
3864       case H_GETTING_MOVES:
3865         /* Should not happen */
3866         DisplayError(_("Error gathering move list: extra board"), 0);
3867         ics_getting_history = H_FALSE;
3868         return;
3869     }
3870
3871    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3872                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3873      /* [HGM] We seem to have switched variant unexpectedly
3874       * Try to guess new variant from board size
3875       */
3876           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3877           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3878           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3879           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3880           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3881           if(!weird) newVariant = VariantNormal;
3882           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3883           /* Get a move list just to see the header, which
3884              will tell us whether this is really bug or zh */
3885           if (ics_getting_history == H_FALSE) {
3886             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3887             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3888             SendToICS(str);
3889           }
3890     }
3891     
3892     /* Take action if this is the first board of a new game, or of a
3893        different game than is currently being displayed.  */
3894     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3895         relation == RELATION_ISOLATED_BOARD) {
3896
3897         /* Forget the old game and get the history (if any) of the new one */
3898         if (gameMode != BeginningOfGame) {
3899           Reset(TRUE, TRUE);
3900         }
3901         newGame = TRUE;
3902         if (appData.autoRaiseBoard) BoardToTop();
3903         prevMove = -3;
3904         if (gamenum == -1) {
3905             newGameMode = IcsIdle;
3906         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3907                    appData.getMoveList && !reqFlag) {
3908             /* Need to get game history */
3909             ics_getting_history = H_REQUESTED;
3910             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3911             SendToICS(str);
3912         }
3913
3914         /* Initially flip the board to have black on the bottom if playing
3915            black or if the ICS flip flag is set, but let the user change
3916            it with the Flip View button. */
3917         flipView = appData.autoFlipView ?
3918           (newGameMode == IcsPlayingBlack) || ics_flip :
3919           appData.flipView;
3920
3921         /* Done with values from previous mode; copy in new ones */
3922         gameMode = newGameMode;
3923         ModeHighlight();
3924         ics_gamenum = gamenum;
3925         if (gamenum == gs_gamenum) {
3926             int klen = strlen(gs_kind);
3927             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3928             sprintf(str, "ICS %s", gs_kind);
3929             gameInfo.event = StrSave(str);
3930         } else {
3931             gameInfo.event = StrSave("ICS game");
3932         }
3933         gameInfo.site = StrSave(appData.icsHost);
3934         gameInfo.date = PGNDate();
3935         gameInfo.round = StrSave("-");
3936         gameInfo.white = StrSave(white);
3937         gameInfo.black = StrSave(black);
3938         timeControl = basetime * 60 * 1000;
3939         timeControl_2 = 0;
3940         timeIncrement = increment * 1000;
3941         movesPerSession = 0;
3942         gameInfo.timeControl = TimeControlTagValue();
3943         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3944   if (appData.debugMode) {
3945     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3946     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3947     setbuf(debugFP, NULL);
3948   }
3949
3950         gameInfo.outOfBook = NULL;
3951
3952         /* Do we have the ratings? */
3953         if (strcmp(player1Name, white) == 0 &&
3954             strcmp(player2Name, black) == 0) {
3955             if (appData.debugMode)
3956               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3957                       player1Rating, player2Rating);
3958             gameInfo.whiteRating = player1Rating;
3959             gameInfo.blackRating = player2Rating;
3960         } else if (strcmp(player2Name, white) == 0 &&
3961                    strcmp(player1Name, black) == 0) {
3962             if (appData.debugMode)
3963               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3964                       player2Rating, player1Rating);
3965             gameInfo.whiteRating = player2Rating;
3966             gameInfo.blackRating = player1Rating;
3967         }
3968         player1Name[0] = player2Name[0] = NULLCHAR;
3969
3970         /* Silence shouts if requested */
3971         if (appData.quietPlay &&
3972             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3973             SendToICS(ics_prefix);
3974             SendToICS("set shout 0\n");
3975         }
3976     }
3977
3978     /* Deal with midgame name changes */
3979     if (!newGame) {
3980         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3981             if (gameInfo.white) free(gameInfo.white);
3982             gameInfo.white = StrSave(white);
3983         }
3984         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3985             if (gameInfo.black) free(gameInfo.black);
3986             gameInfo.black = StrSave(black);
3987         }
3988     }
3989
3990     /* Throw away game result if anything actually changes in examine mode */
3991     if (gameMode == IcsExamining && !newGame) {
3992         gameInfo.result = GameUnfinished;
3993         if (gameInfo.resultDetails != NULL) {
3994             free(gameInfo.resultDetails);
3995             gameInfo.resultDetails = NULL;
3996         }
3997     }
3998
3999     /* In pausing && IcsExamining mode, we ignore boards coming
4000        in if they are in a different variation than we are. */
4001     if (pauseExamInvalid) return;
4002     if (pausing && gameMode == IcsExamining) {
4003         if (moveNum <= pauseExamForwardMostMove) {
4004             pauseExamInvalid = TRUE;
4005             forwardMostMove = pauseExamForwardMostMove;
4006             return;
4007         }
4008     }
4009
4010   if (appData.debugMode) {
4011     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4012   }
4013     /* Parse the board */
4014     for (k = 0; k < ranks; k++) {
4015       for (j = 0; j < files; j++)
4016         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4017       if(gameInfo.holdingsWidth > 1) {
4018            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4019            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4020       }
4021     }
4022     CopyBoard(boards[moveNum], board);
4023     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4024     if (moveNum == 0) {
4025         startedFromSetupPosition =
4026           !CompareBoards(board, initialPosition);
4027         if(startedFromSetupPosition)
4028             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4029     }
4030
4031     /* [HGM] Set castling rights. Take the outermost Rooks,
4032        to make it also work for FRC opening positions. Note that board12
4033        is really defective for later FRC positions, as it has no way to
4034        indicate which Rook can castle if they are on the same side of King.
4035        For the initial position we grant rights to the outermost Rooks,
4036        and remember thos rights, and we then copy them on positions
4037        later in an FRC game. This means WB might not recognize castlings with
4038        Rooks that have moved back to their original position as illegal,
4039        but in ICS mode that is not its job anyway.
4040     */
4041     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4042     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4043
4044         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4045             if(board[0][i] == WhiteRook) j = i;
4046         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4047         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4048             if(board[0][i] == WhiteRook) j = i;
4049         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4050         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4051             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4052         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4053         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4054             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4055         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4056
4057         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4058         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4059             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4060         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4061             if(board[BOARD_HEIGHT-1][k] == bKing)
4062                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4063         if(gameInfo.variant == VariantTwoKings) {
4064             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4065             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4066             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4067         }
4068     } else { int r;
4069         r = boards[moveNum][CASTLING][0] = initialRights[0];
4070         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4071         r = boards[moveNum][CASTLING][1] = initialRights[1];
4072         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4073         r = boards[moveNum][CASTLING][3] = initialRights[3];
4074         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4075         r = boards[moveNum][CASTLING][4] = initialRights[4];
4076         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4077         /* wildcastle kludge: always assume King has rights */
4078         r = boards[moveNum][CASTLING][2] = initialRights[2];
4079         r = boards[moveNum][CASTLING][5] = initialRights[5];
4080     }
4081     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4082     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4083
4084
4085     if (ics_getting_history == H_GOT_REQ_HEADER ||
4086         ics_getting_history == H_GOT_UNREQ_HEADER) {
4087         /* This was an initial position from a move list, not
4088            the current position */
4089         return;
4090     }
4091
4092     /* Update currentMove and known move number limits */
4093     newMove = newGame || moveNum > forwardMostMove;
4094
4095     if (newGame) {
4096         forwardMostMove = backwardMostMove = currentMove = moveNum;
4097         if (gameMode == IcsExamining && moveNum == 0) {
4098           /* Workaround for ICS limitation: we are not told the wild
4099              type when starting to examine a game.  But if we ask for
4100              the move list, the move list header will tell us */
4101             ics_getting_history = H_REQUESTED;
4102             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4103             SendToICS(str);
4104         }
4105     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4106                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4107 #if ZIPPY
4108         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4109         /* [HGM] applied this also to an engine that is silently watching        */
4110         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4111             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4112             gameInfo.variant == currentlyInitializedVariant) {
4113           takeback = forwardMostMove - moveNum;
4114           for (i = 0; i < takeback; i++) {
4115             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4116             SendToProgram("undo\n", &first);
4117           }
4118         }
4119 #endif
4120
4121         forwardMostMove = moveNum;
4122         if (!pausing || currentMove > forwardMostMove)
4123           currentMove = forwardMostMove;
4124     } else {
4125         /* New part of history that is not contiguous with old part */
4126         if (pausing && gameMode == IcsExamining) {
4127             pauseExamInvalid = TRUE;
4128             forwardMostMove = pauseExamForwardMostMove;
4129             return;
4130         }
4131         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4132 #if ZIPPY
4133             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4134                 // [HGM] when we will receive the move list we now request, it will be
4135                 // fed to the engine from the first move on. So if the engine is not
4136                 // in the initial position now, bring it there.
4137                 InitChessProgram(&first, 0);
4138             }
4139 #endif
4140             ics_getting_history = H_REQUESTED;
4141             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4142             SendToICS(str);
4143         }
4144         forwardMostMove = backwardMostMove = currentMove = moveNum;
4145     }
4146
4147     /* Update the clocks */
4148     if (strchr(elapsed_time, '.')) {
4149       /* Time is in ms */
4150       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4151       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4152     } else {
4153       /* Time is in seconds */
4154       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4155       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4156     }
4157
4158
4159 #if ZIPPY
4160     if (appData.zippyPlay && newGame &&
4161         gameMode != IcsObserving && gameMode != IcsIdle &&
4162         gameMode != IcsExamining)
4163       ZippyFirstBoard(moveNum, basetime, increment);
4164 #endif
4165
4166     /* Put the move on the move list, first converting
4167        to canonical algebraic form. */
4168     if (moveNum > 0) {
4169   if (appData.debugMode) {
4170     if (appData.debugMode) { int f = forwardMostMove;
4171         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4172                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4173                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4174     }
4175     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4176     fprintf(debugFP, "moveNum = %d\n", moveNum);
4177     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4178     setbuf(debugFP, NULL);
4179   }
4180         if (moveNum <= backwardMostMove) {
4181             /* We don't know what the board looked like before
4182                this move.  Punt. */
4183             strcpy(parseList[moveNum - 1], move_str);
4184             strcat(parseList[moveNum - 1], " ");
4185             strcat(parseList[moveNum - 1], elapsed_time);
4186             moveList[moveNum - 1][0] = NULLCHAR;
4187         } else if (strcmp(move_str, "none") == 0) {
4188             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4189             /* Again, we don't know what the board looked like;
4190                this is really the start of the game. */
4191             parseList[moveNum - 1][0] = NULLCHAR;
4192             moveList[moveNum - 1][0] = NULLCHAR;
4193             backwardMostMove = moveNum;
4194             startedFromSetupPosition = TRUE;
4195             fromX = fromY = toX = toY = -1;
4196         } else {
4197           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4198           //                 So we parse the long-algebraic move string in stead of the SAN move
4199           int valid; char buf[MSG_SIZ], *prom;
4200
4201           // str looks something like "Q/a1-a2"; kill the slash
4202           if(str[1] == '/')
4203                 sprintf(buf, "%c%s", str[0], str+2);
4204           else  strcpy(buf, str); // might be castling
4205           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4206                 strcat(buf, prom); // long move lacks promo specification!
4207           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4208                 if(appData.debugMode)
4209                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4210                 strcpy(move_str, buf);
4211           }
4212           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4213                                 &fromX, &fromY, &toX, &toY, &promoChar)
4214                || ParseOneMove(buf, moveNum - 1, &moveType,
4215                                 &fromX, &fromY, &toX, &toY, &promoChar);
4216           // end of long SAN patch
4217           if (valid) {
4218             (void) CoordsToAlgebraic(boards[moveNum - 1],
4219                                      PosFlags(moveNum - 1),
4220                                      fromY, fromX, toY, toX, promoChar,
4221                                      parseList[moveNum-1]);
4222             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4223               case MT_NONE:
4224               case MT_STALEMATE:
4225               default:
4226                 break;
4227               case MT_CHECK:
4228                 if(gameInfo.variant != VariantShogi)
4229                     strcat(parseList[moveNum - 1], "+");
4230                 break;
4231               case MT_CHECKMATE:
4232               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4233                 strcat(parseList[moveNum - 1], "#");
4234                 break;
4235             }
4236             strcat(parseList[moveNum - 1], " ");
4237             strcat(parseList[moveNum - 1], elapsed_time);
4238             /* currentMoveString is set as a side-effect of ParseOneMove */
4239             strcpy(moveList[moveNum - 1], currentMoveString);
4240             strcat(moveList[moveNum - 1], "\n");
4241           } else {
4242             /* Move from ICS was illegal!?  Punt. */
4243   if (appData.debugMode) {
4244     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4245     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4246   }
4247             strcpy(parseList[moveNum - 1], move_str);
4248             strcat(parseList[moveNum - 1], " ");
4249             strcat(parseList[moveNum - 1], elapsed_time);
4250             moveList[moveNum - 1][0] = NULLCHAR;
4251             fromX = fromY = toX = toY = -1;
4252           }
4253         }
4254   if (appData.debugMode) {
4255     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4256     setbuf(debugFP, NULL);
4257   }
4258
4259 #if ZIPPY
4260         /* Send move to chess program (BEFORE animating it). */
4261         if (appData.zippyPlay && !newGame && newMove &&
4262            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4263
4264             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4265                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4266                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4267                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4268                             move_str);
4269                     DisplayError(str, 0);
4270                 } else {
4271                     if (first.sendTime) {
4272                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4273                     }
4274                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4275                     if (firstMove && !bookHit) {
4276                         firstMove = FALSE;
4277                         if (first.useColors) {
4278                           SendToProgram(gameMode == IcsPlayingWhite ?
4279                                         "white\ngo\n" :
4280                                         "black\ngo\n", &first);
4281                         } else {
4282                           SendToProgram("go\n", &first);
4283                         }
4284                         first.maybeThinking = TRUE;
4285                     }
4286                 }
4287             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4288               if (moveList[moveNum - 1][0] == NULLCHAR) {
4289                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4290                 DisplayError(str, 0);
4291               } else {
4292                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4293                 SendMoveToProgram(moveNum - 1, &first);
4294               }
4295             }
4296         }
4297 #endif
4298     }
4299
4300     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4301         /* If move comes from a remote source, animate it.  If it
4302            isn't remote, it will have already been animated. */
4303         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4304             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4305         }
4306         if (!pausing && appData.highlightLastMove) {
4307             SetHighlights(fromX, fromY, toX, toY);
4308         }
4309     }
4310
4311     /* Start the clocks */
4312     whiteFlag = blackFlag = FALSE;
4313     appData.clockMode = !(basetime == 0 && increment == 0);
4314     if (ticking == 0) {
4315       ics_clock_paused = TRUE;
4316       StopClocks();
4317     } else if (ticking == 1) {
4318       ics_clock_paused = FALSE;
4319     }
4320     if (gameMode == IcsIdle ||
4321         relation == RELATION_OBSERVING_STATIC ||
4322         relation == RELATION_EXAMINING ||
4323         ics_clock_paused)
4324       DisplayBothClocks();
4325     else
4326       StartClocks();
4327
4328     /* Display opponents and material strengths */
4329     if (gameInfo.variant != VariantBughouse &&
4330         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4331         if (tinyLayout || smallLayout) {
4332             if(gameInfo.variant == VariantNormal)
4333                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4334                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4335                     basetime, increment);
4336             else
4337                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4338                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4339                     basetime, increment, (int) gameInfo.variant);
4340         } else {
4341             if(gameInfo.variant == VariantNormal)
4342                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4343                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4344                     basetime, increment);
4345             else
4346                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4347                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4348                     basetime, increment, VariantName(gameInfo.variant));
4349         }
4350         DisplayTitle(str);
4351   if (appData.debugMode) {
4352     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4353   }
4354     }
4355
4356
4357     /* Display the board */
4358     if (!pausing && !appData.noGUI) {
4359       if (appData.premove)
4360           if (!gotPremove ||
4361              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4362              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4363               ClearPremoveHighlights();
4364
4365       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4366       DrawPosition(j, boards[currentMove]);
4367
4368       DisplayMove(moveNum - 1);
4369       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4370             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4371               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4372         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4373       }
4374     }
4375
4376     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4377 #if ZIPPY
4378     if(bookHit) { // [HGM] book: simulate book reply
4379         static char bookMove[MSG_SIZ]; // a bit generous?
4380
4381         programStats.nodes = programStats.depth = programStats.time =
4382         programStats.score = programStats.got_only_move = 0;
4383         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4384
4385         strcpy(bookMove, "move ");
4386         strcat(bookMove, bookHit);
4387         HandleMachineMove(bookMove, &first);
4388     }
4389 #endif
4390 }
4391
4392 void
4393 GetMoveListEvent()
4394 {
4395     char buf[MSG_SIZ];
4396     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4397         ics_getting_history = H_REQUESTED;
4398         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4399         SendToICS(buf);
4400     }
4401 }
4402
4403 void
4404 AnalysisPeriodicEvent(force)
4405      int force;
4406 {
4407     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4408          && !force) || !appData.periodicUpdates)
4409       return;
4410
4411     /* Send . command to Crafty to collect stats */
4412     SendToProgram(".\n", &first);
4413
4414     /* Don't send another until we get a response (this makes
4415        us stop sending to old Crafty's which don't understand
4416        the "." command (sending illegal cmds resets node count & time,
4417        which looks bad)) */
4418     programStats.ok_to_send = 0;
4419 }
4420
4421 void ics_update_width(new_width)
4422         int new_width;
4423 {
4424         ics_printf("set width %d\n", new_width);
4425 }
4426
4427 void
4428 SendMoveToProgram(moveNum, cps)
4429      int moveNum;
4430      ChessProgramState *cps;
4431 {
4432     char buf[MSG_SIZ];
4433
4434     if (cps->useUsermove) {
4435       SendToProgram("usermove ", cps);
4436     }
4437     if (cps->useSAN) {
4438       char *space;
4439       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4440         int len = space - parseList[moveNum];
4441         memcpy(buf, parseList[moveNum], len);
4442         buf[len++] = '\n';
4443         buf[len] = NULLCHAR;
4444       } else {
4445         sprintf(buf, "%s\n", parseList[moveNum]);
4446       }
4447       SendToProgram(buf, cps);
4448     } else {
4449       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4450         AlphaRank(moveList[moveNum], 4);
4451         SendToProgram(moveList[moveNum], cps);
4452         AlphaRank(moveList[moveNum], 4); // and back
4453       } else
4454       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4455        * the engine. It would be nice to have a better way to identify castle
4456        * moves here. */
4457       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4458                                                                          && cps->useOOCastle) {
4459         int fromX = moveList[moveNum][0] - AAA;
4460         int fromY = moveList[moveNum][1] - ONE;
4461         int toX = moveList[moveNum][2] - AAA;
4462         int toY = moveList[moveNum][3] - ONE;
4463         if((boards[moveNum][fromY][fromX] == WhiteKing
4464             && boards[moveNum][toY][toX] == WhiteRook)
4465            || (boards[moveNum][fromY][fromX] == BlackKing
4466                && boards[moveNum][toY][toX] == BlackRook)) {
4467           if(toX > fromX) SendToProgram("O-O\n", cps);
4468           else SendToProgram("O-O-O\n", cps);
4469         }
4470         else SendToProgram(moveList[moveNum], cps);
4471       }
4472       else SendToProgram(moveList[moveNum], cps);
4473       /* End of additions by Tord */
4474     }
4475
4476     /* [HGM] setting up the opening has brought engine in force mode! */
4477     /*       Send 'go' if we are in a mode where machine should play. */
4478     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4479         (gameMode == TwoMachinesPlay   ||
4480 #ifdef ZIPPY
4481          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4482 #endif
4483          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4484         SendToProgram("go\n", cps);
4485   if (appData.debugMode) {
4486     fprintf(debugFP, "(extra)\n");
4487   }
4488     }
4489     setboardSpoiledMachineBlack = 0;
4490 }
4491
4492 void
4493 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4494      ChessMove moveType;
4495      int fromX, fromY, toX, toY;
4496 {
4497     char user_move[MSG_SIZ];
4498
4499     switch (moveType) {
4500       default:
4501         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4502                 (int)moveType, fromX, fromY, toX, toY);
4503         DisplayError(user_move + strlen("say "), 0);
4504         break;
4505       case WhiteKingSideCastle:
4506       case BlackKingSideCastle:
4507       case WhiteQueenSideCastleWild:
4508       case BlackQueenSideCastleWild:
4509       /* PUSH Fabien */
4510       case WhiteHSideCastleFR:
4511       case BlackHSideCastleFR:
4512       /* POP Fabien */
4513         sprintf(user_move, "o-o\n");
4514         break;
4515       case WhiteQueenSideCastle:
4516       case BlackQueenSideCastle:
4517       case WhiteKingSideCastleWild:
4518       case BlackKingSideCastleWild:
4519       /* PUSH Fabien */
4520       case WhiteASideCastleFR:
4521       case BlackASideCastleFR:
4522       /* POP Fabien */
4523         sprintf(user_move, "o-o-o\n");
4524         break;
4525       case WhitePromotionQueen:
4526       case BlackPromotionQueen:
4527       case WhitePromotionRook:
4528       case BlackPromotionRook:
4529       case WhitePromotionBishop:
4530       case BlackPromotionBishop:
4531       case WhitePromotionKnight:
4532       case BlackPromotionKnight:
4533       case WhitePromotionKing:
4534       case BlackPromotionKing:
4535       case WhitePromotionChancellor:
4536       case BlackPromotionChancellor:
4537       case WhitePromotionArchbishop:
4538       case BlackPromotionArchbishop:
4539         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4540             sprintf(user_move, "%c%c%c%c=%c\n",
4541                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4542                 PieceToChar(WhiteFerz));
4543         else if(gameInfo.variant == VariantGreat)
4544             sprintf(user_move, "%c%c%c%c=%c\n",
4545                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4546                 PieceToChar(WhiteMan));
4547         else
4548             sprintf(user_move, "%c%c%c%c=%c\n",
4549                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4550                 PieceToChar(PromoPiece(moveType)));
4551         break;
4552       case WhiteDrop:
4553       case BlackDrop:
4554         sprintf(user_move, "%c@%c%c\n",
4555                 ToUpper(PieceToChar((ChessSquare) fromX)),
4556                 AAA + toX, ONE + toY);
4557         break;
4558       case NormalMove:
4559       case WhiteCapturesEnPassant:
4560       case BlackCapturesEnPassant:
4561       case IllegalMove:  /* could be a variant we don't quite understand */
4562         sprintf(user_move, "%c%c%c%c\n",
4563                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4564         break;
4565     }
4566     SendToICS(user_move);
4567     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4568         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4569 }
4570
4571 void
4572 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4573      int rf, ff, rt, ft;
4574      char promoChar;
4575      char move[7];
4576 {
4577     if (rf == DROP_RANK) {
4578         sprintf(move, "%c@%c%c\n",
4579                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4580     } else {
4581         if (promoChar == 'x' || promoChar == NULLCHAR) {
4582             sprintf(move, "%c%c%c%c\n",
4583                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4584         } else {
4585             sprintf(move, "%c%c%c%c%c\n",
4586                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4587         }
4588     }
4589 }
4590
4591 void
4592 ProcessICSInitScript(f)
4593      FILE *f;
4594 {
4595     char buf[MSG_SIZ];
4596
4597     while (fgets(buf, MSG_SIZ, f)) {
4598         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4599     }
4600
4601     fclose(f);
4602 }
4603
4604
4605 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4606 void
4607 AlphaRank(char *move, int n)
4608 {
4609 //    char *p = move, c; int x, y;
4610
4611     if (appData.debugMode) {
4612         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4613     }
4614
4615     if(move[1]=='*' &&
4616        move[2]>='0' && move[2]<='9' &&
4617        move[3]>='a' && move[3]<='x'    ) {
4618         move[1] = '@';
4619         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4620         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4621     } else
4622     if(move[0]>='0' && move[0]<='9' &&
4623        move[1]>='a' && move[1]<='x' &&
4624        move[2]>='0' && move[2]<='9' &&
4625        move[3]>='a' && move[3]<='x'    ) {
4626         /* input move, Shogi -> normal */
4627         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4628         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4629         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4630         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4631     } else
4632     if(move[1]=='@' &&
4633        move[3]>='0' && move[3]<='9' &&
4634        move[2]>='a' && move[2]<='x'    ) {
4635         move[1] = '*';
4636         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4637         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4638     } else
4639     if(
4640        move[0]>='a' && move[0]<='x' &&
4641        move[3]>='0' && move[3]<='9' &&
4642        move[2]>='a' && move[2]<='x'    ) {
4643          /* output move, normal -> Shogi */
4644         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4645         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4646         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4647         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4648         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4649     }
4650     if (appData.debugMode) {
4651         fprintf(debugFP, "   out = '%s'\n", move);
4652     }
4653 }
4654
4655 /* Parser for moves from gnuchess, ICS, or user typein box */
4656 Boolean
4657 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4658      char *move;
4659      int moveNum;
4660      ChessMove *moveType;
4661      int *fromX, *fromY, *toX, *toY;
4662      char *promoChar;
4663 {
4664     if (appData.debugMode) {
4665         fprintf(debugFP, "move to parse: %s\n", move);
4666     }
4667     *moveType = yylexstr(moveNum, move);
4668
4669     switch (*moveType) {
4670       case WhitePromotionChancellor:
4671       case BlackPromotionChancellor:
4672       case WhitePromotionArchbishop:
4673       case BlackPromotionArchbishop:
4674       case WhitePromotionQueen:
4675       case BlackPromotionQueen:
4676       case WhitePromotionRook:
4677       case BlackPromotionRook:
4678       case WhitePromotionBishop:
4679       case BlackPromotionBishop:
4680       case WhitePromotionKnight:
4681       case BlackPromotionKnight:
4682       case WhitePromotionKing:
4683       case BlackPromotionKing:
4684       case NormalMove:
4685       case WhiteCapturesEnPassant:
4686       case BlackCapturesEnPassant:
4687       case WhiteKingSideCastle:
4688       case WhiteQueenSideCastle:
4689       case BlackKingSideCastle:
4690       case BlackQueenSideCastle:
4691       case WhiteKingSideCastleWild:
4692       case WhiteQueenSideCastleWild:
4693       case BlackKingSideCastleWild:
4694       case BlackQueenSideCastleWild:
4695       /* Code added by Tord: */
4696       case WhiteHSideCastleFR:
4697       case WhiteASideCastleFR:
4698       case BlackHSideCastleFR:
4699       case BlackASideCastleFR:
4700       /* End of code added by Tord */
4701       case IllegalMove:         /* bug or odd chess variant */
4702         *fromX = currentMoveString[0] - AAA;
4703         *fromY = currentMoveString[1] - ONE;
4704         *toX = currentMoveString[2] - AAA;
4705         *toY = currentMoveString[3] - ONE;
4706         *promoChar = currentMoveString[4];
4707         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4708             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4709     if (appData.debugMode) {
4710         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4711     }
4712             *fromX = *fromY = *toX = *toY = 0;
4713             return FALSE;
4714         }
4715         if (appData.testLegality) {
4716           return (*moveType != IllegalMove);
4717         } else {
4718           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4719                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4720         }
4721
4722       case WhiteDrop:
4723       case BlackDrop:
4724         *fromX = *moveType == WhiteDrop ?
4725           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4726           (int) CharToPiece(ToLower(currentMoveString[0]));
4727         *fromY = DROP_RANK;
4728         *toX = currentMoveString[2] - AAA;
4729         *toY = currentMoveString[3] - ONE;
4730         *promoChar = NULLCHAR;
4731         return TRUE;
4732
4733       case AmbiguousMove:
4734       case ImpossibleMove:
4735       case (ChessMove) 0:       /* end of file */
4736       case ElapsedTime:
4737       case Comment:
4738       case PGNTag:
4739       case NAG:
4740       case WhiteWins:
4741       case BlackWins:
4742       case GameIsDrawn:
4743       default:
4744     if (appData.debugMode) {
4745         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4746     }
4747         /* bug? */
4748         *fromX = *fromY = *toX = *toY = 0;
4749         *promoChar = NULLCHAR;
4750         return FALSE;
4751     }
4752 }
4753
4754
4755 void
4756 ParsePV(char *pv)
4757 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4758   int fromX, fromY, toX, toY; char promoChar;
4759   ChessMove moveType;
4760   Boolean valid;
4761   int nr = 0;
4762
4763   endPV = forwardMostMove;
4764   do {
4765     while(*pv == ' ') pv++;
4766     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4767     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4768 if(appData.debugMode){
4769 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4770 }
4771     if(!valid && nr == 0 &&
4772        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4773         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4774     }
4775     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4776     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4777     nr++;
4778     if(endPV+1 > framePtr) break; // no space, truncate
4779     if(!valid) break;
4780     endPV++;
4781     CopyBoard(boards[endPV], boards[endPV-1]);
4782     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4783     moveList[endPV-1][0] = fromX + AAA;
4784     moveList[endPV-1][1] = fromY + ONE;
4785     moveList[endPV-1][2] = toX + AAA;
4786     moveList[endPV-1][3] = toY + ONE;
4787     parseList[endPV-1][0] = NULLCHAR;
4788   } while(valid);
4789   currentMove = endPV;
4790   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4791   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4792                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4793   DrawPosition(TRUE, boards[currentMove]);
4794 }
4795
4796 static int lastX, lastY;
4797
4798 Boolean
4799 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4800 {
4801         int startPV;
4802
4803         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4804         lastX = x; lastY = y;
4805         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4806         startPV = index;
4807       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4808       index = startPV;
4809         while(buf[index] && buf[index] != '\n') index++;
4810         buf[index] = 0;
4811         ParsePV(buf+startPV);
4812         *start = startPV; *end = index-1;
4813         return TRUE;
4814 }
4815
4816 Boolean
4817 LoadPV(int x, int y)
4818 { // called on right mouse click to load PV
4819   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4820   lastX = x; lastY = y;
4821   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4822   return TRUE;
4823 }
4824
4825 void
4826 UnLoadPV()
4827 {
4828   if(endPV < 0) return;
4829   endPV = -1;
4830   currentMove = forwardMostMove;
4831   ClearPremoveHighlights();
4832   DrawPosition(TRUE, boards[currentMove]);
4833 }
4834
4835 void
4836 MovePV(int x, int y, int h)
4837 { // step through PV based on mouse coordinates (called on mouse move)
4838   int margin = h>>3, step = 0;
4839
4840   if(endPV < 0) return;
4841   // we must somehow check if right button is still down (might be released off board!)
4842   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4843   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4844   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4845   if(!step) return;
4846   lastX = x; lastY = y;
4847   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4848   currentMove += step;
4849   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4850   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4851                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4852   DrawPosition(FALSE, boards[currentMove]);
4853 }
4854
4855
4856 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4857 // All positions will have equal probability, but the current method will not provide a unique
4858 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4859 #define DARK 1
4860 #define LITE 2
4861 #define ANY 3
4862
4863 int squaresLeft[4];
4864 int piecesLeft[(int)BlackPawn];
4865 int seed, nrOfShuffles;
4866
4867 void GetPositionNumber()
4868 {       // sets global variable seed
4869         int i;
4870
4871         seed = appData.defaultFrcPosition;
4872         if(seed < 0) { // randomize based on time for negative FRC position numbers
4873                 for(i=0; i<50; i++) seed += random();
4874                 seed = random() ^ random() >> 8 ^ random() << 8;
4875                 if(seed<0) seed = -seed;
4876         }
4877 }
4878
4879 int put(Board board, int pieceType, int rank, int n, int shade)
4880 // put the piece on the (n-1)-th empty squares of the given shade
4881 {
4882         int i;
4883
4884         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4885                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4886                         board[rank][i] = (ChessSquare) pieceType;
4887                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4888                         squaresLeft[ANY]--;
4889                         piecesLeft[pieceType]--;
4890                         return i;
4891                 }
4892         }
4893         return -1;
4894 }
4895
4896
4897 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4898 // calculate where the next piece goes, (any empty square), and put it there
4899 {
4900         int i;
4901
4902         i = seed % squaresLeft[shade];
4903         nrOfShuffles *= squaresLeft[shade];
4904         seed /= squaresLeft[shade];
4905         put(board, pieceType, rank, i, shade);
4906 }
4907
4908 void AddTwoPieces(Board board, int pieceType, int rank)
4909 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4910 {
4911         int i, n=squaresLeft[ANY], j=n-1, k;
4912
4913         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4914         i = seed % k;  // pick one
4915         nrOfShuffles *= k;
4916         seed /= k;
4917         while(i >= j) i -= j--;
4918         j = n - 1 - j; i += j;
4919         put(board, pieceType, rank, j, ANY);
4920         put(board, pieceType, rank, i, ANY);
4921 }
4922
4923 void SetUpShuffle(Board board, int number)
4924 {
4925         int i, p, first=1;
4926
4927         GetPositionNumber(); nrOfShuffles = 1;
4928
4929         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4930         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4931         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4932
4933         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4934
4935         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4936             p = (int) board[0][i];
4937             if(p < (int) BlackPawn) piecesLeft[p] ++;
4938             board[0][i] = EmptySquare;
4939         }
4940
4941         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4942             // shuffles restricted to allow normal castling put KRR first
4943             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4944                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4945             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4946                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4947             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4948                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4949             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4950                 put(board, WhiteRook, 0, 0, ANY);
4951             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4952         }
4953
4954         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4955             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4956             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4957                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4958                 while(piecesLeft[p] >= 2) {
4959                     AddOnePiece(board, p, 0, LITE);
4960                     AddOnePiece(board, p, 0, DARK);
4961                 }
4962                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4963             }
4964
4965         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4966             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4967             // but we leave King and Rooks for last, to possibly obey FRC restriction
4968             if(p == (int)WhiteRook) continue;
4969             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4970             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4971         }
4972
4973         // now everything is placed, except perhaps King (Unicorn) and Rooks
4974
4975         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4976             // Last King gets castling rights
4977             while(piecesLeft[(int)WhiteUnicorn]) {
4978                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4979                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4980             }
4981
4982             while(piecesLeft[(int)WhiteKing]) {
4983                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4984                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4985             }
4986
4987
4988         } else {
4989             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4990             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4991         }
4992
4993         // Only Rooks can be left; simply place them all
4994         while(piecesLeft[(int)WhiteRook]) {
4995                 i = put(board, WhiteRook, 0, 0, ANY);
4996                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4997                         if(first) {
4998                                 first=0;
4999                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5000                         }
5001                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5002                 }
5003         }
5004         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5005             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5006         }
5007
5008         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5009 }
5010
5011 int SetCharTable( char *table, const char * map )
5012 /* [HGM] moved here from winboard.c because of its general usefulness */
5013 /*       Basically a safe strcpy that uses the last character as King */
5014 {
5015     int result = FALSE; int NrPieces;
5016
5017     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5018                     && NrPieces >= 12 && !(NrPieces&1)) {
5019         int i; /* [HGM] Accept even length from 12 to 34 */
5020
5021         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5022         for( i=0; i<NrPieces/2-1; i++ ) {
5023             table[i] = map[i];
5024             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5025         }
5026         table[(int) WhiteKing]  = map[NrPieces/2-1];
5027         table[(int) BlackKing]  = map[NrPieces-1];
5028
5029         result = TRUE;
5030     }
5031
5032     return result;
5033 }
5034
5035 void Prelude(Board board)
5036 {       // [HGM] superchess: random selection of exo-pieces
5037         int i, j, k; ChessSquare p;
5038         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5039
5040         GetPositionNumber(); // use FRC position number
5041
5042         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5043             SetCharTable(pieceToChar, appData.pieceToCharTable);
5044             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5045                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5046         }
5047
5048         j = seed%4;                 seed /= 4;
5049         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5050         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5051         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5052         j = seed%3 + (seed%3 >= j); seed /= 3;
5053         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5054         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5055         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5056         j = seed%3;                 seed /= 3;
5057         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5058         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5059         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5060         j = seed%2 + (seed%2 >= j); seed /= 2;
5061         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5062         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5063         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5064         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5065         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5066         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5067         put(board, exoPieces[0],    0, 0, ANY);
5068         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5069 }
5070
5071 void
5072 InitPosition(redraw)
5073      int redraw;
5074 {
5075     ChessSquare (* pieces)[BOARD_FILES];
5076     int i, j, pawnRow, overrule,
5077     oldx = gameInfo.boardWidth,
5078     oldy = gameInfo.boardHeight,
5079     oldh = gameInfo.holdingsWidth,
5080     oldv = gameInfo.variant;
5081
5082     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5083
5084     /* [AS] Initialize pv info list [HGM] and game status */
5085     {
5086         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5087             pvInfoList[i].depth = 0;
5088             boards[i][EP_STATUS] = EP_NONE;
5089             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5090         }
5091
5092         initialRulePlies = 0; /* 50-move counter start */
5093
5094         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5095         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5096     }
5097
5098
5099     /* [HGM] logic here is completely changed. In stead of full positions */
5100     /* the initialized data only consist of the two backranks. The switch */
5101     /* selects which one we will use, which is than copied to the Board   */
5102     /* initialPosition, which for the rest is initialized by Pawns and    */
5103     /* empty squares. This initial position is then copied to boards[0],  */
5104     /* possibly after shuffling, so that it remains available.            */
5105
5106     gameInfo.holdingsWidth = 0; /* default board sizes */
5107     gameInfo.boardWidth    = 8;
5108     gameInfo.boardHeight   = 8;
5109     gameInfo.holdingsSize  = 0;
5110     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5111     for(i=0; i<BOARD_FILES-2; i++)
5112       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5113     initialPosition[EP_STATUS] = EP_NONE;
5114     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5115
5116     switch (gameInfo.variant) {
5117     case VariantFischeRandom:
5118       shuffleOpenings = TRUE;
5119     default:
5120       pieces = FIDEArray;
5121       break;
5122     case VariantShatranj:
5123       pieces = ShatranjArray;
5124       nrCastlingRights = 0;
5125       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5126       break;
5127     case VariantMakruk:
5128       pieces = makrukArray;
5129       nrCastlingRights = 0;
5130       startedFromSetupPosition = TRUE;
5131       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5132       break;
5133     case VariantTwoKings:
5134       pieces = twoKingsArray;
5135       break;
5136     case VariantCapaRandom:
5137       shuffleOpenings = TRUE;
5138     case VariantCapablanca:
5139       pieces = CapablancaArray;
5140       gameInfo.boardWidth = 10;
5141       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5142       break;
5143     case VariantGothic:
5144       pieces = GothicArray;
5145       gameInfo.boardWidth = 10;
5146       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5147       break;
5148     case VariantJanus:
5149       pieces = JanusArray;
5150       gameInfo.boardWidth = 10;
5151       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5152       nrCastlingRights = 6;
5153         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5154         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5155         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5156         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5157         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5158         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5159       break;
5160     case VariantFalcon:
5161       pieces = FalconArray;
5162       gameInfo.boardWidth = 10;
5163       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5164       break;
5165     case VariantXiangqi:
5166       pieces = XiangqiArray;
5167       gameInfo.boardWidth  = 9;
5168       gameInfo.boardHeight = 10;
5169       nrCastlingRights = 0;
5170       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5171       break;
5172     case VariantShogi:
5173       pieces = ShogiArray;
5174       gameInfo.boardWidth  = 9;
5175       gameInfo.boardHeight = 9;
5176       gameInfo.holdingsSize = 7;
5177       nrCastlingRights = 0;
5178       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5179       break;
5180     case VariantCourier:
5181       pieces = CourierArray;
5182       gameInfo.boardWidth  = 12;
5183       nrCastlingRights = 0;
5184       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5185       break;
5186     case VariantKnightmate:
5187       pieces = KnightmateArray;
5188       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5189       break;
5190     case VariantFairy:
5191       pieces = fairyArray;
5192       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5193       break;
5194     case VariantGreat:
5195       pieces = GreatArray;
5196       gameInfo.boardWidth = 10;
5197       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5198       gameInfo.holdingsSize = 8;
5199       break;
5200     case VariantSuper:
5201       pieces = FIDEArray;
5202       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5203       gameInfo.holdingsSize = 8;
5204       startedFromSetupPosition = TRUE;
5205       break;
5206     case VariantCrazyhouse:
5207     case VariantBughouse:
5208       pieces = FIDEArray;
5209       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5210       gameInfo.holdingsSize = 5;
5211       break;
5212     case VariantWildCastle:
5213       pieces = FIDEArray;
5214       /* !!?shuffle with kings guaranteed to be on d or e file */
5215       shuffleOpenings = 1;
5216       break;
5217     case VariantNoCastle:
5218       pieces = FIDEArray;
5219       nrCastlingRights = 0;
5220       /* !!?unconstrained back-rank shuffle */
5221       shuffleOpenings = 1;
5222       break;
5223     }
5224
5225     overrule = 0;
5226     if(appData.NrFiles >= 0) {
5227         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5228         gameInfo.boardWidth = appData.NrFiles;
5229     }
5230     if(appData.NrRanks >= 0) {
5231         gameInfo.boardHeight = appData.NrRanks;
5232     }
5233     if(appData.holdingsSize >= 0) {
5234         i = appData.holdingsSize;
5235         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5236         gameInfo.holdingsSize = i;
5237     }
5238     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5239     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5240         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5241
5242     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5243     if(pawnRow < 1) pawnRow = 1;
5244     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5245
5246     /* User pieceToChar list overrules defaults */
5247     if(appData.pieceToCharTable != NULL)
5248         SetCharTable(pieceToChar, appData.pieceToCharTable);
5249
5250     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5251
5252         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5253             s = (ChessSquare) 0; /* account holding counts in guard band */
5254         for( i=0; i<BOARD_HEIGHT; i++ )
5255             initialPosition[i][j] = s;
5256
5257         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5258         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5259         initialPosition[pawnRow][j] = WhitePawn;
5260         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5261         if(gameInfo.variant == VariantXiangqi) {
5262             if(j&1) {
5263                 initialPosition[pawnRow][j] =
5264                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5265                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5266                    initialPosition[2][j] = WhiteCannon;
5267                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5268                 }
5269             }
5270         }
5271         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5272     }
5273     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5274
5275             j=BOARD_LEFT+1;
5276             initialPosition[1][j] = WhiteBishop;
5277             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5278             j=BOARD_RGHT-2;
5279             initialPosition[1][j] = WhiteRook;
5280             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5281     }
5282
5283     if( nrCastlingRights == -1) {
5284         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5285         /*       This sets default castling rights from none to normal corners   */
5286         /* Variants with other castling rights must set them themselves above    */
5287         nrCastlingRights = 6;
5288         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5289         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5290         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5291         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5292         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5293         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5294      }
5295
5296      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5297      if(gameInfo.variant == VariantGreat) { // promotion commoners
5298         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5299         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5300         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5301         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5302      }
5303   if (appData.debugMode) {
5304     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5305   }
5306     if(shuffleOpenings) {
5307         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5308         startedFromSetupPosition = TRUE;
5309     }
5310     if(startedFromPositionFile) {
5311       /* [HGM] loadPos: use PositionFile for every new game */
5312       CopyBoard(initialPosition, filePosition);
5313       for(i=0; i<nrCastlingRights; i++)
5314           initialRights[i] = filePosition[CASTLING][i];
5315       startedFromSetupPosition = TRUE;
5316     }
5317
5318     CopyBoard(boards[0], initialPosition);
5319     if(oldx != gameInfo.boardWidth ||
5320        oldy != gameInfo.boardHeight ||
5321        oldh != gameInfo.holdingsWidth
5322 #ifdef GOTHIC
5323        || oldv == VariantGothic ||        // For licensing popups
5324        gameInfo.variant == VariantGothic
5325 #endif
5326 #ifdef FALCON
5327        || oldv == VariantFalcon ||
5328        gameInfo.variant == VariantFalcon
5329 #endif
5330                                          )
5331       {
5332             InitDrawingSizes(-2 ,0);
5333       }
5334
5335     if (redraw)
5336       DrawPosition(TRUE, boards[currentMove]);
5337
5338 }
5339
5340 void
5341 SendBoard(cps, moveNum)
5342      ChessProgramState *cps;
5343      int moveNum;
5344 {
5345     char message[MSG_SIZ];
5346
5347     if (cps->useSetboard) {
5348       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5349       sprintf(message, "setboard %s\n", fen);
5350       SendToProgram(message, cps);
5351       free(fen);
5352
5353     } else {
5354       ChessSquare *bp;
5355       int i, j;
5356       /* Kludge to set black to move, avoiding the troublesome and now
5357        * deprecated "black" command.
5358        */
5359       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5360
5361       SendToProgram("edit\n", cps);
5362       SendToProgram("#\n", cps);
5363       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5364         bp = &boards[moveNum][i][BOARD_LEFT];
5365         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5366           if ((int) *bp < (int) BlackPawn) {
5367             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5368                     AAA + j, ONE + i);
5369             if(message[0] == '+' || message[0] == '~') {
5370                 sprintf(message, "%c%c%c+\n",
5371                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5372                         AAA + j, ONE + i);
5373             }
5374             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5375                 message[1] = BOARD_RGHT   - 1 - j + '1';
5376                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5377             }
5378             SendToProgram(message, cps);
5379           }
5380         }
5381       }
5382
5383       SendToProgram("c\n", cps);
5384       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5385         bp = &boards[moveNum][i][BOARD_LEFT];
5386         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5387           if (((int) *bp != (int) EmptySquare)
5388               && ((int) *bp >= (int) BlackPawn)) {
5389             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5390                     AAA + j, ONE + i);
5391             if(message[0] == '+' || message[0] == '~') {
5392                 sprintf(message, "%c%c%c+\n",
5393                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5394                         AAA + j, ONE + i);
5395             }
5396             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5397                 message[1] = BOARD_RGHT   - 1 - j + '1';
5398                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5399             }
5400             SendToProgram(message, cps);
5401           }
5402         }
5403       }
5404
5405       SendToProgram(".\n", cps);
5406     }
5407     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5408 }
5409
5410 int
5411 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5412 {
5413     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5414     /* [HGM] add Shogi promotions */
5415     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5416     ChessSquare piece;
5417     ChessMove moveType;
5418     Boolean premove;
5419
5420     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5421     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5422
5423     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5424       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5425         return FALSE;
5426
5427     piece = boards[currentMove][fromY][fromX];
5428     if(gameInfo.variant == VariantShogi) {
5429         promotionZoneSize = 3;
5430         highestPromotingPiece = (int)WhiteFerz;
5431     } else if(gameInfo.variant == VariantMakruk) {
5432         promotionZoneSize = 3;
5433     }
5434
5435     // next weed out all moves that do not touch the promotion zone at all
5436     if((int)piece >= BlackPawn) {
5437         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5438              return FALSE;
5439         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5440     } else {
5441         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5442            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5443     }
5444
5445     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5446
5447     // weed out mandatory Shogi promotions
5448     if(gameInfo.variant == VariantShogi) {
5449         if(piece >= BlackPawn) {
5450             if(toY == 0 && piece == BlackPawn ||
5451                toY == 0 && piece == BlackQueen ||
5452                toY <= 1 && piece == BlackKnight) {
5453                 *promoChoice = '+';
5454                 return FALSE;
5455             }
5456         } else {
5457             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5458                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5459                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5460                 *promoChoice = '+';
5461                 return FALSE;
5462             }
5463         }
5464     }
5465
5466     // weed out obviously illegal Pawn moves
5467     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5468         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5469         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5470         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5471         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5472         // note we are not allowed to test for valid (non-)capture, due to premove
5473     }
5474
5475     // we either have a choice what to promote to, or (in Shogi) whether to promote
5476     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5477         *promoChoice = PieceToChar(BlackFerz);  // no choice
5478         return FALSE;
5479     }
5480     if(appData.alwaysPromoteToQueen) { // predetermined
5481         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5482              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5483         else *promoChoice = PieceToChar(BlackQueen);
5484         return FALSE;
5485     }
5486
5487     // suppress promotion popup on illegal moves that are not premoves
5488     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5489               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5490     if(appData.testLegality && !premove) {
5491         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5492                         fromY, fromX, toY, toX, NULLCHAR);
5493         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5494            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5495             return FALSE;
5496     }
5497
5498     return TRUE;
5499 }
5500
5501 int
5502 InPalace(row, column)
5503      int row, column;
5504 {   /* [HGM] for Xiangqi */
5505     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5506          column < (BOARD_WIDTH + 4)/2 &&
5507          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5508     return FALSE;
5509 }
5510
5511 int
5512 PieceForSquare (x, y)
5513      int x;
5514      int y;
5515 {
5516   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5517      return -1;
5518   else
5519      return boards[currentMove][y][x];
5520 }
5521
5522 int
5523 OKToStartUserMove(x, y)
5524      int x, y;
5525 {
5526     ChessSquare from_piece;
5527     int white_piece;
5528
5529     if (matchMode) return FALSE;
5530     if (gameMode == EditPosition) return TRUE;
5531
5532     if (x >= 0 && y >= 0)
5533       from_piece = boards[currentMove][y][x];
5534     else
5535       from_piece = EmptySquare;
5536
5537     if (from_piece == EmptySquare) return FALSE;
5538
5539     white_piece = (int)from_piece >= (int)WhitePawn &&
5540       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5541
5542     switch (gameMode) {
5543       case PlayFromGameFile:
5544       case AnalyzeFile:
5545       case TwoMachinesPlay:
5546       case EndOfGame:
5547         return FALSE;
5548
5549       case IcsObserving:
5550       case IcsIdle:
5551         return FALSE;
5552
5553       case MachinePlaysWhite:
5554       case IcsPlayingBlack:
5555         if (appData.zippyPlay) return FALSE;
5556         if (white_piece) {
5557             DisplayMoveError(_("You are playing Black"));
5558             return FALSE;
5559         }
5560         break;
5561
5562       case MachinePlaysBlack:
5563       case IcsPlayingWhite:
5564         if (appData.zippyPlay) return FALSE;
5565         if (!white_piece) {
5566             DisplayMoveError(_("You are playing White"));
5567             return FALSE;
5568         }
5569         break;
5570
5571       case EditGame:
5572         if (!white_piece && WhiteOnMove(currentMove)) {
5573             DisplayMoveError(_("It is White's turn"));
5574             return FALSE;
5575         }
5576         if (white_piece && !WhiteOnMove(currentMove)) {
5577             DisplayMoveError(_("It is Black's turn"));
5578             return FALSE;
5579         }
5580         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5581             /* Editing correspondence game history */
5582             /* Could disallow this or prompt for confirmation */
5583             cmailOldMove = -1;
5584         }
5585         break;
5586
5587       case BeginningOfGame:
5588         if (appData.icsActive) return FALSE;
5589         if (!appData.noChessProgram) {
5590             if (!white_piece) {
5591                 DisplayMoveError(_("You are playing White"));
5592                 return FALSE;
5593             }
5594         }
5595         break;
5596
5597       case Training:
5598         if (!white_piece && WhiteOnMove(currentMove)) {
5599             DisplayMoveError(_("It is White's turn"));
5600             return FALSE;
5601         }
5602         if (white_piece && !WhiteOnMove(currentMove)) {
5603             DisplayMoveError(_("It is Black's turn"));
5604             return FALSE;
5605         }
5606         break;
5607
5608       default:
5609       case IcsExamining:
5610         break;
5611     }
5612     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5613         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5614         && gameMode != AnalyzeFile && gameMode != Training) {
5615         DisplayMoveError(_("Displayed position is not current"));
5616         return FALSE;
5617     }
5618     return TRUE;
5619 }
5620
5621 Boolean
5622 OnlyMove(int *x, int *y) {
5623     DisambiguateClosure cl;
5624     if (appData.zippyPlay) return FALSE;
5625     switch(gameMode) {
5626       case MachinePlaysBlack:
5627       case IcsPlayingWhite:
5628       case BeginningOfGame:
5629         if(!WhiteOnMove(currentMove)) return FALSE;
5630         break;
5631       case MachinePlaysWhite:
5632       case IcsPlayingBlack:
5633         if(WhiteOnMove(currentMove)) return FALSE;
5634         break;
5635       default:
5636         return FALSE;
5637     }
5638     cl.pieceIn = EmptySquare; 
5639     cl.rfIn = *y;
5640     cl.ffIn = *x;
5641     cl.rtIn = -1;
5642     cl.ftIn = -1;
5643     cl.promoCharIn = NULLCHAR;
5644     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5645     if(cl.kind == NormalMove) {
5646       fromX = cl.ff;
5647       fromY = cl.rf;
5648       *x = cl.ft;
5649       *y = cl.rt;
5650       return TRUE;
5651     }
5652     if(cl.kind != ImpossibleMove) return FALSE;
5653     cl.pieceIn = EmptySquare;
5654     cl.rfIn = -1;
5655     cl.ffIn = -1;
5656     cl.rtIn = *y;
5657     cl.ftIn = *x;
5658     cl.promoCharIn = NULLCHAR;
5659     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5660     if(cl.kind == NormalMove) {
5661       fromX = cl.ff;
5662       fromY = cl.rf;
5663       *x = cl.ft;
5664       *y = cl.rt;
5665       return TRUE;
5666     }
5667     return FALSE;
5668 }
5669
5670 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5671 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5672 int lastLoadGameUseList = FALSE;
5673 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5674 ChessMove lastLoadGameStart = (ChessMove) 0;
5675
5676 ChessMove
5677 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5678      int fromX, fromY, toX, toY;
5679      int promoChar;
5680      Boolean captureOwn;
5681 {
5682     ChessMove moveType;
5683     ChessSquare pdown, pup;
5684
5685     /* Check if the user is playing in turn.  This is complicated because we
5686        let the user "pick up" a piece before it is his turn.  So the piece he
5687        tried to pick up may have been captured by the time he puts it down!
5688        Therefore we use the color the user is supposed to be playing in this
5689        test, not the color of the piece that is currently on the starting
5690        square---except in EditGame mode, where the user is playing both
5691        sides; fortunately there the capture race can't happen.  (It can
5692        now happen in IcsExamining mode, but that's just too bad.  The user
5693        will get a somewhat confusing message in that case.)
5694        */
5695
5696     switch (gameMode) {
5697       case PlayFromGameFile:
5698       case AnalyzeFile:
5699       case TwoMachinesPlay:
5700       case EndOfGame:
5701       case IcsObserving:
5702       case IcsIdle:
5703         /* We switched into a game mode where moves are not accepted,
5704            perhaps while the mouse button was down. */
5705         return ImpossibleMove;
5706
5707       case MachinePlaysWhite:
5708         /* User is moving for Black */
5709         if (WhiteOnMove(currentMove)) {
5710             DisplayMoveError(_("It is White's turn"));
5711             return ImpossibleMove;
5712         }
5713         break;
5714
5715       case MachinePlaysBlack:
5716         /* User is moving for White */
5717         if (!WhiteOnMove(currentMove)) {
5718             DisplayMoveError(_("It is Black's turn"));
5719             return ImpossibleMove;
5720         }
5721         break;
5722
5723       case EditGame:
5724       case IcsExamining:
5725       case BeginningOfGame:
5726       case AnalyzeMode:
5727       case Training:
5728         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5729             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5730             /* User is moving for Black */
5731             if (WhiteOnMove(currentMove)) {
5732                 DisplayMoveError(_("It is White's turn"));
5733                 return ImpossibleMove;
5734             }
5735         } else {
5736             /* User is moving for White */
5737             if (!WhiteOnMove(currentMove)) {
5738                 DisplayMoveError(_("It is Black's turn"));
5739                 return ImpossibleMove;
5740             }
5741         }
5742         break;
5743
5744       case IcsPlayingBlack:
5745         /* User is moving for Black */
5746         if (WhiteOnMove(currentMove)) {
5747             if (!appData.premove) {
5748                 DisplayMoveError(_("It is White's turn"));
5749             } else if (toX >= 0 && toY >= 0) {
5750                 premoveToX = toX;
5751                 premoveToY = toY;
5752                 premoveFromX = fromX;
5753                 premoveFromY = fromY;
5754                 premovePromoChar = promoChar;
5755                 gotPremove = 1;
5756                 if (appData.debugMode)
5757                     fprintf(debugFP, "Got premove: fromX %d,"
5758                             "fromY %d, toX %d, toY %d\n",
5759                             fromX, fromY, toX, toY);
5760             }
5761             return ImpossibleMove;
5762         }
5763         break;
5764
5765       case IcsPlayingWhite:
5766         /* User is moving for White */
5767         if (!WhiteOnMove(currentMove)) {
5768             if (!appData.premove) {
5769                 DisplayMoveError(_("It is Black's turn"));
5770             } else if (toX >= 0 && toY >= 0) {
5771                 premoveToX = toX;
5772                 premoveToY = toY;
5773                 premoveFromX = fromX;
5774                 premoveFromY = fromY;
5775                 premovePromoChar = promoChar;
5776                 gotPremove = 1;
5777                 if (appData.debugMode)
5778                     fprintf(debugFP, "Got premove: fromX %d,"
5779                             "fromY %d, toX %d, toY %d\n",
5780                             fromX, fromY, toX, toY);
5781             }
5782             return ImpossibleMove;
5783         }
5784         break;
5785
5786       default:
5787         break;
5788
5789       case EditPosition:
5790         /* EditPosition, empty square, or different color piece;
5791            click-click move is possible */
5792         if (toX == -2 || toY == -2) {
5793             boards[0][fromY][fromX] = EmptySquare;
5794             return AmbiguousMove;
5795         } else if (toX >= 0 && toY >= 0) {
5796             boards[0][toY][toX] = boards[0][fromY][fromX];
5797             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5798                 if(boards[0][fromY][0] != EmptySquare) {
5799                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5800                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5801                 }
5802             } else
5803             if(fromX == BOARD_RGHT+1) {
5804                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5805                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5806                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5807                 }
5808             } else
5809             boards[0][fromY][fromX] = EmptySquare;
5810             return AmbiguousMove;
5811         }
5812         return ImpossibleMove;
5813     }
5814
5815     if(toX < 0 || toY < 0) return ImpossibleMove;
5816     pdown = boards[currentMove][fromY][fromX];
5817     pup = boards[currentMove][toY][toX];
5818
5819     /* [HGM] If move started in holdings, it means a drop */
5820     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5821          if( pup != EmptySquare ) return ImpossibleMove;
5822          if(appData.testLegality) {
5823              /* it would be more logical if LegalityTest() also figured out
5824               * which drops are legal. For now we forbid pawns on back rank.
5825               * Shogi is on its own here...
5826               */
5827              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5828                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5829                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5830          }
5831          return WhiteDrop; /* Not needed to specify white or black yet */
5832     }
5833
5834
5835     /* [HGM] always test for legality, to get promotion info */
5836     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5837                                          fromY, fromX, toY, toX, promoChar);
5838     /* [HGM] but possibly ignore an IllegalMove result */
5839     if (appData.testLegality) {
5840         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5841             DisplayMoveError(_("Illegal move"));
5842             return ImpossibleMove;
5843         }
5844     }
5845
5846     return moveType;
5847     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5848        function is made into one that returns an OK move type if FinishMove
5849        should be called. This to give the calling driver routine the
5850        opportunity to finish the userMove input with a promotion popup,
5851        without bothering the user with this for invalid or illegal moves */
5852
5853 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5854 }
5855
5856 /* Common tail of UserMoveEvent and DropMenuEvent */
5857 int
5858 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5859      ChessMove moveType;
5860      int fromX, fromY, toX, toY;
5861      /*char*/int promoChar;
5862 {
5863   char *bookHit = 0;
5864
5865   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5866     {
5867       // [HGM] superchess: suppress promotions to non-available piece
5868       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5869       if(WhiteOnMove(currentMove))
5870         {
5871           if(!boards[currentMove][k][BOARD_WIDTH-2])
5872             return 0;
5873         }
5874       else
5875         {
5876           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5877             return 0;
5878         }
5879     }
5880   
5881   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5882      move type in caller when we know the move is a legal promotion */
5883   if(moveType == NormalMove && promoChar)
5884     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5885   
5886   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5887      move type in caller when we know the move is a legal promotion */
5888   if(moveType == NormalMove && promoChar)
5889     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5890   
5891   /* [HGM] convert drag-and-drop piece drops to standard form */
5892   if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5893     {
5894       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5895       if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5896                                     moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5897       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5898       if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5899       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5900       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5901       fromY = DROP_RANK;
5902     }
5903   
5904   /* [HGM] <popupFix> The following if has been moved here from
5905      UserMoveEvent(). Because it seemed to belong here (why not allow
5906      piece drops in training games?), and because it can only be
5907      performed after it is known to what we promote. */
5908   if (gameMode == Training) 
5909     {
5910       /* compare the move played on the board to the next move in the
5911        * game. If they match, display the move and the opponent's response.
5912        * If they don't match, display an error message.
5913        */
5914       int saveAnimate;
5915       Board testBoard;
5916       CopyBoard(testBoard, boards[currentMove]);
5917       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5918
5919       if (CompareBoards(testBoard, boards[currentMove+1]))
5920         {
5921           ForwardInner(currentMove+1);
5922
5923           /* Autoplay the opponent's response.
5924            * if appData.animate was TRUE when Training mode was entered,
5925            * the response will be animated.
5926            */
5927           saveAnimate = appData.animate;
5928           appData.animate = animateTraining;
5929           ForwardInner(currentMove+1);
5930           appData.animate = saveAnimate;
5931
5932           /* check for the end of the game */
5933           if (currentMove >= forwardMostMove)
5934             {
5935               gameMode = PlayFromGameFile;
5936               ModeHighlight();
5937               SetTrainingModeOff();
5938               DisplayInformation(_("End of game"));
5939             }
5940         }
5941       else
5942         {
5943           DisplayError(_("Incorrect move"), 0);
5944         }
5945       return 1;
5946     }
5947
5948   /* Ok, now we know that the move is good, so we can kill
5949      the previous line in Analysis Mode */
5950   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5951                                 && currentMove < forwardMostMove) {
5952     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5953   }
5954
5955   /* If we need the chess program but it's dead, restart it */
5956   ResurrectChessProgram();
5957
5958   /* A user move restarts a paused game*/
5959   if (pausing)
5960     PauseEvent();
5961
5962   thinkOutput[0] = NULLCHAR;
5963
5964   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5965
5966
5967  if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5968
5969 if (gameMode == BeginningOfGame)
5970     {
5971       if (appData.noChessProgram)
5972         {
5973           gameMode = EditGame;
5974           SetGameInfo();
5975         }
5976       else
5977         {
5978           char buf[MSG_SIZ];
5979           gameMode = MachinePlaysBlack;
5980           StartClocks();
5981           SetGameInfo();
5982           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5983           DisplayTitle(buf);
5984           if (first.sendName)
5985             {
5986               sprintf(buf, "name %s\n", gameInfo.white);
5987               SendToProgram(buf, &first);
5988             }
5989           StartClocks();
5990         }
5991       ModeHighlight();
5992
5993     }
5994
5995   /* Relay move to ICS or chess engine */
5996
5997   if (appData.icsActive) {
5998     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5999         gameMode == IcsExamining) {
6000       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6001         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6002         SendToICS("draw ");
6003         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6004       }
6005       // also send plain move, in case ICS does not understand atomic claims
6006       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6007       ics_user_moved = 1;
6008     }
6009   } else {
6010     if (first.sendTime && (gameMode == BeginningOfGame ||
6011                            gameMode == MachinePlaysWhite ||
6012                            gameMode == MachinePlaysBlack)) {
6013       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6014     }
6015     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6016          // [HGM] book: if program might be playing, let it use book
6017         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6018         first.maybeThinking = TRUE;
6019     } else SendMoveToProgram(forwardMostMove-1, &first);
6020     if (currentMove == cmailOldMove + 1) {
6021       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6022     }
6023     }
6024
6025   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6026
6027   switch (gameMode) 
6028     {
6029     case EditGame:
6030       switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) 
6031         {
6032         case MT_NONE:
6033         case MT_CHECK:
6034           break;
6035         case MT_CHECKMATE:
6036         case MT_STAINMATE:
6037           if (WhiteOnMove(currentMove)) {
6038             GameEnds(BlackWins, "Black mates", GE_PLAYER);
6039           } else {
6040             GameEnds(WhiteWins, "White mates", GE_PLAYER);
6041           }
6042           break;
6043         case MT_STALEMATE:
6044           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6045           break;
6046         }
6047       break;
6048       
6049     case MachinePlaysBlack:
6050     case MachinePlaysWhite:
6051       /* disable certain menu options while machine is thinking */
6052       SetMachineThinkingEnables();
6053       break;
6054       
6055     default:
6056       break;
6057     }
6058   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6059         
6060   if(bookHit)
6061     { // [HGM] book: simulate book reply
6062         static char bookMove[MSG_SIZ]; // a bit generous?
6063
6064
6065       programStats.nodes = programStats.depth = programStats.time =
6066         programStats.score = programStats.got_only_move = 0;
6067       sprintf(programStats.movelist, "%s (xbook)", bookHit);
6068
6069       strcpy(bookMove, "move ");
6070       strcat(bookMove, bookHit);
6071       HandleMachineMove(bookMove, &first);
6072     }
6073
6074   return 1;
6075 }
6076
6077 void
6078 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6079      int fromX, fromY, toX, toY;
6080      int promoChar;
6081 {
6082     /* [HGM] This routine was added to allow calling of its two logical
6083        parts from other modules in the old way. Before, UserMoveEvent()
6084        automatically called FinishMove() if the move was OK, and returned
6085        otherwise. I separated the two, in order to make it possible to
6086        slip a promotion popup in between. But that it always needs two
6087        calls, to the first part, (now called UserMoveTest() ), and to
6088        FinishMove if the first part succeeded. Calls that do not need
6089        to do anything in between, can call this routine the old way.
6090     */
6091   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6092   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6093   if(moveType == AmbiguousMove)
6094     DrawPosition(FALSE, boards[currentMove]);
6095   else if(moveType != ImpossibleMove && moveType != Comment)
6096     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6097 }
6098
6099 void
6100 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6101      Board board;
6102      int flags;
6103      ChessMove kind;
6104      int rf, ff, rt, ft;
6105      VOIDSTAR closure;
6106 {
6107     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6108     Markers *m = (Markers *) closure;
6109     if(rf == fromY && ff == fromX)
6110         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6111                          || kind == WhiteCapturesEnPassant
6112                          || kind == BlackCapturesEnPassant);
6113     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6114 }
6115
6116 void
6117 MarkTargetSquares(int clear)
6118 {
6119   int x, y;
6120   if(!appData.markers || !appData.highlightDragging || 
6121      !appData.testLegality || gameMode == EditPosition) return;
6122   if(clear) {
6123     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6124   } else {
6125     int capt = 0;
6126     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6127     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6128       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6129       if(capt)
6130       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6131     }
6132   }
6133   DrawPosition(TRUE, NULL);
6134 }
6135
6136 void LeftClick(ClickType clickType, int xPix, int yPix)
6137 {
6138     int x, y;
6139     Boolean saveAnimate;
6140     static int second = 0, promotionChoice = 0;
6141     char promoChoice = NULLCHAR;
6142
6143     if(appData.seekGraph && appData.icsActive && loggedOn &&
6144         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6145         SeekGraphClick(clickType, xPix, yPix, 0);
6146         return;
6147     }
6148
6149     if (clickType == Press) ErrorPopDown();
6150     MarkTargetSquares(1);
6151
6152     x = EventToSquare(xPix, BOARD_WIDTH);
6153     y = EventToSquare(yPix, BOARD_HEIGHT);
6154     if (!flipView && y >= 0) {
6155         y = BOARD_HEIGHT - 1 - y;
6156     }
6157     if (flipView && x >= 0) {
6158         x = BOARD_WIDTH - 1 - x;
6159     }
6160
6161     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6162         if(clickType == Release) return; // ignore upclick of click-click destination
6163         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6164         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6165         if(gameInfo.holdingsWidth && 
6166                 (WhiteOnMove(currentMove) 
6167                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6168                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6169             // click in right holdings, for determining promotion piece
6170             ChessSquare p = boards[currentMove][y][x];
6171             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6172             if(p != EmptySquare) {
6173                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6174                 fromX = fromY = -1;
6175                 return;
6176             }
6177         }
6178         DrawPosition(FALSE, boards[currentMove]);
6179         return;
6180     }
6181
6182     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6183     if(clickType == Press
6184             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6185               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6186               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6187         return;
6188
6189     if (fromX == -1) {
6190       if(!appData.oneClick || !OnlyMove(&x, &y)) {
6191         if (clickType == Press) {
6192             /* First square */
6193             if (OKToStartUserMove(x, y)) {
6194                 fromX = x;
6195                 fromY = y;
6196                 second = 0;
6197                 MarkTargetSquares(0);
6198                 DragPieceBegin(xPix, yPix);
6199                 if (appData.highlightDragging) {
6200                     SetHighlights(x, y, -1, -1);
6201                 }
6202             }
6203         }
6204         return;
6205       }
6206     }
6207
6208     /* fromX != -1 */
6209     if (clickType == Press && gameMode != EditPosition) {
6210         ChessSquare fromP;
6211         ChessSquare toP;
6212         int frc;
6213
6214         // ignore off-board to clicks
6215         if(y < 0 || x < 0) return;
6216
6217         /* Check if clicking again on the same color piece */
6218         fromP = boards[currentMove][fromY][fromX];
6219         toP = boards[currentMove][y][x];
6220         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6221         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6222              WhitePawn <= toP && toP <= WhiteKing &&
6223              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6224              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6225             (BlackPawn <= fromP && fromP <= BlackKing && 
6226              BlackPawn <= toP && toP <= BlackKing &&
6227              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6228              !(fromP == BlackKing && toP == BlackRook && frc))) {
6229             /* Clicked again on same color piece -- changed his mind */
6230             second = (x == fromX && y == fromY);
6231             if (appData.highlightDragging) {
6232                 SetHighlights(x, y, -1, -1);
6233             } else {
6234                 ClearHighlights();
6235             }
6236             if (OKToStartUserMove(x, y)) {
6237                 fromX = x;
6238                 fromY = y;
6239                 MarkTargetSquares(0);
6240                 DragPieceBegin(xPix, yPix);
6241             }
6242             return;
6243         }
6244         // ignore clicks on holdings
6245         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6246     }
6247
6248     if (clickType == Release && x == fromX && y == fromY) {
6249         DragPieceEnd(xPix, yPix);
6250         if (appData.animateDragging) {
6251             /* Undo animation damage if any */
6252             DrawPosition(FALSE, NULL);
6253         }
6254         if (second) {
6255             /* Second up/down in same square; just abort move */
6256             second = 0;
6257             fromX = fromY = -1;
6258             ClearHighlights();
6259             gotPremove = 0;
6260             ClearPremoveHighlights();
6261         } else {
6262             /* First upclick in same square; start click-click mode */
6263             SetHighlights(x, y, -1, -1);
6264         }
6265         return;
6266     }
6267
6268     /* we now have a different from- and (possibly off-board) to-square */
6269     /* Completed move */
6270     toX = x;
6271     toY = y;
6272     saveAnimate = appData.animate;
6273     if (clickType == Press) {
6274         /* Finish clickclick move */
6275         if (appData.animate || appData.highlightLastMove) {
6276             SetHighlights(fromX, fromY, toX, toY);
6277         } else {
6278             ClearHighlights();
6279         }
6280     } else {
6281         /* Finish drag move */
6282         if (appData.highlightLastMove) {
6283             SetHighlights(fromX, fromY, toX, toY);
6284         } else {
6285             ClearHighlights();
6286         }
6287         DragPieceEnd(xPix, yPix);
6288         /* Don't animate move and drag both */
6289         appData.animate = FALSE;
6290     }
6291
6292     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6293     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6294         ChessSquare piece = boards[currentMove][fromY][fromX];
6295         if(gameMode == EditPosition && piece != EmptySquare &&
6296            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6297             int n;
6298              
6299             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6300                 n = PieceToNumber(piece - (int)BlackPawn);
6301                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6302                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6303                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6304             } else
6305             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6306                 n = PieceToNumber(piece);
6307                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6308                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6309                 boards[currentMove][n][BOARD_WIDTH-2]++;
6310             }
6311             boards[currentMove][fromY][fromX] = EmptySquare;
6312         }
6313         ClearHighlights();
6314         fromX = fromY = -1;
6315         DrawPosition(TRUE, boards[currentMove]);
6316         return;
6317     }
6318
6319     // off-board moves should not be highlighted
6320     if(x < 0 || x < 0) ClearHighlights();
6321
6322     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6323         SetHighlights(fromX, fromY, toX, toY);
6324         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6325             // [HGM] super: promotion to captured piece selected from holdings
6326             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6327             promotionChoice = TRUE;
6328             // kludge follows to temporarily execute move on display, without promoting yet
6329             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6330             boards[currentMove][toY][toX] = p;
6331             DrawPosition(FALSE, boards[currentMove]);
6332             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6333             boards[currentMove][toY][toX] = q;
6334             DisplayMessage("Click in holdings to choose piece", "");
6335             return;
6336         }
6337         PromotionPopUp();
6338     } else {
6339         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6340         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6341         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6342         fromX = fromY = -1;
6343     }
6344     appData.animate = saveAnimate;
6345     if (appData.animate || appData.animateDragging) {
6346         /* Undo animation damage if needed */
6347         DrawPosition(FALSE, NULL);
6348     }
6349 }
6350
6351 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6352 {   // front-end-free part taken out of PieceMenuPopup
6353     int whichMenu; int xSqr, ySqr;
6354
6355     if(seekGraphUp) { // [HGM] seekgraph
6356         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6357         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6358         return -2;
6359     }
6360
6361     xSqr = EventToSquare(x, BOARD_WIDTH);
6362     ySqr = EventToSquare(y, BOARD_HEIGHT);
6363     if (action == Release) UnLoadPV(); // [HGM] pv
6364     if (action != Press) return -2; // return code to be ignored
6365     switch (gameMode) {
6366       case IcsExamining:
6367         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
6368       case EditPosition:
6369         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
6370         if (xSqr < 0 || ySqr < 0) return -1;
6371         whichMenu = 0; // edit-position menu
6372         break;
6373       case IcsObserving:
6374         if(!appData.icsEngineAnalyze) return -1;
6375       case IcsPlayingWhite:
6376       case IcsPlayingBlack:
6377         if(!appData.zippyPlay) goto noZip;
6378       case AnalyzeMode:
6379       case AnalyzeFile:
6380       case MachinePlaysWhite:
6381       case MachinePlaysBlack:
6382       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6383         if (!appData.dropMenu) {
6384           LoadPV(x, y);
6385           return 2; // flag front-end to grab mouse events
6386         }
6387         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6388            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6389       case EditGame:
6390       noZip:
6391         if (xSqr < 0 || ySqr < 0) return -1;
6392         if (!appData.dropMenu || appData.testLegality &&
6393             gameInfo.variant != VariantBughouse &&
6394             gameInfo.variant != VariantCrazyhouse) return -1;
6395         whichMenu = 1; // drop menu
6396         break;
6397       default:
6398         return -1;
6399     }
6400
6401     if (((*fromX = xSqr) < 0) ||
6402         ((*fromY = ySqr) < 0)) {
6403         *fromX = *fromY = -1;
6404         return -1;
6405     }
6406     if (flipView)
6407       *fromX = BOARD_WIDTH - 1 - *fromX;
6408     else
6409       *fromY = BOARD_HEIGHT - 1 - *fromY;
6410
6411     return whichMenu;
6412 }
6413
6414 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6415 {
6416 //    char * hint = lastHint;
6417     FrontEndProgramStats stats;
6418
6419     stats.which = cps == &first ? 0 : 1;
6420     stats.depth = cpstats->depth;
6421     stats.nodes = cpstats->nodes;
6422     stats.score = cpstats->score;
6423     stats.time = cpstats->time;
6424     stats.pv = cpstats->movelist;
6425     stats.hint = lastHint;
6426     stats.an_move_index = 0;
6427     stats.an_move_count = 0;
6428
6429     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6430         stats.hint = cpstats->move_name;
6431         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6432         stats.an_move_count = cpstats->nr_moves;
6433     }
6434
6435     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6436
6437     SetProgramStats( &stats );
6438 }
6439
6440 int
6441 Adjudicate(ChessProgramState *cps)
6442 {       // [HGM] some adjudications useful with buggy engines
6443         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6444         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6445         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6446         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6447         int k, count = 0; static int bare = 1;
6448         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6449         Boolean canAdjudicate = !appData.icsActive;
6450
6451         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6452         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6453             if( appData.testLegality )
6454             {   /* [HGM] Some more adjudications for obstinate engines */
6455                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6456                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6457                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6458                 static int moveCount = 6;
6459                 ChessMove result;
6460                 char *reason = NULL;
6461
6462
6463                 /* Count what is on board. */
6464                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6465                 {   ChessSquare p = boards[forwardMostMove][i][j];
6466                     int m=i;
6467
6468                     switch((int) p)
6469                     {   /* count B,N,R and other of each side */
6470                         case WhiteKing:
6471                         case BlackKing:
6472                              NrK++; break; // [HGM] atomic: count Kings
6473                         case WhiteKnight:
6474                              NrWN++; break;
6475                         case WhiteBishop:
6476                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6477                              bishopsColor |= 1 << ((i^j)&1);
6478                              NrWB++; break;
6479                         case BlackKnight:
6480                              NrBN++; break;
6481                         case BlackBishop:
6482                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6483                              bishopsColor |= 1 << ((i^j)&1);
6484                              NrBB++; break;
6485                         case WhiteRook:
6486                              NrWR++; break;
6487                         case BlackRook:
6488                              NrBR++; break;
6489                         case WhiteQueen:
6490                              NrWQ++; break;
6491                         case BlackQueen:
6492                              NrBQ++; break;
6493                         case EmptySquare: 
6494                              break;
6495                         case BlackPawn:
6496                              m = 7-i;
6497                         case WhitePawn:
6498                              PawnAdvance += m; NrPawns++;
6499                     }
6500                     NrPieces += (p != EmptySquare);
6501                     NrW += ((int)p < (int)BlackPawn);
6502                     if(gameInfo.variant == VariantXiangqi && 
6503                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6504                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6505                         NrW -= ((int)p < (int)BlackPawn);
6506                     }
6507                 }
6508
6509                 /* Some material-based adjudications that have to be made before stalemate test */
6510                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6511                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6512                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6513                      if(canAdjudicate && appData.checkMates) {
6514                          if(engineOpponent)
6515                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6516                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6517                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6518                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6519                          return 1;
6520                      }
6521                 }
6522
6523                 /* Bare King in Shatranj (loses) or Losers (wins) */
6524                 if( NrW == 1 || NrPieces - NrW == 1) {
6525                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6526                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6527                      if(canAdjudicate && appData.checkMates) {
6528                          if(engineOpponent)
6529                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6530                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6531                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6532                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6533                          return 1;
6534                      }
6535                   } else
6536                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6537                   {    /* bare King */
6538                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6539                         if(canAdjudicate && appData.checkMates) {
6540                             /* but only adjudicate if adjudication enabled */
6541                             if(engineOpponent)
6542                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6543                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6544                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6545                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6546                             return 1;
6547                         }
6548                   }
6549                 } else bare = 1;
6550
6551
6552             // don't wait for engine to announce game end if we can judge ourselves
6553             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6554               case MT_CHECK:
6555                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6556                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6557                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6558                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6559                             checkCnt++;
6560                         if(checkCnt >= 2) {
6561                             reason = "Xboard adjudication: 3rd check";
6562                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6563                             break;
6564                         }
6565                     }
6566                 }
6567               case MT_NONE:
6568               default:
6569                 break;
6570               case MT_STALEMATE:
6571               case MT_STAINMATE:
6572                 reason = "Xboard adjudication: Stalemate";
6573                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6574                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6575                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6576                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6577                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6578                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6579                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6580                                                                         EP_CHECKMATE : EP_WINS);
6581                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6582                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6583                 }
6584                 break;
6585               case MT_CHECKMATE:
6586                 reason = "Xboard adjudication: Checkmate";
6587                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6588                 break;
6589             }
6590
6591                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6592                     case EP_STALEMATE:
6593                         result = GameIsDrawn; break;
6594                     case EP_CHECKMATE:
6595                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6596                     case EP_WINS:
6597                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6598                     default:
6599                         result = (ChessMove) 0;
6600                 }
6601                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6602                     if(engineOpponent)
6603                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6604                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6605                     GameEnds( result, reason, GE_XBOARD );
6606                     return 1;
6607                 }
6608
6609                 /* Next absolutely insufficient mating material. */
6610                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6611                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6612                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6613                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6614                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6615
6616                      /* always flag draws, for judging claims */
6617                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6618
6619                      if(canAdjudicate && appData.materialDraws) {
6620                          /* but only adjudicate them if adjudication enabled */
6621                          if(engineOpponent) {
6622                            SendToProgram("force\n", engineOpponent); // suppress reply
6623                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6624                          }
6625                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6626                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6627                          return 1;
6628                      }
6629                 }
6630
6631                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6632                 if(NrPieces == 4 && 
6633                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6634                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6635                    || NrWN==2 || NrBN==2     /* KNNK */
6636                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6637                   ) ) {
6638                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6639                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6640                           if(engineOpponent) {
6641                             SendToProgram("force\n", engineOpponent); // suppress reply
6642                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6643                           }
6644                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6645                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6646                           return 1;
6647                      }
6648                 } else moveCount = 6;
6649             }
6650         }
6651           
6652         if (appData.debugMode) { int i;
6653             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6654                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6655                     appData.drawRepeats);
6656             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6657               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6658             
6659         }
6660
6661         // Repetition draws and 50-move rule can be applied independently of legality testing
6662
6663                 /* Check for rep-draws */
6664                 count = 0;
6665                 for(k = forwardMostMove-2;
6666                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6667                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6668                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6669                     k-=2)
6670                 {   int rights=0;
6671                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6672                         /* compare castling rights */
6673                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6674                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6675                                 rights++; /* King lost rights, while rook still had them */
6676                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6677                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6678                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6679                                    rights++; /* but at least one rook lost them */
6680                         }
6681                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6682                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6683                                 rights++; 
6684                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6685                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6686                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6687                                    rights++;
6688                         }
6689                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6690                             && appData.drawRepeats > 1) {
6691                              /* adjudicate after user-specified nr of repeats */
6692                              if(engineOpponent) {
6693                                SendToProgram("force\n", engineOpponent); // suppress reply
6694                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6695                              }
6696                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6697                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6698                                 // [HGM] xiangqi: check for forbidden perpetuals
6699                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6700                                 for(m=forwardMostMove; m>k; m-=2) {
6701                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6702                                         ourPerpetual = 0; // the current mover did not always check
6703                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6704                                         hisPerpetual = 0; // the opponent did not always check
6705                                 }
6706                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6707                                                                         ourPerpetual, hisPerpetual);
6708                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6709                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6710                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6711                                     return 1;
6712                                 }
6713                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6714                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6715                                 // Now check for perpetual chases
6716                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6717                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6718                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6719                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6720                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6721                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6722                                         return 1;
6723                                     }
6724                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6725                                         break; // Abort repetition-checking loop.
6726                                 }
6727                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6728                              }
6729                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6730                              return 1;
6731                         }
6732                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6733                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6734                     }
6735                 }
6736
6737                 /* Now we test for 50-move draws. Determine ply count */
6738                 count = forwardMostMove;
6739                 /* look for last irreversble move */
6740                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6741                     count--;
6742                 /* if we hit starting position, add initial plies */
6743                 if( count == backwardMostMove )
6744                     count -= initialRulePlies;
6745                 count = forwardMostMove - count; 
6746                 if( count >= 100)
6747                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6748                          /* this is used to judge if draw claims are legal */
6749                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6750                          if(engineOpponent) {
6751                            SendToProgram("force\n", engineOpponent); // suppress reply
6752                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6753                          }
6754                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6755                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6756                          return 1;
6757                 }
6758
6759                 /* if draw offer is pending, treat it as a draw claim
6760                  * when draw condition present, to allow engines a way to
6761                  * claim draws before making their move to avoid a race
6762                  * condition occurring after their move
6763                  */
6764                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6765                          char *p = NULL;
6766                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6767                              p = "Draw claim: 50-move rule";
6768                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6769                              p = "Draw claim: 3-fold repetition";
6770                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6771                              p = "Draw claim: insufficient mating material";
6772                          if( p != NULL && canAdjudicate) {
6773                              if(engineOpponent) {
6774                                SendToProgram("force\n", engineOpponent); // suppress reply
6775                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6776                              }
6777                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6778                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6779                              return 1;
6780                          }
6781                 }
6782
6783                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6784                     if(engineOpponent) {
6785                       SendToProgram("force\n", engineOpponent); // suppress reply
6786                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6787                     }
6788                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6789                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6790                     return 1;
6791                 }
6792         return 0;
6793 }
6794
6795 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6796 {   // [HGM] book: this routine intercepts moves to simulate book replies
6797     char *bookHit = NULL;
6798
6799     //first determine if the incoming move brings opponent into his book
6800     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6801         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6802     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6803     if(bookHit != NULL && !cps->bookSuspend) {
6804         // make sure opponent is not going to reply after receiving move to book position
6805         SendToProgram("force\n", cps);
6806         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6807     }
6808     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6809     // now arrange restart after book miss
6810     if(bookHit) {
6811         // after a book hit we never send 'go', and the code after the call to this routine
6812         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6813         char buf[MSG_SIZ];
6814         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6815         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6816         SendToProgram(buf, cps);
6817         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6818     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6819         SendToProgram("go\n", cps);
6820         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6821     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6822         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6823             SendToProgram("go\n", cps); 
6824         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6825     }
6826     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6827 }
6828
6829 char *savedMessage;
6830 ChessProgramState *savedState;
6831 void DeferredBookMove(void)
6832 {
6833         if(savedState->lastPing != savedState->lastPong)
6834                     ScheduleDelayedEvent(DeferredBookMove, 10);
6835         else
6836         HandleMachineMove(savedMessage, savedState);
6837 }
6838
6839 void
6840 HandleMachineMove(message, cps)
6841      char *message;
6842      ChessProgramState *cps;
6843 {
6844     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6845     char realname[MSG_SIZ];
6846     int fromX, fromY, toX, toY;
6847     ChessMove moveType;
6848     char promoChar;
6849     char *p;
6850     int machineWhite;
6851     char *bookHit;
6852
6853     cps->userError = 0;
6854
6855 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6856     /*
6857      * Kludge to ignore BEL characters
6858      */
6859     while (*message == '\007') message++;
6860
6861     /*
6862      * [HGM] engine debug message: ignore lines starting with '#' character
6863      */
6864     if(cps->debug && *message == '#') return;
6865
6866     /*
6867      * Look for book output
6868      */
6869     if (cps == &first && bookRequested) {
6870         if (message[0] == '\t' || message[0] == ' ') {
6871             /* Part of the book output is here; append it */
6872             strcat(bookOutput, message);
6873             strcat(bookOutput, "  \n");
6874             return;
6875         } else if (bookOutput[0] != NULLCHAR) {
6876             /* All of book output has arrived; display it */
6877             char *p = bookOutput;
6878             while (*p != NULLCHAR) {
6879                 if (*p == '\t') *p = ' ';
6880                 p++;
6881             }
6882             DisplayInformation(bookOutput);
6883             bookRequested = FALSE;
6884             /* Fall through to parse the current output */
6885         }
6886     }
6887
6888     /*
6889      * Look for machine move.
6890      */
6891     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6892         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6893     {
6894         /* This method is only useful on engines that support ping */
6895         if (cps->lastPing != cps->lastPong) {
6896           if (gameMode == BeginningOfGame) {
6897             /* Extra move from before last new; ignore */
6898             if (appData.debugMode) {
6899                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6900             }
6901           } else {
6902             if (appData.debugMode) {
6903                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6904                         cps->which, gameMode);
6905             }
6906
6907             SendToProgram("undo\n", cps);
6908           }
6909           return;
6910         }
6911
6912         switch (gameMode) {
6913           case BeginningOfGame:
6914             /* Extra move from before last reset; ignore */
6915             if (appData.debugMode) {
6916                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6917             }
6918             return;
6919
6920           case EndOfGame:
6921           case IcsIdle:
6922           default:
6923             /* Extra move after we tried to stop.  The mode test is
6924                not a reliable way of detecting this problem, but it's
6925                the best we can do on engines that don't support ping.
6926             */
6927             if (appData.debugMode) {
6928                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6929                         cps->which, gameMode);
6930             }
6931             SendToProgram("undo\n", cps);
6932             return;
6933
6934           case MachinePlaysWhite:
6935           case IcsPlayingWhite:
6936             machineWhite = TRUE;
6937             break;
6938
6939           case MachinePlaysBlack:
6940           case IcsPlayingBlack:
6941             machineWhite = FALSE;
6942             break;
6943
6944           case TwoMachinesPlay:
6945             machineWhite = (cps->twoMachinesColor[0] == 'w');
6946             break;
6947         }
6948         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6949             if (appData.debugMode) {
6950                 fprintf(debugFP,
6951                         "Ignoring move out of turn by %s, gameMode %d"
6952                         ", forwardMost %d\n",
6953                         cps->which, gameMode, forwardMostMove);
6954             }
6955             return;
6956         }
6957
6958     if (appData.debugMode) { int f = forwardMostMove;
6959         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6960                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6961                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6962     }
6963         if(cps->alphaRank) AlphaRank(machineMove, 4);
6964         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6965                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6966             /* Machine move could not be parsed; ignore it. */
6967             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6968                     machineMove, cps->which);
6969             DisplayError(buf1, 0);
6970             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6971                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6972             if (gameMode == TwoMachinesPlay) {
6973               GameEnds(machineWhite ? BlackWins : WhiteWins,
6974                        buf1, GE_XBOARD);
6975 <<<<<<< HEAD
6976             }
6977             return;
6978         }
6979
6980         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6981         /* So we have to redo legality test with true e.p. status here,  */
6982         /* to make sure an illegal e.p. capture does not slip through,   */
6983         /* to cause a forfeit on a justified illegal-move complaint      */
6984         /* of the opponent.                                              */
6985         if( gameMode==TwoMachinesPlay && appData.testLegality
6986             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6987                                                               ) {
6988            ChessMove moveType;
6989            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6990                              fromY, fromX, toY, toX, promoChar);
6991             if (appData.debugMode) {
6992                 int i;
6993                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6994                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6995                 fprintf(debugFP, "castling rights\n");
6996             }
6997             if(moveType == IllegalMove) {
6998                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6999                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7000                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7001                            buf1, GE_XBOARD);
7002                 return;
7003            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7004            /* [HGM] Kludge to handle engines that send FRC-style castling
7005               when they shouldn't (like TSCP-Gothic) */
7006            switch(moveType) {
7007              case WhiteASideCastleFR:
7008              case BlackASideCastleFR:
7009                toX+=2;
7010                currentMoveString[2]++;
7011                break;
7012              case WhiteHSideCastleFR:
7013              case BlackHSideCastleFR:
7014                toX--;
7015                currentMoveString[2]--;
7016                break;
7017              default: ; // nothing to do, but suppresses warning of pedantic compilers
7018            }
7019         }
7020         hintRequested = FALSE;
7021         lastHint[0] = NULLCHAR;
7022         bookRequested = FALSE;
7023         /* Program may be pondering now */
7024         cps->maybeThinking = TRUE;
7025         if (cps->sendTime == 2) cps->sendTime = 1;
7026         if (cps->offeredDraw) cps->offeredDraw--;
7027
7028 #if ZIPPY
7029         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7030             first.initDone) {
7031           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7032           ics_user_moved = 1;
7033           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7034                 char buf[3*MSG_SIZ];
7035
7036                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7037                         programStats.score / 100.,
7038                         programStats.depth,
7039                         programStats.time / 100.,
7040                         (unsigned int)programStats.nodes,
7041                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7042                         programStats.movelist);
7043                 SendToICS(buf);
7044 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7045           }
7046         }
7047 #endif
7048         /* currentMoveString is set as a side-effect of ParseOneMove */
7049         strcpy(machineMove, currentMoveString);
7050         strcat(machineMove, "\n");
7051         strcpy(moveList[forwardMostMove], machineMove);
7052
7053         /* [AS] Save move info and clear stats for next move */
7054         pvInfoList[ forwardMostMove ].score = programStats.score;
7055         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7056         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7057         ClearProgramStats();
7058         thinkOutput[0] = NULLCHAR;
7059         hiddenThinkOutputState = 0;
7060
7061         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7062
7063         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7064         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7065             int count = 0;
7066
7067             while( count < adjudicateLossPlies ) {
7068                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7069
7070                 if( count & 1 ) {
7071                     score = -score; /* Flip score for winning side */
7072                 }
7073
7074                 if( score > adjudicateLossThreshold ) {
7075                     break;
7076                 }
7077
7078                 count++;
7079             }
7080
7081             if( count >= adjudicateLossPlies ) {
7082                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7083
7084                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7085                     "Xboard adjudication",
7086                     GE_XBOARD );
7087
7088                 return;
7089             }
7090         }
7091
7092         if( gameMode == TwoMachinesPlay ) {
7093           // [HGM] some adjudications useful with buggy engines
7094             int k, count = 0; static int bare = 1;
7095           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7096
7097
7098             if( appData.testLegality )
7099             {   /* [HGM] Some more adjudications for obstinate engines */
7100                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
7101                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
7102                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
7103                 static int moveCount = 6;
7104                 ChessMove result;
7105                 char *reason = NULL;
7106
7107                 /* Count what is on board. */
7108                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
7109                 {   ChessSquare p = boards[forwardMostMove][i][j];
7110                     int m=i;
7111
7112                     switch((int) p)
7113                     {   /* count B,N,R and other of each side */
7114                         case WhiteKing:
7115                         case BlackKing:
7116                              NrK++; break; // [HGM] atomic: count Kings
7117                         case WhiteKnight:
7118                              NrWN++; break;
7119                         case WhiteBishop:
7120                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
7121                              bishopsColor |= 1 << ((i^j)&1);
7122                              NrWB++; break;
7123                         case BlackKnight:
7124                              NrBN++; break;
7125                         case BlackBishop:
7126                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
7127                              bishopsColor |= 1 << ((i^j)&1);
7128                              NrBB++; break;
7129                         case WhiteRook:
7130                              NrWR++; break;
7131                         case BlackRook:
7132                              NrBR++; break;
7133                         case WhiteQueen:
7134                              NrWQ++; break;
7135                         case BlackQueen:
7136                              NrBQ++; break;
7137                         case EmptySquare:
7138                              break;
7139                         case BlackPawn:
7140                              m = 7-i;
7141                         case WhitePawn:
7142                              PawnAdvance += m; NrPawns++;
7143                     }
7144                     NrPieces += (p != EmptySquare);
7145                     NrW += ((int)p < (int)BlackPawn);
7146                     if(gameInfo.variant == VariantXiangqi &&
7147                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
7148                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
7149                         NrW -= ((int)p < (int)BlackPawn);
7150                     }
7151                 }
7152
7153                 /* Some material-based adjudications that have to be made before stalemate test */
7154                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
7155                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7156                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7157                      if(appData.checkMates) {
7158                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
7159                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7160                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7161                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7162                          return;
7163                      }
7164                 }
7165
7166                 /* Bare King in Shatranj (loses) or Losers (wins) */
7167                 if( NrW == 1 || NrPieces - NrW == 1) {
7168                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7169                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7170                      if(appData.checkMates) {
7171                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
7172                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7173                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7174                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7175                          return;
7176                      }
7177                   } else
7178                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7179                   {    /* bare King */
7180                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7181                         if(appData.checkMates) {
7182                             /* but only adjudicate if adjudication enabled */
7183                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
7184                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7185                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
7186                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7187                             return;
7188                         }
7189                   }
7190                 } else bare = 1;
7191
7192
7193             // don't wait for engine to announce game end if we can judge ourselves
7194             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7195               case MT_CHECK:
7196                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7197                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7198                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7199                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7200                             checkCnt++;
7201                         if(checkCnt >= 2) {
7202                             reason = "Xboard adjudication: 3rd check";
7203                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7204                             break;
7205                         }
7206                     }
7207                 }
7208               case MT_NONE:
7209               default:
7210                 break;
7211               case MT_STALEMATE:
7212               case MT_STAINMATE:
7213                 reason = "Xboard adjudication: Stalemate";
7214                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7215                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7216                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7217                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7218                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7219                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
7220                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
7221                                                                         EP_CHECKMATE : EP_WINS);
7222                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7223                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7224                 }
7225                 break;
7226               case MT_CHECKMATE:
7227                 reason = "Xboard adjudication: Checkmate";
7228                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7229                 break;
7230 =======
7231 >>>>>>> master
7232             }
7233             return;
7234         }
7235
7236 <<<<<<< HEAD
7237                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7238                     case EP_STALEMATE:
7239                         result = GameIsDrawn; break;
7240                     case EP_CHECKMATE:
7241                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7242                     case EP_WINS:
7243                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7244                     default:
7245                         result = (ChessMove) 0;
7246                 }
7247                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7248                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
7249                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7250                     GameEnds( result, reason, GE_XBOARD );
7251                     return;
7252                 }
7253
7254                 /* Next absolutely insufficient mating material. */
7255                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
7256                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
7257                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
7258                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
7259                 {    /* KBK, KNK, KK of KBKB with like Bishops */
7260 =======
7261         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7262         /* So we have to redo legality test with true e.p. status here,  */
7263         /* to make sure an illegal e.p. capture does not slip through,   */
7264         /* to cause a forfeit on a justified illegal-move complaint      */
7265         /* of the opponent.                                              */
7266         if( gameMode==TwoMachinesPlay && appData.testLegality
7267             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7268                                                               ) {
7269            ChessMove moveType;
7270            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7271                              fromY, fromX, toY, toX, promoChar);
7272             if (appData.debugMode) {
7273                 int i;
7274                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7275                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7276                 fprintf(debugFP, "castling rights\n");
7277             }
7278             if(moveType == IllegalMove) {
7279                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7280                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7281                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7282                            buf1, GE_XBOARD);
7283                 return;
7284            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7285            /* [HGM] Kludge to handle engines that send FRC-style castling
7286               when they shouldn't (like TSCP-Gothic) */
7287            switch(moveType) {
7288              case WhiteASideCastleFR:
7289              case BlackASideCastleFR:
7290                toX+=2;
7291                currentMoveString[2]++;
7292                break;
7293              case WhiteHSideCastleFR:
7294              case BlackHSideCastleFR:
7295                toX--;
7296                currentMoveString[2]--;
7297                break;
7298              default: ; // nothing to do, but suppresses warning of pedantic compilers
7299            }
7300         }
7301         hintRequested = FALSE;
7302         lastHint[0] = NULLCHAR;
7303         bookRequested = FALSE;
7304         /* Program may be pondering now */
7305         cps->maybeThinking = TRUE;
7306         if (cps->sendTime == 2) cps->sendTime = 1;
7307         if (cps->offeredDraw) cps->offeredDraw--;
7308 >>>>>>> master
7309
7310         /* currentMoveString is set as a side-effect of ParseOneMove */
7311         strcpy(machineMove, currentMoveString);
7312         strcat(machineMove, "\n");
7313         strcpy(moveList[forwardMostMove], machineMove);
7314
7315         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7316
7317 <<<<<<< HEAD
7318                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7319                 if(NrPieces == 4 &&
7320                    (   NrWR == 1 && NrBR == 1 /* KRKR */
7321                    || NrWQ==1 && NrBQ==1     /* KQKQ */
7322                    || NrWN==2 || NrBN==2     /* KNNK */
7323                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
7324                   ) ) {
7325                      if(--moveCount < 0 && appData.trivialDraws)
7326                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7327                           SendToProgram("force\n", cps->other); // suppress reply
7328                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
7329                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7330                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7331                           return;
7332                      }
7333                 } else moveCount = 6;
7334             }
7335           }
7336           
7337           if (appData.debugMode) { int i;
7338             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7339                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7340                     appData.drawRepeats);
7341             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7342               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7343             
7344           }
7345
7346                 /* Check for rep-draws */
7347                 count = 0;
7348                 for(k = forwardMostMove-2;
7349                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7350                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7351                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7352                     k-=2)
7353                 {   int rights=0;
7354                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7355                         /* compare castling rights */
7356                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7357                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7358                                 rights++; /* King lost rights, while rook still had them */
7359                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7360                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7361                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7362                                    rights++; /* but at least one rook lost them */
7363                         }
7364                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7365                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7366                                 rights++; 
7367                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7368                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7369                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7370                                    rights++;
7371                         }
7372                         if( rights == 0 && ++count > appData.drawRepeats-2
7373                             && appData.drawRepeats > 1) {
7374                              /* adjudicate after user-specified nr of repeats */
7375                              SendToProgram("force\n", cps->other); // suppress reply
7376                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
7377                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7378                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7379                                 // [HGM] xiangqi: check for forbidden perpetuals
7380                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7381                                 for(m=forwardMostMove; m>k; m-=2) {
7382                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7383                                         ourPerpetual = 0; // the current mover did not always check
7384                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7385                                         hisPerpetual = 0; // the opponent did not always check
7386                                 }
7387                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7388                                                                         ourPerpetual, hisPerpetual);
7389                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7390                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7391                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
7392                                     return;
7393                                 }
7394                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
7395                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7396                                 // Now check for perpetual chases
7397                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7398                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7399                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7400                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7401                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7402                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
7403                                         return;
7404                                     }
7405                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7406                                         break; // Abort repetition-checking loop.
7407                                 }
7408                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7409                              }
7410                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
7411                              return;
7412                         }
7413                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7414                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7415                     }
7416                 }
7417
7418                 /* Now we test for 50-move draws. Determine ply count */
7419                 count = forwardMostMove;
7420                 /* look for last irreversble move */
7421                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7422                     count--;
7423                 /* if we hit starting position, add initial plies */
7424                 if( count == backwardMostMove )
7425                     count -= initialRulePlies;
7426                 count = forwardMostMove - count;
7427                 if( count >= 100)
7428                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7429                          /* this is used to judge if draw claims are legal */
7430                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7431                          SendToProgram("force\n", cps->other); // suppress reply
7432                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
7433                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7434                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7435                          return;
7436 =======
7437         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7438         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7439             int count = 0;
7440
7441             while( count < adjudicateLossPlies ) {
7442                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7443
7444                 if( count & 1 ) {
7445                     score = -score; /* Flip score for winning side */
7446 >>>>>>> master
7447                 }
7448
7449                 if( score > adjudicateLossThreshold ) {
7450                     break;
7451                 }
7452
7453                 count++;
7454             }
7455
7456             if( count >= adjudicateLossPlies ) {
7457                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7458
7459                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7460                     "Xboard adjudication", 
7461                     GE_XBOARD );
7462
7463                 return;
7464             }
7465         }
7466
7467         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7468
7469 #if ZIPPY
7470         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7471             first.initDone) {
7472           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7473                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7474                 SendToICS("draw ");
7475                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7476           }
7477           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7478           ics_user_moved = 1;
7479           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7480                 char buf[3*MSG_SIZ];
7481
7482                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7483                         programStats.score / 100.,
7484                         programStats.depth,
7485                         programStats.time / 100.,
7486                         (unsigned int)programStats.nodes,
7487                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7488                         programStats.movelist);
7489                 SendToICS(buf);
7490 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7491           }
7492         }
7493 #endif
7494
7495         /* [AS] Save move info and clear stats for next move */
7496         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7497         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7498         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7499         ClearProgramStats();
7500         thinkOutput[0] = NULLCHAR;
7501         hiddenThinkOutputState = 0;
7502
7503         bookHit = NULL;
7504         if (gameMode == TwoMachinesPlay) {
7505             /* [HGM] relaying draw offers moved to after reception of move */
7506             /* and interpreting offer as claim if it brings draw condition */
7507             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7508                 SendToProgram("draw\n", cps->other);
7509             }
7510             if (cps->other->sendTime) {
7511                 SendTimeRemaining(cps->other,
7512                                   cps->other->twoMachinesColor[0] == 'w');
7513             }
7514             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7515             if (firstMove && !bookHit) {
7516                 firstMove = FALSE;
7517                 if (cps->other->useColors) {
7518                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7519                 }
7520                 SendToProgram("go\n", cps->other);
7521             }
7522             cps->other->maybeThinking = TRUE;
7523         }
7524
7525         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7526
7527         if (!pausing && appData.ringBellAfterMoves) {
7528             RingBell();
7529         }
7530
7531         /*
7532          * Reenable menu items that were disabled while
7533          * machine was thinking
7534          */
7535         if (gameMode != TwoMachinesPlay)
7536             SetUserThinkingEnables();
7537
7538         // [HGM] book: after book hit opponent has received move and is now in force mode
7539         // force the book reply into it, and then fake that it outputted this move by jumping
7540         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7541         if(bookHit) {
7542                 static char bookMove[MSG_SIZ]; // a bit generous?
7543
7544                 strcpy(bookMove, "move ");
7545                 strcat(bookMove, bookHit);
7546                 message = bookMove;
7547                 cps = cps->other;
7548                 programStats.nodes = programStats.depth = programStats.time =
7549                 programStats.score = programStats.got_only_move = 0;
7550                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7551
7552                 if(cps->lastPing != cps->lastPong) {
7553                     savedMessage = message; // args for deferred call
7554                     savedState = cps;
7555                     ScheduleDelayedEvent(DeferredBookMove, 10);
7556                     return;
7557                 }
7558                 goto FakeBookMove;
7559         }
7560
7561         return;
7562     }
7563
7564     /* Set special modes for chess engines.  Later something general
7565      *  could be added here; for now there is just one kludge feature,
7566      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7567      *  when "xboard" is given as an interactive command.
7568      */
7569     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7570         cps->useSigint = FALSE;
7571         cps->useSigterm = FALSE;
7572     }
7573     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7574       ParseFeatures(message+8, cps);
7575       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7576     }
7577
7578     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7579      * want this, I was asked to put it in, and obliged.
7580      */
7581     if (!strncmp(message, "setboard ", 9)) {
7582         Board initial_position;
7583
7584         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7585
7586         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7587             DisplayError(_("Bad FEN received from engine"), 0);
7588             return ;
7589         } else {
7590            Reset(TRUE, FALSE);
7591            CopyBoard(boards[0], initial_position);
7592            initialRulePlies = FENrulePlies;
7593            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7594            else gameMode = MachinePlaysBlack;
7595            DrawPosition(FALSE, boards[currentMove]);
7596         }
7597         return;
7598     }
7599
7600     /*
7601      * Look for communication commands
7602      */
7603     if (!strncmp(message, "telluser ", 9)) {
7604         DisplayNote(message + 9);
7605         return;
7606     }
7607     if (!strncmp(message, "tellusererror ", 14)) {
7608         cps->userError = 1;
7609         DisplayError(message + 14, 0);
7610         return;
7611     }
7612     if (!strncmp(message, "tellopponent ", 13)) {
7613       if (appData.icsActive) {
7614         if (loggedOn) {
7615           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7616           SendToICS(buf1);
7617         }
7618       } else {
7619         DisplayNote(message + 13);
7620       }
7621       return;
7622     }
7623     if (!strncmp(message, "tellothers ", 11)) {
7624       if (appData.icsActive) {
7625         if (loggedOn) {
7626           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7627           SendToICS(buf1);
7628         }
7629       }
7630       return;
7631     }
7632     if (!strncmp(message, "tellall ", 8)) {
7633       if (appData.icsActive) {
7634         if (loggedOn) {
7635           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7636           SendToICS(buf1);
7637         }
7638       } else {
7639         DisplayNote(message + 8);
7640       }
7641       return;
7642     }
7643     if (strncmp(message, "warning", 7) == 0) {
7644         /* Undocumented feature, use tellusererror in new code */
7645         DisplayError(message, 0);
7646         return;
7647     }
7648     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7649         strcpy(realname, cps->tidy);
7650         strcat(realname, " query");
7651         AskQuestion(realname, buf2, buf1, cps->pr);
7652         return;
7653     }
7654     /* Commands from the engine directly to ICS.  We don't allow these to be
7655      *  sent until we are logged on. Crafty kibitzes have been known to
7656      *  interfere with the login process.
7657      */
7658     if (loggedOn) {
7659         if (!strncmp(message, "tellics ", 8)) {
7660             SendToICS(message + 8);
7661             SendToICS("\n");
7662             return;
7663         }
7664         if (!strncmp(message, "tellicsnoalias ", 15)) {
7665             SendToICS(ics_prefix);
7666             SendToICS(message + 15);
7667             SendToICS("\n");
7668             return;
7669         }
7670         /* The following are for backward compatibility only */
7671         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7672             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7673             SendToICS(ics_prefix);
7674             SendToICS(message);
7675             SendToICS("\n");
7676             return;
7677         }
7678     }
7679     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7680         return;
7681     }
7682     /*
7683      * If the move is illegal, cancel it and redraw the board.
7684      * Also deal with other error cases.  Matching is rather loose
7685      * here to accommodate engines written before the spec.
7686      */
7687     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7688         strncmp(message, "Error", 5) == 0) {
7689         if (StrStr(message, "name") ||
7690             StrStr(message, "rating") || StrStr(message, "?") ||
7691             StrStr(message, "result") || StrStr(message, "board") ||
7692             StrStr(message, "bk") || StrStr(message, "computer") ||
7693             StrStr(message, "variant") || StrStr(message, "hint") ||
7694             StrStr(message, "random") || StrStr(message, "depth") ||
7695             StrStr(message, "accepted")) {
7696             return;
7697         }
7698         if (StrStr(message, "protover")) {
7699           /* Program is responding to input, so it's apparently done
7700              initializing, and this error message indicates it is
7701              protocol version 1.  So we don't need to wait any longer
7702              for it to initialize and send feature commands. */
7703           FeatureDone(cps, 1);
7704           cps->protocolVersion = 1;
7705           return;
7706         }
7707         cps->maybeThinking = FALSE;
7708
7709         if (StrStr(message, "draw")) {
7710             /* Program doesn't have "draw" command */
7711             cps->sendDrawOffers = 0;
7712             return;
7713         }
7714         if (cps->sendTime != 1 &&
7715             (StrStr(message, "time") || StrStr(message, "otim"))) {
7716           /* Program apparently doesn't have "time" or "otim" command */
7717           cps->sendTime = 0;
7718           return;
7719         }
7720         if (StrStr(message, "analyze")) {
7721             cps->analysisSupport = FALSE;
7722             cps->analyzing = FALSE;
7723             Reset(FALSE, TRUE);
7724             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7725             DisplayError(buf2, 0);
7726             return;
7727         }
7728         if (StrStr(message, "(no matching move)st")) {
7729           /* Special kludge for GNU Chess 4 only */
7730           cps->stKludge = TRUE;
7731           SendTimeControl(cps, movesPerSession, timeControl,
7732                           timeIncrement, appData.searchDepth,
7733                           searchTime);
7734           return;
7735         }
7736         if (StrStr(message, "(no matching move)sd")) {
7737           /* Special kludge for GNU Chess 4 only */
7738           cps->sdKludge = TRUE;
7739           SendTimeControl(cps, movesPerSession, timeControl,
7740                           timeIncrement, appData.searchDepth,
7741                           searchTime);
7742           return;
7743         }
7744         if (!StrStr(message, "llegal")) {
7745             return;
7746         }
7747         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7748             gameMode == IcsIdle) return;
7749         if (forwardMostMove <= backwardMostMove) return;
7750         if (pausing) PauseEvent();
7751       if(appData.forceIllegal) {
7752             // [HGM] illegal: machine refused move; force position after move into it
7753           SendToProgram("force\n", cps);
7754           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7755                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7756                 // when black is to move, while there might be nothing on a2 or black
7757                 // might already have the move. So send the board as if white has the move.
7758                 // But first we must change the stm of the engine, as it refused the last move
7759                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7760                 if(WhiteOnMove(forwardMostMove)) {
7761                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7762                     SendBoard(cps, forwardMostMove); // kludgeless board
7763                 } else {
7764                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7765                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7766                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7767                 }
7768           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7769             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7770                  gameMode == TwoMachinesPlay)
7771               SendToProgram("go\n", cps);
7772             return;
7773       } else
7774         if (gameMode == PlayFromGameFile) {
7775             /* Stop reading this game file */
7776             gameMode = EditGame;
7777             ModeHighlight();
7778         }
7779         currentMove = --forwardMostMove;
7780         DisplayMove(currentMove-1); /* before DisplayMoveError */
7781         SwitchClocks();
7782         DisplayBothClocks();
7783         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7784                 parseList[currentMove], cps->which);
7785         DisplayMoveError(buf1);
7786         DrawPosition(FALSE, boards[currentMove]);
7787
7788         /* [HGM] illegal-move claim should forfeit game when Xboard */
7789         /* only passes fully legal moves                            */
7790         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7791             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7792                                 "False illegal-move claim", GE_XBOARD );
7793         }
7794         return;
7795     }
7796     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7797         /* Program has a broken "time" command that
7798            outputs a string not ending in newline.
7799            Don't use it. */
7800         cps->sendTime = 0;
7801     }
7802
7803     /*
7804      * If chess program startup fails, exit with an error message.
7805      * Attempts to recover here are futile.
7806      */
7807     if ((StrStr(message, "unknown host") != NULL)
7808         || (StrStr(message, "No remote directory") != NULL)
7809         || (StrStr(message, "not found") != NULL)
7810         || (StrStr(message, "No such file") != NULL)
7811         || (StrStr(message, "can't alloc") != NULL)
7812         || (StrStr(message, "Permission denied") != NULL)) {
7813
7814         cps->maybeThinking = FALSE;
7815         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7816                 cps->which, cps->program, cps->host, message);
7817         RemoveInputSource(cps->isr);
7818         DisplayFatalError(buf1, 0, 1);
7819         return;
7820     }
7821
7822     /*
7823      * Look for hint output
7824      */
7825     if (sscanf(message, "Hint: %s", buf1) == 1) {
7826         if (cps == &first && hintRequested) {
7827             hintRequested = FALSE;
7828             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7829                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7830                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7831                                     PosFlags(forwardMostMove),
7832                                     fromY, fromX, toY, toX, promoChar, buf1);
7833                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7834                 DisplayInformation(buf2);
7835             } else {
7836                 /* Hint move could not be parsed!? */
7837               snprintf(buf2, sizeof(buf2),
7838                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7839                         buf1, cps->which);
7840                 DisplayError(buf2, 0);
7841             }
7842         } else {
7843             strcpy(lastHint, buf1);
7844         }
7845         return;
7846     }
7847
7848     /*
7849      * Ignore other messages if game is not in progress
7850      */
7851     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7852         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7853
7854     /*
7855      * look for win, lose, draw, or draw offer
7856      */
7857     if (strncmp(message, "1-0", 3) == 0) {
7858         char *p, *q, *r = "";
7859         p = strchr(message, '{');
7860         if (p) {
7861             q = strchr(p, '}');
7862             if (q) {
7863                 *q = NULLCHAR;
7864                 r = p + 1;
7865             }
7866         }
7867         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7868         return;
7869     } else if (strncmp(message, "0-1", 3) == 0) {
7870         char *p, *q, *r = "";
7871         p = strchr(message, '{');
7872         if (p) {
7873             q = strchr(p, '}');
7874             if (q) {
7875                 *q = NULLCHAR;
7876                 r = p + 1;
7877             }
7878         }
7879         /* Kludge for Arasan 4.1 bug */
7880         if (strcmp(r, "Black resigns") == 0) {
7881             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7882             return;
7883         }
7884         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7885         return;
7886     } else if (strncmp(message, "1/2", 3) == 0) {
7887         char *p, *q, *r = "";
7888         p = strchr(message, '{');
7889         if (p) {
7890             q = strchr(p, '}');
7891             if (q) {
7892                 *q = NULLCHAR;
7893                 r = p + 1;
7894             }
7895         }
7896
7897         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7898         return;
7899
7900     } else if (strncmp(message, "White resign", 12) == 0) {
7901         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7902         return;
7903     } else if (strncmp(message, "Black resign", 12) == 0) {
7904         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7905         return;
7906     } else if (strncmp(message, "White matches", 13) == 0 ||
7907                strncmp(message, "Black matches", 13) == 0   ) {
7908         /* [HGM] ignore GNUShogi noises */
7909         return;
7910     } else if (strncmp(message, "White", 5) == 0 &&
7911                message[5] != '(' &&
7912                StrStr(message, "Black") == NULL) {
7913         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7914         return;
7915     } else if (strncmp(message, "Black", 5) == 0 &&
7916                message[5] != '(') {
7917         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7918         return;
7919     } else if (strcmp(message, "resign") == 0 ||
7920                strcmp(message, "computer resigns") == 0) {
7921         switch (gameMode) {
7922           case MachinePlaysBlack:
7923           case IcsPlayingBlack:
7924             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7925             break;
7926           case MachinePlaysWhite:
7927           case IcsPlayingWhite:
7928             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7929             break;
7930           case TwoMachinesPlay:
7931             if (cps->twoMachinesColor[0] == 'w')
7932               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7933             else
7934               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7935             break;
7936           default:
7937             /* can't happen */
7938             break;
7939         }
7940         return;
7941     } else if (strncmp(message, "opponent mates", 14) == 0) {
7942         switch (gameMode) {
7943           case MachinePlaysBlack:
7944           case IcsPlayingBlack:
7945             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7946             break;
7947           case MachinePlaysWhite:
7948           case IcsPlayingWhite:
7949             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7950             break;
7951           case TwoMachinesPlay:
7952             if (cps->twoMachinesColor[0] == 'w')
7953               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7954             else
7955               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7956             break;
7957           default:
7958             /* can't happen */
7959             break;
7960         }
7961         return;
7962     } else if (strncmp(message, "computer mates", 14) == 0) {
7963         switch (gameMode) {
7964           case MachinePlaysBlack:
7965           case IcsPlayingBlack:
7966             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7967             break;
7968           case MachinePlaysWhite:
7969           case IcsPlayingWhite:
7970             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7971             break;
7972           case TwoMachinesPlay:
7973             if (cps->twoMachinesColor[0] == 'w')
7974               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7975             else
7976               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7977             break;
7978           default:
7979             /* can't happen */
7980             break;
7981         }
7982         return;
7983     } else if (strncmp(message, "checkmate", 9) == 0) {
7984         if (WhiteOnMove(forwardMostMove)) {
7985             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7986         } else {
7987             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7988         }
7989         return;
7990     } else if (strstr(message, "Draw") != NULL ||
7991                strstr(message, "game is a draw") != NULL) {
7992         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7993         return;
7994     } else if (strstr(message, "offer") != NULL &&
7995                strstr(message, "draw") != NULL) {
7996 #if ZIPPY
7997         if (appData.zippyPlay && first.initDone) {
7998             /* Relay offer to ICS */
7999             SendToICS(ics_prefix);
8000             SendToICS("draw\n");
8001         }
8002 #endif
8003         cps->offeredDraw = 2; /* valid until this engine moves twice */
8004         if (gameMode == TwoMachinesPlay) {
8005             if (cps->other->offeredDraw) {
8006                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8007             /* [HGM] in two-machine mode we delay relaying draw offer      */
8008             /* until after we also have move, to see if it is really claim */
8009             }
8010         } else if (gameMode == MachinePlaysWhite ||
8011                    gameMode == MachinePlaysBlack) {
8012           if (userOfferedDraw) {
8013             DisplayInformation(_("Machine accepts your draw offer"));
8014             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8015           } else {
8016             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8017           }
8018         }
8019     }
8020
8021
8022     /*
8023      * Look for thinking output
8024      */
8025     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8026           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8027                                 ) {
8028         int plylev, mvleft, mvtot, curscore, time;
8029         char mvname[MOVE_LEN];
8030         u64 nodes; // [DM]
8031         char plyext;
8032         int ignore = FALSE;
8033         int prefixHint = FALSE;
8034         mvname[0] = NULLCHAR;
8035
8036         switch (gameMode) {
8037           case MachinePlaysBlack:
8038           case IcsPlayingBlack:
8039             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8040             break;
8041           case MachinePlaysWhite:
8042           case IcsPlayingWhite:
8043             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8044             break;
8045           case AnalyzeMode:
8046           case AnalyzeFile:
8047             break;
8048           case IcsObserving: /* [DM] icsEngineAnalyze */
8049             if (!appData.icsEngineAnalyze) ignore = TRUE;
8050             break;
8051           case TwoMachinesPlay:
8052             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8053                 ignore = TRUE;
8054             }
8055             break;
8056           default:
8057             ignore = TRUE;
8058             break;
8059         }
8060
8061         if (!ignore) {
8062             buf1[0] = NULLCHAR;
8063             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8064                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8065
8066                 if (plyext != ' ' && plyext != '\t') {
8067                     time *= 100;
8068                 }
8069
8070                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8071                 if( cps->scoreIsAbsolute && 
8072                     ( gameMode == MachinePlaysBlack ||
8073                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8074                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8075                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8076                      !WhiteOnMove(currentMove)
8077                     ) )
8078                 {
8079                     curscore = -curscore;
8080                 }
8081
8082
8083                 programStats.depth = plylev;
8084                 programStats.nodes = nodes;
8085                 programStats.time = time;
8086                 programStats.score = curscore;
8087                 programStats.got_only_move = 0;
8088
8089                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8090                         int ticklen;
8091
8092                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8093                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8094                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8095                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
8096                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8097                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8098                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
8099                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8100                 }
8101
8102                 /* Buffer overflow protection */
8103                 if (buf1[0] != NULLCHAR) {
8104                     if (strlen(buf1) >= sizeof(programStats.movelist)
8105                         && appData.debugMode) {
8106                         fprintf(debugFP,
8107                                 "PV is too long; using the first %u bytes.\n",
8108                                 (unsigned) sizeof(programStats.movelist) - 1);
8109                     }
8110
8111                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
8112                 } else {
8113                     sprintf(programStats.movelist, " no PV\n");
8114                 }
8115
8116                 if (programStats.seen_stat) {
8117                     programStats.ok_to_send = 1;
8118                 }
8119
8120                 if (strchr(programStats.movelist, '(') != NULL) {
8121                     programStats.line_is_book = 1;
8122                     programStats.nr_moves = 0;
8123                     programStats.moves_left = 0;
8124                 } else {
8125                     programStats.line_is_book = 0;
8126                 }
8127
8128                 SendProgramStatsToFrontend( cps, &programStats );
8129
8130                 /*
8131                     [AS] Protect the thinkOutput buffer from overflow... this
8132                     is only useful if buf1 hasn't overflowed first!
8133                 */
8134                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
8135                         plylev,
8136                         (gameMode == TwoMachinesPlay ?
8137                          ToUpper(cps->twoMachinesColor[0]) : ' '),
8138                         ((double) curscore) / 100.0,
8139                         prefixHint ? lastHint : "",
8140                         prefixHint ? " " : "" );
8141
8142                 if( buf1[0] != NULLCHAR ) {
8143                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8144
8145                     if( strlen(buf1) > max_len ) {
8146                         if( appData.debugMode) {
8147                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8148                         }
8149                         buf1[max_len+1] = '\0';
8150                     }
8151
8152                     strcat( thinkOutput, buf1 );
8153                 }
8154
8155                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8156                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8157                     DisplayMove(currentMove - 1);
8158                 }
8159                 return;
8160
8161             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8162                 /* crafty (9.25+) says "(only move) <move>"
8163                  * if there is only 1 legal move
8164                  */
8165                 sscanf(p, "(only move) %s", buf1);
8166                 sprintf(thinkOutput, "%s (only move)", buf1);
8167                 sprintf(programStats.movelist, "%s (only move)", buf1);
8168                 programStats.depth = 1;
8169                 programStats.nr_moves = 1;
8170                 programStats.moves_left = 1;
8171                 programStats.nodes = 1;
8172                 programStats.time = 1;
8173                 programStats.got_only_move = 1;
8174
8175                 /* Not really, but we also use this member to
8176                    mean "line isn't going to change" (Crafty
8177                    isn't searching, so stats won't change) */
8178                 programStats.line_is_book = 1;
8179
8180                 SendProgramStatsToFrontend( cps, &programStats );
8181
8182                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8183                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8184                     DisplayMove(currentMove - 1);
8185                 }
8186                 return;
8187             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8188                               &time, &nodes, &plylev, &mvleft,
8189                               &mvtot, mvname) >= 5) {
8190                 /* The stat01: line is from Crafty (9.29+) in response
8191                    to the "." command */
8192                 programStats.seen_stat = 1;
8193                 cps->maybeThinking = TRUE;
8194
8195                 if (programStats.got_only_move || !appData.periodicUpdates)
8196                   return;
8197
8198                 programStats.depth = plylev;
8199                 programStats.time = time;
8200                 programStats.nodes = nodes;
8201                 programStats.moves_left = mvleft;
8202                 programStats.nr_moves = mvtot;
8203                 strcpy(programStats.move_name, mvname);
8204                 programStats.ok_to_send = 1;
8205                 programStats.movelist[0] = '\0';
8206
8207                 SendProgramStatsToFrontend( cps, &programStats );
8208
8209                 return;
8210
8211             } else if (strncmp(message,"++",2) == 0) {
8212                 /* Crafty 9.29+ outputs this */
8213                 programStats.got_fail = 2;
8214                 return;
8215
8216             } else if (strncmp(message,"--",2) == 0) {
8217                 /* Crafty 9.29+ outputs this */
8218                 programStats.got_fail = 1;
8219                 return;
8220
8221             } else if (thinkOutput[0] != NULLCHAR &&
8222                        strncmp(message, "    ", 4) == 0) {
8223                 unsigned message_len;
8224
8225                 p = message;
8226                 while (*p && *p == ' ') p++;
8227
8228                 message_len = strlen( p );
8229
8230                 /* [AS] Avoid buffer overflow */
8231                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8232                     strcat(thinkOutput, " ");
8233                     strcat(thinkOutput, p);
8234                 }
8235
8236                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8237                     strcat(programStats.movelist, " ");
8238                     strcat(programStats.movelist, p);
8239                 }
8240
8241                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8242                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8243                     DisplayMove(currentMove - 1);
8244                 }
8245                 return;
8246             }
8247         }
8248         else {
8249             buf1[0] = NULLCHAR;
8250
8251             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8252                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8253             {
8254                 ChessProgramStats cpstats;
8255
8256                 if (plyext != ' ' && plyext != '\t') {
8257                     time *= 100;
8258                 }
8259
8260                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8261                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8262                     curscore = -curscore;
8263                 }
8264
8265                 cpstats.depth = plylev;
8266                 cpstats.nodes = nodes;
8267                 cpstats.time = time;
8268                 cpstats.score = curscore;
8269                 cpstats.got_only_move = 0;
8270                 cpstats.movelist[0] = '\0';
8271
8272                 if (buf1[0] != NULLCHAR) {
8273                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8274                 }
8275
8276                 cpstats.ok_to_send = 0;
8277                 cpstats.line_is_book = 0;
8278                 cpstats.nr_moves = 0;
8279                 cpstats.moves_left = 0;
8280
8281                 SendProgramStatsToFrontend( cps, &cpstats );
8282             }
8283         }
8284     }
8285 }
8286
8287
8288 /* Parse a game score from the character string "game", and
8289    record it as the history of the current game.  The game
8290    score is NOT assumed to start from the standard position.
8291    The display is not updated in any way.
8292    */
8293 void
8294 ParseGameHistory(game)
8295      char *game;
8296 {
8297     ChessMove moveType;
8298     int fromX, fromY, toX, toY, boardIndex;
8299     char promoChar;
8300     char *p, *q;
8301     char buf[MSG_SIZ];
8302
8303     if (appData.debugMode)
8304       fprintf(debugFP, "Parsing game history: %s\n", game);
8305
8306     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8307     gameInfo.site = StrSave(appData.icsHost);
8308     gameInfo.date = PGNDate();
8309     gameInfo.round = StrSave("-");
8310
8311     /* Parse out names of players */
8312     while (*game == ' ') game++;
8313     p = buf;
8314     while (*game != ' ') *p++ = *game++;
8315     *p = NULLCHAR;
8316     gameInfo.white = StrSave(buf);
8317     while (*game == ' ') game++;
8318     p = buf;
8319     while (*game != ' ' && *game != '\n') *p++ = *game++;
8320     *p = NULLCHAR;
8321     gameInfo.black = StrSave(buf);
8322
8323     /* Parse moves */
8324     boardIndex = blackPlaysFirst ? 1 : 0;
8325     yynewstr(game);
8326     for (;;) {
8327         yyboardindex = boardIndex;
8328         moveType = (ChessMove) yylex();
8329         switch (moveType) {
8330           case IllegalMove:             /* maybe suicide chess, etc. */
8331   if (appData.debugMode) {
8332     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8333     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8334     setbuf(debugFP, NULL);
8335   }
8336           case WhitePromotionChancellor:
8337           case BlackPromotionChancellor:
8338           case WhitePromotionArchbishop:
8339           case BlackPromotionArchbishop:
8340           case WhitePromotionQueen:
8341           case BlackPromotionQueen:
8342           case WhitePromotionRook:
8343           case BlackPromotionRook:
8344           case WhitePromotionBishop:
8345           case BlackPromotionBishop:
8346           case WhitePromotionKnight:
8347           case BlackPromotionKnight:
8348           case WhitePromotionKing:
8349           case BlackPromotionKing:
8350           case NormalMove:
8351           case WhiteCapturesEnPassant:
8352           case BlackCapturesEnPassant:
8353           case WhiteKingSideCastle:
8354           case WhiteQueenSideCastle:
8355           case BlackKingSideCastle:
8356           case BlackQueenSideCastle:
8357           case WhiteKingSideCastleWild:
8358           case WhiteQueenSideCastleWild:
8359           case BlackKingSideCastleWild:
8360           case BlackQueenSideCastleWild:
8361           /* PUSH Fabien */
8362           case WhiteHSideCastleFR:
8363           case WhiteASideCastleFR:
8364           case BlackHSideCastleFR:
8365           case BlackASideCastleFR:
8366           /* POP Fabien */
8367             fromX = currentMoveString[0] - AAA;
8368             fromY = currentMoveString[1] - ONE;
8369             toX = currentMoveString[2] - AAA;
8370             toY = currentMoveString[3] - ONE;
8371             promoChar = currentMoveString[4];
8372             break;
8373           case WhiteDrop:
8374           case BlackDrop:
8375             fromX = moveType == WhiteDrop ?
8376               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8377             (int) CharToPiece(ToLower(currentMoveString[0]));
8378             fromY = DROP_RANK;
8379             toX = currentMoveString[2] - AAA;
8380             toY = currentMoveString[3] - ONE;
8381             promoChar = NULLCHAR;
8382             break;
8383           case AmbiguousMove:
8384             /* bug? */
8385             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8386   if (appData.debugMode) {
8387     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8388     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8389     setbuf(debugFP, NULL);
8390   }
8391             DisplayError(buf, 0);
8392             return;
8393           case ImpossibleMove:
8394             /* bug? */
8395             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8396   if (appData.debugMode) {
8397     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8398     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8399     setbuf(debugFP, NULL);
8400   }
8401             DisplayError(buf, 0);
8402             return;
8403           case (ChessMove) 0:   /* end of file */
8404             if (boardIndex < backwardMostMove) {
8405                 /* Oops, gap.  How did that happen? */
8406                 DisplayError(_("Gap in move list"), 0);
8407                 return;
8408             }
8409             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8410             if (boardIndex > forwardMostMove) {
8411                 forwardMostMove = boardIndex;
8412             }
8413             return;
8414           case ElapsedTime:
8415             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8416                 strcat(parseList[boardIndex-1], " ");
8417                 strcat(parseList[boardIndex-1], yy_text);
8418             }
8419             continue;
8420           case Comment:
8421           case PGNTag:
8422           case NAG:
8423           default:
8424             /* ignore */
8425             continue;
8426           case WhiteWins:
8427           case BlackWins:
8428           case GameIsDrawn:
8429           case GameUnfinished:
8430             if (gameMode == IcsExamining) {
8431                 if (boardIndex < backwardMostMove) {
8432                     /* Oops, gap.  How did that happen? */
8433                     return;
8434                 }
8435                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8436                 return;
8437             }
8438             gameInfo.result = moveType;
8439             p = strchr(yy_text, '{');
8440             if (p == NULL) p = strchr(yy_text, '(');
8441             if (p == NULL) {
8442                 p = yy_text;
8443                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8444             } else {
8445                 q = strchr(p, *p == '{' ? '}' : ')');
8446                 if (q != NULL) *q = NULLCHAR;
8447                 p++;
8448             }
8449             gameInfo.resultDetails = StrSave(p);
8450             continue;
8451         }
8452         if (boardIndex >= forwardMostMove &&
8453             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8454             backwardMostMove = blackPlaysFirst ? 1 : 0;
8455             return;
8456         }
8457         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8458                                  fromY, fromX, toY, toX, promoChar,
8459                                  parseList[boardIndex]);
8460         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8461         /* currentMoveString is set as a side-effect of yylex */
8462         strcpy(moveList[boardIndex], currentMoveString);
8463         strcat(moveList[boardIndex], "\n");
8464         boardIndex++;
8465         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8466         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8467           case MT_NONE:
8468           case MT_STALEMATE:
8469           default:
8470             break;
8471           case MT_CHECK:
8472             if(gameInfo.variant != VariantShogi)
8473                 strcat(parseList[boardIndex - 1], "+");
8474             break;
8475           case MT_CHECKMATE:
8476           case MT_STAINMATE:
8477             strcat(parseList[boardIndex - 1], "#");
8478             break;
8479         }
8480     }
8481 }
8482
8483
8484 /* Apply a move to the given board  */
8485 void
8486 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8487      int fromX, fromY, toX, toY;
8488      int promoChar;
8489      Board board;
8490 {
8491   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8492   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8493
8494     /* [HGM] compute & store e.p. status and castling rights for new position */
8495     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8496     { int i;
8497
8498       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8499       oldEP = (signed char)board[EP_STATUS];
8500       board[EP_STATUS] = EP_NONE;
8501
8502       if( board[toY][toX] != EmptySquare ) 
8503            board[EP_STATUS] = EP_CAPTURE;  
8504
8505       if( board[fromY][fromX] == WhitePawn ) {
8506            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8507                board[EP_STATUS] = EP_PAWN_MOVE;
8508            if( toY-fromY==2) {
8509                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8510                         gameInfo.variant != VariantBerolina || toX < fromX)
8511                       board[EP_STATUS] = toX | berolina;
8512                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8513                   gameInfo.variant != VariantBerolina || toX > fromX) 
8514                  board[EP_STATUS] = toX;
8515            }
8516       } else
8517       if( board[fromY][fromX] == BlackPawn ) {
8518            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8519                board[EP_STATUS] = EP_PAWN_MOVE; 
8520            if( toY-fromY== -2) {
8521                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8522                         gameInfo.variant != VariantBerolina || toX < fromX)
8523                       board[EP_STATUS] = toX | berolina;
8524                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8525                         gameInfo.variant != VariantBerolina || toX > fromX) 
8526                       board[EP_STATUS] = toX;
8527            }
8528        }
8529
8530        for(i=0; i<nrCastlingRights; i++) {
8531            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8532               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8533              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8534        }
8535
8536     }
8537
8538   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8539   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8540        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8541
8542   if (fromX == toX && fromY == toY) return;
8543
8544   if (fromY == DROP_RANK) {
8545         /* must be first */
8546         piece = board[toY][toX] = (ChessSquare) fromX;
8547   } else {
8548      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8549      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8550      if(gameInfo.variant == VariantKnightmate)
8551          king += (int) WhiteUnicorn - (int) WhiteKing;
8552
8553     /* Code added by Tord: */
8554     /* FRC castling assumed when king captures friendly rook. */
8555     if (board[fromY][fromX] == WhiteKing &&
8556              board[toY][toX] == WhiteRook) {
8557       board[fromY][fromX] = EmptySquare;
8558       board[toY][toX] = EmptySquare;
8559       if(toX > fromX) {
8560         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8561       } else {
8562         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8563       }
8564     } else if (board[fromY][fromX] == BlackKing &&
8565                board[toY][toX] == BlackRook) {
8566       board[fromY][fromX] = EmptySquare;
8567       board[toY][toX] = EmptySquare;
8568       if(toX > fromX) {
8569         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8570       } else {
8571         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8572       }
8573     /* End of code added by Tord */
8574
8575     } else if (board[fromY][fromX] == king
8576         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8577         && toY == fromY && toX > fromX+1) {
8578         board[fromY][fromX] = EmptySquare;
8579         board[toY][toX] = king;
8580         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8581         board[fromY][BOARD_RGHT-1] = EmptySquare;
8582     } else if (board[fromY][fromX] == king
8583         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8584                && toY == fromY && toX < fromX-1) {
8585         board[fromY][fromX] = EmptySquare;
8586         board[toY][toX] = king;
8587         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8588         board[fromY][BOARD_LEFT] = EmptySquare;
8589     } else if (board[fromY][fromX] == WhitePawn
8590                && toY >= BOARD_HEIGHT-promoRank
8591                && gameInfo.variant != VariantXiangqi
8592                ) {
8593         /* white pawn promotion */
8594         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8595         if (board[toY][toX] == EmptySquare) {
8596             board[toY][toX] = WhiteQueen;
8597         }
8598         if(gameInfo.variant==VariantBughouse ||
8599            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8600             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8601         board[fromY][fromX] = EmptySquare;
8602     } else if ((fromY == BOARD_HEIGHT-4)
8603                && (toX != fromX)
8604                && gameInfo.variant != VariantXiangqi
8605                && gameInfo.variant != VariantBerolina
8606                && (board[fromY][fromX] == WhitePawn)
8607                && (board[toY][toX] == EmptySquare)) {
8608         board[fromY][fromX] = EmptySquare;
8609         board[toY][toX] = WhitePawn;
8610         captured = board[toY - 1][toX];
8611         board[toY - 1][toX] = EmptySquare;
8612     } else if ((fromY == BOARD_HEIGHT-4)
8613                && (toX == fromX)
8614                && gameInfo.variant == VariantBerolina
8615                && (board[fromY][fromX] == WhitePawn)
8616                && (board[toY][toX] == EmptySquare)) {
8617         board[fromY][fromX] = EmptySquare;
8618         board[toY][toX] = WhitePawn;
8619         if(oldEP & EP_BEROLIN_A) {
8620                 captured = board[fromY][fromX-1];
8621                 board[fromY][fromX-1] = EmptySquare;
8622         }else{  captured = board[fromY][fromX+1];
8623                 board[fromY][fromX+1] = EmptySquare;
8624         }
8625     } else if (board[fromY][fromX] == king
8626         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8627                && toY == fromY && toX > fromX+1) {
8628         board[fromY][fromX] = EmptySquare;
8629         board[toY][toX] = king;
8630         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8631         board[fromY][BOARD_RGHT-1] = EmptySquare;
8632     } else if (board[fromY][fromX] == king
8633         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8634                && toY == fromY && toX < fromX-1) {
8635         board[fromY][fromX] = EmptySquare;
8636         board[toY][toX] = king;
8637         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8638         board[fromY][BOARD_LEFT] = EmptySquare;
8639     } else if (fromY == 7 && fromX == 3
8640                && board[fromY][fromX] == BlackKing
8641                && toY == 7 && toX == 5) {
8642         board[fromY][fromX] = EmptySquare;
8643         board[toY][toX] = BlackKing;
8644         board[fromY][7] = EmptySquare;
8645         board[toY][4] = BlackRook;
8646     } else if (fromY == 7 && fromX == 3
8647                && board[fromY][fromX] == BlackKing
8648                && toY == 7 && toX == 1) {
8649         board[fromY][fromX] = EmptySquare;
8650         board[toY][toX] = BlackKing;
8651         board[fromY][0] = EmptySquare;
8652         board[toY][2] = BlackRook;
8653     } else if (board[fromY][fromX] == BlackPawn
8654                && toY < promoRank
8655                && gameInfo.variant != VariantXiangqi
8656                ) {
8657         /* black pawn promotion */
8658         board[toY][toX] = CharToPiece(ToLower(promoChar));
8659         if (board[toY][toX] == EmptySquare) {
8660             board[toY][toX] = BlackQueen;
8661         }
8662         if(gameInfo.variant==VariantBughouse ||
8663            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8664             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8665         board[fromY][fromX] = EmptySquare;
8666     } else if ((fromY == 3)
8667                && (toX != fromX)
8668                && gameInfo.variant != VariantXiangqi
8669                && gameInfo.variant != VariantBerolina
8670                && (board[fromY][fromX] == BlackPawn)
8671                && (board[toY][toX] == EmptySquare)) {
8672         board[fromY][fromX] = EmptySquare;
8673         board[toY][toX] = BlackPawn;
8674         captured = board[toY + 1][toX];
8675         board[toY + 1][toX] = EmptySquare;
8676     } else if ((fromY == 3)
8677                && (toX == fromX)
8678                && gameInfo.variant == VariantBerolina
8679                && (board[fromY][fromX] == BlackPawn)
8680                && (board[toY][toX] == EmptySquare)) {
8681         board[fromY][fromX] = EmptySquare;
8682         board[toY][toX] = BlackPawn;
8683         if(oldEP & EP_BEROLIN_A) {
8684                 captured = board[fromY][fromX-1];
8685                 board[fromY][fromX-1] = EmptySquare;
8686         }else{  captured = board[fromY][fromX+1];
8687                 board[fromY][fromX+1] = EmptySquare;
8688         }
8689     } else {
8690         board[toY][toX] = board[fromY][fromX];
8691         board[fromY][fromX] = EmptySquare;
8692     }
8693
8694     /* [HGM] now we promote for Shogi, if needed */
8695     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8696         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8697   }
8698
8699     if (gameInfo.holdingsWidth != 0) {
8700
8701       /* !!A lot more code needs to be written to support holdings  */
8702       /* [HGM] OK, so I have written it. Holdings are stored in the */
8703       /* penultimate board files, so they are automaticlly stored   */
8704       /* in the game history.                                       */
8705       if (fromY == DROP_RANK) {
8706         /* Delete from holdings, by decreasing count */
8707         /* and erasing image if necessary            */
8708         p = (int) fromX;
8709         if(p < (int) BlackPawn) { /* white drop */
8710              p -= (int)WhitePawn;
8711                  p = PieceToNumber((ChessSquare)p);
8712              if(p >= gameInfo.holdingsSize) p = 0;
8713              if(--board[p][BOARD_WIDTH-2] <= 0)
8714                   board[p][BOARD_WIDTH-1] = EmptySquare;
8715              if((int)board[p][BOARD_WIDTH-2] < 0)
8716                         board[p][BOARD_WIDTH-2] = 0;
8717         } else {                  /* black drop */
8718              p -= (int)BlackPawn;
8719                  p = PieceToNumber((ChessSquare)p);
8720              if(p >= gameInfo.holdingsSize) p = 0;
8721              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8722                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8723              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8724                         board[BOARD_HEIGHT-1-p][1] = 0;
8725         }
8726       }
8727       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8728           && gameInfo.variant != VariantBughouse        ) {
8729         /* [HGM] holdings: Add to holdings, if holdings exist */
8730         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8731                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8732                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8733         }
8734         p = (int) captured;
8735         if (p >= (int) BlackPawn) {
8736           p -= (int)BlackPawn;
8737           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8738                   /* in Shogi restore piece to its original  first */
8739                   captured = (ChessSquare) (DEMOTED captured);
8740                   p = DEMOTED p;
8741           }
8742           p = PieceToNumber((ChessSquare)p);
8743           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8744           board[p][BOARD_WIDTH-2]++;
8745           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8746         } else {
8747           p -= (int)WhitePawn;
8748           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8749                   captured = (ChessSquare) (DEMOTED captured);
8750                   p = DEMOTED p;
8751           }
8752           p = PieceToNumber((ChessSquare)p);
8753           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8754           board[BOARD_HEIGHT-1-p][1]++;
8755           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8756         }
8757       }
8758     } else if (gameInfo.variant == VariantAtomic) {
8759       if (captured != EmptySquare) {
8760         int y, x;
8761         for (y = toY-1; y <= toY+1; y++) {
8762           for (x = toX-1; x <= toX+1; x++) {
8763             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8764                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8765               board[y][x] = EmptySquare;
8766             }
8767           }
8768         }
8769         board[toY][toX] = EmptySquare;
8770       }
8771     }
8772     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8773         /* [HGM] Shogi promotions */
8774         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8775     }
8776
8777     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8778                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8779         // [HGM] superchess: take promotion piece out of holdings
8780         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8781         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8782             if(!--board[k][BOARD_WIDTH-2])
8783                 board[k][BOARD_WIDTH-1] = EmptySquare;
8784         } else {
8785             if(!--board[BOARD_HEIGHT-1-k][1])
8786                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8787         }
8788     }
8789
8790 }
8791
8792 /* Updates forwardMostMove */
8793 void
8794 MakeMove(fromX, fromY, toX, toY, promoChar)
8795      int fromX, fromY, toX, toY;
8796      int promoChar;
8797 {
8798 //    forwardMostMove++; // [HGM] bare: moved downstream
8799
8800     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8801         int timeLeft; static int lastLoadFlag=0; int king, piece;
8802         piece = boards[forwardMostMove][fromY][fromX];
8803         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8804         if(gameInfo.variant == VariantKnightmate)
8805             king += (int) WhiteUnicorn - (int) WhiteKing;
8806         if(forwardMostMove == 0) {
8807             if(blackPlaysFirst)
8808                 fprintf(serverMoves, "%s;", second.tidy);
8809             fprintf(serverMoves, "%s;", first.tidy);
8810             if(!blackPlaysFirst)
8811                 fprintf(serverMoves, "%s;", second.tidy);
8812         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8813         lastLoadFlag = loadFlag;
8814         // print base move
8815         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8816         // print castling suffix
8817         if( toY == fromY && piece == king ) {
8818             if(toX-fromX > 1)
8819                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8820             if(fromX-toX >1)
8821                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8822         }
8823         // e.p. suffix
8824         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8825              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8826              boards[forwardMostMove][toY][toX] == EmptySquare
8827              && fromX != toX )
8828                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8829         // promotion suffix
8830         if(promoChar != NULLCHAR)
8831                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8832         if(!loadFlag) {
8833             fprintf(serverMoves, "/%d/%d",
8834                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8835             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8836             else                      timeLeft = blackTimeRemaining/1000;
8837             fprintf(serverMoves, "/%d", timeLeft);
8838         }
8839         fflush(serverMoves);
8840     }
8841
8842     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8843       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8844                         0, 1);
8845       return;
8846     }
8847     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8848     if (commentList[forwardMostMove+1] != NULL) {
8849         free(commentList[forwardMostMove+1]);
8850         commentList[forwardMostMove+1] = NULL;
8851     }
8852     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8853     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8854     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8855     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8856     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8857     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8858     gameInfo.result = GameUnfinished;
8859     if (gameInfo.resultDetails != NULL) {
8860         free(gameInfo.resultDetails);
8861         gameInfo.resultDetails = NULL;
8862     }
8863     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8864                               moveList[forwardMostMove - 1]);
8865     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8866                              PosFlags(forwardMostMove - 1),
8867                              fromY, fromX, toY, toX, promoChar,
8868                              parseList[forwardMostMove - 1]);
8869     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8870       case MT_NONE:
8871       case MT_STALEMATE:
8872       default:
8873         break;
8874       case MT_CHECK:
8875         if(gameInfo.variant != VariantShogi)
8876             strcat(parseList[forwardMostMove - 1], "+");
8877         break;
8878       case MT_CHECKMATE:
8879       case MT_STAINMATE:
8880         strcat(parseList[forwardMostMove - 1], "#");
8881         break;
8882     }
8883     if (appData.debugMode) {
8884         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8885     }
8886
8887 }
8888
8889 /* Updates currentMove if not pausing */
8890 void
8891 ShowMove(fromX, fromY, toX, toY)
8892 {
8893     int instant = (gameMode == PlayFromGameFile) ?
8894         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8895
8896     if(appData.noGUI) return;
8897
8898     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
8899       {
8900         if (!instant)
8901           {
8902             if (forwardMostMove == currentMove + 1)
8903               {
8904 //TODO
8905 //              AnimateMove(boards[forwardMostMove - 1],
8906 //                          fromX, fromY, toX, toY);
8907               }
8908             if (appData.highlightLastMove)
8909               {
8910                 SetHighlights(fromX, fromY, toX, toY);
8911               }
8912           }
8913         currentMove = forwardMostMove;
8914     }
8915
8916     if (instant) return;
8917
8918     DisplayMove(currentMove - 1);
8919     DrawPosition(FALSE, boards[currentMove]);
8920     DisplayBothClocks();
8921     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8922
8923     return;
8924 }
8925
8926 void SendEgtPath(ChessProgramState *cps)
8927 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8928         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8929
8930         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8931
8932         while(*p) {
8933             char c, *q = name+1, *r, *s;
8934
8935             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8936             while(*p && *p != ',') *q++ = *p++;
8937             *q++ = ':'; *q = 0;
8938             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8939                 strcmp(name, ",nalimov:") == 0 ) {
8940                 // take nalimov path from the menu-changeable option first, if it is defined
8941                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8942                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8943             } else
8944             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8945                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8946                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8947                 s = r = StrStr(s, ":") + 1; // beginning of path info
8948                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8949                 c = *r; *r = 0;             // temporarily null-terminate path info
8950                     *--q = 0;               // strip of trailig ':' from name
8951                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8952                 *r = c;
8953                 SendToProgram(buf,cps);     // send egtbpath command for this format
8954             }
8955             if(*p == ',') p++; // read away comma to position for next format name
8956         }
8957 }
8958
8959 void
8960 InitChessProgram(cps, setup)
8961      ChessProgramState *cps;
8962      int setup; /* [HGM] needed to setup FRC opening position */
8963 {
8964     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8965     if (appData.noChessProgram) return;
8966     hintRequested = FALSE;
8967     bookRequested = FALSE;
8968
8969     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8970     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8971     if(cps->memSize) { /* [HGM] memory */
8972         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8973         SendToProgram(buf, cps);
8974     }
8975     SendEgtPath(cps); /* [HGM] EGT */
8976     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8977         sprintf(buf, "cores %d\n", appData.smpCores);
8978         SendToProgram(buf, cps);
8979     }
8980
8981     SendToProgram(cps->initString, cps);
8982     if (gameInfo.variant != VariantNormal &&
8983         gameInfo.variant != VariantLoadable
8984         /* [HGM] also send variant if board size non-standard */
8985         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8986                                             ) {
8987       char *v = VariantName(gameInfo.variant);
8988       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8989         /* [HGM] in protocol 1 we have to assume all variants valid */
8990         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8991         DisplayFatalError(buf, 0, 1);
8992         return;
8993       }
8994
8995       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8996       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8997       if( gameInfo.variant == VariantXiangqi )
8998            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8999       if( gameInfo.variant == VariantShogi )
9000            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9001       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9002            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9003       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9004                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
9005            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9006       if( gameInfo.variant == VariantCourier )
9007            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9008       if( gameInfo.variant == VariantSuper )
9009            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9010       if( gameInfo.variant == VariantGreat )
9011            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9012
9013       if(overruled) {
9014            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9015                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9016            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9017            if(StrStr(cps->variants, b) == NULL) {
9018                // specific sized variant not known, check if general sizing allowed
9019                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9020                    if(StrStr(cps->variants, "boardsize") == NULL) {
9021                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
9022                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9023                        DisplayFatalError(buf, 0, 1);
9024                        return;
9025                    }
9026                    /* [HGM] here we really should compare with the maximum supported board size */
9027                }
9028            }
9029       } else sprintf(b, "%s", VariantName(gameInfo.variant));
9030       sprintf(buf, "variant %s\n", b);
9031       SendToProgram(buf, cps);
9032     }
9033     currentlyInitializedVariant = gameInfo.variant;
9034
9035     /* [HGM] send opening position in FRC to first engine */
9036     if(setup) {
9037           SendToProgram("force\n", cps);
9038           SendBoard(cps, 0);
9039           /* engine is now in force mode! Set flag to wake it up after first move. */
9040           setboardSpoiledMachineBlack = 1;
9041     }
9042
9043     if (cps->sendICS) {
9044       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9045       SendToProgram(buf, cps);
9046     }
9047     cps->maybeThinking = FALSE;
9048     cps->offeredDraw = 0;
9049     if (!appData.icsActive) {
9050         SendTimeControl(cps, movesPerSession, timeControl,
9051                         timeIncrement, appData.searchDepth,
9052                         searchTime);
9053     }
9054     if (appData.showThinking
9055         // [HGM] thinking: four options require thinking output to be sent
9056         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9057                                 ) {
9058         SendToProgram("post\n", cps);
9059     }
9060     SendToProgram("hard\n", cps);
9061     if (!appData.ponderNextMove) {
9062         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9063            it without being sure what state we are in first.  "hard"
9064            is not a toggle, so that one is OK.
9065          */
9066         SendToProgram("easy\n", cps);
9067     }
9068     if (cps->usePing) {
9069       sprintf(buf, "ping %d\n", ++cps->lastPing);
9070       SendToProgram(buf, cps);
9071     }
9072     cps->initDone = TRUE;
9073 }
9074
9075
9076 void
9077 StartChessProgram(cps)
9078      ChessProgramState *cps;
9079 {
9080     char buf[MSG_SIZ];
9081     int err;
9082
9083     if (appData.noChessProgram) return;
9084     cps->initDone = FALSE;
9085
9086     if (strcmp(cps->host, "localhost") == 0) {
9087         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9088     } else if (*appData.remoteShell == NULLCHAR) {
9089         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9090     } else {
9091         if (*appData.remoteUser == NULLCHAR) {
9092           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9093                     cps->program);
9094         } else {
9095           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9096                     cps->host, appData.remoteUser, cps->program);
9097         }
9098         err = StartChildProcess(buf, "", &cps->pr);
9099     }
9100
9101     if (err != 0) {
9102         sprintf(buf, _("Startup failure on '%s'"), cps->program);
9103         DisplayFatalError(buf, err, 1);
9104         cps->pr = NoProc;
9105         cps->isr = NULL;
9106         return;
9107     }
9108
9109     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9110     if (cps->protocolVersion > 1) {
9111       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
9112       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9113       cps->comboCnt = 0;  //                and values of combo boxes
9114       SendToProgram(buf, cps);
9115     } else {
9116       SendToProgram("xboard\n", cps);
9117     }
9118 }
9119
9120
9121 void
9122 TwoMachinesEventIfReady P((void))
9123 {
9124   if (first.lastPing != first.lastPong) {
9125     DisplayMessage("", _("Waiting for first chess program"));
9126     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9127     return;
9128   }
9129   if (second.lastPing != second.lastPong) {
9130     DisplayMessage("", _("Waiting for second chess program"));
9131     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9132     return;
9133   }
9134   ThawUI();
9135   TwoMachinesEvent();
9136 }
9137
9138 void
9139 NextMatchGame P((void))
9140 {
9141     int index; /* [HGM] autoinc: step load index during match */
9142     Reset(FALSE, TRUE);
9143     if (*appData.loadGameFile != NULLCHAR) {
9144         index = appData.loadGameIndex;
9145         if(index < 0) { // [HGM] autoinc
9146             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9147             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9148         }
9149         LoadGameFromFile(appData.loadGameFile,
9150                          index,
9151                          appData.loadGameFile, FALSE);
9152     } else if (*appData.loadPositionFile != NULLCHAR) {
9153         index = appData.loadPositionIndex;
9154         if(index < 0) { // [HGM] autoinc
9155             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9156             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9157         }
9158         LoadPositionFromFile(appData.loadPositionFile,
9159                              index,
9160                              appData.loadPositionFile);
9161     }
9162     TwoMachinesEventIfReady();
9163 }
9164
9165 void UserAdjudicationEvent( int result )
9166 {
9167     ChessMove gameResult = GameIsDrawn;
9168
9169     if( result > 0 ) {
9170         gameResult = WhiteWins;
9171     }
9172     else if( result < 0 ) {
9173         gameResult = BlackWins;
9174     }
9175
9176     if( gameMode == TwoMachinesPlay ) {
9177         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9178     }
9179 }
9180
9181
9182 // [HGM] save: calculate checksum of game to make games easily identifiable
9183 int StringCheckSum(char *s)
9184 {
9185         int i = 0;
9186         if(s==NULL) return 0;
9187         while(*s) i = i*259 + *s++;
9188         return i;
9189 }
9190
9191 int GameCheckSum()
9192 {
9193         int i, sum=0;
9194         for(i=backwardMostMove; i<forwardMostMove; i++) {
9195                 sum += pvInfoList[i].depth;
9196                 sum += StringCheckSum(parseList[i]);
9197                 sum += StringCheckSum(commentList[i]);
9198                 sum *= 261;
9199         }
9200         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9201         return sum + StringCheckSum(commentList[i]);
9202 } // end of save patch
9203
9204 void
9205 GameEnds(result, resultDetails, whosays)
9206      ChessMove result;
9207      char *resultDetails;
9208      int whosays;
9209 {
9210     GameMode nextGameMode;
9211     int isIcsGame;
9212     char buf[MSG_SIZ];
9213
9214     if(endingGame) return; /* [HGM] crash: forbid recursion */
9215     endingGame = 1;
9216
9217     if (appData.debugMode) {
9218       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9219               result, resultDetails ? resultDetails : "(null)", whosays);
9220     }
9221
9222     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9223         /* If we are playing on ICS, the server decides when the
9224            game is over, but the engine can offer to draw, claim
9225            a draw, or resign.
9226          */
9227 #if ZIPPY
9228         if (appData.zippyPlay && first.initDone) {
9229             if (result == GameIsDrawn) {
9230                 /* In case draw still needs to be claimed */
9231                 SendToICS(ics_prefix);
9232                 SendToICS("draw\n");
9233             } else if (StrCaseStr(resultDetails, "resign")) {
9234                 SendToICS(ics_prefix);
9235                 SendToICS("resign\n");
9236             }
9237         }
9238 #endif
9239         endingGame = 0; /* [HGM] crash */
9240         return;
9241     }
9242
9243     /* If we're loading the game from a file, stop */
9244     if (whosays == GE_FILE) {
9245       (void) StopLoadGameTimer();
9246       gameFileFP = NULL;
9247     }
9248
9249     /* Cancel draw offers */
9250     first.offeredDraw = second.offeredDraw = 0;
9251
9252     /* If this is an ICS game, only ICS can really say it's done;
9253        if not, anyone can. */
9254     isIcsGame = (gameMode == IcsPlayingWhite ||
9255                  gameMode == IcsPlayingBlack ||
9256                  gameMode == IcsObserving    ||
9257                  gameMode == IcsExamining);
9258
9259     if (!isIcsGame || whosays == GE_ICS) {
9260         /* OK -- not an ICS game, or ICS said it was done */
9261         StopClocks();
9262         if (!isIcsGame && !appData.noChessProgram)
9263           SetUserThinkingEnables();
9264
9265         /* [HGM] if a machine claims the game end we verify this claim */
9266         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9267             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9268                 char claimer;
9269                 ChessMove trueResult = (ChessMove) -1;
9270
9271                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9272                                             first.twoMachinesColor[0] :
9273                                             second.twoMachinesColor[0] ;
9274
9275                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9276                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9277                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9278                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9279                 } else
9280                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9281                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9282                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9283                 } else
9284                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9285                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9286                 }
9287
9288                 // now verify win claims, but not in drop games, as we don't understand those yet
9289                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9290                                                  || gameInfo.variant == VariantGreat) &&
9291                     (result == WhiteWins && claimer == 'w' ||
9292                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9293                       if (appData.debugMode) {
9294                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9295                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9296                       }
9297                       if(result != trueResult) {
9298                               sprintf(buf, "False win claim: '%s'", resultDetails);
9299                               result = claimer == 'w' ? BlackWins : WhiteWins;
9300                               resultDetails = buf;
9301                       }
9302                 } else
9303                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9304                     && (forwardMostMove <= backwardMostMove ||
9305                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9306                         (claimer=='b')==(forwardMostMove&1))
9307                                                                                   ) {
9308                       /* [HGM] verify: draws that were not flagged are false claims */
9309                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9310                       result = claimer == 'w' ? BlackWins : WhiteWins;
9311                       resultDetails = buf;
9312                 }
9313                 /* (Claiming a loss is accepted no questions asked!) */
9314             }
9315
9316             /* [HGM] bare: don't allow bare King to win */
9317             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9318                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9319                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9320                && result != GameIsDrawn)
9321             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9322                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9323                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9324                         if(p >= 0 && p <= (int)WhiteKing) k++;
9325                 }
9326                 if (appData.debugMode) {
9327                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9328                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9329                 }
9330                 if(k <= 1) {
9331                         result = GameIsDrawn;
9332                         sprintf(buf, "%s but bare king", resultDetails);
9333                         resultDetails = buf;
9334                 }
9335             }
9336         }
9337
9338         if(serverMoves != NULL && !loadFlag) { char c = '=';
9339             if(result==WhiteWins) c = '+';
9340             if(result==BlackWins) c = '-';
9341             if(resultDetails != NULL)
9342                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9343         }
9344         if (resultDetails != NULL) {
9345             gameInfo.result = result;
9346             gameInfo.resultDetails = StrSave(resultDetails);
9347
9348             /* display last move only if game was not loaded from file */
9349             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9350                 DisplayMove(currentMove - 1);
9351
9352             if (forwardMostMove != 0) {
9353                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9354                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9355                                                                 ) {
9356                     if (*appData.saveGameFile != NULLCHAR) {
9357                         SaveGameToFile(appData.saveGameFile, TRUE);
9358                     } else if (appData.autoSaveGames) {
9359                         AutoSaveGame();
9360                     }
9361                     if (*appData.savePositionFile != NULLCHAR) {
9362                         SavePositionToFile(appData.savePositionFile);
9363                     }
9364                 }
9365             }
9366
9367             /* Tell program how game ended in case it is learning */
9368             /* [HGM] Moved this to after saving the PGN, just in case */
9369             /* engine died and we got here through time loss. In that */
9370             /* case we will get a fatal error writing the pipe, which */
9371             /* would otherwise lose us the PGN.                       */
9372             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9373             /* output during GameEnds should never be fatal anymore   */
9374             if (gameMode == MachinePlaysWhite ||
9375                 gameMode == MachinePlaysBlack ||
9376                 gameMode == TwoMachinesPlay ||
9377                 gameMode == IcsPlayingWhite ||
9378                 gameMode == IcsPlayingBlack ||
9379                 gameMode == BeginningOfGame) {
9380                 char buf[MSG_SIZ];
9381                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9382                         resultDetails);
9383                 if (first.pr != NoProc) {
9384                     SendToProgram(buf, &first);
9385                 }
9386                 if (second.pr != NoProc &&
9387                     gameMode == TwoMachinesPlay) {
9388                     SendToProgram(buf, &second);
9389                 }
9390             }
9391         }
9392
9393         if (appData.icsActive) {
9394             if (appData.quietPlay &&
9395                 (gameMode == IcsPlayingWhite ||
9396                  gameMode == IcsPlayingBlack)) {
9397                 SendToICS(ics_prefix);
9398                 SendToICS("set shout 1\n");
9399             }
9400             nextGameMode = IcsIdle;
9401             ics_user_moved = FALSE;
9402             /* clean up premove.  It's ugly when the game has ended and the
9403              * premove highlights are still on the board.
9404              */
9405             if (gotPremove) {
9406               gotPremove = FALSE;
9407               ClearPremoveHighlights();
9408               DrawPosition(FALSE, boards[currentMove]);
9409             }
9410             if (whosays == GE_ICS) {
9411                 switch (result) {
9412                 case WhiteWins:
9413                     if (gameMode == IcsPlayingWhite)
9414                         PlayIcsWinSound();
9415                     else if(gameMode == IcsPlayingBlack)
9416                         PlayIcsLossSound();
9417                     break;
9418                 case BlackWins:
9419                     if (gameMode == IcsPlayingBlack)
9420                         PlayIcsWinSound();
9421                     else if(gameMode == IcsPlayingWhite)
9422                         PlayIcsLossSound();
9423                     break;
9424                 case GameIsDrawn:
9425                     PlayIcsDrawSound();
9426                     break;
9427                 default:
9428                     PlayIcsUnfinishedSound();
9429                 }
9430             }
9431         } else if (gameMode == EditGame ||
9432                    gameMode == PlayFromGameFile ||
9433                    gameMode == AnalyzeMode ||
9434                    gameMode == AnalyzeFile) {
9435             nextGameMode = gameMode;
9436         } else {
9437             nextGameMode = EndOfGame;
9438         }
9439         pausing = FALSE;
9440         ModeHighlight();
9441     } else {
9442         nextGameMode = gameMode;
9443     }
9444
9445     if (appData.noChessProgram) {
9446         gameMode = nextGameMode;
9447         ModeHighlight();
9448         endingGame = 0; /* [HGM] crash */
9449         return;
9450     }
9451
9452     if (first.reuse) {
9453         /* Put first chess program into idle state */
9454         if (first.pr != NoProc &&
9455             (gameMode == MachinePlaysWhite ||
9456              gameMode == MachinePlaysBlack ||
9457              gameMode == TwoMachinesPlay ||
9458              gameMode == IcsPlayingWhite ||
9459              gameMode == IcsPlayingBlack ||
9460              gameMode == BeginningOfGame)) {
9461             SendToProgram("force\n", &first);
9462             if (first.usePing) {
9463               char buf[MSG_SIZ];
9464               sprintf(buf, "ping %d\n", ++first.lastPing);
9465               SendToProgram(buf, &first);
9466             }
9467         }
9468     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9469         /* Kill off first chess program */
9470         if (first.isr != NULL)
9471           RemoveInputSource(first.isr);
9472         first.isr = NULL;
9473
9474         if (first.pr != NoProc) {
9475             ExitAnalyzeMode();
9476             DoSleep( appData.delayBeforeQuit );
9477             SendToProgram("quit\n", &first);
9478             DoSleep( appData.delayAfterQuit );
9479             DestroyChildProcess(first.pr, first.useSigterm);
9480         }
9481         first.pr = NoProc;
9482     }
9483     if (second.reuse) {
9484         /* Put second chess program into idle state */
9485         if (second.pr != NoProc &&
9486             gameMode == TwoMachinesPlay) {
9487             SendToProgram("force\n", &second);
9488             if (second.usePing) {
9489               char buf[MSG_SIZ];
9490               sprintf(buf, "ping %d\n", ++second.lastPing);
9491               SendToProgram(buf, &second);
9492             }
9493         }
9494     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9495         /* Kill off second chess program */
9496         if (second.isr != NULL)
9497           RemoveInputSource(second.isr);
9498         second.isr = NULL;
9499
9500         if (second.pr != NoProc) {
9501             DoSleep( appData.delayBeforeQuit );
9502             SendToProgram("quit\n", &second);
9503             DoSleep( appData.delayAfterQuit );
9504             DestroyChildProcess(second.pr, second.useSigterm);
9505         }
9506         second.pr = NoProc;
9507     }
9508
9509     if (matchMode && gameMode == TwoMachinesPlay) {
9510         switch (result) {
9511         case WhiteWins:
9512           if (first.twoMachinesColor[0] == 'w') {
9513             first.matchWins++;
9514           } else {
9515             second.matchWins++;
9516           }
9517           break;
9518         case BlackWins:
9519           if (first.twoMachinesColor[0] == 'b') {
9520             first.matchWins++;
9521           } else {
9522             second.matchWins++;
9523           }
9524           break;
9525         default:
9526           break;
9527         }
9528         if (matchGame < appData.matchGames) {
9529             char *tmp;
9530             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9531                 tmp = first.twoMachinesColor;
9532                 first.twoMachinesColor = second.twoMachinesColor;
9533                 second.twoMachinesColor = tmp;
9534             }
9535             gameMode = nextGameMode;
9536             matchGame++;
9537             if(appData.matchPause>10000 || appData.matchPause<10)
9538                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9539             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9540             endingGame = 0; /* [HGM] crash */
9541             return;
9542         } else {
9543             char buf[MSG_SIZ];
9544             gameMode = nextGameMode;
9545             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9546                     first.tidy, second.tidy,
9547                     first.matchWins, second.matchWins,
9548                     appData.matchGames - (first.matchWins + second.matchWins));
9549             DisplayFatalError(buf, 0, 0);
9550         }
9551     }
9552     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9553         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9554       ExitAnalyzeMode();
9555     gameMode = nextGameMode;
9556     ModeHighlight();
9557     endingGame = 0;  /* [HGM] crash */
9558 }
9559
9560 /* Assumes program was just initialized (initString sent).
9561    Leaves program in force mode. */
9562 void
9563 FeedMovesToProgram(cps, upto)
9564      ChessProgramState *cps;
9565      int upto;
9566 {
9567     int i;
9568
9569     if (appData.debugMode)
9570       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9571               startedFromSetupPosition ? "position and " : "",
9572               backwardMostMove, upto, cps->which);
9573     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9574         // [HGM] variantswitch: make engine aware of new variant
9575         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9576                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9577         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9578         SendToProgram(buf, cps);
9579         currentlyInitializedVariant = gameInfo.variant;
9580     }
9581     SendToProgram("force\n", cps);
9582     if (startedFromSetupPosition) {
9583         SendBoard(cps, backwardMostMove);
9584     if (appData.debugMode) {
9585         fprintf(debugFP, "feedMoves\n");
9586     }
9587     }
9588     for (i = backwardMostMove; i < upto; i++) {
9589         SendMoveToProgram(i, cps);
9590     }
9591 }
9592
9593
9594 void
9595 ResurrectChessProgram()
9596 {
9597      /* The chess program may have exited.
9598         If so, restart it and feed it all the moves made so far. */
9599
9600     if (appData.noChessProgram || first.pr != NoProc) return;
9601
9602     StartChessProgram(&first);
9603     InitChessProgram(&first, FALSE);
9604     FeedMovesToProgram(&first, currentMove);
9605
9606     if (!first.sendTime) {
9607         /* can't tell gnuchess what its clock should read,
9608            so we bow to its notion. */
9609         ResetClocks();
9610         timeRemaining[0][currentMove] = whiteTimeRemaining;
9611         timeRemaining[1][currentMove] = blackTimeRemaining;
9612     }
9613
9614     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9615                 appData.icsEngineAnalyze) && first.analysisSupport) {
9616       SendToProgram("analyze\n", &first);
9617       first.analyzing = TRUE;
9618     }
9619 }
9620
9621 /*
9622  * Button procedures
9623  */
9624 void
9625 Reset(redraw, init)
9626      int redraw, init;
9627 {
9628     int i;
9629
9630     if (appData.debugMode) {
9631         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9632                 redraw, init, gameMode);
9633     }
9634     CleanupTail(); // [HGM] vari: delete any stored variations
9635     pausing = pauseExamInvalid = FALSE;
9636     startedFromSetupPosition = blackPlaysFirst = FALSE;
9637     firstMove = TRUE;
9638     whiteFlag = blackFlag = FALSE;
9639     userOfferedDraw = FALSE;
9640     hintRequested = bookRequested = FALSE;
9641     first.maybeThinking = FALSE;
9642     second.maybeThinking = FALSE;
9643     first.bookSuspend = FALSE; // [HGM] book
9644     second.bookSuspend = FALSE;
9645     thinkOutput[0] = NULLCHAR;
9646     lastHint[0] = NULLCHAR;
9647     ClearGameInfo(&gameInfo);
9648     gameInfo.variant = StringToVariant(appData.variant);
9649     ics_user_moved = ics_clock_paused = FALSE;
9650     ics_getting_history = H_FALSE;
9651     ics_gamenum = -1;
9652     white_holding[0] = black_holding[0] = NULLCHAR;
9653     ClearProgramStats();
9654     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9655
9656     ResetFrontEnd();
9657     ClearHighlights();
9658     flipView = appData.flipView;
9659     ClearPremoveHighlights();
9660     gotPremove = FALSE;
9661     alarmSounded = FALSE;
9662
9663     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9664     if(appData.serverMovesName != NULL) {
9665         /* [HGM] prepare to make moves file for broadcasting */
9666         clock_t t = clock();
9667         if(serverMoves != NULL) fclose(serverMoves);
9668         serverMoves = fopen(appData.serverMovesName, "r");
9669         if(serverMoves != NULL) {
9670             fclose(serverMoves);
9671             /* delay 15 sec before overwriting, so all clients can see end */
9672             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9673         }
9674         serverMoves = fopen(appData.serverMovesName, "w");
9675     }
9676
9677     ExitAnalyzeMode();
9678     gameMode = BeginningOfGame;
9679     ModeHighlight();
9680
9681     if(appData.icsActive) gameInfo.variant = VariantNormal;
9682     currentMove = forwardMostMove = backwardMostMove = 0;
9683     InitPosition(redraw);
9684     for (i = 0; i < MAX_MOVES; i++) {
9685         if (commentList[i] != NULL) {
9686             free(commentList[i]);
9687             commentList[i] = NULL;
9688         }
9689     }
9690
9691     ResetClocks();
9692     timeRemaining[0][0] = whiteTimeRemaining;
9693     timeRemaining[1][0] = blackTimeRemaining;
9694     if (first.pr == NULL) {
9695         StartChessProgram(&first);
9696     }
9697     if (init) {
9698             InitChessProgram(&first, startedFromSetupPosition);
9699     }
9700
9701     DisplayTitle("");
9702     DisplayMessage("", "");
9703     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9704     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9705     return;
9706 }
9707
9708 void
9709 AutoPlayGameLoop()
9710 {
9711     for (;;) {
9712         if (!AutoPlayOneMove())
9713           return;
9714         if (matchMode || appData.timeDelay == 0)
9715           continue;
9716         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9717           return;
9718         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9719         break;
9720     }
9721 }
9722
9723
9724 int
9725 AutoPlayOneMove()
9726 {
9727     int fromX, fromY, toX, toY;
9728
9729     if (appData.debugMode) {
9730       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9731     }
9732
9733     if (gameMode != PlayFromGameFile)
9734       return FALSE;
9735
9736     if (currentMove >= forwardMostMove) {
9737       gameMode = EditGame;
9738       ModeHighlight();
9739
9740       /* [AS] Clear current move marker at the end of a game */
9741       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9742
9743       return FALSE;
9744     }
9745
9746     toX = moveList[currentMove][2] - AAA;
9747     toY = moveList[currentMove][3] - ONE;
9748
9749     if (moveList[currentMove][1] == '@') {
9750         if (appData.highlightLastMove) {
9751             SetHighlights(-1, -1, toX, toY);
9752         }
9753     } else {
9754         fromX = moveList[currentMove][0] - AAA;
9755         fromY = moveList[currentMove][1] - ONE;
9756
9757         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9758
9759         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9760
9761         if (appData.highlightLastMove) {
9762             SetHighlights(fromX, fromY, toX, toY);
9763         }
9764     }
9765     DisplayMove(currentMove);
9766     SendMoveToProgram(currentMove++, &first);
9767     DisplayBothClocks();
9768     DrawPosition(FALSE, boards[currentMove]);
9769     // [HGM] PV info: always display, routine tests if empty
9770     DisplayComment(currentMove - 1, commentList[currentMove]);
9771     return TRUE;
9772 }
9773
9774
9775 int
9776 LoadGameOneMove(readAhead)
9777      ChessMove readAhead;
9778 {
9779     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9780     char promoChar = NULLCHAR;
9781     ChessMove moveType;
9782     char move[MSG_SIZ];
9783     char *p, *q;
9784
9785     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9786         gameMode != AnalyzeMode && gameMode != Training) {
9787         gameFileFP = NULL;
9788         return FALSE;
9789     }
9790
9791     yyboardindex = forwardMostMove;
9792     if (readAhead != (ChessMove)0) {
9793       moveType = readAhead;
9794     } else {
9795       if (gameFileFP == NULL)
9796           return FALSE;
9797       moveType = (ChessMove) yylex();
9798     }
9799
9800     done = FALSE;
9801     switch (moveType) {
9802       case Comment:
9803         if (appData.debugMode)
9804           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9805         p = yy_text;
9806
9807         /* append the comment but don't display it */
9808         AppendComment(currentMove, p, FALSE);
9809         return TRUE;
9810
9811       case WhiteCapturesEnPassant:
9812       case BlackCapturesEnPassant:
9813       case WhitePromotionChancellor:
9814       case BlackPromotionChancellor:
9815       case WhitePromotionArchbishop:
9816       case BlackPromotionArchbishop:
9817       case WhitePromotionCentaur:
9818       case BlackPromotionCentaur:
9819       case WhitePromotionQueen:
9820       case BlackPromotionQueen:
9821       case WhitePromotionRook:
9822       case BlackPromotionRook:
9823       case WhitePromotionBishop:
9824       case BlackPromotionBishop:
9825       case WhitePromotionKnight:
9826       case BlackPromotionKnight:
9827       case WhitePromotionKing:
9828       case BlackPromotionKing:
9829       case NormalMove:
9830       case WhiteKingSideCastle:
9831       case WhiteQueenSideCastle:
9832       case BlackKingSideCastle:
9833       case BlackQueenSideCastle:
9834       case WhiteKingSideCastleWild:
9835       case WhiteQueenSideCastleWild:
9836       case BlackKingSideCastleWild:
9837       case BlackQueenSideCastleWild:
9838       /* PUSH Fabien */
9839       case WhiteHSideCastleFR:
9840       case WhiteASideCastleFR:
9841       case BlackHSideCastleFR:
9842       case BlackASideCastleFR:
9843       /* POP Fabien */
9844         if (appData.debugMode)
9845           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9846         fromX = currentMoveString[0] - AAA;
9847         fromY = currentMoveString[1] - ONE;
9848         toX = currentMoveString[2] - AAA;
9849         toY = currentMoveString[3] - ONE;
9850         promoChar = currentMoveString[4];
9851         break;
9852
9853       case WhiteDrop:
9854       case BlackDrop:
9855         if (appData.debugMode)
9856           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9857         fromX = moveType == WhiteDrop ?
9858           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9859         (int) CharToPiece(ToLower(currentMoveString[0]));
9860         fromY = DROP_RANK;
9861         toX = currentMoveString[2] - AAA;
9862         toY = currentMoveString[3] - ONE;
9863         break;
9864
9865       case WhiteWins:
9866       case BlackWins:
9867       case GameIsDrawn:
9868       case GameUnfinished:
9869         if (appData.debugMode)
9870           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9871         p = strchr(yy_text, '{');
9872         if (p == NULL) p = strchr(yy_text, '(');
9873         if (p == NULL) {
9874             p = yy_text;
9875             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9876         } else {
9877             q = strchr(p, *p == '{' ? '}' : ')');
9878             if (q != NULL) *q = NULLCHAR;
9879             p++;
9880         }
9881         GameEnds(moveType, p, GE_FILE);
9882         done = TRUE;
9883         if (cmailMsgLoaded) {
9884             ClearHighlights();
9885             flipView = WhiteOnMove(currentMove);
9886             if (moveType == GameUnfinished) flipView = !flipView;
9887             if (appData.debugMode)
9888               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9889         }
9890         break;
9891
9892       case (ChessMove) 0:       /* end of file */
9893         if (appData.debugMode)
9894           fprintf(debugFP, "Parser hit end of file\n");
9895         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9896           case MT_NONE:
9897           case MT_CHECK:
9898             break;
9899           case MT_CHECKMATE:
9900           case MT_STAINMATE:
9901             if (WhiteOnMove(currentMove)) {
9902                 GameEnds(BlackWins, "Black mates", GE_FILE);
9903             } else {
9904                 GameEnds(WhiteWins, "White mates", GE_FILE);
9905             }
9906             break;
9907           case MT_STALEMATE:
9908             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9909             break;
9910         }
9911         done = TRUE;
9912         break;
9913
9914       case MoveNumberOne:
9915         if (lastLoadGameStart == GNUChessGame) {
9916             /* GNUChessGames have numbers, but they aren't move numbers */
9917             if (appData.debugMode)
9918               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9919                       yy_text, (int) moveType);
9920             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9921         }
9922         /* else fall thru */
9923
9924       case XBoardGame:
9925       case GNUChessGame:
9926       case PGNTag:
9927         /* Reached start of next game in file */
9928         if (appData.debugMode)
9929           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9930         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9931           case MT_NONE:
9932           case MT_CHECK:
9933             break;
9934           case MT_CHECKMATE:
9935           case MT_STAINMATE:
9936             if (WhiteOnMove(currentMove)) {
9937                 GameEnds(BlackWins, "Black mates", GE_FILE);
9938             } else {
9939                 GameEnds(WhiteWins, "White mates", GE_FILE);
9940             }
9941             break;
9942           case MT_STALEMATE:
9943             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9944             break;
9945         }
9946         done = TRUE;
9947         break;
9948
9949       case PositionDiagram:     /* should not happen; ignore */
9950       case ElapsedTime:         /* ignore */
9951       case NAG:                 /* ignore */
9952         if (appData.debugMode)
9953           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9954                   yy_text, (int) moveType);
9955         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9956
9957       case IllegalMove:
9958         if (appData.testLegality) {
9959             if (appData.debugMode)
9960               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9961             sprintf(move, _("Illegal move: %d.%s%s"),
9962                     (forwardMostMove / 2) + 1,
9963                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9964             DisplayError(move, 0);
9965             done = TRUE;
9966         } else {
9967             if (appData.debugMode)
9968               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9969                       yy_text, currentMoveString);
9970             fromX = currentMoveString[0] - AAA;
9971             fromY = currentMoveString[1] - ONE;
9972             toX = currentMoveString[2] - AAA;
9973             toY = currentMoveString[3] - ONE;
9974             promoChar = currentMoveString[4];
9975         }
9976         break;
9977
9978       case AmbiguousMove:
9979         if (appData.debugMode)
9980           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9981         sprintf(move, _("Ambiguous move: %d.%s%s"),
9982                 (forwardMostMove / 2) + 1,
9983                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9984         DisplayError(move, 0);
9985         done = TRUE;
9986         break;
9987
9988       default:
9989       case ImpossibleMove:
9990         if (appData.debugMode)
9991           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9992         sprintf(move, _("Illegal move: %d.%s%s"),
9993                 (forwardMostMove / 2) + 1,
9994                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9995         DisplayError(move, 0);
9996         done = TRUE;
9997         break;
9998     }
9999
10000     if (done) {
10001         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10002             DrawPosition(FALSE, boards[currentMove]);
10003             DisplayBothClocks();
10004             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10005               DisplayComment(currentMove - 1, commentList[currentMove]);
10006         }
10007         (void) StopLoadGameTimer();
10008         gameFileFP = NULL;
10009         cmailOldMove = forwardMostMove;
10010         return FALSE;
10011     } else {
10012         /* currentMoveString is set as a side-effect of yylex */
10013         strcat(currentMoveString, "\n");
10014         strcpy(moveList[forwardMostMove], currentMoveString);
10015
10016         thinkOutput[0] = NULLCHAR;
10017         MakeMove(fromX, fromY, toX, toY, promoChar);
10018         currentMove = forwardMostMove;
10019         return TRUE;
10020     }
10021 }
10022
10023 /* Load the nth game from the given file */
10024 int
10025 LoadGameFromFile(filename, n, title, useList)
10026      char *filename;
10027      int n;
10028      char *title;
10029      /*Boolean*/ int useList;
10030 {
10031     FILE *f;
10032     char buf[MSG_SIZ];
10033
10034     if (strcmp(filename, "-") == 0) {
10035         f = stdin;
10036         title = "stdin";
10037     } else {
10038         f = fopen(filename, "rb");
10039         if (f == NULL) {
10040           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10041             DisplayError(buf, errno);
10042             return FALSE;
10043         }
10044     }
10045     if (fseek(f, 0, 0) == -1) {
10046         /* f is not seekable; probably a pipe */
10047         useList = FALSE;
10048     }
10049     if (useList && n == 0) {
10050         int error = GameListBuild(f);
10051         if (error) {
10052             DisplayError(_("Cannot build game list"), error);
10053         } else if (!ListEmpty(&gameList) &&
10054                    ((ListGame *) gameList.tailPred)->number > 1) {
10055           // TODO convert to GTK
10056           //        GameListPopUp(f, title);
10057             return TRUE;
10058         }
10059         GameListDestroy();
10060         n = 1;
10061     }
10062     if (n == 0) n = 1;
10063     return LoadGame(f, n, title, FALSE);
10064 }
10065
10066
10067 void
10068 MakeRegisteredMove()
10069 {
10070     int fromX, fromY, toX, toY;
10071     char promoChar;
10072     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10073         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10074           case CMAIL_MOVE:
10075           case CMAIL_DRAW:
10076             if (appData.debugMode)
10077               fprintf(debugFP, "Restoring %s for game %d\n",
10078                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10079
10080             thinkOutput[0] = NULLCHAR;
10081             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
10082             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10083             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10084             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10085             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10086             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10087             MakeMove(fromX, fromY, toX, toY, promoChar);
10088             ShowMove(fromX, fromY, toX, toY);
10089             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10090               case MT_NONE:
10091               case MT_CHECK:
10092                 break;
10093
10094               case MT_CHECKMATE:
10095               case MT_STAINMATE:
10096                 if (WhiteOnMove(currentMove)) {
10097                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10098                 } else {
10099                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10100                 }
10101                 break;
10102
10103               case MT_STALEMATE:
10104                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10105                 break;
10106             }
10107
10108             break;
10109
10110           case CMAIL_RESIGN:
10111             if (WhiteOnMove(currentMove)) {
10112                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10113             } else {
10114                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10115             }
10116             break;
10117
10118           case CMAIL_ACCEPT:
10119             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10120             break;
10121
10122           default:
10123             break;
10124         }
10125     }
10126
10127     return;
10128 }
10129
10130 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10131 int
10132 CmailLoadGame(f, gameNumber, title, useList)
10133      FILE *f;
10134      int gameNumber;
10135      char *title;
10136      int useList;
10137 {
10138     int retVal;
10139
10140     if (gameNumber > nCmailGames) {
10141         DisplayError(_("No more games in this message"), 0);
10142         return FALSE;
10143     }
10144     if (f == lastLoadGameFP) {
10145         int offset = gameNumber - lastLoadGameNumber;
10146         if (offset == 0) {
10147             cmailMsg[0] = NULLCHAR;
10148             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10149                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10150                 nCmailMovesRegistered--;
10151             }
10152             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10153             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10154                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10155             }
10156         } else {
10157             if (! RegisterMove()) return FALSE;
10158         }
10159     }
10160
10161     retVal = LoadGame(f, gameNumber, title, useList);
10162
10163     /* Make move registered during previous look at this game, if any */
10164     MakeRegisteredMove();
10165
10166     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10167         commentList[currentMove]
10168           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10169         DisplayComment(currentMove - 1, commentList[currentMove]);
10170     }
10171
10172     return retVal;
10173 }
10174
10175 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10176 int
10177 ReloadGame(offset)
10178      int offset;
10179 {
10180     int gameNumber = lastLoadGameNumber + offset;
10181     if (lastLoadGameFP == NULL) {
10182         DisplayError(_("No game has been loaded yet"), 0);
10183         return FALSE;
10184     }
10185     if (gameNumber <= 0) {
10186         DisplayError(_("Can't back up any further"), 0);
10187         return FALSE;
10188     }
10189     if (cmailMsgLoaded) {
10190         return CmailLoadGame(lastLoadGameFP, gameNumber,
10191                              lastLoadGameTitle, lastLoadGameUseList);
10192     } else {
10193         return LoadGame(lastLoadGameFP, gameNumber,
10194                         lastLoadGameTitle, lastLoadGameUseList);
10195     }
10196 }
10197
10198
10199
10200 /* Load the nth game from open file f */
10201 int
10202 LoadGame(f, gameNumber, title, useList)
10203      FILE *f;
10204      int gameNumber;
10205      char *title;
10206      int useList;
10207 {
10208     ChessMove cm;
10209     char buf[MSG_SIZ];
10210     int gn = gameNumber;
10211     ListGame *lg = NULL;
10212     int numPGNTags = 0;
10213     int err;
10214     GameMode oldGameMode;
10215     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10216
10217     if (appData.debugMode)
10218         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10219
10220     if (gameMode == Training )
10221         SetTrainingModeOff();
10222
10223     oldGameMode = gameMode;
10224     if (gameMode != BeginningOfGame) 
10225       {
10226         Reset(FALSE, TRUE);
10227       };
10228
10229     gameFileFP = f;
10230     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
10231       {
10232         fclose(lastLoadGameFP);
10233       };
10234
10235     if (useList) 
10236       {
10237         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10238         
10239         if (lg) 
10240           {
10241             fseek(f, lg->offset, 0);
10242             GameListHighlight(gameNumber);
10243             gn = 1;
10244           }
10245         else 
10246           {
10247             DisplayError(_("Game number out of range"), 0);
10248             return FALSE;
10249           };
10250       } 
10251     else 
10252       {
10253         GameListDestroy();
10254         if (fseek(f, 0, 0) == -1) 
10255           {
10256             if (f == lastLoadGameFP ?
10257                 gameNumber == lastLoadGameNumber + 1 :
10258                 gameNumber == 1) 
10259               {
10260                 gn = 1;
10261               } 
10262             else 
10263               {
10264                 DisplayError(_("Can't seek on game file"), 0);
10265                 return FALSE;
10266               };
10267           };
10268       };
10269
10270     lastLoadGameFP      = f;
10271     lastLoadGameNumber  = gameNumber;
10272     strcpy(lastLoadGameTitle, title);
10273     lastLoadGameUseList = useList;
10274
10275     yynewfile(f);
10276
10277     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
10278       {
10279         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10280                  lg->gameInfo.black);
10281         DisplayTitle(buf);
10282       } 
10283     else if (*title != NULLCHAR) 
10284       {
10285         if (gameNumber > 1) 
10286           {
10287             sprintf(buf, "%s %d", title, gameNumber);
10288             DisplayTitle(buf);
10289           } 
10290         else 
10291           {
10292             DisplayTitle(title);
10293           };
10294       };
10295
10296     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
10297       {
10298         gameMode = PlayFromGameFile;
10299         ModeHighlight();
10300       };
10301
10302     currentMove = forwardMostMove = backwardMostMove = 0;
10303     CopyBoard(boards[0], initialPosition);
10304     StopClocks();
10305
10306     /*
10307      * Skip the first gn-1 games in the file.
10308      * Also skip over anything that precedes an identifiable
10309      * start of game marker, to avoid being confused by
10310      * garbage at the start of the file.  Currently
10311      * recognized start of game markers are the move number "1",
10312      * the pattern "gnuchess .* game", the pattern
10313      * "^[#;%] [^ ]* game file", and a PGN tag block.
10314      * A game that starts with one of the latter two patterns
10315      * will also have a move number 1, possibly
10316      * following a position diagram.
10317      * 5-4-02: Let's try being more lenient and allowing a game to
10318      * start with an unnumbered move.  Does that break anything?
10319      */
10320     cm = lastLoadGameStart = (ChessMove) 0;
10321     while (gn > 0) {
10322         yyboardindex = forwardMostMove;
10323         cm = (ChessMove) yylex();
10324         switch (cm) {
10325           case (ChessMove) 0:
10326             if (cmailMsgLoaded) {
10327                 nCmailGames = CMAIL_MAX_GAMES - gn;
10328             } else {
10329                 Reset(TRUE, TRUE);
10330                 DisplayError(_("Game not found in file"), 0);
10331             }
10332             return FALSE;
10333
10334           case GNUChessGame:
10335           case XBoardGame:
10336             gn--;
10337             lastLoadGameStart = cm;
10338             break;
10339
10340           case MoveNumberOne:
10341             switch (lastLoadGameStart) {
10342               case GNUChessGame:
10343               case XBoardGame:
10344               case PGNTag:
10345                 break;
10346               case MoveNumberOne:
10347               case (ChessMove) 0:
10348                 gn--;           /* count this game */
10349                 lastLoadGameStart = cm;
10350                 break;
10351               default:
10352                 /* impossible */
10353                 break;
10354             }
10355             break;
10356
10357           case PGNTag:
10358             switch (lastLoadGameStart) {
10359               case GNUChessGame:
10360               case PGNTag:
10361               case MoveNumberOne:
10362               case (ChessMove) 0:
10363                 gn--;           /* count this game */
10364                 lastLoadGameStart = cm;
10365                 break;
10366               case XBoardGame:
10367                 lastLoadGameStart = cm; /* game counted already */
10368                 break;
10369               default:
10370                 /* impossible */
10371                 break;
10372             }
10373             if (gn > 0) {
10374                 do {
10375                     yyboardindex = forwardMostMove;
10376                     cm = (ChessMove) yylex();
10377                 } while (cm == PGNTag || cm == Comment);
10378             }
10379             break;
10380
10381           case WhiteWins:
10382           case BlackWins:
10383           case GameIsDrawn:
10384             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10385                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10386                     != CMAIL_OLD_RESULT) {
10387                     nCmailResults ++ ;
10388                     cmailResult[  CMAIL_MAX_GAMES
10389                                 - gn - 1] = CMAIL_OLD_RESULT;
10390                 }
10391             }
10392             break;
10393
10394           case NormalMove:
10395             /* Only a NormalMove can be at the start of a game
10396              * without a position diagram. */
10397             if (lastLoadGameStart == (ChessMove) 0) {
10398               gn--;
10399               lastLoadGameStart = MoveNumberOne;
10400             }
10401             break;
10402
10403           default:
10404             break;
10405         }
10406     }
10407
10408     if (appData.debugMode)
10409       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10410
10411     if (cm == XBoardGame) {
10412         /* Skip any header junk before position diagram and/or move 1 */
10413         for (;;) {
10414             yyboardindex = forwardMostMove;
10415             cm = (ChessMove) yylex();
10416
10417             if (cm == (ChessMove) 0 ||
10418                 cm == GNUChessGame || cm == XBoardGame) {
10419                 /* Empty game; pretend end-of-file and handle later */
10420                 cm = (ChessMove) 0;
10421                 break;
10422             }
10423
10424             if (cm == MoveNumberOne || cm == PositionDiagram ||
10425                 cm == PGNTag || cm == Comment)
10426               break;
10427         }
10428     } else if (cm == GNUChessGame) {
10429         if (gameInfo.event != NULL) {
10430             free(gameInfo.event);
10431         }
10432         gameInfo.event = StrSave(yy_text);
10433     }
10434
10435     startedFromSetupPosition = FALSE;
10436     while (cm == PGNTag) {
10437         if (appData.debugMode)
10438           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10439         err = ParsePGNTag(yy_text, &gameInfo);
10440         if (!err) numPGNTags++;
10441
10442         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10443         if(gameInfo.variant != oldVariant) {
10444             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10445             InitPosition(TRUE);
10446             oldVariant = gameInfo.variant;
10447             if (appData.debugMode)
10448               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10449         }
10450
10451
10452         if (gameInfo.fen != NULL) {
10453           Board initial_position;
10454           startedFromSetupPosition = TRUE;
10455           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10456             Reset(TRUE, TRUE);
10457             DisplayError(_("Bad FEN position in file"), 0);
10458             return FALSE;
10459           }
10460           CopyBoard(boards[0], initial_position);
10461           if (blackPlaysFirst) {
10462             currentMove = forwardMostMove = backwardMostMove = 1;
10463             CopyBoard(boards[1], initial_position);
10464             strcpy(moveList[0], "");
10465             strcpy(parseList[0], "");
10466             timeRemaining[0][1] = whiteTimeRemaining;
10467             timeRemaining[1][1] = blackTimeRemaining;
10468             if (commentList[0] != NULL) {
10469               commentList[1] = commentList[0];
10470               commentList[0] = NULL;
10471             }
10472           } else {
10473             currentMove = forwardMostMove = backwardMostMove = 0;
10474           }
10475           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10476           {   int i;
10477               initialRulePlies = FENrulePlies;
10478               for( i=0; i< nrCastlingRights; i++ )
10479                   initialRights[i] = initial_position[CASTLING][i];
10480           }
10481           yyboardindex = forwardMostMove;
10482           free(gameInfo.fen);
10483           gameInfo.fen = NULL;
10484         }
10485
10486         yyboardindex = forwardMostMove;
10487         cm = (ChessMove) yylex();
10488
10489         /* Handle comments interspersed among the tags */
10490         while (cm == Comment) {
10491             char *p;
10492             if (appData.debugMode)
10493               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10494             p = yy_text;
10495             AppendComment(currentMove, p, FALSE);
10496             yyboardindex = forwardMostMove;
10497             cm = (ChessMove) yylex();
10498         }
10499     }
10500
10501     /* don't rely on existence of Event tag since if game was
10502      * pasted from clipboard the Event tag may not exist
10503      */
10504     if (numPGNTags > 0){
10505         char *tags;
10506         if (gameInfo.variant == VariantNormal) {
10507           gameInfo.variant = StringToVariant(gameInfo.event);
10508         }
10509         if (!matchMode) {
10510           if( appData.autoDisplayTags ) {
10511             tags = PGNTags(&gameInfo);
10512             TagsPopUp(tags, CmailMsg());
10513             free(tags);
10514           }
10515         }
10516     } else {
10517         /* Make something up, but don't display it now */
10518         SetGameInfo();
10519         TagsPopDown();
10520     }
10521
10522     if (cm == PositionDiagram) {
10523         int i, j;
10524         char *p;
10525         Board initial_position;
10526
10527         if (appData.debugMode)
10528           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10529
10530         if (!startedFromSetupPosition) {
10531             p = yy_text;
10532             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10533               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10534                 switch (*p) {
10535                   case '[':
10536                   case '-':
10537                   case ' ':
10538                   case '\t':
10539                   case '\n':
10540                   case '\r':
10541                     break;
10542                   default:
10543                     initial_position[i][j++] = CharToPiece(*p);
10544                     break;
10545                 }
10546             while (*p == ' ' || *p == '\t' ||
10547                    *p == '\n' || *p == '\r') p++;
10548
10549             if (strncmp(p, "black", strlen("black"))==0)
10550               blackPlaysFirst = TRUE;
10551             else
10552               blackPlaysFirst = FALSE;
10553             startedFromSetupPosition = TRUE;
10554
10555             CopyBoard(boards[0], initial_position);
10556             if (blackPlaysFirst) {
10557                 currentMove = forwardMostMove = backwardMostMove = 1;
10558                 CopyBoard(boards[1], initial_position);
10559                 strcpy(moveList[0], "");
10560                 strcpy(parseList[0], "");
10561                 timeRemaining[0][1] = whiteTimeRemaining;
10562                 timeRemaining[1][1] = blackTimeRemaining;
10563                 if (commentList[0] != NULL) {
10564                     commentList[1] = commentList[0];
10565                     commentList[0] = NULL;
10566                 }
10567             } else {
10568                 currentMove = forwardMostMove = backwardMostMove = 0;
10569             }
10570         }
10571         yyboardindex = forwardMostMove;
10572         cm = (ChessMove) yylex();
10573     }
10574
10575     if (first.pr == NoProc) {
10576         StartChessProgram(&first);
10577     }
10578     InitChessProgram(&first, FALSE);
10579     SendToProgram("force\n", &first);
10580     if (startedFromSetupPosition) {
10581         SendBoard(&first, forwardMostMove);
10582     if (appData.debugMode) {
10583         fprintf(debugFP, "Load Game\n");
10584     }
10585         DisplayBothClocks();
10586     }
10587
10588     /* [HGM] server: flag to write setup moves in broadcast file as one */
10589     loadFlag = appData.suppressLoadMoves;
10590
10591     while (cm == Comment) {
10592         char *p;
10593         if (appData.debugMode)
10594           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10595         p = yy_text;
10596         AppendComment(currentMove, p, FALSE);
10597         yyboardindex = forwardMostMove;
10598         cm = (ChessMove) yylex();
10599     }
10600
10601     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10602         cm == WhiteWins || cm == BlackWins ||
10603         cm == GameIsDrawn || cm == GameUnfinished) {
10604         DisplayMessage("", _("No moves in game"));
10605         if (cmailMsgLoaded) {
10606             if (appData.debugMode)
10607               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10608             ClearHighlights();
10609             flipView = FALSE;
10610         }
10611         DrawPosition(FALSE, boards[currentMove]);
10612         DisplayBothClocks();
10613         gameMode = EditGame;
10614         ModeHighlight();
10615         gameFileFP = NULL;
10616         cmailOldMove = 0;
10617         return TRUE;
10618     }
10619
10620     // [HGM] PV info: routine tests if comment empty
10621     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10622         DisplayComment(currentMove - 1, commentList[currentMove]);
10623     }
10624     if (!matchMode && appData.timeDelay != 0)
10625       DrawPosition(FALSE, boards[currentMove]);
10626
10627     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10628       programStats.ok_to_send = 1;
10629     }
10630
10631     /* if the first token after the PGN tags is a move
10632      * and not move number 1, retrieve it from the parser
10633      */
10634     if (cm != MoveNumberOne)
10635         LoadGameOneMove(cm);
10636
10637     /* load the remaining moves from the file */
10638     while (LoadGameOneMove((ChessMove)0)) {
10639       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10640       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10641     }
10642
10643     /* rewind to the start of the game */
10644     currentMove = backwardMostMove;
10645
10646     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10647
10648     if (oldGameMode == AnalyzeFile ||
10649         oldGameMode == AnalyzeMode) {
10650       AnalyzeFileEvent();
10651     }
10652
10653     if (matchMode || appData.timeDelay == 0) {
10654       ToEndEvent();
10655       gameMode = EditGame;
10656       ModeHighlight();
10657     } else if (appData.timeDelay > 0) {
10658       AutoPlayGameLoop();
10659     }
10660
10661     if (appData.debugMode)
10662         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10663
10664     loadFlag = 0; /* [HGM] true game starts */
10665     return TRUE;
10666 }
10667
10668 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10669 int
10670 ReloadPosition(offset)
10671      int offset;
10672 {
10673     int positionNumber = lastLoadPositionNumber + offset;
10674     if (lastLoadPositionFP == NULL) {
10675         DisplayError(_("No position has been loaded yet"), 0);
10676         return FALSE;
10677     }
10678     if (positionNumber <= 0) {
10679         DisplayError(_("Can't back up any further"), 0);
10680         return FALSE;
10681     }
10682     return LoadPosition(lastLoadPositionFP, positionNumber,
10683                         lastLoadPositionTitle);
10684 }
10685
10686 /* Load the nth position from the given file */
10687 int
10688 LoadPositionFromFile(filename, n, title)
10689      char *filename;
10690      int n;
10691      char *title;
10692 {
10693     FILE *f;
10694     char buf[MSG_SIZ];
10695
10696     if (strcmp(filename, "-") == 0) {
10697         return LoadPosition(stdin, n, "stdin");
10698     } else {
10699         f = fopen(filename, "rb");
10700         if (f == NULL) {
10701             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10702             DisplayError(buf, errno);
10703             return FALSE;
10704         } else {
10705             return LoadPosition(f, n, title);
10706         }
10707     }
10708 }
10709
10710 /* Load the nth position from the given open file, and close it */
10711 int
10712 LoadPosition(f, positionNumber, title)
10713      FILE *f;
10714      int positionNumber;
10715      char *title;
10716 {
10717     char *p, line[MSG_SIZ];
10718     Board initial_position;
10719     int i, j, fenMode, pn;
10720
10721     if (gameMode == Training )
10722         SetTrainingModeOff();
10723
10724     if (gameMode != BeginningOfGame) {
10725         Reset(FALSE, TRUE);
10726     }
10727     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10728         fclose(lastLoadPositionFP);
10729     }
10730     if (positionNumber == 0) positionNumber = 1;
10731     lastLoadPositionFP = f;
10732     lastLoadPositionNumber = positionNumber;
10733     strcpy(lastLoadPositionTitle, title);
10734     if (first.pr == NoProc) {
10735       StartChessProgram(&first);
10736       InitChessProgram(&first, FALSE);
10737     }
10738     pn = positionNumber;
10739     if (positionNumber < 0) {
10740         /* Negative position number means to seek to that byte offset */
10741         if (fseek(f, -positionNumber, 0) == -1) {
10742             DisplayError(_("Can't seek on position file"), 0);
10743             return FALSE;
10744         };
10745         pn = 1;
10746     } else {
10747         if (fseek(f, 0, 0) == -1) {
10748             if (f == lastLoadPositionFP ?
10749                 positionNumber == lastLoadPositionNumber + 1 :
10750                 positionNumber == 1) {
10751                 pn = 1;
10752             } else {
10753                 DisplayError(_("Can't seek on position file"), 0);
10754                 return FALSE;
10755             }
10756         }
10757     }
10758     /* See if this file is FEN or old-style xboard */
10759     if (fgets(line, MSG_SIZ, f) == NULL) {
10760         DisplayError(_("Position not found in file"), 0);
10761         return FALSE;
10762     }
10763     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10764     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10765
10766     if (pn >= 2) {
10767         if (fenMode || line[0] == '#') pn--;
10768         while (pn > 0) {
10769             /* skip positions before number pn */
10770             if (fgets(line, MSG_SIZ, f) == NULL) {
10771                 Reset(TRUE, TRUE);
10772                 DisplayError(_("Position not found in file"), 0);
10773                 return FALSE;
10774             }
10775             if (fenMode || line[0] == '#') pn--;
10776         }
10777     }
10778
10779     if (fenMode) {
10780         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10781             DisplayError(_("Bad FEN position in file"), 0);
10782             return FALSE;
10783         }
10784     } else {
10785         (void) fgets(line, MSG_SIZ, f);
10786         (void) fgets(line, MSG_SIZ, f);
10787
10788         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10789             (void) fgets(line, MSG_SIZ, f);
10790             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10791                 if (*p == ' ')
10792                   continue;
10793                 initial_position[i][j++] = CharToPiece(*p);
10794             }
10795         }
10796
10797         blackPlaysFirst = FALSE;
10798         if (!feof(f)) {
10799             (void) fgets(line, MSG_SIZ, f);
10800             if (strncmp(line, "black", strlen("black"))==0)
10801               blackPlaysFirst = TRUE;
10802         }
10803     }
10804     startedFromSetupPosition = TRUE;
10805
10806     SendToProgram("force\n", &first);
10807     CopyBoard(boards[0], initial_position);
10808     if (blackPlaysFirst) {
10809         currentMove = forwardMostMove = backwardMostMove = 1;
10810         strcpy(moveList[0], "");
10811         strcpy(parseList[0], "");
10812         CopyBoard(boards[1], initial_position);
10813         DisplayMessage("", _("Black to play"));
10814     } else {
10815         currentMove = forwardMostMove = backwardMostMove = 0;
10816         DisplayMessage("", _("White to play"));
10817     }
10818     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10819     SendBoard(&first, forwardMostMove);
10820     if (appData.debugMode) {
10821 int i, j;
10822   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10823   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10824         fprintf(debugFP, "Load Position\n");
10825     }
10826
10827     if (positionNumber > 1) {
10828         sprintf(line, "%s %d", title, positionNumber);
10829         DisplayTitle(line);
10830     } else {
10831         DisplayTitle(title);
10832     }
10833     gameMode = EditGame;
10834     ModeHighlight();
10835     ResetClocks();
10836     timeRemaining[0][1] = whiteTimeRemaining;
10837     timeRemaining[1][1] = blackTimeRemaining;
10838     DrawPosition(FALSE, boards[currentMove]);
10839
10840     return TRUE;
10841 }
10842
10843
10844 void
10845 CopyPlayerNameIntoFileName(dest, src)
10846      char **dest, *src;
10847 {
10848     while (*src != NULLCHAR && *src != ',') {
10849         if (*src == ' ') {
10850             *(*dest)++ = '_';
10851             src++;
10852         } else {
10853             *(*dest)++ = *src++;
10854         }
10855     }
10856 }
10857
10858 char *DefaultFileName(ext)
10859      char *ext;
10860 {
10861     static char def[MSG_SIZ];
10862     char *p;
10863
10864     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10865         p = def;
10866         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10867         *p++ = '-';
10868         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10869         *p++ = '.';
10870         strcpy(p, ext);
10871     } else {
10872         def[0] = NULLCHAR;
10873     }
10874     return def;
10875 }
10876
10877 /* Save the current game to the given file */
10878 int
10879 SaveGameToFile(filename, append)
10880      char *filename;
10881      int append;
10882 {
10883     FILE *f;
10884     char buf[MSG_SIZ];
10885
10886     if (strcmp(filename, "-") == 0) {
10887         return SaveGame(stdout, 0, NULL);
10888     } else {
10889         f = fopen(filename, append ? "a" : "w");
10890         if (f == NULL) {
10891             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10892             DisplayError(buf, errno);
10893             return FALSE;
10894         } else {
10895             return SaveGame(f, 0, NULL);
10896         }
10897     }
10898 }
10899
10900 char *
10901 SavePart(str)
10902      char *str;
10903 {
10904     static char buf[MSG_SIZ];
10905     char *p;
10906
10907     p = strchr(str, ' ');
10908     if (p == NULL) return str;
10909     strncpy(buf, str, p - str);
10910     buf[p - str] = NULLCHAR;
10911     return buf;
10912 }
10913
10914 #define PGN_MAX_LINE 75
10915
10916 #define PGN_SIDE_WHITE  0
10917 #define PGN_SIDE_BLACK  1
10918
10919 /* [AS] */
10920 static int FindFirstMoveOutOfBook( int side )
10921 {
10922     int result = -1;
10923
10924     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10925         int index = backwardMostMove;
10926         int has_book_hit = 0;
10927
10928         if( (index % 2) != side ) {
10929             index++;
10930         }
10931
10932         while( index < forwardMostMove ) {
10933             /* Check to see if engine is in book */
10934             int depth = pvInfoList[index].depth;
10935             int score = pvInfoList[index].score;
10936             int in_book = 0;
10937
10938             if( depth <= 2 ) {
10939                 in_book = 1;
10940             }
10941             else if( score == 0 && depth == 63 ) {
10942                 in_book = 1; /* Zappa */
10943             }
10944             else if( score == 2 && depth == 99 ) {
10945                 in_book = 1; /* Abrok */
10946             }
10947
10948             has_book_hit += in_book;
10949
10950             if( ! in_book ) {
10951                 result = index;
10952
10953                 break;
10954             }
10955
10956             index += 2;
10957         }
10958     }
10959
10960     return result;
10961 }
10962
10963 /* [AS] */
10964 void GetOutOfBookInfo( char * buf )
10965 {
10966     int oob[2];
10967     int i;
10968     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10969
10970     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10971     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10972
10973     *buf = '\0';
10974
10975     if( oob[0] >= 0 || oob[1] >= 0 ) {
10976         for( i=0; i<2; i++ ) {
10977             int idx = oob[i];
10978
10979             if( idx >= 0 ) {
10980                 if( i > 0 && oob[0] >= 0 ) {
10981                     strcat( buf, "   " );
10982                 }
10983
10984                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10985                 sprintf( buf+strlen(buf), "%s%.2f",
10986                     pvInfoList[idx].score >= 0 ? "+" : "",
10987                     pvInfoList[idx].score / 100.0 );
10988             }
10989         }
10990     }
10991 }
10992
10993 /* Save game in PGN style and close the file */
10994 int
10995 SaveGamePGN(f)
10996      FILE *f;
10997 {
10998     int i, offset, linelen, newblock;
10999     time_t tm;
11000 //    char *movetext;
11001     char numtext[32];
11002     int movelen, numlen, blank;
11003     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11004
11005     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11006
11007     tm = time((time_t *) NULL);
11008
11009     PrintPGNTags(f, &gameInfo);
11010
11011     if (backwardMostMove > 0 || startedFromSetupPosition) {
11012         char *fen = PositionToFEN(backwardMostMove, NULL);
11013         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11014         fprintf(f, "\n{--------------\n");
11015         PrintPosition(f, backwardMostMove);
11016         fprintf(f, "--------------}\n");
11017         free(fen);
11018     }
11019     else {
11020         /* [AS] Out of book annotation */
11021         if( appData.saveOutOfBookInfo ) {
11022             char buf[64];
11023
11024             GetOutOfBookInfo( buf );
11025
11026             if( buf[0] != '\0' ) {
11027                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11028             }
11029         }
11030
11031         fprintf(f, "\n");
11032     }
11033
11034     i = backwardMostMove;
11035     linelen = 0;
11036     newblock = TRUE;
11037
11038     while (i < forwardMostMove) {
11039         /* Print comments preceding this move */
11040         if (commentList[i] != NULL) {
11041             if (linelen > 0) fprintf(f, "\n");
11042             fprintf(f, "%s", commentList[i]);
11043             linelen = 0;
11044             newblock = TRUE;
11045         }
11046
11047         /* Format move number */
11048         if ((i % 2) == 0) {
11049             sprintf(numtext, "%d.", (i - offset)/2 + 1);
11050         } else {
11051             if (newblock) {
11052                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
11053             } else {
11054                 numtext[0] = NULLCHAR;
11055             }
11056         }
11057         numlen = strlen(numtext);
11058         newblock = FALSE;
11059
11060         /* Print move number */
11061         blank = linelen > 0 && numlen > 0;
11062         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11063             fprintf(f, "\n");
11064             linelen = 0;
11065             blank = 0;
11066         }
11067         if (blank) {
11068             fprintf(f, " ");
11069             linelen++;
11070         }
11071         fprintf(f, "%s", numtext);
11072         linelen += numlen;
11073
11074         /* Get move */
11075         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
11076         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11077
11078         /* Print move */
11079         blank = linelen > 0 && movelen > 0;
11080         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11081             fprintf(f, "\n");
11082             linelen = 0;
11083             blank = 0;
11084         }
11085         if (blank) {
11086             fprintf(f, " ");
11087             linelen++;
11088         }
11089         fprintf(f, "%s", move_buffer);
11090         linelen += movelen;
11091
11092         /* [AS] Add PV info if present */
11093         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11094             /* [HGM] add time */
11095             char buf[MSG_SIZ]; int seconds;
11096
11097             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11098
11099             if( seconds <= 0) buf[0] = 0; else
11100             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
11101                 seconds = (seconds + 4)/10; // round to full seconds
11102                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
11103                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
11104             }
11105
11106             sprintf( move_buffer, "{%s%.2f/%d%s}",
11107                 pvInfoList[i].score >= 0 ? "+" : "",
11108                 pvInfoList[i].score / 100.0,
11109                 pvInfoList[i].depth,
11110                 buf );
11111
11112             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11113
11114             /* Print score/depth */
11115             blank = linelen > 0 && movelen > 0;
11116             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11117                 fprintf(f, "\n");
11118                 linelen = 0;
11119                 blank = 0;
11120             }
11121             if (blank) {
11122                 fprintf(f, " ");
11123                 linelen++;
11124             }
11125             fprintf(f, "%s", move_buffer);
11126             linelen += movelen;
11127         }
11128
11129         i++;
11130     }
11131
11132     /* Start a new line */
11133     if (linelen > 0) fprintf(f, "\n");
11134
11135     /* Print comments after last move */
11136     if (commentList[i] != NULL) {
11137         fprintf(f, "%s\n", commentList[i]);
11138     }
11139
11140     /* Print result */
11141     if (gameInfo.resultDetails != NULL &&
11142         gameInfo.resultDetails[0] != NULLCHAR) {
11143         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11144                 PGNResult(gameInfo.result));
11145     } else {
11146         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11147     }
11148
11149     fclose(f);
11150     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11151     return TRUE;
11152 }
11153
11154 /* Save game in old style and close the file */
11155 int
11156 SaveGameOldStyle(f)
11157      FILE *f;
11158 {
11159     int i, offset;
11160     time_t tm;
11161
11162     tm = time((time_t *) NULL);
11163
11164     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11165     PrintOpponents(f);
11166
11167     if (backwardMostMove > 0 || startedFromSetupPosition) {
11168         fprintf(f, "\n[--------------\n");
11169         PrintPosition(f, backwardMostMove);
11170         fprintf(f, "--------------]\n");
11171     } else {
11172         fprintf(f, "\n");
11173     }
11174
11175     i = backwardMostMove;
11176     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11177
11178     while (i < forwardMostMove) {
11179         if (commentList[i] != NULL) {
11180             fprintf(f, "[%s]\n", commentList[i]);
11181         }
11182
11183         if ((i % 2) == 1) {
11184             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11185             i++;
11186         } else {
11187             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11188             i++;
11189             if (commentList[i] != NULL) {
11190                 fprintf(f, "\n");
11191                 continue;
11192             }
11193             if (i >= forwardMostMove) {
11194                 fprintf(f, "\n");
11195                 break;
11196             }
11197             fprintf(f, "%s\n", parseList[i]);
11198             i++;
11199         }
11200     }
11201
11202     if (commentList[i] != NULL) {
11203         fprintf(f, "[%s]\n", commentList[i]);
11204     }
11205
11206     /* This isn't really the old style, but it's close enough */
11207     if (gameInfo.resultDetails != NULL &&
11208         gameInfo.resultDetails[0] != NULLCHAR) {
11209         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11210                 gameInfo.resultDetails);
11211     } else {
11212         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11213     }
11214
11215     fclose(f);
11216     return TRUE;
11217 }
11218
11219 /* Save the current game to open file f and close the file */
11220 int
11221 SaveGame(f, dummy, dummy2)
11222      FILE *f;
11223      int dummy;
11224      char *dummy2;
11225 {
11226     if (gameMode == EditPosition) EditPositionDone(TRUE);
11227     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11228     if (appData.oldSaveStyle)
11229       return SaveGameOldStyle(f);
11230     else
11231       return SaveGamePGN(f);
11232 }
11233
11234 /* Save the current position to the given file */
11235 int
11236 SavePositionToFile(filename)
11237      char *filename;
11238 {
11239     FILE *f;
11240     char buf[MSG_SIZ];
11241
11242     if (strcmp(filename, "-") == 0) {
11243         return SavePosition(stdout, 0, NULL);
11244     } else {
11245         f = fopen(filename, "a");
11246         if (f == NULL) {
11247             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11248             DisplayError(buf, errno);
11249             return FALSE;
11250         } else {
11251             SavePosition(f, 0, NULL);
11252             return TRUE;
11253         }
11254     }
11255 }
11256
11257 /* Save the current position to the given open file and close the file */
11258 int
11259 SavePosition(f, dummy, dummy2)
11260      FILE *f;
11261      int dummy;
11262      char *dummy2;
11263 {
11264     time_t tm;
11265     char *fen;
11266     if (gameMode == EditPosition) EditPositionDone(TRUE);
11267     if (appData.oldSaveStyle) {
11268         tm = time((time_t *) NULL);
11269
11270         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11271         PrintOpponents(f);
11272         fprintf(f, "[--------------\n");
11273         PrintPosition(f, currentMove);
11274         fprintf(f, "--------------]\n");
11275     } else {
11276         fen = PositionToFEN(currentMove, NULL);
11277         fprintf(f, "%s\n", fen);
11278         free(fen);
11279     }
11280     fclose(f);
11281     return TRUE;
11282 }
11283
11284 void
11285 ReloadCmailMsgEvent(unregister)
11286      int unregister;
11287 {
11288 #if !WIN32
11289     static char *inFilename = NULL;
11290     static char *outFilename;
11291     int i;
11292     struct stat inbuf, outbuf;
11293     int status;
11294
11295     /* Any registered moves are unregistered if unregister is set, */
11296     /* i.e. invoked by the signal handler */
11297     if (unregister) {
11298         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11299             cmailMoveRegistered[i] = FALSE;
11300             if (cmailCommentList[i] != NULL) {
11301                 free(cmailCommentList[i]);
11302                 cmailCommentList[i] = NULL;
11303             }
11304         }
11305         nCmailMovesRegistered = 0;
11306     }
11307
11308     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11309         cmailResult[i] = CMAIL_NOT_RESULT;
11310     }
11311     nCmailResults = 0;
11312
11313     if (inFilename == NULL) {
11314         /* Because the filenames are static they only get malloced once  */
11315         /* and they never get freed                                      */
11316         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11317         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11318
11319         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11320         sprintf(outFilename, "%s.out", appData.cmailGameName);
11321     }
11322
11323     status = stat(outFilename, &outbuf);
11324     if (status < 0) {
11325         cmailMailedMove = FALSE;
11326     } else {
11327         status = stat(inFilename, &inbuf);
11328         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11329     }
11330
11331     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11332        counts the games, notes how each one terminated, etc.
11333
11334        It would be nice to remove this kludge and instead gather all
11335        the information while building the game list.  (And to keep it
11336        in the game list nodes instead of having a bunch of fixed-size
11337        parallel arrays.)  Note this will require getting each game's
11338        termination from the PGN tags, as the game list builder does
11339        not process the game moves.  --mann
11340        */
11341     cmailMsgLoaded = TRUE;
11342     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11343
11344     /* Load first game in the file or popup game menu */
11345     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11346
11347 #endif /* !WIN32 */
11348     return;
11349 }
11350
11351 int
11352 RegisterMove()
11353 {
11354     FILE *f;
11355     char string[MSG_SIZ];
11356
11357     if (   cmailMailedMove
11358         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11359         return TRUE;            /* Allow free viewing  */
11360     }
11361
11362     /* Unregister move to ensure that we don't leave RegisterMove        */
11363     /* with the move registered when the conditions for registering no   */
11364     /* longer hold                                                       */
11365     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11366         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11367         nCmailMovesRegistered --;
11368
11369         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11370           {
11371               free(cmailCommentList[lastLoadGameNumber - 1]);
11372               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11373           }
11374     }
11375
11376     if (cmailOldMove == -1) {
11377         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11378         return FALSE;
11379     }
11380
11381     if (currentMove > cmailOldMove + 1) {
11382         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11383         return FALSE;
11384     }
11385
11386     if (currentMove < cmailOldMove) {
11387         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11388         return FALSE;
11389     }
11390
11391     if (forwardMostMove > currentMove) {
11392         /* Silently truncate extra moves */
11393         TruncateGame();
11394     }
11395
11396     if (   (currentMove == cmailOldMove + 1)
11397         || (   (currentMove == cmailOldMove)
11398             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11399                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11400         if (gameInfo.result != GameUnfinished) {
11401             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11402         }
11403
11404         if (commentList[currentMove] != NULL) {
11405             cmailCommentList[lastLoadGameNumber - 1]
11406               = StrSave(commentList[currentMove]);
11407         }
11408         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11409
11410         if (appData.debugMode)
11411           fprintf(debugFP, "Saving %s for game %d\n",
11412                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11413
11414         sprintf(string,
11415                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11416
11417         f = fopen(string, "w");
11418         if (appData.oldSaveStyle) {
11419             SaveGameOldStyle(f); /* also closes the file */
11420
11421             sprintf(string, "%s.pos.out", appData.cmailGameName);
11422             f = fopen(string, "w");
11423             SavePosition(f, 0, NULL); /* also closes the file */
11424         } else {
11425             fprintf(f, "{--------------\n");
11426             PrintPosition(f, currentMove);
11427             fprintf(f, "--------------}\n\n");
11428
11429             SaveGame(f, 0, NULL); /* also closes the file*/
11430         }
11431
11432         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11433         nCmailMovesRegistered ++;
11434     } else if (nCmailGames == 1) {
11435         DisplayError(_("You have not made a move yet"), 0);
11436         return FALSE;
11437     }
11438
11439     return TRUE;
11440 }
11441
11442 void
11443 MailMoveEvent()
11444 {
11445 #if !WIN32
11446     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11447     FILE *commandOutput;
11448     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11449     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11450     int nBuffers;
11451     int i;
11452     int archived;
11453     char *arcDir;
11454
11455     if (! cmailMsgLoaded) {
11456         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11457         return;
11458     }
11459
11460     if (nCmailGames == nCmailResults) {
11461         DisplayError(_("No unfinished games"), 0);
11462         return;
11463     }
11464
11465 #if CMAIL_PROHIBIT_REMAIL
11466     if (cmailMailedMove) {
11467         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);
11468         DisplayError(msg, 0);
11469         return;
11470     }
11471 #endif
11472
11473     if (! (cmailMailedMove || RegisterMove())) return;
11474
11475     if (   cmailMailedMove
11476         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11477         sprintf(string, partCommandString,
11478                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11479         commandOutput = popen(string, "r");
11480
11481         if (commandOutput == NULL) {
11482             DisplayError(_("Failed to invoke cmail"), 0);
11483         } else {
11484             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11485                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11486             }
11487             if (nBuffers > 1) {
11488                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11489                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11490                 nBytes = MSG_SIZ - 1;
11491             } else {
11492                 (void) memcpy(msg, buffer, nBytes);
11493             }
11494             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11495
11496             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11497                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11498
11499                 archived = TRUE;
11500                 for (i = 0; i < nCmailGames; i ++) {
11501                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11502                         archived = FALSE;
11503                     }
11504                 }
11505                 if (   archived
11506                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11507                         != NULL)) {
11508                     sprintf(buffer, "%s/%s.%s.archive",
11509                             arcDir,
11510                             appData.cmailGameName,
11511                             gameInfo.date);
11512                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11513                     cmailMsgLoaded = FALSE;
11514                 }
11515             }
11516
11517             DisplayInformation(msg);
11518             pclose(commandOutput);
11519         }
11520     } else {
11521         if ((*cmailMsg) != '\0') {
11522             DisplayInformation(cmailMsg);
11523         }
11524     }
11525
11526     return;
11527 #endif /* !WIN32 */
11528 }
11529
11530 char *
11531 CmailMsg()
11532 {
11533 #if WIN32
11534     return NULL;
11535 #else
11536     int  prependComma = 0;
11537     char number[5];
11538     char string[MSG_SIZ];       /* Space for game-list */
11539     int  i;
11540
11541     if (!cmailMsgLoaded) return "";
11542
11543     if (cmailMailedMove) {
11544         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11545     } else {
11546         /* Create a list of games left */
11547         sprintf(string, "[");
11548         for (i = 0; i < nCmailGames; i ++) {
11549             if (! (   cmailMoveRegistered[i]
11550                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11551                 if (prependComma) {
11552                     sprintf(number, ",%d", i + 1);
11553                 } else {
11554                     sprintf(number, "%d", i + 1);
11555                     prependComma = 1;
11556                 }
11557
11558                 strcat(string, number);
11559             }
11560         }
11561         strcat(string, "]");
11562
11563         if (nCmailMovesRegistered + nCmailResults == 0) {
11564             switch (nCmailGames) {
11565               case 1:
11566                 sprintf(cmailMsg,
11567                         _("Still need to make move for game\n"));
11568                 break;
11569
11570               case 2:
11571                 sprintf(cmailMsg,
11572                         _("Still need to make moves for both games\n"));
11573                 break;
11574
11575               default:
11576                 sprintf(cmailMsg,
11577                         _("Still need to make moves for all %d games\n"),
11578                         nCmailGames);
11579                 break;
11580             }
11581         } else {
11582             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11583               case 1:
11584                 sprintf(cmailMsg,
11585                         _("Still need to make a move for game %s\n"),
11586                         string);
11587                 break;
11588
11589               case 0:
11590                 if (nCmailResults == nCmailGames) {
11591                     sprintf(cmailMsg, _("No unfinished games\n"));
11592                 } else {
11593                     sprintf(cmailMsg, _("Ready to send mail\n"));
11594                 }
11595                 break;
11596
11597               default:
11598                 sprintf(cmailMsg,
11599                         _("Still need to make moves for games %s\n"),
11600                         string);
11601             }
11602         }
11603     }
11604     return cmailMsg;
11605 #endif /* WIN32 */
11606 }
11607
11608 void
11609 ResetGameEvent()
11610 {
11611     if (gameMode == Training)
11612       SetTrainingModeOff();
11613
11614     Reset(TRUE, TRUE);
11615     cmailMsgLoaded = FALSE;
11616     if (appData.icsActive) {
11617       SendToICS(ics_prefix);
11618       SendToICS("refresh\n");
11619     }
11620 }
11621
11622 void
11623 ExitEvent(status)
11624      int status;
11625 {
11626     exiting++;
11627     if (exiting > 2) {
11628       /* Give up on clean exit */
11629       exit(status);
11630     }
11631     if (exiting > 1) {
11632       /* Keep trying for clean exit */
11633       return;
11634     }
11635
11636     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11637
11638     if (telnetISR != NULL) {
11639       RemoveInputSource(telnetISR);
11640     }
11641     if (icsPR != NoProc) {
11642       DestroyChildProcess(icsPR, TRUE);
11643     }
11644
11645     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11646     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11647
11648     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11649     /* make sure this other one finishes before killing it!                  */
11650     if(endingGame) { int count = 0;
11651         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11652         while(endingGame && count++ < 10) DoSleep(1);
11653         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11654     }
11655
11656     /* Kill off chess programs */
11657     if (first.pr != NoProc) {
11658         ExitAnalyzeMode();
11659
11660         DoSleep( appData.delayBeforeQuit );
11661         SendToProgram("quit\n", &first);
11662         DoSleep( appData.delayAfterQuit );
11663         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11664     }
11665     if (second.pr != NoProc) {
11666         DoSleep( appData.delayBeforeQuit );
11667         SendToProgram("quit\n", &second);
11668         DoSleep( appData.delayAfterQuit );
11669         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11670     }
11671     if (first.isr != NULL) {
11672         RemoveInputSource(first.isr);
11673     }
11674     if (second.isr != NULL) {
11675         RemoveInputSource(second.isr);
11676     }
11677
11678     ShutDownFrontEnd();
11679     exit(status);
11680 }
11681
11682 void
11683 PauseEvent()
11684 {
11685     if (appData.debugMode)
11686         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11687     if (pausing) {
11688         pausing = FALSE;
11689         ModeHighlight();
11690         if (gameMode == MachinePlaysWhite ||
11691             gameMode == MachinePlaysBlack) {
11692             StartClocks();
11693         } else {
11694             DisplayBothClocks();
11695         }
11696         if (gameMode == PlayFromGameFile) {
11697             if (appData.timeDelay >= 0)
11698                 AutoPlayGameLoop();
11699         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11700             Reset(FALSE, TRUE);
11701             SendToICS(ics_prefix);
11702             SendToICS("refresh\n");
11703         } else if (currentMove < forwardMostMove) {
11704             ForwardInner(forwardMostMove);
11705         }
11706         pauseExamInvalid = FALSE;
11707     } else {
11708         switch (gameMode) {
11709           default:
11710             return;
11711           case IcsExamining:
11712             pauseExamForwardMostMove = forwardMostMove;
11713             pauseExamInvalid = FALSE;
11714             /* fall through */
11715           case IcsObserving:
11716           case IcsPlayingWhite:
11717           case IcsPlayingBlack:
11718             pausing = TRUE;
11719             ModeHighlight();
11720             return;
11721           case PlayFromGameFile:
11722             (void) StopLoadGameTimer();
11723             pausing = TRUE;
11724             ModeHighlight();
11725             break;
11726           case BeginningOfGame:
11727             if (appData.icsActive) return;
11728             /* else fall through */
11729           case MachinePlaysWhite:
11730           case MachinePlaysBlack:
11731           case TwoMachinesPlay:
11732             if (forwardMostMove == 0)
11733               return;           /* don't pause if no one has moved */
11734             if ((gameMode == MachinePlaysWhite &&
11735                  !WhiteOnMove(forwardMostMove)) ||
11736                 (gameMode == MachinePlaysBlack &&
11737                  WhiteOnMove(forwardMostMove))) {
11738                 StopClocks();
11739             }
11740             pausing = TRUE;
11741             ModeHighlight();
11742             break;
11743         }
11744     }
11745 }
11746
11747 void
11748 EditCommentEvent()
11749 {
11750     char title[MSG_SIZ];
11751
11752     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11753         strcpy(title, _("Edit comment"));
11754     } else {
11755         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11756                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11757                 parseList[currentMove - 1]);
11758     }
11759
11760     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11761 }
11762
11763
11764 void
11765 EditTagsEvent()
11766 {
11767     char *tags = PGNTags(&gameInfo);
11768     EditTagsPopUp(tags);
11769     free(tags);
11770 }
11771
11772 void
11773 AnalyzeModeEvent()
11774 {
11775     if (appData.noChessProgram || gameMode == AnalyzeMode)
11776       return;
11777
11778     if (gameMode != AnalyzeFile) {
11779         if (!appData.icsEngineAnalyze) {
11780                EditGameEvent();
11781                if (gameMode != EditGame) return;
11782         }
11783         ResurrectChessProgram();
11784         SendToProgram("analyze\n", &first);
11785         first.analyzing = TRUE;
11786         /*first.maybeThinking = TRUE;*/
11787         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11788         EngineOutputPopUp();
11789     }
11790     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11791     pausing = FALSE;
11792     ModeHighlight();
11793     SetGameInfo();
11794
11795     StartAnalysisClock();
11796     GetTimeMark(&lastNodeCountTime);
11797     lastNodeCount = 0;
11798 }
11799
11800 void
11801 AnalyzeFileEvent()
11802 {
11803     if (appData.noChessProgram || gameMode == AnalyzeFile)
11804       return;
11805
11806     if (gameMode != AnalyzeMode) {
11807         EditGameEvent();
11808         if (gameMode != EditGame) return;
11809         ResurrectChessProgram();
11810         SendToProgram("analyze\n", &first);
11811         first.analyzing = TRUE;
11812         /*first.maybeThinking = TRUE;*/
11813         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11814         EngineOutputPopUp();
11815     }
11816     gameMode = AnalyzeFile;
11817     pausing = FALSE;
11818     ModeHighlight();
11819     SetGameInfo();
11820
11821     StartAnalysisClock();
11822     GetTimeMark(&lastNodeCountTime);
11823     lastNodeCount = 0;
11824 }
11825
11826 void
11827 MachineWhiteEvent()
11828 {
11829     char buf[MSG_SIZ];
11830     char *bookHit = NULL;
11831
11832     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11833       return;
11834
11835
11836     if (gameMode == PlayFromGameFile ||
11837         gameMode == TwoMachinesPlay  ||
11838         gameMode == Training         ||
11839         gameMode == AnalyzeMode      ||
11840         gameMode == EndOfGame)
11841         EditGameEvent();
11842
11843     if (gameMode == EditPosition) 
11844         EditPositionDone(TRUE);
11845
11846     if (!WhiteOnMove(currentMove)) {
11847         DisplayError(_("It is not White's turn"), 0);
11848         return;
11849     }
11850
11851     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11852       ExitAnalyzeMode();
11853
11854     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11855         gameMode == AnalyzeFile)
11856         TruncateGame();
11857
11858     ResurrectChessProgram();    /* in case it isn't running */
11859     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11860         gameMode = MachinePlaysWhite;
11861         ResetClocks();
11862     } else
11863     gameMode = MachinePlaysWhite;
11864     pausing = FALSE;
11865     ModeHighlight();
11866     SetGameInfo();
11867     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11868     DisplayTitle(buf);
11869     if (first.sendName) {
11870       sprintf(buf, "name %s\n", gameInfo.black);
11871       SendToProgram(buf, &first);
11872     }
11873     if (first.sendTime) {
11874       if (first.useColors) {
11875         SendToProgram("black\n", &first); /*gnu kludge*/
11876       }
11877       SendTimeRemaining(&first, TRUE);
11878     }
11879     if (first.useColors) {
11880       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11881     }
11882     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11883     SetMachineThinkingEnables();
11884     first.maybeThinking = TRUE;
11885     StartClocks();
11886     firstMove = FALSE;
11887
11888     if (appData.autoFlipView && !flipView) {
11889       flipView = !flipView;
11890       DrawPosition(FALSE, NULL);
11891       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11892     }
11893
11894     if(bookHit) { // [HGM] book: simulate book reply
11895         static char bookMove[MSG_SIZ]; // a bit generous?
11896
11897         programStats.nodes = programStats.depth = programStats.time =
11898         programStats.score = programStats.got_only_move = 0;
11899         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11900
11901         strcpy(bookMove, "move ");
11902         strcat(bookMove, bookHit);
11903         HandleMachineMove(bookMove, &first);
11904     }
11905 }
11906
11907 void
11908 MachineBlackEvent()
11909 {
11910   char buf[MSG_SIZ];
11911   char *bookHit = NULL;
11912   
11913   if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11914     return;
11915   
11916   
11917   if (gameMode == PlayFromGameFile 
11918       || gameMode == TwoMachinesPlay  
11919       || gameMode == Training     
11920       || gameMode == AnalyzeMode
11921       || gameMode == EndOfGame)
11922     EditGameEvent();
11923   
11924   if (gameMode == EditPosition) 
11925     EditPositionDone(TRUE);
11926   
11927   if (WhiteOnMove(currentMove)) 
11928     {
11929       DisplayError(_("It is not Black's turn"), 0);
11930       return;
11931     }
11932   
11933   if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11934     ExitAnalyzeMode();
11935   
11936   if (gameMode == EditGame || gameMode == AnalyzeMode 
11937       || gameMode == AnalyzeFile)
11938     TruncateGame();
11939   
11940   ResurrectChessProgram();      /* in case it isn't running */
11941   gameMode = MachinePlaysBlack;
11942   pausing  = FALSE;
11943   ModeHighlight();
11944   SetGameInfo();
11945   sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11946   DisplayTitle(buf);
11947   if (first.sendName) 
11948     {
11949       sprintf(buf, "name %s\n", gameInfo.white);
11950       SendToProgram(buf, &first);
11951     }
11952   if (first.sendTime) 
11953     {
11954       if (first.useColors) 
11955         {
11956           SendToProgram("white\n", &first); /*gnu kludge*/
11957         }
11958       SendTimeRemaining(&first, FALSE);
11959     }
11960   if (first.useColors) 
11961     {
11962       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11963     }
11964   bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11965   SetMachineThinkingEnables();
11966   first.maybeThinking = TRUE;
11967   StartClocks();
11968   
11969   if (appData.autoFlipView && flipView) 
11970     {
11971       flipView = !flipView;
11972       DrawPosition(FALSE, NULL);
11973       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11974     }
11975   if(bookHit) 
11976     { // [HGM] book: simulate book reply
11977       static char bookMove[MSG_SIZ]; // a bit generous?
11978       
11979       programStats.nodes = programStats.depth = programStats.time 
11980         = programStats.score = programStats.got_only_move = 0;
11981       sprintf(programStats.movelist, "%s (xbook)", bookHit);
11982       
11983       strcpy(bookMove, "move ");
11984       strcat(bookMove, bookHit);
11985       HandleMachineMove(bookMove, &first);
11986     }
11987   return;
11988 }
11989
11990
11991 void
11992 DisplayTwoMachinesTitle()
11993 {
11994     char buf[MSG_SIZ];
11995     if (appData.matchGames > 0) {
11996         if (first.twoMachinesColor[0] == 'w') {
11997             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11998                     gameInfo.white, gameInfo.black,
11999                     first.matchWins, second.matchWins,
12000                     matchGame - 1 - (first.matchWins + second.matchWins));
12001         } else {
12002             sprintf(buf, "%s vs. %s (%d-%d-%d)",
12003                     gameInfo.white, gameInfo.black,
12004                     second.matchWins, first.matchWins,
12005                     matchGame - 1 - (first.matchWins + second.matchWins));
12006         }
12007     } else {
12008         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
12009     }
12010     DisplayTitle(buf);
12011 }
12012
12013 void
12014 TwoMachinesEvent P((void))
12015 {
12016     int i;
12017     char buf[MSG_SIZ];
12018     ChessProgramState *onmove;
12019     char *bookHit = NULL;
12020
12021     if (appData.noChessProgram) return;
12022
12023     switch (gameMode) {
12024       case TwoMachinesPlay:
12025         return;
12026       case MachinePlaysWhite:
12027       case MachinePlaysBlack:
12028         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12029             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12030             return;
12031         }
12032         /* fall through */
12033       case BeginningOfGame:
12034       case PlayFromGameFile:
12035       case EndOfGame:
12036         EditGameEvent();
12037         if (gameMode != EditGame) return;
12038         break;
12039       case EditPosition:
12040         EditPositionDone(TRUE);
12041         break;
12042       case AnalyzeMode:
12043       case AnalyzeFile:
12044         ExitAnalyzeMode();
12045         break;
12046       case EditGame:
12047       default:
12048         break;
12049     }
12050
12051 //    forwardMostMove = currentMove;
12052     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12053     ResurrectChessProgram();    /* in case first program isn't running */
12054
12055     if (second.pr == NULL) {
12056         StartChessProgram(&second);
12057         if (second.protocolVersion == 1) {
12058           TwoMachinesEventIfReady();
12059         } else {
12060           /* kludge: allow timeout for initial "feature" command */
12061           FreezeUI();
12062           DisplayMessage("", _("Starting second chess program"));
12063           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
12064         }
12065         return;
12066     }
12067     DisplayMessage("", "");
12068     InitChessProgram(&second, FALSE);
12069     SendToProgram("force\n", &second);
12070     if (startedFromSetupPosition) {
12071         SendBoard(&second, backwardMostMove);
12072     if (appData.debugMode) {
12073         fprintf(debugFP, "Two Machines\n");
12074     }
12075     }
12076     for (i = backwardMostMove; i < forwardMostMove; i++) {
12077         SendMoveToProgram(i, &second);
12078     }
12079
12080     gameMode = TwoMachinesPlay;
12081     pausing = FALSE;
12082     ModeHighlight();
12083     SetGameInfo();
12084     DisplayTwoMachinesTitle();
12085     firstMove = TRUE;
12086     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12087         onmove = &first;
12088     } else {
12089         onmove = &second;
12090     }
12091
12092     SendToProgram(first.computerString, &first);
12093     if (first.sendName) {
12094       sprintf(buf, "name %s\n", second.tidy);
12095       SendToProgram(buf, &first);
12096     }
12097     SendToProgram(second.computerString, &second);
12098     if (second.sendName) {
12099       sprintf(buf, "name %s\n", first.tidy);
12100       SendToProgram(buf, &second);
12101     }
12102
12103     ResetClocks();
12104     if (!first.sendTime || !second.sendTime) {
12105         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12106         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12107     }
12108     if (onmove->sendTime) {
12109       if (onmove->useColors) {
12110         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12111       }
12112       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12113     }
12114     if (onmove->useColors) {
12115       SendToProgram(onmove->twoMachinesColor, onmove);
12116     }
12117     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12118 //    SendToProgram("go\n", onmove);
12119     onmove->maybeThinking = TRUE;
12120     SetMachineThinkingEnables();
12121
12122     StartClocks();
12123
12124     if(bookHit) { // [HGM] book: simulate book reply
12125         static char bookMove[MSG_SIZ]; // a bit generous?
12126
12127         programStats.nodes = programStats.depth = programStats.time =
12128         programStats.score = programStats.got_only_move = 0;
12129         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12130
12131         strcpy(bookMove, "move ");
12132         strcat(bookMove, bookHit);
12133         savedMessage = bookMove; // args for deferred call
12134         savedState = onmove;
12135         ScheduleDelayedEvent(DeferredBookMove, 1);
12136     }
12137 }
12138
12139 void
12140 TrainingEvent()
12141 {
12142     if (gameMode == Training) {
12143       SetTrainingModeOff();
12144       gameMode = PlayFromGameFile;
12145       DisplayMessage("", _("Training mode off"));
12146     } else {
12147       gameMode = Training;
12148       animateTraining = appData.animate;
12149
12150       /* make sure we are not already at the end of the game */
12151       if (currentMove < forwardMostMove) {
12152         SetTrainingModeOn();
12153         DisplayMessage("", _("Training mode on"));
12154       } else {
12155         gameMode = PlayFromGameFile;
12156         DisplayError(_("Already at end of game"), 0);
12157       }
12158     }
12159     ModeHighlight();
12160 }
12161
12162 void
12163 IcsClientEvent()
12164 {
12165     if (!appData.icsActive) return;
12166     switch (gameMode) {
12167       case IcsPlayingWhite:
12168       case IcsPlayingBlack:
12169       case IcsObserving:
12170       case IcsIdle:
12171       case BeginningOfGame:
12172       case IcsExamining:
12173         return;
12174
12175       case EditGame:
12176         break;
12177
12178       case EditPosition:
12179         EditPositionDone(TRUE);
12180         break;
12181
12182       case AnalyzeMode:
12183       case AnalyzeFile:
12184         ExitAnalyzeMode();
12185         break;
12186
12187       default:
12188         EditGameEvent();
12189         break;
12190     }
12191
12192     gameMode = IcsIdle;
12193     ModeHighlight();
12194     return;
12195 }
12196
12197
12198 void
12199 EditGameEvent()
12200 {
12201     int i;
12202
12203     switch (gameMode) {
12204       case Training:
12205         SetTrainingModeOff();
12206         break;
12207       case MachinePlaysWhite:
12208       case MachinePlaysBlack:
12209       case BeginningOfGame:
12210         SendToProgram("force\n", &first);
12211         SetUserThinkingEnables();
12212         break;
12213       case PlayFromGameFile:
12214         (void) StopLoadGameTimer();
12215         if (gameFileFP != NULL) {
12216             gameFileFP = NULL;
12217         }
12218         break;
12219       case EditPosition:
12220         EditPositionDone(TRUE);
12221         break;
12222       case AnalyzeMode:
12223       case AnalyzeFile:
12224         ExitAnalyzeMode();
12225         SendToProgram("force\n", &first);
12226         break;
12227       case TwoMachinesPlay:
12228         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
12229         ResurrectChessProgram();
12230         SetUserThinkingEnables();
12231         break;
12232       case EndOfGame:
12233         ResurrectChessProgram();
12234         break;
12235       case IcsPlayingBlack:
12236       case IcsPlayingWhite:
12237         DisplayError(_("Warning: You are still playing a game"), 0);
12238         break;
12239       case IcsObserving:
12240         DisplayError(_("Warning: You are still observing a game"), 0);
12241         break;
12242       case IcsExamining:
12243         DisplayError(_("Warning: You are still examining a game"), 0);
12244         break;
12245       case IcsIdle:
12246         break;
12247       case EditGame:
12248       default:
12249         return;
12250     }
12251
12252     pausing = FALSE;
12253     StopClocks();
12254     first.offeredDraw = second.offeredDraw = 0;
12255
12256     if (gameMode == PlayFromGameFile) {
12257         whiteTimeRemaining = timeRemaining[0][currentMove];
12258         blackTimeRemaining = timeRemaining[1][currentMove];
12259         DisplayTitle("");
12260     }
12261
12262     if (gameMode == MachinePlaysWhite ||
12263         gameMode == MachinePlaysBlack ||
12264         gameMode == TwoMachinesPlay ||
12265         gameMode == EndOfGame) {
12266         i = forwardMostMove;
12267         while (i > currentMove) {
12268             SendToProgram("undo\n", &first);
12269             i--;
12270         }
12271         whiteTimeRemaining = timeRemaining[0][currentMove];
12272         blackTimeRemaining = timeRemaining[1][currentMove];
12273         DisplayBothClocks();
12274         if (whiteFlag || blackFlag) {
12275             whiteFlag = blackFlag = 0;
12276         }
12277         DisplayTitle("");
12278     }
12279
12280     gameMode = EditGame;
12281     ModeHighlight();
12282     SetGameInfo();
12283 }
12284
12285
12286 void
12287 EditPositionEvent()
12288 {
12289     if (gameMode == EditPosition) {
12290         EditGameEvent();
12291         return;
12292     }
12293
12294     EditGameEvent();
12295     if (gameMode != EditGame) return;
12296
12297     gameMode = EditPosition;
12298     ModeHighlight();
12299     SetGameInfo();
12300     if (currentMove > 0)
12301       CopyBoard(boards[0], boards[currentMove]);
12302
12303     blackPlaysFirst = !WhiteOnMove(currentMove);
12304     ResetClocks();
12305     currentMove = forwardMostMove = backwardMostMove = 0;
12306     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12307     DisplayMove(-1);
12308 }
12309
12310 void
12311 ExitAnalyzeMode()
12312 {
12313     /* [DM] icsEngineAnalyze - possible call from other functions */
12314     if (appData.icsEngineAnalyze) {
12315         appData.icsEngineAnalyze = FALSE;
12316
12317         DisplayMessage("",_("Close ICS engine analyze..."));
12318     }
12319     if (first.analysisSupport && first.analyzing) {
12320       SendToProgram("exit\n", &first);
12321       first.analyzing = FALSE;
12322     }
12323     thinkOutput[0] = NULLCHAR;
12324 }
12325
12326 void
12327 EditPositionDone(Boolean fakeRights)
12328 {
12329     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12330
12331     startedFromSetupPosition = TRUE;
12332     InitChessProgram(&first, FALSE);
12333     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12334       boards[0][EP_STATUS] = EP_NONE;
12335       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12336     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12337         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12338         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12339       } else boards[0][CASTLING][2] = NoRights;
12340     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12341         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12342         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12343       } else boards[0][CASTLING][5] = NoRights;
12344     }
12345     SendToProgram("force\n", &first);
12346     if (blackPlaysFirst) {
12347         strcpy(moveList[0], "");
12348         strcpy(parseList[0], "");
12349         currentMove = forwardMostMove = backwardMostMove = 1;
12350         CopyBoard(boards[1], boards[0]);
12351     } else {
12352         currentMove = forwardMostMove = backwardMostMove = 0;
12353     }
12354     SendBoard(&first, forwardMostMove);
12355     if (appData.debugMode) {
12356         fprintf(debugFP, "EditPosDone\n");
12357     }
12358     DisplayTitle("");
12359     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12360     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12361     gameMode = EditGame;
12362     ModeHighlight();
12363     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12364     ClearHighlights(); /* [AS] */
12365 }
12366
12367 /* Pause for `ms' milliseconds */
12368 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12369 void
12370 TimeDelay(ms)
12371      long ms;
12372 {
12373     TimeMark m1, m2;
12374
12375     GetTimeMark(&m1);
12376     do {
12377         GetTimeMark(&m2);
12378     } while (SubtractTimeMarks(&m2, &m1) < ms);
12379 }
12380
12381 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12382 void
12383 SendMultiLineToICS(buf)
12384      char *buf;
12385 {
12386     char temp[MSG_SIZ+1], *p;
12387     int len;
12388
12389     len = strlen(buf);
12390     if (len > MSG_SIZ)
12391       len = MSG_SIZ;
12392
12393     strncpy(temp, buf, len);
12394     temp[len] = 0;
12395
12396     p = temp;
12397     while (*p) {
12398         if (*p == '\n' || *p == '\r')
12399           *p = ' ';
12400         ++p;
12401     }
12402
12403     strcat(temp, "\n");
12404     SendToICS(temp);
12405     SendToPlayer(temp, strlen(temp));
12406 }
12407
12408 void
12409 SetWhiteToPlayEvent()
12410 {
12411     if (gameMode == EditPosition) {
12412         blackPlaysFirst = FALSE;
12413         DisplayBothClocks();    /* works because currentMove is 0 */
12414     } else if (gameMode == IcsExamining) {
12415         SendToICS(ics_prefix);
12416         SendToICS("tomove white\n");
12417     }
12418 }
12419
12420 void
12421 SetBlackToPlayEvent()
12422 {
12423     if (gameMode == EditPosition) {
12424         blackPlaysFirst = TRUE;
12425         currentMove = 1;        /* kludge */
12426         DisplayBothClocks();
12427         currentMove = 0;
12428     } else if (gameMode == IcsExamining) {
12429         SendToICS(ics_prefix);
12430         SendToICS("tomove black\n");
12431     }
12432 }
12433
12434 void
12435 EditPositionMenuEvent(selection, x, y)
12436      ChessSquare selection;
12437      int x, y;
12438 {
12439     char buf[MSG_SIZ];
12440     ChessSquare piece = boards[0][y][x];
12441
12442     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12443
12444     switch (selection) {
12445       case ClearBoard:
12446         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12447             SendToICS(ics_prefix);
12448             SendToICS("bsetup clear\n");
12449         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12450             SendToICS(ics_prefix);
12451             SendToICS("clearboard\n");
12452         } else {
12453             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12454                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12455                 for (y = 0; y < BOARD_HEIGHT; y++) {
12456                     if (gameMode == IcsExamining) {
12457                         if (boards[currentMove][y][x] != EmptySquare) {
12458                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12459                                     AAA + x, ONE + y);
12460                             SendToICS(buf);
12461                         }
12462                     } else {
12463                         boards[0][y][x] = p;
12464                     }
12465                 }
12466             }
12467         }
12468         if (gameMode == EditPosition) {
12469             DrawPosition(FALSE, boards[0]);
12470         }
12471         break;
12472
12473       case WhitePlay:
12474         SetWhiteToPlayEvent();
12475         break;
12476
12477       case BlackPlay:
12478         SetBlackToPlayEvent();
12479         break;
12480
12481       case EmptySquare:
12482         if (gameMode == IcsExamining) {
12483             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12484             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12485             SendToICS(buf);
12486         } else {
12487             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12488                 if(x == BOARD_LEFT-2) {
12489                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12490                     boards[0][y][1] = 0;
12491                 } else
12492                 if(x == BOARD_RGHT+1) {
12493                     if(y >= gameInfo.holdingsSize) break;
12494                     boards[0][y][BOARD_WIDTH-2] = 0;
12495                 } else break;
12496             }
12497             boards[0][y][x] = EmptySquare;
12498             DrawPosition(FALSE, boards[0]);
12499         }
12500         break;
12501
12502       case PromotePiece:
12503         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12504            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12505             selection = (ChessSquare) (PROMOTED piece);
12506         } else if(piece == EmptySquare) selection = WhiteSilver;
12507         else selection = (ChessSquare)((int)piece - 1);
12508         goto defaultlabel;
12509
12510       case DemotePiece:
12511         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12512            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12513             selection = (ChessSquare) (DEMOTED piece);
12514         } else if(piece == EmptySquare) selection = BlackSilver;
12515         else selection = (ChessSquare)((int)piece + 1);
12516         goto defaultlabel;
12517
12518       case WhiteQueen:
12519       case BlackQueen:
12520         if(gameInfo.variant == VariantShatranj ||
12521            gameInfo.variant == VariantXiangqi  ||
12522            gameInfo.variant == VariantCourier  ||
12523            gameInfo.variant == VariantMakruk     )
12524             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12525         goto defaultlabel;
12526
12527       case WhiteKing:
12528       case BlackKing:
12529         if(gameInfo.variant == VariantXiangqi)
12530             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12531         if(gameInfo.variant == VariantKnightmate)
12532             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12533       default:
12534         defaultlabel:
12535         if (gameMode == IcsExamining) {
12536             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12537             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12538                     PieceToChar(selection), AAA + x, ONE + y);
12539             SendToICS(buf);
12540         } else {
12541             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12542                 int n;
12543                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12544                     n = PieceToNumber(selection - BlackPawn);
12545                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12546                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12547                     boards[0][BOARD_HEIGHT-1-n][1]++;
12548                 } else
12549                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12550                     n = PieceToNumber(selection);
12551                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12552                     boards[0][n][BOARD_WIDTH-1] = selection;
12553                     boards[0][n][BOARD_WIDTH-2]++;
12554                 }
12555             } else
12556             boards[0][y][x] = selection;
12557             DrawPosition(TRUE, boards[0]);
12558         }
12559         break;
12560     }
12561 }
12562
12563
12564 void
12565 DropMenuEvent(selection, x, y)
12566      ChessSquare selection;
12567      int x, y;
12568 {
12569     ChessMove moveType;
12570
12571     switch (gameMode) {
12572       case IcsPlayingWhite:
12573       case MachinePlaysBlack:
12574         if (!WhiteOnMove(currentMove)) {
12575             DisplayMoveError(_("It is Black's turn"));
12576             return;
12577         }
12578         moveType = WhiteDrop;
12579         break;
12580       case IcsPlayingBlack:
12581       case MachinePlaysWhite:
12582         if (WhiteOnMove(currentMove)) {
12583             DisplayMoveError(_("It is White's turn"));
12584             return;
12585         }
12586         moveType = BlackDrop;
12587         break;
12588       case EditGame:
12589         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12590         break;
12591       default:
12592         return;
12593     }
12594
12595     if (moveType == BlackDrop && selection < BlackPawn) {
12596       selection = (ChessSquare) ((int) selection
12597                                  + (int) BlackPawn - (int) WhitePawn);
12598     }
12599     if (boards[currentMove][y][x] != EmptySquare) {
12600         DisplayMoveError(_("That square is occupied"));
12601         return;
12602     }
12603
12604     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12605 }
12606
12607 void
12608 AcceptEvent()
12609 {
12610     /* Accept a pending offer of any kind from opponent */
12611
12612     if (appData.icsActive) {
12613         SendToICS(ics_prefix);
12614         SendToICS("accept\n");
12615     } else if (cmailMsgLoaded) {
12616         if (currentMove == cmailOldMove &&
12617             commentList[cmailOldMove] != NULL &&
12618             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12619                    "Black offers a draw" : "White offers a draw")) {
12620             TruncateGame();
12621             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12622             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12623         } else {
12624             DisplayError(_("There is no pending offer on this move"), 0);
12625             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12626         }
12627     } else {
12628         /* Not used for offers from chess program */
12629     }
12630 }
12631
12632 void
12633 DeclineEvent()
12634 {
12635     /* Decline a pending offer of any kind from opponent */
12636
12637     if (appData.icsActive) {
12638         SendToICS(ics_prefix);
12639         SendToICS("decline\n");
12640     } else if (cmailMsgLoaded) {
12641         if (currentMove == cmailOldMove &&
12642             commentList[cmailOldMove] != NULL &&
12643             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12644                    "Black offers a draw" : "White offers a draw")) {
12645 #ifdef NOTDEF
12646             AppendComment(cmailOldMove, "Draw declined", TRUE);
12647             DisplayComment(cmailOldMove - 1, "Draw declined");
12648 #endif /*NOTDEF*/
12649         } else {
12650             DisplayError(_("There is no pending offer on this move"), 0);
12651         }
12652     } else {
12653         /* Not used for offers from chess program */
12654     }
12655 }
12656
12657 void
12658 RematchEvent()
12659 {
12660     /* Issue ICS rematch command */
12661     if (appData.icsActive) {
12662         SendToICS(ics_prefix);
12663         SendToICS("rematch\n");
12664     }
12665 }
12666
12667 void
12668 CallFlagEvent()
12669 {
12670     /* Call your opponent's flag (claim a win on time) */
12671     if (appData.icsActive) {
12672         SendToICS(ics_prefix);
12673         SendToICS("flag\n");
12674     } else {
12675         switch (gameMode) {
12676           default:
12677             return;
12678           case MachinePlaysWhite:
12679             if (whiteFlag) {
12680                 if (blackFlag)
12681                   GameEnds(GameIsDrawn, "Both players ran out of time",
12682                            GE_PLAYER);
12683                 else
12684                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12685             } else {
12686                 DisplayError(_("Your opponent is not out of time"), 0);
12687             }
12688             break;
12689           case MachinePlaysBlack:
12690             if (blackFlag) {
12691                 if (whiteFlag)
12692                   GameEnds(GameIsDrawn, "Both players ran out of time",
12693                            GE_PLAYER);
12694                 else
12695                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12696             } else {
12697                 DisplayError(_("Your opponent is not out of time"), 0);
12698             }
12699             break;
12700         }
12701     }
12702 }
12703
12704 void
12705 DrawEvent()
12706 {
12707     /* Offer draw or accept pending draw offer from opponent */
12708
12709     if (appData.icsActive) {
12710         /* Note: tournament rules require draw offers to be
12711            made after you make your move but before you punch
12712            your clock.  Currently ICS doesn't let you do that;
12713            instead, you immediately punch your clock after making
12714            a move, but you can offer a draw at any time. */
12715
12716         SendToICS(ics_prefix);
12717         SendToICS("draw\n");
12718         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12719     } else if (cmailMsgLoaded) {
12720         if (currentMove == cmailOldMove &&
12721             commentList[cmailOldMove] != NULL &&
12722             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12723                    "Black offers a draw" : "White offers a draw")) {
12724             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12725             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12726         } else if (currentMove == cmailOldMove + 1) {
12727             char *offer = WhiteOnMove(cmailOldMove) ?
12728               "White offers a draw" : "Black offers a draw";
12729             AppendComment(currentMove, offer, TRUE);
12730             DisplayComment(currentMove - 1, offer);
12731             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12732         } else {
12733             DisplayError(_("You must make your move before offering a draw"), 0);
12734             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12735         }
12736     } else if (first.offeredDraw) {
12737         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12738     } else {
12739         if (first.sendDrawOffers) {
12740             SendToProgram("draw\n", &first);
12741             userOfferedDraw = TRUE;
12742         }
12743     }
12744 }
12745
12746 void
12747 AdjournEvent()
12748 {
12749     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12750
12751     if (appData.icsActive) {
12752         SendToICS(ics_prefix);
12753         SendToICS("adjourn\n");
12754     } else {
12755         /* Currently GNU Chess doesn't offer or accept Adjourns */
12756     }
12757 }
12758
12759
12760 void
12761 AbortEvent()
12762 {
12763     /* Offer Abort or accept pending Abort offer from opponent */
12764
12765     if (appData.icsActive) {
12766         SendToICS(ics_prefix);
12767         SendToICS("abort\n");
12768     } else {
12769         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12770     }
12771 }
12772
12773 void
12774 ResignEvent()
12775 {
12776     /* Resign.  You can do this even if it's not your turn. */
12777
12778     if (appData.icsActive) {
12779         SendToICS(ics_prefix);
12780         SendToICS("resign\n");
12781     } else {
12782         switch (gameMode) {
12783           case MachinePlaysWhite:
12784             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12785             break;
12786           case MachinePlaysBlack:
12787             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12788             break;
12789           case EditGame:
12790             if (cmailMsgLoaded) {
12791                 TruncateGame();
12792                 if (WhiteOnMove(cmailOldMove)) {
12793                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12794                 } else {
12795                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12796                 }
12797                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12798             }
12799             break;
12800           default:
12801             break;
12802         }
12803     }
12804 }
12805
12806
12807 void
12808 StopObservingEvent()
12809 {
12810     /* Stop observing current games */
12811     SendToICS(ics_prefix);
12812     SendToICS("unobserve\n");
12813 }
12814
12815 void
12816 StopExaminingEvent()
12817 {
12818     /* Stop observing current game */
12819     SendToICS(ics_prefix);
12820     SendToICS("unexamine\n");
12821 }
12822
12823 void
12824 ForwardInner(target)
12825      int target;
12826 {
12827     int limit;
12828
12829     if (appData.debugMode)
12830         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12831                 target, currentMove, forwardMostMove);
12832
12833     if (gameMode == EditPosition)
12834       return;
12835
12836     if (gameMode == PlayFromGameFile && !pausing)
12837       PauseEvent();
12838
12839     if (gameMode == IcsExamining && pausing)
12840       limit = pauseExamForwardMostMove;
12841     else
12842       limit = forwardMostMove;
12843
12844     if (target > limit) target = limit;
12845
12846     if (target > 0 && moveList[target - 1][0]) {
12847         int fromX, fromY, toX, toY;
12848         toX = moveList[target - 1][2] - AAA;
12849         toY = moveList[target - 1][3] - ONE;
12850         if (moveList[target - 1][1] == '@') {
12851             if (appData.highlightLastMove) {
12852                 SetHighlights(-1, -1, toX, toY);
12853             }
12854         } else {
12855             fromX = moveList[target - 1][0] - AAA;
12856             fromY = moveList[target - 1][1] - ONE;
12857             if (target == currentMove + 1) {
12858                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12859             }
12860             if (appData.highlightLastMove) {
12861                 SetHighlights(fromX, fromY, toX, toY);
12862             }
12863         }
12864     }
12865     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12866         gameMode == Training || gameMode == PlayFromGameFile ||
12867         gameMode == AnalyzeFile) {
12868         while (currentMove < target) {
12869             SendMoveToProgram(currentMove++, &first);
12870         }
12871     } else {
12872         currentMove = target;
12873     }
12874
12875     if (gameMode == EditGame || gameMode == EndOfGame) {
12876         whiteTimeRemaining = timeRemaining[0][currentMove];
12877         blackTimeRemaining = timeRemaining[1][currentMove];
12878     }
12879     DisplayBothClocks();
12880     DisplayMove(currentMove - 1);
12881     DrawPosition(FALSE, boards[currentMove]);
12882     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12883     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12884         DisplayComment(currentMove - 1, commentList[currentMove]);
12885     }
12886 }
12887
12888
12889 void
12890 ForwardEvent()
12891 {
12892     if (gameMode == IcsExamining && !pausing) {
12893         SendToICS(ics_prefix);
12894         SendToICS("forward\n");
12895     } else {
12896         ForwardInner(currentMove + 1);
12897     }
12898 }
12899
12900 void
12901 ToEndEvent()
12902 {
12903     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12904         /* to optimze, we temporarily turn off analysis mode while we feed
12905          * the remaining moves to the engine. Otherwise we get analysis output
12906          * after each move.
12907          */
12908         if (first.analysisSupport) {
12909           SendToProgram("exit\nforce\n", &first);
12910           first.analyzing = FALSE;
12911         }
12912     }
12913
12914     if (gameMode == IcsExamining && !pausing) {
12915         SendToICS(ics_prefix);
12916         SendToICS("forward 999999\n");
12917     } else {
12918         ForwardInner(forwardMostMove);
12919     }
12920
12921     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12922         /* we have fed all the moves, so reactivate analysis mode */
12923         SendToProgram("analyze\n", &first);
12924         first.analyzing = TRUE;
12925         /*first.maybeThinking = TRUE;*/
12926         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12927     }
12928 }
12929
12930 void
12931 BackwardInner(target)
12932      int target;
12933 {
12934     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12935
12936     if (appData.debugMode)
12937         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12938                 target, currentMove, forwardMostMove);
12939
12940     if (gameMode == EditPosition) return;
12941     if (currentMove <= backwardMostMove) {
12942         ClearHighlights();
12943         DrawPosition(full_redraw, boards[currentMove]);
12944         return;
12945     }
12946     if (gameMode == PlayFromGameFile && !pausing)
12947       PauseEvent();
12948
12949     if (moveList[target][0]) {
12950         int fromX, fromY, toX, toY;
12951         toX = moveList[target][2] - AAA;
12952         toY = moveList[target][3] - ONE;
12953         if (moveList[target][1] == '@') {
12954             if (appData.highlightLastMove) {
12955                 SetHighlights(-1, -1, toX, toY);
12956             }
12957         } else {
12958             fromX = moveList[target][0] - AAA;
12959             fromY = moveList[target][1] - ONE;
12960             if (target == currentMove - 1) {
12961                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12962             }
12963             if (appData.highlightLastMove) {
12964                 SetHighlights(fromX, fromY, toX, toY);
12965             }
12966         }
12967     }
12968     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12969         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12970         while (currentMove > target) {
12971             SendToProgram("undo\n", &first);
12972             currentMove--;
12973         }
12974     } else {
12975         currentMove = target;
12976     }
12977
12978     if (gameMode == EditGame || gameMode == EndOfGame) {
12979         whiteTimeRemaining = timeRemaining[0][currentMove];
12980         blackTimeRemaining = timeRemaining[1][currentMove];
12981     }
12982     DisplayBothClocks();
12983     DisplayMove(currentMove - 1);
12984     DrawPosition(full_redraw, boards[currentMove]);
12985     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12986     // [HGM] PV info: routine tests if comment empty
12987     DisplayComment(currentMove - 1, commentList[currentMove]);
12988 }
12989
12990 void
12991 BackwardEvent()
12992 {
12993     if (gameMode == IcsExamining && !pausing) {
12994         SendToICS(ics_prefix);
12995         SendToICS("backward\n");
12996     } else {
12997         BackwardInner(currentMove - 1);
12998     }
12999 }
13000
13001 void
13002 ToStartEvent()
13003 {
13004     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13005         /* to optimize, we temporarily turn off analysis mode while we undo
13006          * all the moves. Otherwise we get analysis output after each undo.
13007          */
13008         if (first.analysisSupport) {
13009           SendToProgram("exit\nforce\n", &first);
13010           first.analyzing = FALSE;
13011         }
13012     }
13013
13014     if (gameMode == IcsExamining && !pausing) {
13015         SendToICS(ics_prefix);
13016         SendToICS("backward 999999\n");
13017     } else {
13018         BackwardInner(backwardMostMove);
13019     }
13020
13021     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13022         /* we have fed all the moves, so reactivate analysis mode */
13023         SendToProgram("analyze\n", &first);
13024         first.analyzing = TRUE;
13025         /*first.maybeThinking = TRUE;*/
13026         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13027     }
13028 }
13029
13030 void
13031 ToNrEvent(int to)
13032 {
13033   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13034   if (to >= forwardMostMove) to = forwardMostMove;
13035   if (to <= backwardMostMove) to = backwardMostMove;
13036   if (to < currentMove) {
13037     BackwardInner(to);
13038   } else {
13039     ForwardInner(to);
13040   }
13041 }
13042
13043 void
13044 RevertEvent()
13045 {
13046     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
13047         return;
13048     }
13049     if (gameMode != IcsExamining) {
13050         DisplayError(_("You are not examining a game"), 0);
13051         return;
13052     }
13053     if (pausing) {
13054         DisplayError(_("You can't revert while pausing"), 0);
13055         return;
13056     }
13057     SendToICS(ics_prefix);
13058     SendToICS("revert\n");
13059 }
13060
13061 void
13062 RetractMoveEvent()
13063 {
13064     switch (gameMode) {
13065       case MachinePlaysWhite:
13066       case MachinePlaysBlack:
13067         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13068             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13069             return;
13070         }
13071         if (forwardMostMove < 2) return;
13072         currentMove = forwardMostMove = forwardMostMove - 2;
13073         whiteTimeRemaining = timeRemaining[0][currentMove];
13074         blackTimeRemaining = timeRemaining[1][currentMove];
13075         DisplayBothClocks();
13076         DisplayMove(currentMove - 1);
13077         ClearHighlights();/*!! could figure this out*/
13078         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13079         SendToProgram("remove\n", &first);
13080         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13081         break;
13082
13083       case BeginningOfGame:
13084       default:
13085         break;
13086
13087       case IcsPlayingWhite:
13088       case IcsPlayingBlack:
13089         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13090             SendToICS(ics_prefix);
13091             SendToICS("takeback 2\n");
13092         } else {
13093             SendToICS(ics_prefix);
13094             SendToICS("takeback 1\n");
13095         }
13096         break;
13097     }
13098 }
13099
13100 void
13101 MoveNowEvent()
13102 {
13103     ChessProgramState *cps;
13104
13105     switch (gameMode) {
13106       case MachinePlaysWhite:
13107         if (!WhiteOnMove(forwardMostMove)) {
13108             DisplayError(_("It is your turn"), 0);
13109             return;
13110         }
13111         cps = &first;
13112         break;
13113       case MachinePlaysBlack:
13114         if (WhiteOnMove(forwardMostMove)) {
13115             DisplayError(_("It is your turn"), 0);
13116             return;
13117         }
13118         cps = &first;
13119         break;
13120       case TwoMachinesPlay:
13121         if (WhiteOnMove(forwardMostMove) ==
13122             (first.twoMachinesColor[0] == 'w')) {
13123             cps = &first;
13124         } else {
13125             cps = &second;
13126         }
13127         break;
13128       case BeginningOfGame:
13129       default:
13130         return;
13131     }
13132     SendToProgram("?\n", cps);
13133 }
13134
13135 void
13136 TruncateGameEvent()
13137 {
13138     EditGameEvent();
13139     if (gameMode != EditGame) return;
13140     TruncateGame();
13141 }
13142
13143 void
13144 TruncateGame()
13145 {
13146     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13147     if (forwardMostMove > currentMove) {
13148         if (gameInfo.resultDetails != NULL) {
13149             free(gameInfo.resultDetails);
13150             gameInfo.resultDetails = NULL;
13151             gameInfo.result = GameUnfinished;
13152         }
13153         forwardMostMove = currentMove;
13154         HistorySet(parseList, backwardMostMove, forwardMostMove,
13155                    currentMove-1);
13156     }
13157 }
13158
13159 void
13160 HintEvent()
13161 {
13162     if (appData.noChessProgram) return;
13163     switch (gameMode) {
13164       case MachinePlaysWhite:
13165         if (WhiteOnMove(forwardMostMove)) {
13166             DisplayError(_("Wait until your turn"), 0);
13167             return;
13168         }
13169         break;
13170       case BeginningOfGame:
13171       case MachinePlaysBlack:
13172         if (!WhiteOnMove(forwardMostMove)) {
13173             DisplayError(_("Wait until your turn"), 0);
13174             return;
13175         }
13176         break;
13177       default:
13178         DisplayError(_("No hint available"), 0);
13179         return;
13180     }
13181     SendToProgram("hint\n", &first);
13182     hintRequested = TRUE;
13183 }
13184
13185 void
13186 BookEvent()
13187 {
13188     if (appData.noChessProgram) return;
13189     switch (gameMode) {
13190       case MachinePlaysWhite:
13191         if (WhiteOnMove(forwardMostMove)) {
13192             DisplayError(_("Wait until your turn"), 0);
13193             return;
13194         }
13195         break;
13196       case BeginningOfGame:
13197       case MachinePlaysBlack:
13198         if (!WhiteOnMove(forwardMostMove)) {
13199             DisplayError(_("Wait until your turn"), 0);
13200             return;
13201         }
13202         break;
13203       case EditPosition:
13204         EditPositionDone(TRUE);
13205         break;
13206       case TwoMachinesPlay:
13207         return;
13208       default:
13209         break;
13210     }
13211     SendToProgram("bk\n", &first);
13212     bookOutput[0] = NULLCHAR;
13213     bookRequested = TRUE;
13214 }
13215
13216 void
13217 AboutGameEvent()
13218 {
13219     char *tags = PGNTags(&gameInfo);
13220     TagsPopUp(tags, CmailMsg());
13221     free(tags);
13222 }
13223
13224 /* end button procedures */
13225
13226 void
13227 PrintPosition(fp, move)
13228      FILE *fp;
13229      int move;
13230 {
13231     int i, j;
13232
13233     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13234         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13235             char c = PieceToChar(boards[move][i][j]);
13236             fputc(c == 'x' ? '.' : c, fp);
13237             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13238         }
13239     }
13240     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13241       fprintf(fp, "white to play\n");
13242     else
13243       fprintf(fp, "black to play\n");
13244 }
13245
13246 void
13247 PrintOpponents(fp)
13248      FILE *fp;
13249 {
13250     if (gameInfo.white != NULL) {
13251         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13252     } else {
13253         fprintf(fp, "\n");
13254     }
13255 }
13256
13257 /* Find last component of program's own name, using some heuristics */
13258 void
13259 TidyProgramName(prog, host, buf)
13260      char *prog, *host, buf[MSG_SIZ];
13261 {
13262     char *p, *q;
13263     int local = (strcmp(host, "localhost") == 0);
13264     while (!local && (p = strchr(prog, ';')) != NULL) {
13265         p++;
13266         while (*p == ' ') p++;
13267         prog = p;
13268     }
13269     if (*prog == '"' || *prog == '\'') {
13270         q = strchr(prog + 1, *prog);
13271     } else {
13272         q = strchr(prog, ' ');
13273     }
13274     if (q == NULL) q = prog + strlen(prog);
13275     p = q;
13276     while (p >= prog && *p != '/' && *p != '\\') p--;
13277     p++;
13278     if(p == prog && *p == '"') p++;
13279     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13280     memcpy(buf, p, q - p);
13281     buf[q - p] = NULLCHAR;
13282     if (!local) {
13283         strcat(buf, "@");
13284         strcat(buf, host);
13285     }
13286 }
13287
13288 char *
13289 TimeControlTagValue()
13290 {
13291     char buf[MSG_SIZ];
13292     if (!appData.clockMode) {
13293         strcpy(buf, "-");
13294     } else if (movesPerSession > 0) {
13295         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13296     } else if (timeIncrement == 0) {
13297         sprintf(buf, "%ld", timeControl/1000);
13298     } else {
13299         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13300     }
13301     return StrSave(buf);
13302 }
13303
13304 void
13305 SetGameInfo()
13306 {
13307     /* This routine is used only for certain modes */
13308     VariantClass v = gameInfo.variant;
13309     ChessMove r = GameUnfinished;
13310     char *p = NULL;
13311
13312     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13313         r = gameInfo.result; 
13314         p = gameInfo.resultDetails; 
13315         gameInfo.resultDetails = NULL;
13316     }
13317     ClearGameInfo(&gameInfo);
13318     gameInfo.variant = v;
13319
13320     switch (gameMode) {
13321       case MachinePlaysWhite:
13322         gameInfo.event = StrSave( appData.pgnEventHeader );
13323         gameInfo.site = StrSave(HostName());
13324         gameInfo.date = PGNDate();
13325         gameInfo.round = StrSave("-");
13326         gameInfo.white = StrSave(first.tidy);
13327         gameInfo.black = StrSave(UserName());
13328         gameInfo.timeControl = TimeControlTagValue();
13329         break;
13330
13331       case MachinePlaysBlack:
13332         gameInfo.event = StrSave( appData.pgnEventHeader );
13333         gameInfo.site = StrSave(HostName());
13334         gameInfo.date = PGNDate();
13335         gameInfo.round = StrSave("-");
13336         gameInfo.white = StrSave(UserName());
13337         gameInfo.black = StrSave(first.tidy);
13338         gameInfo.timeControl = TimeControlTagValue();
13339         break;
13340
13341       case TwoMachinesPlay:
13342         gameInfo.event = StrSave( appData.pgnEventHeader );
13343         gameInfo.site = StrSave(HostName());
13344         gameInfo.date = PGNDate();
13345         if (matchGame > 0) {
13346             char buf[MSG_SIZ];
13347             sprintf(buf, "%d", matchGame);
13348             gameInfo.round = StrSave(buf);
13349         } else {
13350             gameInfo.round = StrSave("-");
13351         }
13352         if (first.twoMachinesColor[0] == 'w') {
13353             gameInfo.white = StrSave(first.tidy);
13354             gameInfo.black = StrSave(second.tidy);
13355         } else {
13356             gameInfo.white = StrSave(second.tidy);
13357             gameInfo.black = StrSave(first.tidy);
13358         }
13359         gameInfo.timeControl = TimeControlTagValue();
13360         break;
13361
13362       case EditGame:
13363         gameInfo.event = StrSave("Edited game");
13364         gameInfo.site = StrSave(HostName());
13365         gameInfo.date = PGNDate();
13366         gameInfo.round = StrSave("-");
13367         gameInfo.white = StrSave("-");
13368         gameInfo.black = StrSave("-");
13369         gameInfo.result = r;
13370         gameInfo.resultDetails = p;
13371         break;
13372
13373       case EditPosition:
13374         gameInfo.event = StrSave("Edited position");
13375         gameInfo.site = StrSave(HostName());
13376         gameInfo.date = PGNDate();
13377         gameInfo.round = StrSave("-");
13378         gameInfo.white = StrSave("-");
13379         gameInfo.black = StrSave("-");
13380         break;
13381
13382       case IcsPlayingWhite:
13383       case IcsPlayingBlack:
13384       case IcsObserving:
13385       case IcsExamining:
13386         break;
13387
13388       case PlayFromGameFile:
13389         gameInfo.event = StrSave("Game from non-PGN file");
13390         gameInfo.site = StrSave(HostName());
13391         gameInfo.date = PGNDate();
13392         gameInfo.round = StrSave("-");
13393         gameInfo.white = StrSave("?");
13394         gameInfo.black = StrSave("?");
13395         break;
13396
13397       default:
13398         break;
13399     }
13400 }
13401
13402 void
13403 ReplaceComment(index, text)
13404      int index;
13405      char *text;
13406 {
13407     int len;
13408
13409     while (*text == '\n') text++;
13410     len = strlen(text);
13411     while (len > 0 && text[len - 1] == '\n') len--;
13412
13413     if (commentList[index] != NULL)
13414       free(commentList[index]);
13415
13416     if (len == 0) {
13417         commentList[index] = NULL;
13418         return;
13419     }
13420   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13421       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13422       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13423     commentList[index] = (char *) malloc(len + 2);
13424     strncpy(commentList[index], text, len);
13425     commentList[index][len] = '\n';
13426     commentList[index][len + 1] = NULLCHAR;
13427   } else { 
13428     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13429     char *p;
13430     commentList[index] = (char *) malloc(len + 6);
13431     strcpy(commentList[index], "{\n");
13432     strncpy(commentList[index]+2, text, len);
13433     commentList[index][len+2] = NULLCHAR;
13434     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13435     strcat(commentList[index], "\n}\n");
13436   }
13437 }
13438
13439 void
13440 CrushCRs(text)
13441      char *text;
13442 {
13443   char *p = text;
13444   char *q = text;
13445   char ch;
13446
13447   do {
13448     ch = *p++;
13449     if (ch == '\r') continue;
13450     *q++ = ch;
13451   } while (ch != '\0');
13452 }
13453
13454 void
13455 AppendComment(index, text, addBraces)
13456      int index;
13457      char *text;
13458      Boolean addBraces; // [HGM] braces: tells if we should add {}
13459 {
13460     int oldlen, len;
13461     char *old;
13462
13463 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13464     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13465
13466     CrushCRs(text);
13467     while (*text == '\n') text++;
13468     len = strlen(text);
13469     while (len > 0 && text[len - 1] == '\n') len--;
13470
13471     if (len == 0) return;
13472
13473     if (commentList[index] != NULL) {
13474         old = commentList[index];
13475         oldlen = strlen(old);
13476         while(commentList[index][oldlen-1] ==  '\n')
13477           commentList[index][--oldlen] = NULLCHAR;
13478         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13479         strcpy(commentList[index], old);
13480         free(old);
13481         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13482         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13483           if(addBraces) addBraces = FALSE; else { text++; len--; }
13484           while (*text == '\n') { text++; len--; }
13485           commentList[index][--oldlen] = NULLCHAR;
13486       }
13487         if(addBraces) strcat(commentList[index], "\n{\n");
13488         else          strcat(commentList[index], "\n");
13489         strcat(commentList[index], text);
13490         if(addBraces) strcat(commentList[index], "\n}\n");
13491         else          strcat(commentList[index], "\n");
13492     } else {
13493         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13494         if(addBraces)
13495              strcpy(commentList[index], "{\n");
13496         else commentList[index][0] = NULLCHAR;
13497         strcat(commentList[index], text);
13498         strcat(commentList[index], "\n");
13499         if(addBraces) strcat(commentList[index], "}\n");
13500     }
13501 }
13502
13503 static char * FindStr( char * text, char * sub_text )
13504 {
13505     char * result = strstr( text, sub_text );
13506
13507     if( result != NULL ) {
13508         result += strlen( sub_text );
13509     }
13510
13511     return result;
13512 }
13513
13514 /* [AS] Try to extract PV info from PGN comment */
13515 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13516 char *GetInfoFromComment( int index, char * text )
13517 {
13518     char * sep = text;
13519
13520     if( text != NULL && index > 0 ) {
13521         int score = 0;
13522         int depth = 0;
13523         int time = -1, sec = 0, deci;
13524         char * s_eval = FindStr( text, "[%eval " );
13525         char * s_emt = FindStr( text, "[%emt " );
13526
13527         if( s_eval != NULL || s_emt != NULL ) {
13528             /* New style */
13529             char delim;
13530
13531             if( s_eval != NULL ) {
13532                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13533                     return text;
13534                 }
13535
13536                 if( delim != ']' ) {
13537                     return text;
13538                 }
13539             }
13540
13541             if( s_emt != NULL ) {
13542             }
13543                 return text;
13544         }
13545         else {
13546             /* We expect something like: [+|-]nnn.nn/dd */
13547             int score_lo = 0;
13548
13549             if(*text != '{') return text; // [HGM] braces: must be normal comment
13550
13551             sep = strchr( text, '/' );
13552             if( sep == NULL || sep < (text+4) ) {
13553                 return text;
13554             }
13555
13556             time = -1; sec = -1; deci = -1;
13557             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13558                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13559                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13560                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13561                 return text;
13562             }
13563
13564             if( score_lo < 0 || score_lo >= 100 ) {
13565                 return text;
13566             }
13567
13568             if(sec >= 0) time = 600*time + 10*sec; else
13569             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13570
13571             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13572
13573             /* [HGM] PV time: now locate end of PV info */
13574             while( *++sep >= '0' && *sep <= '9'); // strip depth
13575             if(time >= 0)
13576             while( *++sep >= '0' && *sep <= '9'); // strip time
13577             if(sec >= 0)
13578             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13579             if(deci >= 0)
13580             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13581             while(*sep == ' ') sep++;
13582         }
13583
13584         if( depth <= 0 ) {
13585             return text;
13586         }
13587
13588         if( time < 0 ) {
13589             time = -1;
13590         }
13591
13592         pvInfoList[index-1].depth = depth;
13593         pvInfoList[index-1].score = score;
13594         pvInfoList[index-1].time  = 10*time; // centi-sec
13595         if(*sep == '}') *sep = 0; else *--sep = '{';
13596     }
13597     return sep;
13598 }
13599
13600 void
13601 SendToProgram(message, cps)
13602      char *message;
13603      ChessProgramState *cps;
13604 {
13605     int count, outCount, error;
13606     char buf[MSG_SIZ];
13607
13608     if (cps->pr == NULL) return;
13609     Attention(cps);
13610
13611     if (appData.debugMode) {
13612         TimeMark now;
13613         GetTimeMark(&now);
13614         fprintf(debugFP, "%ld >%-6s: %s",
13615                 SubtractTimeMarks(&now, &programStartTime),
13616                 cps->which, message);
13617     }
13618
13619     count = strlen(message);
13620     outCount = OutputToProcess(cps->pr, message, count, &error);
13621     if (outCount < count && !exiting
13622                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13623         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13624         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13625             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13626                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13627                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13628             } else {
13629                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13630             }
13631             gameInfo.resultDetails = StrSave(buf);
13632         }
13633         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13634     }
13635 }
13636
13637 void
13638 ReceiveFromProgram(isr, closure, message, count, error)
13639      InputSourceRef isr;
13640      VOIDSTAR closure;
13641      char *message;
13642      int count;
13643      int error;
13644 {
13645     char *end_str;
13646     char buf[MSG_SIZ];
13647     ChessProgramState *cps = (ChessProgramState *)closure;
13648
13649     if (isr != cps->isr) return; /* Killed intentionally */
13650     if (count <= 0) {
13651         if (count == 0) {
13652             sprintf(buf,
13653                     _("Error: %s chess program (%s) exited unexpectedly"),
13654                     cps->which, cps->program);
13655         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13656                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13657                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13658                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13659                 } else {
13660                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13661                 }
13662                 gameInfo.resultDetails = StrSave(buf);
13663             }
13664             RemoveInputSource(cps->isr);
13665             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13666         } else {
13667             sprintf(buf,
13668                     _("Error reading from %s chess program (%s)"),
13669                     cps->which, cps->program);
13670             RemoveInputSource(cps->isr);
13671
13672             /* [AS] Program is misbehaving badly... kill it */
13673             if( count == -2 ) {
13674                 DestroyChildProcess( cps->pr, 9 );
13675                 cps->pr = NoProc;
13676             }
13677
13678             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13679         }
13680         return;
13681     }
13682
13683     if ((end_str = strchr(message, '\r')) != NULL)
13684       *end_str = NULLCHAR;
13685     if ((end_str = strchr(message, '\n')) != NULL)
13686       *end_str = NULLCHAR;
13687
13688     if (appData.debugMode) {
13689         TimeMark now; int print = 1;
13690         char *quote = ""; char c; int i;
13691
13692         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13693                 char start = message[0];
13694                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13695                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13696                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13697                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13698                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13699                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13700                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13701                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13702                         { quote = "# "; print = (appData.engineComments == 2); }
13703                 message[0] = start; // restore original message
13704         }
13705         if(print) {
13706                 GetTimeMark(&now);
13707                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13708                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13709                         quote,
13710                         message);
13711         }
13712     }
13713
13714     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13715     if (appData.icsEngineAnalyze) {
13716         if (strstr(message, "whisper") != NULL ||
13717              strstr(message, "kibitz") != NULL ||
13718             strstr(message, "tellics") != NULL) return;
13719     }
13720
13721     HandleMachineMove(message, cps);
13722 }
13723
13724
13725 void
13726 SendTimeControl(cps, mps, tc, inc, sd, st)
13727      ChessProgramState *cps;
13728      int mps, inc, sd, st;
13729      long tc;
13730 {
13731     char buf[MSG_SIZ];
13732     int seconds;
13733
13734     if( timeControl_2 > 0 ) {
13735         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13736             tc = timeControl_2;
13737         }
13738     }
13739     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13740     inc /= cps->timeOdds;
13741     st  /= cps->timeOdds;
13742
13743     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13744
13745     if (st > 0) {
13746       /* Set exact time per move, normally using st command */
13747       if (cps->stKludge) {
13748         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13749         seconds = st % 60;
13750         if (seconds == 0) {
13751           sprintf(buf, "level 1 %d\n", st/60);
13752         } else {
13753           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13754         }
13755       } else {
13756         sprintf(buf, "st %d\n", st);
13757       }
13758     } else {
13759       /* Set conventional or incremental time control, using level command */
13760       if (seconds == 0) {
13761         /* Note old gnuchess bug -- minutes:seconds used to not work.
13762            Fixed in later versions, but still avoid :seconds
13763            when seconds is 0. */
13764         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13765       } else {
13766         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13767                 seconds, inc/1000);
13768       }
13769     }
13770     SendToProgram(buf, cps);
13771
13772     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13773     /* Orthogonally, limit search to given depth */
13774     if (sd > 0) {
13775       if (cps->sdKludge) {
13776         sprintf(buf, "depth\n%d\n", sd);
13777       } else {
13778         sprintf(buf, "sd %d\n", sd);
13779       }
13780       SendToProgram(buf, cps);
13781     }
13782
13783     if(cps->nps > 0) { /* [HGM] nps */
13784         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13785         else {
13786                 sprintf(buf, "nps %d\n", cps->nps);
13787               SendToProgram(buf, cps);
13788         }
13789     }
13790 }
13791
13792 ChessProgramState *WhitePlayer()
13793 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13794 {
13795     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13796        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13797         return &second;
13798     return &first;
13799 }
13800
13801 void
13802 SendTimeRemaining(cps, machineWhite)
13803      ChessProgramState *cps;
13804      int /*boolean*/ machineWhite;
13805 {
13806     char message[MSG_SIZ];
13807     long time, otime;
13808
13809     /* Note: this routine must be called when the clocks are stopped
13810        or when they have *just* been set or switched; otherwise
13811        it will be off by the time since the current tick started.
13812     */
13813     if (machineWhite) {
13814         time = whiteTimeRemaining / 10;
13815         otime = blackTimeRemaining / 10;
13816     } else {
13817         time = blackTimeRemaining / 10;
13818         otime = whiteTimeRemaining / 10;
13819     }
13820     /* [HGM] translate opponent's time by time-odds factor */
13821     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13822     if (appData.debugMode) {
13823         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13824     }
13825
13826     if (time <= 0) time = 1;
13827     if (otime <= 0) otime = 1;
13828
13829     sprintf(message, "time %ld\n", time);
13830     SendToProgram(message, cps);
13831
13832     sprintf(message, "otim %ld\n", otime);
13833     SendToProgram(message, cps);
13834 }
13835
13836 int
13837 BoolFeature(p, name, loc, cps)
13838      char **p;
13839      char *name;
13840      int *loc;
13841      ChessProgramState *cps;
13842 {
13843   char buf[MSG_SIZ];
13844   int len = strlen(name);
13845   int val;
13846   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13847     (*p) += len + 1;
13848     sscanf(*p, "%d", &val);
13849     *loc = (val != 0);
13850     while (**p && **p != ' ') (*p)++;
13851     sprintf(buf, "accepted %s\n", name);
13852     SendToProgram(buf, cps);
13853     return TRUE;
13854   }
13855   return FALSE;
13856 }
13857
13858 int
13859 IntFeature(p, name, loc, cps)
13860      char **p;
13861      char *name;
13862      int *loc;
13863      ChessProgramState *cps;
13864 {
13865   char buf[MSG_SIZ];
13866   int len = strlen(name);
13867   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13868     (*p) += len + 1;
13869     sscanf(*p, "%d", loc);
13870     while (**p && **p != ' ') (*p)++;
13871     sprintf(buf, "accepted %s\n", name);
13872     SendToProgram(buf, cps);
13873     return TRUE;
13874   }
13875   return FALSE;
13876 }
13877
13878 int
13879 StringFeature(p, name, loc, cps)
13880      char **p;
13881      char *name;
13882      char loc[];
13883      ChessProgramState *cps;
13884 {
13885   char buf[MSG_SIZ];
13886   int len = strlen(name);
13887   if (strncmp((*p), name, len) == 0
13888       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13889     (*p) += len + 2;
13890     sscanf(*p, "%[^\"]", loc);
13891     while (**p && **p != '\"') (*p)++;
13892     if (**p == '\"') (*p)++;
13893     sprintf(buf, "accepted %s\n", name);
13894     SendToProgram(buf, cps);
13895     return TRUE;
13896   }
13897   return FALSE;
13898 }
13899
13900 int
13901 ParseOption(Option *opt, ChessProgramState *cps)
13902 // [HGM] options: process the string that defines an engine option, and determine
13903 // name, type, default value, and allowed value range
13904 {
13905         char *p, *q, buf[MSG_SIZ];
13906         int n, min = (-1)<<31, max = 1<<31, def;
13907
13908         if(p = strstr(opt->name, " -spin ")) {
13909             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13910             if(max < min) max = min; // enforce consistency
13911             if(def < min) def = min;
13912             if(def > max) def = max;
13913             opt->value = def;
13914             opt->min = min;
13915             opt->max = max;
13916             opt->type = Spin;
13917         } else if((p = strstr(opt->name, " -slider "))) {
13918             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13919             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13920             if(max < min) max = min; // enforce consistency
13921             if(def < min) def = min;
13922             if(def > max) def = max;
13923             opt->value = def;
13924             opt->min = min;
13925             opt->max = max;
13926             opt->type = Spin; // Slider;
13927         } else if((p = strstr(opt->name, " -string "))) {
13928             opt->textValue = p+9;
13929             opt->type = TextBox;
13930         } else if((p = strstr(opt->name, " -file "))) {
13931             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13932             opt->textValue = p+7;
13933             opt->type = TextBox; // FileName;
13934         } else if((p = strstr(opt->name, " -path "))) {
13935             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13936             opt->textValue = p+7;
13937             opt->type = TextBox; // PathName;
13938         } else if(p = strstr(opt->name, " -check ")) {
13939             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13940             opt->value = (def != 0);
13941             opt->type = CheckBox;
13942         } else if(p = strstr(opt->name, " -combo ")) {
13943             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13944             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13945             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13946             opt->value = n = 0;
13947             while(q = StrStr(q, " /// ")) {
13948                 n++; *q = 0;    // count choices, and null-terminate each of them
13949                 q += 5;
13950                 if(*q == '*') { // remember default, which is marked with * prefix
13951                     q++;
13952                     opt->value = n;
13953                 }
13954                 cps->comboList[cps->comboCnt++] = q;
13955             }
13956             cps->comboList[cps->comboCnt++] = NULL;
13957             opt->max = n + 1;
13958             opt->type = ComboBox;
13959         } else if(p = strstr(opt->name, " -button")) {
13960             opt->type = Button;
13961         } else if(p = strstr(opt->name, " -save")) {
13962             opt->type = SaveButton;
13963         } else return FALSE;
13964         *p = 0; // terminate option name
13965         // now look if the command-line options define a setting for this engine option.
13966         if(cps->optionSettings && cps->optionSettings[0])
13967             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13968         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13969                 sprintf(buf, "option %s", p);
13970                 if(p = strstr(buf, ",")) *p = 0;
13971                 strcat(buf, "\n");
13972                 SendToProgram(buf, cps);
13973         }
13974         return TRUE;
13975 }
13976
13977 void
13978 FeatureDone(cps, val)
13979      ChessProgramState* cps;
13980      int val;
13981 {
13982   DelayedEventCallback cb = GetDelayedEvent();
13983   if ((cb == InitBackEnd3 && cps == &first) ||
13984       (cb == TwoMachinesEventIfReady && cps == &second)) {
13985     CancelDelayedEvent();
13986     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13987   }
13988   cps->initDone = val;
13989 }
13990
13991 /* Parse feature command from engine */
13992 void
13993 ParseFeatures(args, cps)
13994      char* args;
13995      ChessProgramState *cps;
13996 {
13997   char *p = args;
13998   char *q;
13999   int val;
14000   char buf[MSG_SIZ];
14001
14002   for (;;) {
14003     while (*p == ' ') p++;
14004     if (*p == NULLCHAR) return;
14005
14006     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14007     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14008     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14009     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14010     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14011     if (BoolFeature(&p, "reuse", &val, cps)) {
14012       /* Engine can disable reuse, but can't enable it if user said no */
14013       if (!val) cps->reuse = FALSE;
14014       continue;
14015     }
14016     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14017     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14018       if (gameMode == TwoMachinesPlay) {
14019         DisplayTwoMachinesTitle();
14020       } else {
14021         DisplayTitle("");
14022       }
14023       continue;
14024     }
14025     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14026     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14027     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14028     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14029     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14030     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14031     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14032     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14033     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14034     if (IntFeature(&p, "done", &val, cps)) {
14035       FeatureDone(cps, val);
14036       continue;
14037     }
14038     /* Added by Tord: */
14039     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14040     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14041     /* End of additions by Tord */
14042
14043     /* [HGM] added features: */
14044     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14045     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14046     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14047     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14048     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14049     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14050     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14051         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14052             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14053             SendToProgram(buf, cps);
14054             continue;
14055         }
14056         if(cps->nrOptions >= MAX_OPTIONS) {
14057             cps->nrOptions--;
14058             sprintf(buf, "%s engine has too many options\n", cps->which);
14059             DisplayError(buf, 0);
14060         }
14061         continue;
14062     }
14063     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14064     /* End of additions by HGM */
14065
14066     /* unknown feature: complain and skip */
14067     q = p;
14068     while (*q && *q != '=') q++;
14069     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
14070     SendToProgram(buf, cps);
14071     p = q;
14072     if (*p == '=') {
14073       p++;
14074       if (*p == '\"') {
14075         p++;
14076         while (*p && *p != '\"') p++;
14077         if (*p == '\"') p++;
14078       } else {
14079         while (*p && *p != ' ') p++;
14080       }
14081     }
14082   }
14083
14084 }
14085
14086 void
14087 PeriodicUpdatesEvent(newState)
14088      int newState;
14089 {
14090     if (newState == appData.periodicUpdates)
14091       return;
14092
14093     appData.periodicUpdates=newState;
14094
14095     /* Display type changes, so update it now */
14096 //    DisplayAnalysis();
14097
14098     /* Get the ball rolling again... */
14099     if (newState) {
14100         AnalysisPeriodicEvent(1);
14101         StartAnalysisClock();
14102     }
14103 }
14104
14105 void
14106 PonderNextMoveEvent(newState)
14107      int newState;
14108 {
14109     if (newState == appData.ponderNextMove) return;
14110     if (gameMode == EditPosition) EditPositionDone(TRUE);
14111     if (newState) {
14112         SendToProgram("hard\n", &first);
14113         if (gameMode == TwoMachinesPlay) {
14114             SendToProgram("hard\n", &second);
14115         }
14116     } else {
14117         SendToProgram("easy\n", &first);
14118         thinkOutput[0] = NULLCHAR;
14119         if (gameMode == TwoMachinesPlay) {
14120             SendToProgram("easy\n", &second);
14121         }
14122     }
14123     appData.ponderNextMove = newState;
14124 }
14125
14126 void
14127 NewSettingEvent(option, command, value)
14128      char *command;
14129      int option, value;
14130 {
14131     char buf[MSG_SIZ];
14132
14133     if (gameMode == EditPosition) EditPositionDone(TRUE);
14134     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
14135     SendToProgram(buf, &first);
14136     if (gameMode == TwoMachinesPlay) {
14137         SendToProgram(buf, &second);
14138     }
14139 }
14140
14141 void
14142 ShowThinkingEvent()
14143 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14144 {
14145     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14146     int newState = appData.showThinking
14147         // [HGM] thinking: other features now need thinking output as well
14148         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14149
14150     if (oldState == newState) return;
14151     oldState = newState;
14152     if (gameMode == EditPosition) EditPositionDone(TRUE);
14153     if (oldState) {
14154         SendToProgram("post\n", &first);
14155         if (gameMode == TwoMachinesPlay) {
14156             SendToProgram("post\n", &second);
14157         }
14158     } else {
14159         SendToProgram("nopost\n", &first);
14160         thinkOutput[0] = NULLCHAR;
14161         if (gameMode == TwoMachinesPlay) {
14162             SendToProgram("nopost\n", &second);
14163         }
14164     }
14165 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14166 }
14167
14168 void
14169 AskQuestionEvent(title, question, replyPrefix, which)
14170      char *title; char *question; char *replyPrefix; char *which;
14171 {
14172   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14173   if (pr == NoProc) return;
14174   AskQuestion(title, question, replyPrefix, pr);
14175 }
14176
14177 void
14178 DisplayMove(moveNumber)
14179      int moveNumber;
14180 {
14181     char message[MSG_SIZ];
14182     char res[MSG_SIZ];
14183     char cpThinkOutput[MSG_SIZ];
14184
14185     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14186
14187     if (moveNumber == forwardMostMove - 1 ||
14188         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14189
14190         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
14191
14192         if (strchr(cpThinkOutput, '\n')) {
14193             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14194         }
14195     } else {
14196         *cpThinkOutput = NULLCHAR;
14197     }
14198
14199     /* [AS] Hide thinking from human user */
14200     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14201         *cpThinkOutput = NULLCHAR;
14202         if( thinkOutput[0] != NULLCHAR ) {
14203             int i;
14204
14205             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14206                 cpThinkOutput[i] = '.';
14207             }
14208             cpThinkOutput[i] = NULLCHAR;
14209             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14210         }
14211     }
14212
14213     if (moveNumber == forwardMostMove - 1 &&
14214         gameInfo.resultDetails != NULL) {
14215         if (gameInfo.resultDetails[0] == NULLCHAR) {
14216             sprintf(res, " %s", PGNResult(gameInfo.result));
14217         } else {
14218             sprintf(res, " {%s} %s",
14219                     gameInfo.resultDetails, PGNResult(gameInfo.result));
14220         }
14221     } else {
14222         res[0] = NULLCHAR;
14223     }
14224
14225     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14226         DisplayMessage(res, cpThinkOutput);
14227     } else {
14228         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
14229                 WhiteOnMove(moveNumber) ? " " : ".. ",
14230                 parseList[moveNumber], res);
14231         DisplayMessage(message, cpThinkOutput);
14232     }
14233 }
14234
14235 void
14236 DisplayComment(moveNumber, text)
14237      int moveNumber;
14238      char *text;
14239 {
14240     char title[MSG_SIZ];
14241     char buf[8000]; // comment can be long!
14242     int score, depth;
14243     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14244       strcpy(title, "Comment");
14245     } else {
14246       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
14247               WhiteOnMove(moveNumber) ? " " : ".. ",
14248               parseList[moveNumber]);
14249     }
14250     // [HGM] PV info: display PV info together with (or as) comment
14251     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14252       if(text == NULL) text = "";                                           
14253       score = pvInfoList[moveNumber].score;
14254       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14255               depth, (pvInfoList[moveNumber].time+50)/100, text);
14256       text = buf;
14257     }
14258     if (text != NULL && (appData.autoDisplayComment || commentUp))
14259       CommentPopUp(title, text);
14260 }
14261
14262 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14263  * might be busy thinking or pondering.  It can be omitted if your
14264  * gnuchess is configured to stop thinking immediately on any user
14265  * input.  However, that gnuchess feature depends on the FIONREAD
14266  * ioctl, which does not work properly on some flavors of Unix.
14267  */
14268 void
14269 Attention(cps)
14270      ChessProgramState *cps;
14271 {
14272 #if ATTENTION
14273     if (!cps->useSigint) return;
14274     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14275     switch (gameMode) {
14276       case MachinePlaysWhite:
14277       case MachinePlaysBlack:
14278       case TwoMachinesPlay:
14279       case IcsPlayingWhite:
14280       case IcsPlayingBlack:
14281       case AnalyzeMode:
14282       case AnalyzeFile:
14283         /* Skip if we know it isn't thinking */
14284         if (!cps->maybeThinking) return;
14285         if (appData.debugMode)
14286           fprintf(debugFP, "Interrupting %s\n", cps->which);
14287         InterruptChildProcess(cps->pr);
14288         cps->maybeThinking = FALSE;
14289         break;
14290       default:
14291         break;
14292     }
14293 #endif /*ATTENTION*/
14294 }
14295
14296 int
14297 CheckFlags()
14298 {
14299     if (whiteTimeRemaining <= 0) {
14300         if (!whiteFlag) {
14301             whiteFlag = TRUE;
14302             if (appData.icsActive) {
14303                 if (appData.autoCallFlag &&
14304                     gameMode == IcsPlayingBlack && !blackFlag) {
14305                   SendToICS(ics_prefix);
14306                   SendToICS("flag\n");
14307                 }
14308             } else {
14309                 if (blackFlag) {
14310                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14311                 } else {
14312                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14313                     if (appData.autoCallFlag) {
14314                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14315                         return TRUE;
14316                     }
14317                 }
14318             }
14319         }
14320     }
14321     if (blackTimeRemaining <= 0) {
14322         if (!blackFlag) {
14323             blackFlag = TRUE;
14324             if (appData.icsActive) {
14325                 if (appData.autoCallFlag &&
14326                     gameMode == IcsPlayingWhite && !whiteFlag) {
14327                   SendToICS(ics_prefix);
14328                   SendToICS("flag\n");
14329                 }
14330             } else {
14331                 if (whiteFlag) {
14332                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14333                 } else {
14334                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14335                     if (appData.autoCallFlag) {
14336                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14337                         return TRUE;
14338                     }
14339                 }
14340             }
14341         }
14342     }
14343     return FALSE;
14344 }
14345
14346 void
14347 CheckTimeControl()
14348 {
14349     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14350         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14351
14352     /*
14353      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14354      */
14355     if ( !WhiteOnMove(forwardMostMove) )
14356         /* White made time control */
14357         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14358         /* [HGM] time odds: correct new time quota for time odds! */
14359                                             / WhitePlayer()->timeOdds;
14360       else
14361         /* Black made time control */
14362         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14363                                             / WhitePlayer()->other->timeOdds;
14364 }
14365
14366 void
14367 DisplayBothClocks()
14368 {
14369     int wom = gameMode == EditPosition ?
14370       !blackPlaysFirst : WhiteOnMove(currentMove);
14371     DisplayWhiteClock(whiteTimeRemaining, wom);
14372     DisplayBlackClock(blackTimeRemaining, !wom);
14373 }
14374
14375
14376 /* Timekeeping seems to be a portability nightmare.  I think everyone
14377    has ftime(), but I'm really not sure, so I'm including some ifdefs
14378    to use other calls if you don't.  Clocks will be less accurate if
14379    you have neither ftime nor gettimeofday.
14380 */
14381
14382 /* VS 2008 requires the #include outside of the function */
14383 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14384 #include <sys/timeb.h>
14385 #endif
14386
14387 /* Get the current time as a TimeMark */
14388 void
14389 GetTimeMark(tm)
14390      TimeMark *tm;
14391 {
14392 #if HAVE_GETTIMEOFDAY
14393
14394     struct timeval timeVal;
14395     struct timezone timeZone;
14396
14397     gettimeofday(&timeVal, &timeZone);
14398     tm->sec = (long) timeVal.tv_sec;
14399     tm->ms = (int) (timeVal.tv_usec / 1000L);
14400
14401 #else /*!HAVE_GETTIMEOFDAY*/
14402 #if HAVE_FTIME
14403
14404 // include <sys/timeb.h> / moved to just above start of function
14405     struct timeb timeB;
14406
14407     ftime(&timeB);
14408     tm->sec = (long) timeB.time;
14409     tm->ms = (int) timeB.millitm;
14410
14411 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14412     tm->sec = (long) time(NULL);
14413     tm->ms = 0;
14414 #endif
14415 #endif
14416 }
14417
14418 /* Return the difference in milliseconds between two
14419    time marks.  We assume the difference will fit in a long!
14420 */
14421 long
14422 SubtractTimeMarks(tm2, tm1)
14423      TimeMark *tm2, *tm1;
14424 {
14425     return 1000L*(tm2->sec - tm1->sec) +
14426            (long) (tm2->ms - tm1->ms);
14427 }
14428
14429
14430 /*
14431  * Code to manage the game clocks.
14432  *
14433  * In tournament play, black starts the clock and then white makes a move.
14434  * We give the human user a slight advantage if he is playing white---the
14435  * clocks don't run until he makes his first move, so it takes zero time.
14436  * Also, we don't account for network lag, so we could get out of sync
14437  * with GNU Chess's clock -- but then, referees are always right.
14438  */
14439
14440 static TimeMark tickStartTM;
14441 static long intendedTickLength;
14442
14443 long
14444 NextTickLength(timeRemaining)
14445      long timeRemaining;
14446 {
14447     long nominalTickLength, nextTickLength;
14448
14449     if (timeRemaining > 0L && timeRemaining <= 10000L)
14450       nominalTickLength = 100L;
14451     else
14452       nominalTickLength = 1000L;
14453     nextTickLength = timeRemaining % nominalTickLength;
14454     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14455
14456     return nextTickLength;
14457 }
14458
14459 /* Adjust clock one minute up or down */
14460 void
14461 AdjustClock(Boolean which, int dir)
14462 {
14463     if(which) blackTimeRemaining += 60000*dir;
14464     else      whiteTimeRemaining += 60000*dir;
14465     DisplayBothClocks();
14466 }
14467
14468 /* Stop clocks and reset to a fresh time control */
14469 void
14470 ResetClocks()
14471 {
14472     (void) StopClockTimer();
14473     if (appData.icsActive) {
14474         whiteTimeRemaining = blackTimeRemaining = 0;
14475     } else if (searchTime) {
14476         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14477         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14478     } else { /* [HGM] correct new time quote for time odds */
14479         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14480         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14481     }
14482     if (whiteFlag || blackFlag) {
14483         DisplayTitle("");
14484         whiteFlag = blackFlag = FALSE;
14485     }
14486     DisplayBothClocks();
14487 }
14488
14489 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14490
14491 /* Decrement running clock by amount of time that has passed */
14492 void
14493 DecrementClocks()
14494 {
14495     long timeRemaining;
14496     long lastTickLength, fudge;
14497     TimeMark now;
14498
14499     if (!appData.clockMode) return;
14500     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14501
14502     GetTimeMark(&now);
14503
14504     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14505
14506     /* Fudge if we woke up a little too soon */
14507     fudge = intendedTickLength - lastTickLength;
14508     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14509
14510     if (WhiteOnMove(forwardMostMove)) {
14511         if(whiteNPS >= 0) lastTickLength = 0;
14512         timeRemaining = whiteTimeRemaining -= lastTickLength;
14513         DisplayWhiteClock(whiteTimeRemaining - fudge,
14514                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14515     } else {
14516         if(blackNPS >= 0) lastTickLength = 0;
14517         timeRemaining = blackTimeRemaining -= lastTickLength;
14518         DisplayBlackClock(blackTimeRemaining - fudge,
14519                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14520     }
14521
14522     if (CheckFlags()) return;
14523
14524     tickStartTM = now;
14525     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14526     StartClockTimer(intendedTickLength);
14527
14528     /* if the time remaining has fallen below the alarm threshold, sound the
14529      * alarm. if the alarm has sounded and (due to a takeback or time control
14530      * with increment) the time remaining has increased to a level above the
14531      * threshold, reset the alarm so it can sound again.
14532      */
14533
14534     if (appData.icsActive && appData.icsAlarm) {
14535
14536         /* make sure we are dealing with the user's clock */
14537         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14538                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14539            )) return;
14540
14541         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14542             alarmSounded = FALSE;
14543         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14544             PlayAlarmSound();
14545             alarmSounded = TRUE;
14546         }
14547     }
14548 }
14549
14550
14551 /* A player has just moved, so stop the previously running
14552    clock and (if in clock mode) start the other one.
14553    We redisplay both clocks in case we're in ICS mode, because
14554    ICS gives us an update to both clocks after every move.
14555    Note that this routine is called *after* forwardMostMove
14556    is updated, so the last fractional tick must be subtracted
14557    from the color that is *not* on move now.
14558 */
14559 void
14560 SwitchClocks()
14561 {
14562     long lastTickLength;
14563     TimeMark now;
14564     int flagged = FALSE;
14565
14566     GetTimeMark(&now);
14567
14568     if (StopClockTimer() && appData.clockMode) {
14569         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14570         if (WhiteOnMove(forwardMostMove)) {
14571             if(blackNPS >= 0) lastTickLength = 0;
14572             blackTimeRemaining -= lastTickLength;
14573            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14574 //         if(pvInfoList[forwardMostMove-1].time == -1)
14575                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14576                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14577         } else {
14578            if(whiteNPS >= 0) lastTickLength = 0;
14579            whiteTimeRemaining -= lastTickLength;
14580            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14581 //         if(pvInfoList[forwardMostMove-1].time == -1)
14582                  pvInfoList[forwardMostMove-1].time =
14583                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14584         }
14585         flagged = CheckFlags();
14586     }
14587     CheckTimeControl();
14588
14589     if (flagged || !appData.clockMode) return;
14590
14591     switch (gameMode) {
14592       case MachinePlaysBlack:
14593       case MachinePlaysWhite:
14594       case BeginningOfGame:
14595         if (pausing) return;
14596         break;
14597
14598       case EditGame:
14599       case PlayFromGameFile:
14600       case IcsExamining:
14601         return;
14602
14603       default:
14604         break;
14605     }
14606
14607     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14608         if(WhiteOnMove(forwardMostMove))
14609              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14610         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14611     }
14612
14613     tickStartTM = now;
14614     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14615       whiteTimeRemaining : blackTimeRemaining);
14616     StartClockTimer(intendedTickLength);
14617 }
14618
14619
14620 /* Stop both clocks */
14621 void
14622 StopClocks()
14623 {
14624     long lastTickLength;
14625     TimeMark now;
14626
14627     if (!StopClockTimer()) return;
14628     if (!appData.clockMode) return;
14629
14630     GetTimeMark(&now);
14631
14632     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14633     if (WhiteOnMove(forwardMostMove)) {
14634         if(whiteNPS >= 0) lastTickLength = 0;
14635         whiteTimeRemaining -= lastTickLength;
14636         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14637     } else {
14638         if(blackNPS >= 0) lastTickLength = 0;
14639         blackTimeRemaining -= lastTickLength;
14640         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14641     }
14642     CheckFlags();
14643 }
14644
14645 /* Start clock of player on move.  Time may have been reset, so
14646    if clock is already running, stop and restart it. */
14647 void
14648 StartClocks()
14649 {
14650     (void) StopClockTimer(); /* in case it was running already */
14651     DisplayBothClocks();
14652     if (CheckFlags()) return;
14653
14654     if (!appData.clockMode) return;
14655     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14656
14657     GetTimeMark(&tickStartTM);
14658     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14659       whiteTimeRemaining : blackTimeRemaining);
14660
14661    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14662     whiteNPS = blackNPS = -1;
14663     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14664        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14665         whiteNPS = first.nps;
14666     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14667        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14668         blackNPS = first.nps;
14669     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14670         whiteNPS = second.nps;
14671     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14672         blackNPS = second.nps;
14673     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14674
14675     StartClockTimer(intendedTickLength);
14676 }
14677
14678 char *
14679 TimeString(ms)
14680      long ms;
14681 {
14682     long second, minute, hour, day;
14683     char *sign = "";
14684     static char buf[32];
14685
14686     if (ms > 0 && ms <= 9900) {
14687       /* convert milliseconds to tenths, rounding up */
14688       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14689
14690       sprintf(buf, " %03.1f ", tenths/10.0);
14691       return buf;
14692     }
14693
14694     /* convert milliseconds to seconds, rounding up */
14695     /* use floating point to avoid strangeness of integer division
14696        with negative dividends on many machines */
14697     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14698
14699     if (second < 0) {
14700         sign = "-";
14701         second = -second;
14702     }
14703
14704     day = second / (60 * 60 * 24);
14705     second = second % (60 * 60 * 24);
14706     hour = second / (60 * 60);
14707     second = second % (60 * 60);
14708     minute = second / 60;
14709     second = second % 60;
14710
14711     if (day > 0)
14712       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14713               sign, day, hour, minute, second);
14714     else if (hour > 0)
14715       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14716     else
14717       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14718
14719     return buf;
14720 }
14721
14722
14723 /*
14724  * This is necessary because some C libraries aren't ANSI C compliant yet.
14725  */
14726 char *
14727 StrStr(string, match)
14728      char *string, *match;
14729 {
14730     int i, length;
14731
14732     length = strlen(match);
14733
14734     for (i = strlen(string) - length; i >= 0; i--, string++)
14735       if (!strncmp(match, string, length))
14736         return string;
14737
14738     return NULL;
14739 }
14740
14741 char *
14742 StrCaseStr(string, match)
14743      char *string, *match;
14744 {
14745     int i, j, length;
14746
14747     length = strlen(match);
14748
14749     for (i = strlen(string) - length; i >= 0; i--, string++) {
14750         for (j = 0; j < length; j++) {
14751             if (ToLower(match[j]) != ToLower(string[j]))
14752               break;
14753         }
14754         if (j == length) return string;
14755     }
14756
14757     return NULL;
14758 }
14759
14760 #ifndef _amigados
14761 int
14762 StrCaseCmp(s1, s2)
14763      char *s1, *s2;
14764 {
14765     char c1, c2;
14766
14767     for (;;) {
14768         c1 = ToLower(*s1++);
14769         c2 = ToLower(*s2++);
14770         if (c1 > c2) return 1;
14771         if (c1 < c2) return -1;
14772         if (c1 == NULLCHAR) return 0;
14773     }
14774 }
14775
14776
14777 int
14778 ToLower(c)
14779      int c;
14780 {
14781     return isupper(c) ? tolower(c) : c;
14782 }
14783
14784
14785 int
14786 ToUpper(c)
14787      int c;
14788 {
14789     return islower(c) ? toupper(c) : c;
14790 }
14791 #endif /* !_amigados    */
14792
14793 char *
14794 StrSave(s)
14795      char *s;
14796 {
14797     char *ret;
14798
14799     if ((ret = (char *) malloc(strlen(s) + 1))) {
14800         strcpy(ret, s);
14801     }
14802     return ret;
14803 }
14804
14805 char *
14806 StrSavePtr(s, savePtr)
14807      char *s, **savePtr;
14808 {
14809     if (*savePtr) {
14810         free(*savePtr);
14811     }
14812     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14813         strcpy(*savePtr, s);
14814     }
14815     return(*savePtr);
14816 }
14817
14818 char *
14819 PGNDate()
14820 {
14821     time_t clock;
14822     struct tm *tm;
14823     char buf[MSG_SIZ];
14824
14825     clock = time((time_t *)NULL);
14826     tm = localtime(&clock);
14827     sprintf(buf, "%04d.%02d.%02d",
14828             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14829     return StrSave(buf);
14830 }
14831
14832
14833 char *
14834 PositionToFEN(move, overrideCastling)
14835      int move;
14836      char *overrideCastling;
14837 {
14838     int i, j, fromX, fromY, toX, toY;
14839     int whiteToPlay;
14840     char buf[128];
14841     char *p, *q;
14842     int emptycount;
14843     ChessSquare piece;
14844
14845     whiteToPlay = (gameMode == EditPosition) ?
14846       !blackPlaysFirst : (move % 2 == 0);
14847     p = buf;
14848
14849     /* Piece placement data */
14850     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14851         emptycount = 0;
14852         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14853             if (boards[move][i][j] == EmptySquare) {
14854                 emptycount++;
14855             } else { ChessSquare piece = boards[move][i][j];
14856                 if (emptycount > 0) {
14857                     if(emptycount<10) /* [HGM] can be >= 10 */
14858                         *p++ = '0' + emptycount;
14859                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14860                     emptycount = 0;
14861                 }
14862                 if(PieceToChar(piece) == '+') {
14863                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14864                     *p++ = '+';
14865                     piece = (ChessSquare)(DEMOTED piece);
14866                 }
14867                 *p++ = PieceToChar(piece);
14868                 if(p[-1] == '~') {
14869                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14870                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14871                     *p++ = '~';
14872                 }
14873             }
14874         }
14875         if (emptycount > 0) {
14876             if(emptycount<10) /* [HGM] can be >= 10 */
14877                 *p++ = '0' + emptycount;
14878             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14879             emptycount = 0;
14880         }
14881         *p++ = '/';
14882     }
14883     *(p - 1) = ' ';
14884
14885     /* [HGM] print Crazyhouse or Shogi holdings */
14886     if( gameInfo.holdingsWidth ) {
14887         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14888         q = p;
14889         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14890             piece = boards[move][i][BOARD_WIDTH-1];
14891             if( piece != EmptySquare )
14892               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14893                   *p++ = PieceToChar(piece);
14894         }
14895         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14896             piece = boards[move][BOARD_HEIGHT-i-1][0];
14897             if( piece != EmptySquare )
14898               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14899                   *p++ = PieceToChar(piece);
14900         }
14901
14902         if( q == p ) *p++ = '-';
14903         *p++ = ']';
14904         *p++ = ' ';
14905     }
14906
14907     /* Active color */
14908     *p++ = whiteToPlay ? 'w' : 'b';
14909     *p++ = ' ';
14910
14911   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14912     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14913   } else {
14914   if(nrCastlingRights) {
14915      q = p;
14916      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14917        /* [HGM] write directly from rights */
14918            if(boards[move][CASTLING][2] != NoRights &&
14919               boards[move][CASTLING][0] != NoRights   )
14920                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14921            if(boards[move][CASTLING][2] != NoRights &&
14922               boards[move][CASTLING][1] != NoRights   )
14923                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14924            if(boards[move][CASTLING][5] != NoRights &&
14925               boards[move][CASTLING][3] != NoRights   )
14926                 *p++ = boards[move][CASTLING][3] + AAA;
14927            if(boards[move][CASTLING][5] != NoRights &&
14928               boards[move][CASTLING][4] != NoRights   )
14929                 *p++ = boards[move][CASTLING][4] + AAA;
14930      } else {
14931
14932         /* [HGM] write true castling rights */
14933         if( nrCastlingRights == 6 ) {
14934             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14935                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14936             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14937                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14938             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14939                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14940             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14941                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14942         }
14943      }
14944      if (q == p) *p++ = '-'; /* No castling rights */
14945      *p++ = ' ';
14946   }
14947
14948   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14949      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14950     /* En passant target square */
14951     if (move > backwardMostMove) {
14952         fromX = moveList[move - 1][0] - AAA;
14953         fromY = moveList[move - 1][1] - ONE;
14954         toX = moveList[move - 1][2] - AAA;
14955         toY = moveList[move - 1][3] - ONE;
14956         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14957             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14958             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14959             fromX == toX) {
14960             /* 2-square pawn move just happened */
14961             *p++ = toX + AAA;
14962             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14963         } else {
14964             *p++ = '-';
14965         }
14966     } else if(move == backwardMostMove) {
14967         // [HGM] perhaps we should always do it like this, and forget the above?
14968         if((signed char)boards[move][EP_STATUS] >= 0) {
14969             *p++ = boards[move][EP_STATUS] + AAA;
14970             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14971         } else {
14972             *p++ = '-';
14973         }
14974     } else {
14975         *p++ = '-';
14976     }
14977     *p++ = ' ';
14978   }
14979   }
14980
14981     /* [HGM] find reversible plies */
14982     {   int i = 0, j=move;
14983
14984         if (appData.debugMode) { int k;
14985             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14986             for(k=backwardMostMove; k<=forwardMostMove; k++)
14987                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14988
14989         }
14990
14991         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14992         if( j == backwardMostMove ) i += initialRulePlies;
14993         sprintf(p, "%d ", i);
14994         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14995     }
14996     /* Fullmove number */
14997     sprintf(p, "%d", (move / 2) + 1);
14998
14999     return StrSave(buf);
15000 }
15001
15002 Boolean
15003 ParseFEN(board, blackPlaysFirst, fen)
15004     Board board;
15005      int *blackPlaysFirst;
15006      char *fen;
15007 {
15008     int i, j;
15009     char *p;
15010     int emptycount;
15011     ChessSquare piece;
15012
15013     p = fen;
15014
15015     /* [HGM] by default clear Crazyhouse holdings, if present */
15016     if(gameInfo.holdingsWidth) {
15017        for(i=0; i<BOARD_HEIGHT; i++) {
15018            board[i][0]             = EmptySquare; /* black holdings */
15019            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15020            board[i][1]             = (ChessSquare) 0; /* black counts */
15021            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15022        }
15023     }
15024
15025     /* Piece placement data */
15026     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15027         j = 0;
15028         for (;;) {
15029             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15030                 if (*p == '/') p++;
15031                 emptycount = gameInfo.boardWidth - j;
15032                 while (emptycount--)
15033                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15034                 break;
15035 #if(BOARD_FILES >= 10)
15036             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15037                 p++; emptycount=10;
15038                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15039                 while (emptycount--)
15040                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15041 #endif
15042             } else if (isdigit(*p)) {
15043                 emptycount = *p++ - '0';
15044                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15045                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15046                 while (emptycount--)
15047                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15048             } else if (*p == '+' || isalpha(*p)) {
15049                 if (j >= gameInfo.boardWidth) return FALSE;
15050                 if(*p=='+') {
15051                     piece = CharToPiece(*++p);
15052                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15053                     piece = (ChessSquare) (PROMOTED piece ); p++;
15054                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15055                 } else piece = CharToPiece(*p++);
15056
15057                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15058                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15059                     piece = (ChessSquare) (PROMOTED piece);
15060                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15061                     p++;
15062                 }
15063                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15064             } else {
15065                 return FALSE;
15066             }
15067         }
15068     }
15069     while (*p == '/' || *p == ' ') p++;
15070
15071     /* [HGM] look for Crazyhouse holdings here */
15072     while(*p==' ') p++;
15073     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15074         if(*p == '[') p++;
15075         if(*p == '-' ) *p++; /* empty holdings */ else {
15076             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15077             /* if we would allow FEN reading to set board size, we would   */
15078             /* have to add holdings and shift the board read so far here   */
15079             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15080                 *p++;
15081                 if((int) piece >= (int) BlackPawn ) {
15082                     i = (int)piece - (int)BlackPawn;
15083                     i = PieceToNumber((ChessSquare)i);
15084                     if( i >= gameInfo.holdingsSize ) return FALSE;
15085                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15086                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15087                 } else {
15088                     i = (int)piece - (int)WhitePawn;
15089                     i = PieceToNumber((ChessSquare)i);
15090                     if( i >= gameInfo.holdingsSize ) return FALSE;
15091                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15092                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15093                 }
15094             }
15095         }
15096         if(*p == ']') *p++;
15097     }
15098
15099     while(*p == ' ') p++;
15100
15101     /* Active color */
15102     switch (*p++) {
15103       case 'w':
15104         *blackPlaysFirst = FALSE;
15105         break;
15106       case 'b':
15107         *blackPlaysFirst = TRUE;
15108         break;
15109       default:
15110         return FALSE;
15111     }
15112
15113     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15114     /* return the extra info in global variiables             */
15115
15116     /* set defaults in case FEN is incomplete */
15117     board[EP_STATUS] = EP_UNKNOWN;
15118     for(i=0; i<nrCastlingRights; i++ ) {
15119         board[CASTLING][i] =
15120             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15121     }   /* assume possible unless obviously impossible */
15122     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15123     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15124     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15125                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15126     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15127     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15128     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15129                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15130     FENrulePlies = 0;
15131
15132     while(*p==' ') p++;
15133     if(nrCastlingRights) {
15134       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15135           /* castling indicator present, so default becomes no castlings */
15136           for(i=0; i<nrCastlingRights; i++ ) {
15137                  board[CASTLING][i] = NoRights;
15138           }
15139       }
15140       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15141              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15142              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15143              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15144         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15145
15146         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15147             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15148             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15149         }
15150         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15151             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15152         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15153                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15154         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15155                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15156         switch(c) {
15157           case'K':
15158               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15159               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15160               board[CASTLING][2] = whiteKingFile;
15161               break;
15162           case'Q':
15163               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15164               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15165               board[CASTLING][2] = whiteKingFile;
15166               break;
15167           case'k':
15168               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15169               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15170               board[CASTLING][5] = blackKingFile;
15171               break;
15172           case'q':
15173               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15174               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15175               board[CASTLING][5] = blackKingFile;
15176           case '-':
15177               break;
15178           default: /* FRC castlings */
15179               if(c >= 'a') { /* black rights */
15180                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15181                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15182                   if(i == BOARD_RGHT) break;
15183                   board[CASTLING][5] = i;
15184                   c -= AAA;
15185                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15186                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15187                   if(c > i)
15188                       board[CASTLING][3] = c;
15189                   else
15190                       board[CASTLING][4] = c;
15191               } else { /* white rights */
15192                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15193                     if(board[0][i] == WhiteKing) break;
15194                   if(i == BOARD_RGHT) break;
15195                   board[CASTLING][2] = i;
15196                   c -= AAA - 'a' + 'A';
15197                   if(board[0][c] >= WhiteKing) break;
15198                   if(c > i)
15199                       board[CASTLING][0] = c;
15200                   else
15201                       board[CASTLING][1] = c;
15202               }
15203         }
15204       }
15205       for(i=0; i<nrCastlingRights; i++)
15206         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15207     if (appData.debugMode) {
15208         fprintf(debugFP, "FEN castling rights:");
15209         for(i=0; i<nrCastlingRights; i++)
15210         fprintf(debugFP, " %d", board[CASTLING][i]);
15211         fprintf(debugFP, "\n");
15212     }
15213
15214       while(*p==' ') p++;
15215     }
15216
15217     /* read e.p. field in games that know e.p. capture */
15218     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15219        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
15220       if(*p=='-') {
15221         p++; board[EP_STATUS] = EP_NONE;
15222       } else {
15223          char c = *p++ - AAA;
15224
15225          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15226          if(*p >= '0' && *p <='9') *p++;
15227          board[EP_STATUS] = c;
15228       }
15229     }
15230
15231
15232     if(sscanf(p, "%d", &i) == 1) {
15233         FENrulePlies = i; /* 50-move ply counter */
15234         /* (The move number is still ignored)    */
15235     }
15236
15237     return TRUE;
15238 }
15239
15240 void
15241 EditPositionPasteFEN(char *fen)
15242 {
15243   if (fen != NULL) {
15244     Board initial_position;
15245
15246     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15247       DisplayError(_("Bad FEN position in clipboard"), 0);
15248       return ;
15249     } else {
15250       int savedBlackPlaysFirst = blackPlaysFirst;
15251       EditPositionEvent();
15252       blackPlaysFirst = savedBlackPlaysFirst;
15253       CopyBoard(boards[0], initial_position);
15254       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15255       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15256       DisplayBothClocks();
15257       DrawPosition(FALSE, boards[currentMove]);
15258     }
15259   }
15260 }
15261
15262 static char cseq[12] = "\\   ";
15263
15264 Boolean set_cont_sequence(char *new_seq)
15265 {
15266     int len;
15267     Boolean ret;
15268
15269     // handle bad attempts to set the sequence
15270         if (!new_seq)
15271                 return 0; // acceptable error - no debug
15272
15273     len = strlen(new_seq);
15274     ret = (len > 0) && (len < sizeof(cseq));
15275     if (ret)
15276         strcpy(cseq, new_seq);
15277     else if (appData.debugMode)
15278         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15279     return ret;
15280 }
15281
15282 /*
15283     reformat a source message so words don't cross the width boundary.  internal
15284     newlines are not removed.  returns the wrapped size (no null character unless
15285     included in source message).  If dest is NULL, only calculate the size required
15286     for the dest buffer.  lp argument indicats line position upon entry, and it's
15287     passed back upon exit.
15288 */
15289 int wrap(char *dest, char *src, int count, int width, int *lp)
15290 {
15291     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15292
15293     cseq_len = strlen(cseq);
15294     old_line = line = *lp;
15295     ansi = len = clen = 0;
15296
15297     for (i=0; i < count; i++)
15298     {
15299         if (src[i] == '\033')
15300             ansi = 1;
15301
15302         // if we hit the width, back up
15303         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15304         {
15305             // store i & len in case the word is too long
15306             old_i = i, old_len = len;
15307
15308             // find the end of the last word
15309             while (i && src[i] != ' ' && src[i] != '\n')
15310             {
15311                 i--;
15312                 len--;
15313             }
15314
15315             // word too long?  restore i & len before splitting it
15316             if ((old_i-i+clen) >= width)
15317             {
15318                 i = old_i;
15319                 len = old_len;
15320             }
15321
15322             // extra space?
15323             if (i && src[i-1] == ' ')
15324                 len--;
15325
15326             if (src[i] != ' ' && src[i] != '\n')
15327             {
15328                 i--;
15329                 if (len)
15330                     len--;
15331             }
15332
15333             // now append the newline and continuation sequence
15334             if (dest)
15335                 dest[len] = '\n';
15336             len++;
15337             if (dest)
15338                 strncpy(dest+len, cseq, cseq_len);
15339             len += cseq_len;
15340             line = cseq_len;
15341             clen = cseq_len;
15342             continue;
15343         }
15344
15345         if (dest)
15346             dest[len] = src[i];
15347         len++;
15348         if (!ansi)
15349             line++;
15350         if (src[i] == '\n')
15351             line = 0;
15352         if (src[i] == 'm')
15353             ansi = 0;
15354     }
15355     if (dest && appData.debugMode)
15356     {
15357         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15358             count, width, line, len, *lp);
15359         show_bytes(debugFP, src, count);
15360         fprintf(debugFP, "\ndest: ");
15361         show_bytes(debugFP, dest, len);
15362         fprintf(debugFP, "\n");
15363     }
15364     *lp = dest ? line : old_line;
15365
15366     return len;
15367 }
15368
15369 // [HGM] vari: routines for shelving variations
15370
15371 void 
15372 PushTail(int firstMove, int lastMove)
15373 {
15374         int i, j, nrMoves = lastMove - firstMove;
15375
15376         if(appData.icsActive) { // only in local mode
15377                 forwardMostMove = currentMove; // mimic old ICS behavior
15378                 return;
15379         }
15380         if(storedGames >= MAX_VARIATIONS-1) return;
15381
15382         // push current tail of game on stack
15383         savedResult[storedGames] = gameInfo.result;
15384         savedDetails[storedGames] = gameInfo.resultDetails;
15385         gameInfo.resultDetails = NULL;
15386         savedFirst[storedGames] = firstMove;
15387         savedLast [storedGames] = lastMove;
15388         savedFramePtr[storedGames] = framePtr;
15389         framePtr -= nrMoves; // reserve space for the boards
15390         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15391             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15392             for(j=0; j<MOVE_LEN; j++)
15393                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15394             for(j=0; j<2*MOVE_LEN; j++)
15395                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15396             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15397             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15398             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15399             pvInfoList[firstMove+i-1].depth = 0;
15400             commentList[framePtr+i] = commentList[firstMove+i];
15401             commentList[firstMove+i] = NULL;
15402         }
15403
15404         storedGames++;
15405         forwardMostMove = currentMove; // truncte game so we can start variation
15406         if(storedGames == 1) GreyRevert(FALSE);
15407 }
15408
15409 Boolean
15410 PopTail(Boolean annotate)
15411 {
15412         int i, j, nrMoves;
15413         char buf[8000], moveBuf[20];
15414
15415         if(appData.icsActive) return FALSE; // only in local mode
15416         if(!storedGames) return FALSE; // sanity
15417
15418         storedGames--;
15419         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15420         nrMoves = savedLast[storedGames] - currentMove;
15421         if(annotate) {
15422                 int cnt = 10;
15423                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15424                 else strcpy(buf, "(");
15425                 for(i=currentMove; i<forwardMostMove; i++) {
15426                         if(WhiteOnMove(i))
15427                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15428                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15429                         strcat(buf, moveBuf);
15430                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15431                 }
15432                 strcat(buf, ")");
15433         }
15434         for(i=1; i<nrMoves; i++) { // copy last variation back
15435             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15436             for(j=0; j<MOVE_LEN; j++)
15437                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15438             for(j=0; j<2*MOVE_LEN; j++)
15439                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15440             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15441             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15442             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15443             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15444             commentList[currentMove+i] = commentList[framePtr+i];
15445             commentList[framePtr+i] = NULL;
15446         }
15447         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15448         framePtr = savedFramePtr[storedGames];
15449         gameInfo.result = savedResult[storedGames];
15450         if(gameInfo.resultDetails != NULL) {
15451             free(gameInfo.resultDetails);
15452       }
15453         gameInfo.resultDetails = savedDetails[storedGames];
15454         forwardMostMove = currentMove + nrMoves;
15455         if(storedGames == 0) GreyRevert(TRUE);
15456         return TRUE;
15457 }
15458
15459 void 
15460 CleanupTail()
15461 {       // remove all shelved variations
15462         int i;
15463         for(i=0; i<storedGames; i++) {
15464             if(savedDetails[i])
15465                 free(savedDetails[i]);
15466             savedDetails[i] = NULL;
15467         }
15468         for(i=framePtr; i<MAX_MOVES; i++) {
15469                 if(commentList[i]) free(commentList[i]);
15470                 commentList[i] = NULL;
15471         }
15472         framePtr = MAX_MOVES-1;
15473         storedGames = 0;
15474 }