4eaa5b1ef5c00eed1947326dc86706e77f2382a6
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 char*
313 safeStrCpy( char *dst, const char *src, size_t count )
314 {
315   /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html
316    *
317    * usage:   safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]);
318    *
319    */
320
321   assert( dst != NULL );
322   assert( src != NULL );
323   assert( count > 0 );
324
325   strncpy( dst, src, count );
326   if(  dst[ count-1 ] != '\0' )
327     {
328       if(appData.debugMode)
329       printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
330     }
331   dst[ count-1 ] = '\0';
332
333   return dst;
334 }
335
336 /* Some compiler can't cast u64 to double
337  * This function do the job for us:
338
339  * We use the highest bit for cast, this only
340  * works if the highest bit is not
341  * in use (This should not happen)
342  *
343  * We used this for all compiler
344  */
345 double
346 u64ToDouble(u64 value)
347 {
348   double r;
349   u64 tmp = value & u64Const(0x7fffffffffffffff);
350   r = (double)(s64)tmp;
351   if (value & u64Const(0x8000000000000000))
352        r +=  9.2233720368547758080e18; /* 2^63 */
353  return r;
354 }
355
356 /* Fake up flags for now, as we aren't keeping track of castling
357    availability yet. [HGM] Change of logic: the flag now only
358    indicates the type of castlings allowed by the rule of the game.
359    The actual rights themselves are maintained in the array
360    castlingRights, as part of the game history, and are not probed
361    by this function.
362  */
363 int
364 PosFlags(index)
365 {
366   int flags = F_ALL_CASTLE_OK;
367   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
368   switch (gameInfo.variant) {
369   case VariantSuicide:
370     flags &= ~F_ALL_CASTLE_OK;
371   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
372     flags |= F_IGNORE_CHECK;
373   case VariantLosers:
374     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
375     break;
376   case VariantAtomic:
377     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
378     break;
379   case VariantKriegspiel:
380     flags |= F_KRIEGSPIEL_CAPTURE;
381     break;
382   case VariantCapaRandom:
383   case VariantFischeRandom:
384     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
385   case VariantNoCastle:
386   case VariantShatranj:
387   case VariantCourier:
388   case VariantMakruk:
389     flags &= ~F_ALL_CASTLE_OK;
390     break;
391   default:
392     break;
393   }
394   return flags;
395 }
396
397 FILE *gameFileFP, *debugFP;
398
399 /*
400     [AS] Note: sometimes, the sscanf() function is used to parse the input
401     into a fixed-size buffer. Because of this, we must be prepared to
402     receive strings as long as the size of the input buffer, which is currently
403     set to 4K for Windows and 8K for the rest.
404     So, we must either allocate sufficiently large buffers here, or
405     reduce the size of the input buffer in the input reading part.
406 */
407
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
411
412 ChessProgramState first, second;
413
414 /* premove variables */
415 int premoveToX = 0;
416 int premoveToY = 0;
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
420 int gotPremove = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
423
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
426
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
460 int matchGame = 0;
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
464
465 /* animateTraining preserves the state of appData.animate
466  * when Training mode is activated. This allows the
467  * response to be animated when appData.animate == TRUE and
468  * appData.animateDragging == TRUE.
469  */
470 Boolean animateTraining;
471
472 GameInfo gameInfo;
473
474 AppData appData;
475
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char  initialRights[BOARD_FILES];
480 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int   initialRulePlies, FENrulePlies;
482 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
483 int loadFlag = 0;
484 int shuffleOpenings;
485 int mute; // mute all sounds
486
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
490 int storedGames = 0;
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
496
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
500
501 ChessSquare  FIDEArray[2][BOARD_FILES] = {
502     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505         BlackKing, BlackBishop, BlackKnight, BlackRook }
506 };
507
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512         BlackKing, BlackKing, BlackKnight, BlackRook }
513 };
514
515 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518     { BlackRook, BlackMan, BlackBishop, BlackQueen,
519         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
520 };
521
522 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
526         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
527 };
528
529 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
530     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
531         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
533         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
534 };
535
536 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
538         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
539     { BlackRook, BlackKnight, BlackMan, BlackFerz,
540         BlackKing, BlackMan, BlackKnight, BlackRook }
541 };
542
543
544 #if (BOARD_FILES>=10)
545 ChessSquare ShogiArray[2][BOARD_FILES] = {
546     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
547         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
548     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
549         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
550 };
551
552 ChessSquare XiangqiArray[2][BOARD_FILES] = {
553     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
554         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
556         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
557 };
558
559 ChessSquare CapablancaArray[2][BOARD_FILES] = {
560     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
561         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
563         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
564 };
565
566 ChessSquare GreatArray[2][BOARD_FILES] = {
567     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
568         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
569     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
570         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
571 };
572
573 ChessSquare JanusArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
575         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
576     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
577         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
578 };
579
580 #ifdef GOTHIC
581 ChessSquare GothicArray[2][BOARD_FILES] = {
582     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
583         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
584     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
585         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
586 };
587 #else // !GOTHIC
588 #define GothicArray CapablancaArray
589 #endif // !GOTHIC
590
591 #ifdef FALCON
592 ChessSquare FalconArray[2][BOARD_FILES] = {
593     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
594         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
596         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
597 };
598 #else // !FALCON
599 #define FalconArray CapablancaArray
600 #endif // !FALCON
601
602 #else // !(BOARD_FILES>=10)
603 #define XiangqiPosition FIDEArray
604 #define CapablancaArray FIDEArray
605 #define GothicArray FIDEArray
606 #define GreatArray FIDEArray
607 #endif // !(BOARD_FILES>=10)
608
609 #if (BOARD_FILES>=12)
610 ChessSquare CourierArray[2][BOARD_FILES] = {
611     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
612         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
613     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
614         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
615 };
616 #else // !(BOARD_FILES>=12)
617 #define CourierArray CapablancaArray
618 #endif // !(BOARD_FILES>=12)
619
620
621 Board initialPosition;
622
623
624 /* Convert str to a rating. Checks for special cases of "----",
625
626    "++++", etc. Also strips ()'s */
627 int
628 string_to_rating(str)
629   char *str;
630 {
631   while(*str && !isdigit(*str)) ++str;
632   if (!*str)
633     return 0;   /* One of the special "no rating" cases */
634   else
635     return atoi(str);
636 }
637
638 void
639 ClearProgramStats()
640 {
641     /* Init programStats */
642     programStats.movelist[0] = 0;
643     programStats.depth = 0;
644     programStats.nr_moves = 0;
645     programStats.moves_left = 0;
646     programStats.nodes = 0;
647     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
648     programStats.score = 0;
649     programStats.got_only_move = 0;
650     programStats.got_fail = 0;
651     programStats.line_is_book = 0;
652 }
653
654 void
655 InitBackEnd1()
656 {
657     int matched, min, sec;
658
659     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
660     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
661
662     GetTimeMark(&programStartTime);
663     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
664
665     ClearProgramStats();
666     programStats.ok_to_send = 1;
667     programStats.seen_stat = 0;
668
669     /*
670      * Initialize game list
671      */
672     ListNew(&gameList);
673
674
675     /*
676      * Internet chess server status
677      */
678     if (appData.icsActive) {
679         appData.matchMode = FALSE;
680         appData.matchGames = 0;
681 #if ZIPPY
682         appData.noChessProgram = !appData.zippyPlay;
683 #else
684         appData.zippyPlay = FALSE;
685         appData.zippyTalk = FALSE;
686         appData.noChessProgram = TRUE;
687 #endif
688         if (*appData.icsHelper != NULLCHAR) {
689             appData.useTelnet = TRUE;
690             appData.telnetProgram = appData.icsHelper;
691         }
692     } else {
693         appData.zippyTalk = appData.zippyPlay = FALSE;
694     }
695
696     /* [AS] Initialize pv info list [HGM] and game state */
697     {
698         int i, j;
699
700         for( i=0; i<=framePtr; i++ ) {
701             pvInfoList[i].depth = -1;
702             boards[i][EP_STATUS] = EP_NONE;
703             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
704         }
705     }
706
707     /*
708      * Parse timeControl resource
709      */
710     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
711                           appData.movesPerSession)) {
712         char buf[MSG_SIZ];
713         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
714         DisplayFatalError(buf, 0, 2);
715     }
716
717     /*
718      * Parse searchTime resource
719      */
720     if (*appData.searchTime != NULLCHAR) {
721         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
722         if (matched == 1) {
723             searchTime = min * 60;
724         } else if (matched == 2) {
725             searchTime = min * 60 + sec;
726         } else {
727             char buf[MSG_SIZ];
728             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
729             DisplayFatalError(buf, 0, 2);
730         }
731     }
732
733     /* [AS] Adjudication threshold */
734     adjudicateLossThreshold = appData.adjudicateLossThreshold;
735
736     first.which = _("first");
737     second.which = _("second");
738     first.maybeThinking = second.maybeThinking = FALSE;
739     first.pr = second.pr = NoProc;
740     first.isr = second.isr = NULL;
741     first.sendTime = second.sendTime = 2;
742     first.sendDrawOffers = 1;
743     if (appData.firstPlaysBlack) {
744         first.twoMachinesColor = "black\n";
745         second.twoMachinesColor = "white\n";
746     } else {
747         first.twoMachinesColor = "white\n";
748         second.twoMachinesColor = "black\n";
749     }
750     first.program = appData.firstChessProgram;
751     second.program = appData.secondChessProgram;
752     first.host = appData.firstHost;
753     second.host = appData.secondHost;
754     first.dir = appData.firstDirectory;
755     second.dir = appData.secondDirectory;
756     first.other = &second;
757     second.other = &first;
758     first.initString = appData.initString;
759     second.initString = appData.secondInitString;
760     first.computerString = appData.firstComputerString;
761     second.computerString = appData.secondComputerString;
762     first.useSigint = second.useSigint = TRUE;
763     first.useSigterm = second.useSigterm = TRUE;
764     first.reuse = appData.reuseFirst;
765     second.reuse = appData.reuseSecond;
766     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
767     second.nps = appData.secondNPS;
768     first.useSetboard = second.useSetboard = FALSE;
769     first.useSAN = second.useSAN = FALSE;
770     first.usePing = second.usePing = FALSE;
771     first.lastPing = second.lastPing = 0;
772     first.lastPong = second.lastPong = 0;
773     first.usePlayother = second.usePlayother = FALSE;
774     first.useColors = second.useColors = TRUE;
775     first.useUsermove = second.useUsermove = FALSE;
776     first.sendICS = second.sendICS = FALSE;
777     first.sendName = second.sendName = appData.icsActive;
778     first.sdKludge = second.sdKludge = FALSE;
779     first.stKludge = second.stKludge = FALSE;
780     TidyProgramName(first.program, first.host, first.tidy);
781     TidyProgramName(second.program, second.host, second.tidy);
782     first.matchWins = second.matchWins = 0;
783     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
784     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
785     first.analysisSupport = second.analysisSupport = 2; /* detect */
786     first.analyzing = second.analyzing = FALSE;
787     first.initDone = second.initDone = FALSE;
788
789     /* New features added by Tord: */
790     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
791     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
792     /* End of new features added by Tord. */
793     first.fenOverride  = appData.fenOverride1;
794     second.fenOverride = appData.fenOverride2;
795
796     /* [HGM] time odds: set factor for each machine */
797     first.timeOdds  = appData.firstTimeOdds;
798     second.timeOdds = appData.secondTimeOdds;
799     { float norm = 1;
800         if(appData.timeOddsMode) {
801             norm = first.timeOdds;
802             if(norm > second.timeOdds) norm = second.timeOdds;
803         }
804         first.timeOdds /= norm;
805         second.timeOdds /= norm;
806     }
807
808     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
809     first.accumulateTC = appData.firstAccumulateTC;
810     second.accumulateTC = appData.secondAccumulateTC;
811     first.maxNrOfSessions = second.maxNrOfSessions = 1;
812
813     /* [HGM] debug */
814     first.debug = second.debug = FALSE;
815     first.supportsNPS = second.supportsNPS = UNKNOWN;
816
817     /* [HGM] options */
818     first.optionSettings  = appData.firstOptions;
819     second.optionSettings = appData.secondOptions;
820
821     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
822     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
823     first.isUCI = appData.firstIsUCI; /* [AS] */
824     second.isUCI = appData.secondIsUCI; /* [AS] */
825     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
826     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
827
828     if (appData.firstProtocolVersion > PROTOVER ||
829         appData.firstProtocolVersion < 1) {
830       char buf[MSG_SIZ];
831       sprintf(buf, _("protocol version %d not supported"),
832               appData.firstProtocolVersion);
833       DisplayFatalError(buf, 0, 2);
834     } else {
835       first.protocolVersion = appData.firstProtocolVersion;
836     }
837
838     if (appData.secondProtocolVersion > PROTOVER ||
839         appData.secondProtocolVersion < 1) {
840       char buf[MSG_SIZ];
841       sprintf(buf, _("protocol version %d not supported"),
842               appData.secondProtocolVersion);
843       DisplayFatalError(buf, 0, 2);
844     } else {
845       second.protocolVersion = appData.secondProtocolVersion;
846     }
847
848     if (appData.icsActive) {
849         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
850 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
851     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
852         appData.clockMode = FALSE;
853         first.sendTime = second.sendTime = 0;
854     }
855
856 #if ZIPPY
857     /* Override some settings from environment variables, for backward
858        compatibility.  Unfortunately it's not feasible to have the env
859        vars just set defaults, at least in xboard.  Ugh.
860     */
861     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
862       ZippyInit();
863     }
864 #endif
865
866     if (appData.noChessProgram) {
867         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
868         sprintf(programVersion, "%s", PACKAGE_STRING);
869     } else {
870       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
871       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
872       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
873     }
874
875     if (!appData.icsActive) {
876       char buf[MSG_SIZ];
877       /* Check for variants that are supported only in ICS mode,
878          or not at all.  Some that are accepted here nevertheless
879          have bugs; see comments below.
880       */
881       VariantClass variant = StringToVariant(appData.variant);
882       switch (variant) {
883       case VariantBughouse:     /* need four players and two boards */
884       case VariantKriegspiel:   /* need to hide pieces and move details */
885       /* case VariantFischeRandom: (Fabien: moved below) */
886         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
887         DisplayFatalError(buf, 0, 2);
888         return;
889
890       case VariantUnknown:
891       case VariantLoadable:
892       case Variant29:
893       case Variant30:
894       case Variant31:
895       case Variant32:
896       case Variant33:
897       case Variant34:
898       case Variant35:
899       case Variant36:
900       default:
901         sprintf(buf, _("Unknown variant name %s"), appData.variant);
902         DisplayFatalError(buf, 0, 2);
903         return;
904
905       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
906       case VariantFairy:      /* [HGM] TestLegality definitely off! */
907       case VariantGothic:     /* [HGM] should work */
908       case VariantCapablanca: /* [HGM] should work */
909       case VariantCourier:    /* [HGM] initial forced moves not implemented */
910       case VariantShogi:      /* [HGM] could still mate with pawn drop */
911       case VariantKnightmate: /* [HGM] should work */
912       case VariantCylinder:   /* [HGM] untested */
913       case VariantFalcon:     /* [HGM] untested */
914       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
915                                  offboard interposition not understood */
916       case VariantNormal:     /* definitely works! */
917       case VariantWildCastle: /* pieces not automatically shuffled */
918       case VariantNoCastle:   /* pieces not automatically shuffled */
919       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
920       case VariantLosers:     /* should work except for win condition,
921                                  and doesn't know captures are mandatory */
922       case VariantSuicide:    /* should work except for win condition,
923                                  and doesn't know captures are mandatory */
924       case VariantGiveaway:   /* should work except for win condition,
925                                  and doesn't know captures are mandatory */
926       case VariantTwoKings:   /* should work */
927       case VariantAtomic:     /* should work except for win condition */
928       case Variant3Check:     /* should work except for win condition */
929       case VariantShatranj:   /* should work except for all win conditions */
930       case VariantMakruk:     /* should work except for daw countdown */
931       case VariantBerolina:   /* might work if TestLegality is off */
932       case VariantCapaRandom: /* should work */
933       case VariantJanus:      /* should work */
934       case VariantSuper:      /* experimental */
935       case VariantGreat:      /* experimental, requires legality testing to be off */
936         break;
937       }
938     }
939
940     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
941     InitEngineUCI( installDir, &second );
942 }
943
944 int NextIntegerFromString( char ** str, long * value )
945 {
946     int result = -1;
947     char * s = *str;
948
949     while( *s == ' ' || *s == '\t' ) {
950         s++;
951     }
952
953     *value = 0;
954
955     if( *s >= '0' && *s <= '9' ) {
956         while( *s >= '0' && *s <= '9' ) {
957             *value = *value * 10 + (*s - '0');
958             s++;
959         }
960
961         result = 0;
962     }
963
964     *str = s;
965
966     return result;
967 }
968
969 int NextTimeControlFromString( char ** str, long * value )
970 {
971     long temp;
972     int result = NextIntegerFromString( str, &temp );
973
974     if( result == 0 ) {
975         *value = temp * 60; /* Minutes */
976         if( **str == ':' ) {
977             (*str)++;
978             result = NextIntegerFromString( str, &temp );
979             *value += temp; /* Seconds */
980         }
981     }
982
983     return result;
984 }
985
986 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
987 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
988     int result = -1, type = 0; long temp, temp2;
989
990     if(**str != ':') return -1; // old params remain in force!
991     (*str)++;
992     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
993     if( NextIntegerFromString( str, &temp ) ) return -1;
994     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
995
996     if(**str != '/') {
997         /* time only: incremental or sudden-death time control */
998         if(**str == '+') { /* increment follows; read it */
999             (*str)++;
1000             if(**str == '!') type = *(*str)++; // Bronstein TC
1001             if(result = NextIntegerFromString( str, &temp2)) return -1;
1002             *inc = temp2 * 1000;
1003         } else *inc = 0;
1004         *moves = 0; *tc = temp * 1000; *incType = type;
1005         return 0;
1006     }
1007
1008     (*str)++; /* classical time control */
1009     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1010
1011     if(result == 0) {
1012         *moves = temp;
1013         *tc    = temp2 * 1000;
1014         *inc   = 0;
1015         *incType = type;
1016     }
1017     return result;
1018 }
1019
1020 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1021 {   /* [HGM] get time to add from the multi-session time-control string */
1022     int incType, moves=1; /* kludge to force reading of first session */
1023     long time, increment;
1024     char *s = tcString;
1025
1026     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1027     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1028     do {
1029         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1030         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1031         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1032         if(movenr == -1) return time;    /* last move before new session     */
1033         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1034         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1035         if(!moves) return increment;     /* current session is incremental   */
1036         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1037     } while(movenr >= -1);               /* try again for next session       */
1038
1039     return 0; // no new time quota on this move
1040 }
1041
1042 int
1043 ParseTimeControl(tc, ti, mps)
1044      char *tc;
1045      int ti;
1046      int mps;
1047 {
1048   long tc1;
1049   long tc2;
1050   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1051   int min, sec=0;
1052   
1053   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1054   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1055       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1056   if(ti > 0) {
1057     if(mps)
1058       sprintf(buf, ":%d/%s+%d", mps, mytc, ti);
1059     else sprintf(buf, ":%s+%d", mytc, ti);
1060   } else {
1061     if(mps)
1062              sprintf(buf, ":%d/%s", mps, mytc);
1063     else sprintf(buf, ":%s", mytc);
1064   }
1065   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1066   
1067   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1068     return FALSE;
1069   }
1070
1071   if( *tc == '/' ) {
1072     /* Parse second time control */
1073     tc++;
1074
1075     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1076       return FALSE;
1077     }
1078
1079     if( tc2 == 0 ) {
1080       return FALSE;
1081     }
1082
1083     timeControl_2 = tc2 * 1000;
1084   }
1085   else {
1086     timeControl_2 = 0;
1087   }
1088
1089   if( tc1 == 0 ) {
1090     return FALSE;
1091   }
1092
1093   timeControl = tc1 * 1000;
1094
1095   if (ti >= 0) {
1096     timeIncrement = ti * 1000;  /* convert to ms */
1097     movesPerSession = 0;
1098   } else {
1099     timeIncrement = 0;
1100     movesPerSession = mps;
1101   }
1102   return TRUE;
1103 }
1104
1105 void
1106 InitBackEnd2()
1107 {
1108     if (appData.debugMode) {
1109         fprintf(debugFP, "%s\n", programVersion);
1110     }
1111
1112     set_cont_sequence(appData.wrapContSeq);
1113     if (appData.matchGames > 0) {
1114         appData.matchMode = TRUE;
1115     } else if (appData.matchMode) {
1116         appData.matchGames = 1;
1117     }
1118     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1119         appData.matchGames = appData.sameColorGames;
1120     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1121         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1122         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1123     }
1124     Reset(TRUE, FALSE);
1125     if (appData.noChessProgram || first.protocolVersion == 1) {
1126       InitBackEnd3();
1127     } else {
1128       /* kludge: allow timeout for initial "feature" commands */
1129       FreezeUI();
1130       DisplayMessage("", _("Starting chess program"));
1131       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1132     }
1133 }
1134
1135 void
1136 InitBackEnd3 P((void))
1137 {
1138     GameMode initialMode;
1139     char buf[MSG_SIZ];
1140     int err;
1141
1142     InitChessProgram(&first, startedFromSetupPosition);
1143
1144     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1145         free(programVersion);
1146         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1147         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1148     }
1149
1150     if (appData.icsActive) {
1151 #ifdef WIN32
1152         /* [DM] Make a console window if needed [HGM] merged ifs */
1153         ConsoleCreate();
1154 #endif
1155         err = establish();
1156         if (err != 0) {
1157             if (*appData.icsCommPort != NULLCHAR) {
1158                 sprintf(buf, _("Could not open comm port %s"),
1159                         appData.icsCommPort);
1160             } else {
1161                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1162                         appData.icsHost, appData.icsPort);
1163             }
1164             DisplayFatalError(buf, err, 1);
1165             return;
1166         }
1167         SetICSMode();
1168         telnetISR =
1169           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1170         fromUserISR =
1171           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1172         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1173             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1174     } else if (appData.noChessProgram) {
1175         SetNCPMode();
1176     } else {
1177         SetGNUMode();
1178     }
1179
1180     if (*appData.cmailGameName != NULLCHAR) {
1181         SetCmailMode();
1182         OpenLoopback(&cmailPR);
1183         cmailISR =
1184           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1185     }
1186
1187     ThawUI();
1188     DisplayMessage("", "");
1189     if (StrCaseCmp(appData.initialMode, "") == 0) {
1190       initialMode = BeginningOfGame;
1191     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1192       initialMode = TwoMachinesPlay;
1193     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1194       initialMode = AnalyzeFile;
1195     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1196       initialMode = AnalyzeMode;
1197     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1198       initialMode = MachinePlaysWhite;
1199     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1200       initialMode = MachinePlaysBlack;
1201     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1202       initialMode = EditGame;
1203     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1204       initialMode = EditPosition;
1205     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1206       initialMode = Training;
1207     } else {
1208       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1209       DisplayFatalError(buf, 0, 2);
1210       return;
1211     }
1212
1213     if (appData.matchMode) {
1214         /* Set up machine vs. machine match */
1215         if (appData.noChessProgram) {
1216             DisplayFatalError(_("Can't have a match with no chess programs"),
1217                               0, 2);
1218             return;
1219         }
1220         matchMode = TRUE;
1221         matchGame = 1;
1222         if (*appData.loadGameFile != NULLCHAR) {
1223             int index = appData.loadGameIndex; // [HGM] autoinc
1224             if(index<0) lastIndex = index = 1;
1225             if (!LoadGameFromFile(appData.loadGameFile,
1226                                   index,
1227                                   appData.loadGameFile, FALSE)) {
1228                 DisplayFatalError(_("Bad game file"), 0, 1);
1229                 return;
1230             }
1231         } else if (*appData.loadPositionFile != NULLCHAR) {
1232             int index = appData.loadPositionIndex; // [HGM] autoinc
1233             if(index<0) lastIndex = index = 1;
1234             if (!LoadPositionFromFile(appData.loadPositionFile,
1235                                       index,
1236                                       appData.loadPositionFile)) {
1237                 DisplayFatalError(_("Bad position file"), 0, 1);
1238                 return;
1239             }
1240         }
1241         TwoMachinesEvent();
1242     } else if (*appData.cmailGameName != NULLCHAR) {
1243         /* Set up cmail mode */
1244         ReloadCmailMsgEvent(TRUE);
1245     } else {
1246         /* Set up other modes */
1247         if (initialMode == AnalyzeFile) {
1248           if (*appData.loadGameFile == NULLCHAR) {
1249             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1250             return;
1251           }
1252         }
1253         if (*appData.loadGameFile != NULLCHAR) {
1254             (void) LoadGameFromFile(appData.loadGameFile,
1255                                     appData.loadGameIndex,
1256                                     appData.loadGameFile, TRUE);
1257         } else if (*appData.loadPositionFile != NULLCHAR) {
1258             (void) LoadPositionFromFile(appData.loadPositionFile,
1259                                         appData.loadPositionIndex,
1260                                         appData.loadPositionFile);
1261             /* [HGM] try to make self-starting even after FEN load */
1262             /* to allow automatic setup of fairy variants with wtm */
1263             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1264                 gameMode = BeginningOfGame;
1265                 setboardSpoiledMachineBlack = 1;
1266             }
1267             /* [HGM] loadPos: make that every new game uses the setup */
1268             /* from file as long as we do not switch variant          */
1269             if(!blackPlaysFirst) {
1270                 startedFromPositionFile = TRUE;
1271                 CopyBoard(filePosition, boards[0]);
1272             }
1273         }
1274         if (initialMode == AnalyzeMode) {
1275           if (appData.noChessProgram) {
1276             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1277             return;
1278           }
1279           if (appData.icsActive) {
1280             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1281             return;
1282           }
1283           AnalyzeModeEvent();
1284         } else if (initialMode == AnalyzeFile) {
1285           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1286           ShowThinkingEvent();
1287           AnalyzeFileEvent();
1288           AnalysisPeriodicEvent(1);
1289         } else if (initialMode == MachinePlaysWhite) {
1290           if (appData.noChessProgram) {
1291             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1292                               0, 2);
1293             return;
1294           }
1295           if (appData.icsActive) {
1296             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1297                               0, 2);
1298             return;
1299           }
1300           MachineWhiteEvent();
1301         } else if (initialMode == MachinePlaysBlack) {
1302           if (appData.noChessProgram) {
1303             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1304                               0, 2);
1305             return;
1306           }
1307           if (appData.icsActive) {
1308             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1309                               0, 2);
1310             return;
1311           }
1312           MachineBlackEvent();
1313         } else if (initialMode == TwoMachinesPlay) {
1314           if (appData.noChessProgram) {
1315             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1316                               0, 2);
1317             return;
1318           }
1319           if (appData.icsActive) {
1320             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1321                               0, 2);
1322             return;
1323           }
1324           TwoMachinesEvent();
1325         } else if (initialMode == EditGame) {
1326           EditGameEvent();
1327         } else if (initialMode == EditPosition) {
1328           EditPositionEvent();
1329         } else if (initialMode == Training) {
1330           if (*appData.loadGameFile == NULLCHAR) {
1331             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1332             return;
1333           }
1334           TrainingEvent();
1335         }
1336     }
1337 }
1338
1339 /*
1340  * Establish will establish a contact to a remote host.port.
1341  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1342  *  used to talk to the host.
1343  * Returns 0 if okay, error code if not.
1344  */
1345 int
1346 establish()
1347 {
1348     char buf[MSG_SIZ];
1349
1350     if (*appData.icsCommPort != NULLCHAR) {
1351         /* Talk to the host through a serial comm port */
1352         return OpenCommPort(appData.icsCommPort, &icsPR);
1353
1354     } else if (*appData.gateway != NULLCHAR) {
1355         if (*appData.remoteShell == NULLCHAR) {
1356             /* Use the rcmd protocol to run telnet program on a gateway host */
1357             snprintf(buf, sizeof(buf), "%s %s %s",
1358                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1359             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1360
1361         } else {
1362             /* Use the rsh program to run telnet program on a gateway host */
1363             if (*appData.remoteUser == NULLCHAR) {
1364                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1365                         appData.gateway, appData.telnetProgram,
1366                         appData.icsHost, appData.icsPort);
1367             } else {
1368                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1369                         appData.remoteShell, appData.gateway,
1370                         appData.remoteUser, appData.telnetProgram,
1371                         appData.icsHost, appData.icsPort);
1372             }
1373             return StartChildProcess(buf, "", &icsPR);
1374
1375         }
1376     } else if (appData.useTelnet) {
1377         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1378
1379     } else {
1380         /* TCP socket interface differs somewhat between
1381            Unix and NT; handle details in the front end.
1382            */
1383         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1384     }
1385 }
1386
1387 void EscapeExpand(char *p, char *q)
1388 {       // [HGM] initstring: routine to shape up string arguments
1389         while(*p++ = *q++) if(p[-1] == '\\')
1390             switch(*q++) {
1391                 case 'n': p[-1] = '\n'; break;
1392                 case 'r': p[-1] = '\r'; break;
1393                 case 't': p[-1] = '\t'; break;
1394                 case '\\': p[-1] = '\\'; break;
1395                 case 0: *p = 0; return;
1396                 default: p[-1] = q[-1]; break;
1397             }
1398 }
1399
1400 void
1401 show_bytes(fp, buf, count)
1402      FILE *fp;
1403      char *buf;
1404      int count;
1405 {
1406     while (count--) {
1407         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1408             fprintf(fp, "\\%03o", *buf & 0xff);
1409         } else {
1410             putc(*buf, fp);
1411         }
1412         buf++;
1413     }
1414     fflush(fp);
1415 }
1416
1417 /* Returns an errno value */
1418 int
1419 OutputMaybeTelnet(pr, message, count, outError)
1420      ProcRef pr;
1421      char *message;
1422      int count;
1423      int *outError;
1424 {
1425     char buf[8192], *p, *q, *buflim;
1426     int left, newcount, outcount;
1427
1428     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1429         *appData.gateway != NULLCHAR) {
1430         if (appData.debugMode) {
1431             fprintf(debugFP, ">ICS: ");
1432             show_bytes(debugFP, message, count);
1433             fprintf(debugFP, "\n");
1434         }
1435         return OutputToProcess(pr, message, count, outError);
1436     }
1437
1438     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1439     p = message;
1440     q = buf;
1441     left = count;
1442     newcount = 0;
1443     while (left) {
1444         if (q >= buflim) {
1445             if (appData.debugMode) {
1446                 fprintf(debugFP, ">ICS: ");
1447                 show_bytes(debugFP, buf, newcount);
1448                 fprintf(debugFP, "\n");
1449             }
1450             outcount = OutputToProcess(pr, buf, newcount, outError);
1451             if (outcount < newcount) return -1; /* to be sure */
1452             q = buf;
1453             newcount = 0;
1454         }
1455         if (*p == '\n') {
1456             *q++ = '\r';
1457             newcount++;
1458         } else if (((unsigned char) *p) == TN_IAC) {
1459             *q++ = (char) TN_IAC;
1460             newcount ++;
1461         }
1462         *q++ = *p++;
1463         newcount++;
1464         left--;
1465     }
1466     if (appData.debugMode) {
1467         fprintf(debugFP, ">ICS: ");
1468         show_bytes(debugFP, buf, newcount);
1469         fprintf(debugFP, "\n");
1470     }
1471     outcount = OutputToProcess(pr, buf, newcount, outError);
1472     if (outcount < newcount) return -1; /* to be sure */
1473     return count;
1474 }
1475
1476 void
1477 read_from_player(isr, closure, message, count, error)
1478      InputSourceRef isr;
1479      VOIDSTAR closure;
1480      char *message;
1481      int count;
1482      int error;
1483 {
1484     int outError, outCount;
1485     static int gotEof = 0;
1486
1487     /* Pass data read from player on to ICS */
1488     if (count > 0) {
1489         gotEof = 0;
1490         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1491         if (outCount < count) {
1492             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1493         }
1494     } else if (count < 0) {
1495         RemoveInputSource(isr);
1496         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1497     } else if (gotEof++ > 0) {
1498         RemoveInputSource(isr);
1499         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1500     }
1501 }
1502
1503 void
1504 KeepAlive()
1505 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1506     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1507     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1508     SendToICS("date\n");
1509     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1510 }
1511
1512 /* added routine for printf style output to ics */
1513 void ics_printf(char *format, ...)
1514 {
1515     char buffer[MSG_SIZ];
1516     va_list args;
1517
1518     va_start(args, format);
1519     vsnprintf(buffer, sizeof(buffer), format, args);
1520     buffer[sizeof(buffer)-1] = '\0';
1521     SendToICS(buffer);
1522     va_end(args);
1523 }
1524
1525 void
1526 SendToICS(s)
1527      char *s;
1528 {
1529     int count, outCount, outError;
1530
1531     if (icsPR == NULL) return;
1532
1533     count = strlen(s);
1534     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1535     if (outCount < count) {
1536         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1537     }
1538 }
1539
1540 /* This is used for sending logon scripts to the ICS. Sending
1541    without a delay causes problems when using timestamp on ICC
1542    (at least on my machine). */
1543 void
1544 SendToICSDelayed(s,msdelay)
1545      char *s;
1546      long msdelay;
1547 {
1548     int count, outCount, outError;
1549
1550     if (icsPR == NULL) return;
1551
1552     count = strlen(s);
1553     if (appData.debugMode) {
1554         fprintf(debugFP, ">ICS: ");
1555         show_bytes(debugFP, s, count);
1556         fprintf(debugFP, "\n");
1557     }
1558     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1559                                       msdelay);
1560     if (outCount < count) {
1561         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1562     }
1563 }
1564
1565
1566 /* Remove all highlighting escape sequences in s
1567    Also deletes any suffix starting with '('
1568    */
1569 char *
1570 StripHighlightAndTitle(s)
1571      char *s;
1572 {
1573     static char retbuf[MSG_SIZ];
1574     char *p = retbuf;
1575
1576     while (*s != NULLCHAR) {
1577         while (*s == '\033') {
1578             while (*s != NULLCHAR && !isalpha(*s)) s++;
1579             if (*s != NULLCHAR) s++;
1580         }
1581         while (*s != NULLCHAR && *s != '\033') {
1582             if (*s == '(' || *s == '[') {
1583                 *p = NULLCHAR;
1584                 return retbuf;
1585             }
1586             *p++ = *s++;
1587         }
1588     }
1589     *p = NULLCHAR;
1590     return retbuf;
1591 }
1592
1593 /* Remove all highlighting escape sequences in s */
1594 char *
1595 StripHighlight(s)
1596      char *s;
1597 {
1598     static char retbuf[MSG_SIZ];
1599     char *p = retbuf;
1600
1601     while (*s != NULLCHAR) {
1602         while (*s == '\033') {
1603             while (*s != NULLCHAR && !isalpha(*s)) s++;
1604             if (*s != NULLCHAR) s++;
1605         }
1606         while (*s != NULLCHAR && *s != '\033') {
1607             *p++ = *s++;
1608         }
1609     }
1610     *p = NULLCHAR;
1611     return retbuf;
1612 }
1613
1614 char *variantNames[] = VARIANT_NAMES;
1615 char *
1616 VariantName(v)
1617      VariantClass v;
1618 {
1619     return variantNames[v];
1620 }
1621
1622
1623 /* Identify a variant from the strings the chess servers use or the
1624    PGN Variant tag names we use. */
1625 VariantClass
1626 StringToVariant(e)
1627      char *e;
1628 {
1629     char *p;
1630     int wnum = -1;
1631     VariantClass v = VariantNormal;
1632     int i, found = FALSE;
1633     char buf[MSG_SIZ];
1634
1635     if (!e) return v;
1636
1637     /* [HGM] skip over optional board-size prefixes */
1638     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1639         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1640         while( *e++ != '_');
1641     }
1642
1643     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1644         v = VariantNormal;
1645         found = TRUE;
1646     } else
1647     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1648       if (StrCaseStr(e, variantNames[i])) {
1649         v = (VariantClass) i;
1650         found = TRUE;
1651         break;
1652       }
1653     }
1654
1655     if (!found) {
1656       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1657           || StrCaseStr(e, "wild/fr")
1658           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1659         v = VariantFischeRandom;
1660       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1661                  (i = 1, p = StrCaseStr(e, "w"))) {
1662         p += i;
1663         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1664         if (isdigit(*p)) {
1665           wnum = atoi(p);
1666         } else {
1667           wnum = -1;
1668         }
1669         switch (wnum) {
1670         case 0: /* FICS only, actually */
1671         case 1:
1672           /* Castling legal even if K starts on d-file */
1673           v = VariantWildCastle;
1674           break;
1675         case 2:
1676         case 3:
1677         case 4:
1678           /* Castling illegal even if K & R happen to start in
1679              normal positions. */
1680           v = VariantNoCastle;
1681           break;
1682         case 5:
1683         case 7:
1684         case 8:
1685         case 10:
1686         case 11:
1687         case 12:
1688         case 13:
1689         case 14:
1690         case 15:
1691         case 18:
1692         case 19:
1693           /* Castling legal iff K & R start in normal positions */
1694           v = VariantNormal;
1695           break;
1696         case 6:
1697         case 20:
1698         case 21:
1699           /* Special wilds for position setup; unclear what to do here */
1700           v = VariantLoadable;
1701           break;
1702         case 9:
1703           /* Bizarre ICC game */
1704           v = VariantTwoKings;
1705           break;
1706         case 16:
1707           v = VariantKriegspiel;
1708           break;
1709         case 17:
1710           v = VariantLosers;
1711           break;
1712         case 22:
1713           v = VariantFischeRandom;
1714           break;
1715         case 23:
1716           v = VariantCrazyhouse;
1717           break;
1718         case 24:
1719           v = VariantBughouse;
1720           break;
1721         case 25:
1722           v = Variant3Check;
1723           break;
1724         case 26:
1725           /* Not quite the same as FICS suicide! */
1726           v = VariantGiveaway;
1727           break;
1728         case 27:
1729           v = VariantAtomic;
1730           break;
1731         case 28:
1732           v = VariantShatranj;
1733           break;
1734
1735         /* Temporary names for future ICC types.  The name *will* change in
1736            the next xboard/WinBoard release after ICC defines it. */
1737         case 29:
1738           v = Variant29;
1739           break;
1740         case 30:
1741           v = Variant30;
1742           break;
1743         case 31:
1744           v = Variant31;
1745           break;
1746         case 32:
1747           v = Variant32;
1748           break;
1749         case 33:
1750           v = Variant33;
1751           break;
1752         case 34:
1753           v = Variant34;
1754           break;
1755         case 35:
1756           v = Variant35;
1757           break;
1758         case 36:
1759           v = Variant36;
1760           break;
1761         case 37:
1762           v = VariantShogi;
1763           break;
1764         case 38:
1765           v = VariantXiangqi;
1766           break;
1767         case 39:
1768           v = VariantCourier;
1769           break;
1770         case 40:
1771           v = VariantGothic;
1772           break;
1773         case 41:
1774           v = VariantCapablanca;
1775           break;
1776         case 42:
1777           v = VariantKnightmate;
1778           break;
1779         case 43:
1780           v = VariantFairy;
1781           break;
1782         case 44:
1783           v = VariantCylinder;
1784           break;
1785         case 45:
1786           v = VariantFalcon;
1787           break;
1788         case 46:
1789           v = VariantCapaRandom;
1790           break;
1791         case 47:
1792           v = VariantBerolina;
1793           break;
1794         case 48:
1795           v = VariantJanus;
1796           break;
1797         case 49:
1798           v = VariantSuper;
1799           break;
1800         case 50:
1801           v = VariantGreat;
1802           break;
1803         case -1:
1804           /* Found "wild" or "w" in the string but no number;
1805              must assume it's normal chess. */
1806           v = VariantNormal;
1807           break;
1808         default:
1809           sprintf(buf, _("Unknown wild type %d"), wnum);
1810           DisplayError(buf, 0);
1811           v = VariantUnknown;
1812           break;
1813         }
1814       }
1815     }
1816     if (appData.debugMode) {
1817       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1818               e, wnum, VariantName(v));
1819     }
1820     return v;
1821 }
1822
1823 static int leftover_start = 0, leftover_len = 0;
1824 char star_match[STAR_MATCH_N][MSG_SIZ];
1825
1826 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1827    advance *index beyond it, and set leftover_start to the new value of
1828    *index; else return FALSE.  If pattern contains the character '*', it
1829    matches any sequence of characters not containing '\r', '\n', or the
1830    character following the '*' (if any), and the matched sequence(s) are
1831    copied into star_match.
1832    */
1833 int
1834 looking_at(buf, index, pattern)
1835      char *buf;
1836      int *index;
1837      char *pattern;
1838 {
1839     char *bufp = &buf[*index], *patternp = pattern;
1840     int star_count = 0;
1841     char *matchp = star_match[0];
1842
1843     for (;;) {
1844         if (*patternp == NULLCHAR) {
1845             *index = leftover_start = bufp - buf;
1846             *matchp = NULLCHAR;
1847             return TRUE;
1848         }
1849         if (*bufp == NULLCHAR) return FALSE;
1850         if (*patternp == '*') {
1851             if (*bufp == *(patternp + 1)) {
1852                 *matchp = NULLCHAR;
1853                 matchp = star_match[++star_count];
1854                 patternp += 2;
1855                 bufp++;
1856                 continue;
1857             } else if (*bufp == '\n' || *bufp == '\r') {
1858                 patternp++;
1859                 if (*patternp == NULLCHAR)
1860                   continue;
1861                 else
1862                   return FALSE;
1863             } else {
1864                 *matchp++ = *bufp++;
1865                 continue;
1866             }
1867         }
1868         if (*patternp != *bufp) return FALSE;
1869         patternp++;
1870         bufp++;
1871     }
1872 }
1873
1874 void
1875 SendToPlayer(data, length)
1876      char *data;
1877      int length;
1878 {
1879     int error, outCount;
1880     outCount = OutputToProcess(NoProc, data, length, &error);
1881     if (outCount < length) {
1882         DisplayFatalError(_("Error writing to display"), error, 1);
1883     }
1884 }
1885
1886 void
1887 PackHolding(packed, holding)
1888      char packed[];
1889      char *holding;
1890 {
1891     char *p = holding;
1892     char *q = packed;
1893     int runlength = 0;
1894     int curr = 9999;
1895     do {
1896         if (*p == curr) {
1897             runlength++;
1898         } else {
1899             switch (runlength) {
1900               case 0:
1901                 break;
1902               case 1:
1903                 *q++ = curr;
1904                 break;
1905               case 2:
1906                 *q++ = curr;
1907                 *q++ = curr;
1908                 break;
1909               default:
1910                 sprintf(q, "%d", runlength);
1911                 while (*q) q++;
1912                 *q++ = curr;
1913                 break;
1914             }
1915             runlength = 1;
1916             curr = *p;
1917         }
1918     } while (*p++);
1919     *q = NULLCHAR;
1920 }
1921
1922 /* Telnet protocol requests from the front end */
1923 void
1924 TelnetRequest(ddww, option)
1925      unsigned char ddww, option;
1926 {
1927     unsigned char msg[3];
1928     int outCount, outError;
1929
1930     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1931
1932     if (appData.debugMode) {
1933         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1934         switch (ddww) {
1935           case TN_DO:
1936             ddwwStr = "DO";
1937             break;
1938           case TN_DONT:
1939             ddwwStr = "DONT";
1940             break;
1941           case TN_WILL:
1942             ddwwStr = "WILL";
1943             break;
1944           case TN_WONT:
1945             ddwwStr = "WONT";
1946             break;
1947           default:
1948             ddwwStr = buf1;
1949             sprintf(buf1, "%d", ddww);
1950             break;
1951         }
1952         switch (option) {
1953           case TN_ECHO:
1954             optionStr = "ECHO";
1955             break;
1956           default:
1957             optionStr = buf2;
1958             sprintf(buf2, "%d", option);
1959             break;
1960         }
1961         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1962     }
1963     msg[0] = TN_IAC;
1964     msg[1] = ddww;
1965     msg[2] = option;
1966     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1967     if (outCount < 3) {
1968         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1969     }
1970 }
1971
1972 void
1973 DoEcho()
1974 {
1975     if (!appData.icsActive) return;
1976     TelnetRequest(TN_DO, TN_ECHO);
1977 }
1978
1979 void
1980 DontEcho()
1981 {
1982     if (!appData.icsActive) return;
1983     TelnetRequest(TN_DONT, TN_ECHO);
1984 }
1985
1986 void
1987 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1988 {
1989     /* put the holdings sent to us by the server on the board holdings area */
1990     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1991     char p;
1992     ChessSquare piece;
1993
1994     if(gameInfo.holdingsWidth < 2)  return;
1995     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1996         return; // prevent overwriting by pre-board holdings
1997
1998     if( (int)lowestPiece >= BlackPawn ) {
1999         holdingsColumn = 0;
2000         countsColumn = 1;
2001         holdingsStartRow = BOARD_HEIGHT-1;
2002         direction = -1;
2003     } else {
2004         holdingsColumn = BOARD_WIDTH-1;
2005         countsColumn = BOARD_WIDTH-2;
2006         holdingsStartRow = 0;
2007         direction = 1;
2008     }
2009
2010     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2011         board[i][holdingsColumn] = EmptySquare;
2012         board[i][countsColumn]   = (ChessSquare) 0;
2013     }
2014     while( (p=*holdings++) != NULLCHAR ) {
2015         piece = CharToPiece( ToUpper(p) );
2016         if(piece == EmptySquare) continue;
2017         /*j = (int) piece - (int) WhitePawn;*/
2018         j = PieceToNumber(piece);
2019         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2020         if(j < 0) continue;               /* should not happen */
2021         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2022         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2023         board[holdingsStartRow+j*direction][countsColumn]++;
2024     }
2025 }
2026
2027
2028 void
2029 VariantSwitch(Board board, VariantClass newVariant)
2030 {
2031    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2032    static Board oldBoard;
2033
2034    startedFromPositionFile = FALSE;
2035    if(gameInfo.variant == newVariant) return;
2036
2037    /* [HGM] This routine is called each time an assignment is made to
2038     * gameInfo.variant during a game, to make sure the board sizes
2039     * are set to match the new variant. If that means adding or deleting
2040     * holdings, we shift the playing board accordingly
2041     * This kludge is needed because in ICS observe mode, we get boards
2042     * of an ongoing game without knowing the variant, and learn about the
2043     * latter only later. This can be because of the move list we requested,
2044     * in which case the game history is refilled from the beginning anyway,
2045     * but also when receiving holdings of a crazyhouse game. In the latter
2046     * case we want to add those holdings to the already received position.
2047     */
2048
2049
2050    if (appData.debugMode) {
2051      fprintf(debugFP, "Switch board from %s to %s\n",
2052              VariantName(gameInfo.variant), VariantName(newVariant));
2053      setbuf(debugFP, NULL);
2054    }
2055    shuffleOpenings = 0;       /* [HGM] shuffle */
2056    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2057    switch(newVariant)
2058      {
2059      case VariantShogi:
2060        newWidth = 9;  newHeight = 9;
2061        gameInfo.holdingsSize = 7;
2062      case VariantBughouse:
2063      case VariantCrazyhouse:
2064        newHoldingsWidth = 2; break;
2065      case VariantGreat:
2066        newWidth = 10;
2067      case VariantSuper:
2068        newHoldingsWidth = 2;
2069        gameInfo.holdingsSize = 8;
2070        break;
2071      case VariantGothic:
2072      case VariantCapablanca:
2073      case VariantCapaRandom:
2074        newWidth = 10;
2075      default:
2076        newHoldingsWidth = gameInfo.holdingsSize = 0;
2077      };
2078
2079    if(newWidth  != gameInfo.boardWidth  ||
2080       newHeight != gameInfo.boardHeight ||
2081       newHoldingsWidth != gameInfo.holdingsWidth ) {
2082
2083      /* shift position to new playing area, if needed */
2084      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2085        for(i=0; i<BOARD_HEIGHT; i++)
2086          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2087            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2088              board[i][j];
2089        for(i=0; i<newHeight; i++) {
2090          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2091          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2092        }
2093      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2094        for(i=0; i<BOARD_HEIGHT; i++)
2095          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2096            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2097              board[i][j];
2098      }
2099      gameInfo.boardWidth  = newWidth;
2100      gameInfo.boardHeight = newHeight;
2101      gameInfo.holdingsWidth = newHoldingsWidth;
2102      gameInfo.variant = newVariant;
2103      InitDrawingSizes(-2, 0);
2104    } else gameInfo.variant = newVariant;
2105    CopyBoard(oldBoard, board);   // remember correctly formatted board
2106      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2107    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2108 }
2109
2110 static int loggedOn = FALSE;
2111
2112 /*-- Game start info cache: --*/
2113 int gs_gamenum;
2114 char gs_kind[MSG_SIZ];
2115 static char player1Name[128] = "";
2116 static char player2Name[128] = "";
2117 static char cont_seq[] = "\n\\   ";
2118 static int player1Rating = -1;
2119 static int player2Rating = -1;
2120 /*----------------------------*/
2121
2122 ColorClass curColor = ColorNormal;
2123 int suppressKibitz = 0;
2124
2125 // [HGM] seekgraph
2126 Boolean soughtPending = FALSE;
2127 Boolean seekGraphUp;
2128 #define MAX_SEEK_ADS 200
2129 #define SQUARE 0x80
2130 char *seekAdList[MAX_SEEK_ADS];
2131 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2132 float tcList[MAX_SEEK_ADS];
2133 char colorList[MAX_SEEK_ADS];
2134 int nrOfSeekAds = 0;
2135 int minRating = 1010, maxRating = 2800;
2136 int hMargin = 10, vMargin = 20, h, w;
2137 extern int squareSize, lineGap;
2138
2139 void
2140 PlotSeekAd(int i)
2141 {
2142         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2143         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2144         if(r < minRating+100 && r >=0 ) r = minRating+100;
2145         if(r > maxRating) r = maxRating;
2146         if(tc < 1.) tc = 1.;
2147         if(tc > 95.) tc = 95.;
2148         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2149         y = ((double)r - minRating)/(maxRating - minRating)
2150             * (h-vMargin-squareSize/8-1) + vMargin;
2151         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2152         if(strstr(seekAdList[i], " u ")) color = 1;
2153         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2154            !strstr(seekAdList[i], "bullet") &&
2155            !strstr(seekAdList[i], "blitz") &&
2156            !strstr(seekAdList[i], "standard") ) color = 2;
2157         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2158         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2159 }
2160
2161 void
2162 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2163 {
2164         char buf[MSG_SIZ], *ext = "";
2165         VariantClass v = StringToVariant(type);
2166         if(strstr(type, "wild")) {
2167             ext = type + 4; // append wild number
2168             if(v == VariantFischeRandom) type = "chess960"; else
2169             if(v == VariantLoadable) type = "setup"; else
2170             type = VariantName(v);
2171         }
2172         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2173         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2174             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2175             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2176             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2177             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2178             seekNrList[nrOfSeekAds] = nr;
2179             zList[nrOfSeekAds] = 0;
2180             seekAdList[nrOfSeekAds++] = StrSave(buf);
2181             if(plot) PlotSeekAd(nrOfSeekAds-1);
2182         }
2183 }
2184
2185 void
2186 EraseSeekDot(int i)
2187 {
2188     int x = xList[i], y = yList[i], d=squareSize/4, k;
2189     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2190     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2191     // now replot every dot that overlapped
2192     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2193         int xx = xList[k], yy = yList[k];
2194         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2195             DrawSeekDot(xx, yy, colorList[k]);
2196     }
2197 }
2198
2199 void
2200 RemoveSeekAd(int nr)
2201 {
2202         int i;
2203         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2204             EraseSeekDot(i);
2205             if(seekAdList[i]) free(seekAdList[i]);
2206             seekAdList[i] = seekAdList[--nrOfSeekAds];
2207             seekNrList[i] = seekNrList[nrOfSeekAds];
2208             ratingList[i] = ratingList[nrOfSeekAds];
2209             colorList[i]  = colorList[nrOfSeekAds];
2210             tcList[i] = tcList[nrOfSeekAds];
2211             xList[i]  = xList[nrOfSeekAds];
2212             yList[i]  = yList[nrOfSeekAds];
2213             zList[i]  = zList[nrOfSeekAds];
2214             seekAdList[nrOfSeekAds] = NULL;
2215             break;
2216         }
2217 }
2218
2219 Boolean
2220 MatchSoughtLine(char *line)
2221 {
2222     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2223     int nr, base, inc, u=0; char dummy;
2224
2225     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2226        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2227        (u=1) &&
2228        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2229         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2230         // match: compact and save the line
2231         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2232         return TRUE;
2233     }
2234     return FALSE;
2235 }
2236
2237 int
2238 DrawSeekGraph()
2239 {
2240     int i;
2241     if(!seekGraphUp) return FALSE;
2242     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2243     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2244
2245     DrawSeekBackground(0, 0, w, h);
2246     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2247     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2248     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2249         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2250         yy = h-1-yy;
2251         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2252         if(i%500 == 0) {
2253             char buf[MSG_SIZ];
2254             sprintf(buf, "%d", i);
2255             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2256         }
2257     }
2258     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2259     for(i=1; i<100; i+=(i<10?1:5)) {
2260         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2261         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2262         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2263             char buf[MSG_SIZ];
2264             sprintf(buf, "%d", i);
2265             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2266         }
2267     }
2268     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2269     return TRUE;
2270 }
2271
2272 int SeekGraphClick(ClickType click, int x, int y, int moving)
2273 {
2274     static int lastDown = 0, displayed = 0, lastSecond;
2275     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2276         if(click == Release || moving) return FALSE;
2277         nrOfSeekAds = 0;
2278         soughtPending = TRUE;
2279         SendToICS(ics_prefix);
2280         SendToICS("sought\n"); // should this be "sought all"?
2281     } else { // issue challenge based on clicked ad
2282         int dist = 10000; int i, closest = 0, second = 0;
2283         for(i=0; i<nrOfSeekAds; i++) {
2284             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2285             if(d < dist) { dist = d; closest = i; }
2286             second += (d - zList[i] < 120); // count in-range ads
2287             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2288         }
2289         if(dist < 120) {
2290             char buf[MSG_SIZ];
2291             second = (second > 1);
2292             if(displayed != closest || second != lastSecond) {
2293                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2294                 lastSecond = second; displayed = closest;
2295             }
2296             if(click == Press) {
2297                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2298                 lastDown = closest;
2299                 return TRUE;
2300             } // on press 'hit', only show info
2301             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2302             sprintf(buf, "play %d\n", seekNrList[closest]);
2303             SendToICS(ics_prefix);
2304             SendToICS(buf);
2305             return TRUE; // let incoming board of started game pop down the graph
2306         } else if(click == Release) { // release 'miss' is ignored
2307             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2308             if(moving == 2) { // right up-click
2309                 nrOfSeekAds = 0; // refresh graph
2310                 soughtPending = TRUE;
2311                 SendToICS(ics_prefix);
2312                 SendToICS("sought\n"); // should this be "sought all"?
2313             }
2314             return TRUE;
2315         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2316         // press miss or release hit 'pop down' seek graph
2317         seekGraphUp = FALSE;
2318         DrawPosition(TRUE, NULL);
2319     }
2320     return TRUE;
2321 }
2322
2323 void
2324 read_from_ics(isr, closure, data, count, error)
2325      InputSourceRef isr;
2326      VOIDSTAR closure;
2327      char *data;
2328      int count;
2329      int error;
2330 {
2331 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2332 #define STARTED_NONE 0
2333 #define STARTED_MOVES 1
2334 #define STARTED_BOARD 2
2335 #define STARTED_OBSERVE 3
2336 #define STARTED_HOLDINGS 4
2337 #define STARTED_CHATTER 5
2338 #define STARTED_COMMENT 6
2339 #define STARTED_MOVES_NOHIDE 7
2340
2341     static int started = STARTED_NONE;
2342     static char parse[20000];
2343     static int parse_pos = 0;
2344     static char buf[BUF_SIZE + 1];
2345     static int firstTime = TRUE, intfSet = FALSE;
2346     static ColorClass prevColor = ColorNormal;
2347     static int savingComment = FALSE;
2348     static int cmatch = 0; // continuation sequence match
2349     char *bp;
2350     char str[MSG_SIZ];
2351     int i, oldi;
2352     int buf_len;
2353     int next_out;
2354     int tkind;
2355     int backup;    /* [DM] For zippy color lines */
2356     char *p;
2357     char talker[MSG_SIZ]; // [HGM] chat
2358     int channel;
2359
2360     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2361
2362     if (appData.debugMode) {
2363       if (!error) {
2364         fprintf(debugFP, "<ICS: ");
2365         show_bytes(debugFP, data, count);
2366         fprintf(debugFP, "\n");
2367       }
2368     }
2369
2370     if (appData.debugMode) { int f = forwardMostMove;
2371         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2372                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2373                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2374     }
2375     if (count > 0) {
2376         /* If last read ended with a partial line that we couldn't parse,
2377            prepend it to the new read and try again. */
2378         if (leftover_len > 0) {
2379             for (i=0; i<leftover_len; i++)
2380               buf[i] = buf[leftover_start + i];
2381         }
2382
2383     /* copy new characters into the buffer */
2384     bp = buf + leftover_len;
2385     buf_len=leftover_len;
2386     for (i=0; i<count; i++)
2387     {
2388         // ignore these
2389         if (data[i] == '\r')
2390             continue;
2391
2392         // join lines split by ICS?
2393         if (!appData.noJoin)
2394         {
2395             /*
2396                 Joining just consists of finding matches against the
2397                 continuation sequence, and discarding that sequence
2398                 if found instead of copying it.  So, until a match
2399                 fails, there's nothing to do since it might be the
2400                 complete sequence, and thus, something we don't want
2401                 copied.
2402             */
2403             if (data[i] == cont_seq[cmatch])
2404             {
2405                 cmatch++;
2406                 if (cmatch == strlen(cont_seq))
2407                 {
2408                     cmatch = 0; // complete match.  just reset the counter
2409
2410                     /*
2411                         it's possible for the ICS to not include the space
2412                         at the end of the last word, making our [correct]
2413                         join operation fuse two separate words.  the server
2414                         does this when the space occurs at the width setting.
2415                     */
2416                     if (!buf_len || buf[buf_len-1] != ' ')
2417                     {
2418                         *bp++ = ' ';
2419                         buf_len++;
2420                     }
2421                 }
2422                 continue;
2423             }
2424             else if (cmatch)
2425             {
2426                 /*
2427                     match failed, so we have to copy what matched before
2428                     falling through and copying this character.  In reality,
2429                     this will only ever be just the newline character, but
2430                     it doesn't hurt to be precise.
2431                 */
2432                 strncpy(bp, cont_seq, cmatch);
2433                 bp += cmatch;
2434                 buf_len += cmatch;
2435                 cmatch = 0;
2436             }
2437         }
2438
2439         // copy this char
2440         *bp++ = data[i];
2441         buf_len++;
2442     }
2443
2444         buf[buf_len] = NULLCHAR;
2445 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2446         next_out = 0;
2447         leftover_start = 0;
2448
2449         i = 0;
2450         while (i < buf_len) {
2451             /* Deal with part of the TELNET option negotiation
2452                protocol.  We refuse to do anything beyond the
2453                defaults, except that we allow the WILL ECHO option,
2454                which ICS uses to turn off password echoing when we are
2455                directly connected to it.  We reject this option
2456                if localLineEditing mode is on (always on in xboard)
2457                and we are talking to port 23, which might be a real
2458                telnet server that will try to keep WILL ECHO on permanently.
2459              */
2460             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2461                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2462                 unsigned char option;
2463                 oldi = i;
2464                 switch ((unsigned char) buf[++i]) {
2465                   case TN_WILL:
2466                     if (appData.debugMode)
2467                       fprintf(debugFP, "\n<WILL ");
2468                     switch (option = (unsigned char) buf[++i]) {
2469                       case TN_ECHO:
2470                         if (appData.debugMode)
2471                           fprintf(debugFP, "ECHO ");
2472                         /* Reply only if this is a change, according
2473                            to the protocol rules. */
2474                         if (remoteEchoOption) break;
2475                         if (appData.localLineEditing &&
2476                             atoi(appData.icsPort) == TN_PORT) {
2477                             TelnetRequest(TN_DONT, TN_ECHO);
2478                         } else {
2479                             EchoOff();
2480                             TelnetRequest(TN_DO, TN_ECHO);
2481                             remoteEchoOption = TRUE;
2482                         }
2483                         break;
2484                       default:
2485                         if (appData.debugMode)
2486                           fprintf(debugFP, "%d ", option);
2487                         /* Whatever this is, we don't want it. */
2488                         TelnetRequest(TN_DONT, option);
2489                         break;
2490                     }
2491                     break;
2492                   case TN_WONT:
2493                     if (appData.debugMode)
2494                       fprintf(debugFP, "\n<WONT ");
2495                     switch (option = (unsigned char) buf[++i]) {
2496                       case TN_ECHO:
2497                         if (appData.debugMode)
2498                           fprintf(debugFP, "ECHO ");
2499                         /* Reply only if this is a change, according
2500                            to the protocol rules. */
2501                         if (!remoteEchoOption) break;
2502                         EchoOn();
2503                         TelnetRequest(TN_DONT, TN_ECHO);
2504                         remoteEchoOption = FALSE;
2505                         break;
2506                       default:
2507                         if (appData.debugMode)
2508                           fprintf(debugFP, "%d ", (unsigned char) option);
2509                         /* Whatever this is, it must already be turned
2510                            off, because we never agree to turn on
2511                            anything non-default, so according to the
2512                            protocol rules, we don't reply. */
2513                         break;
2514                     }
2515                     break;
2516                   case TN_DO:
2517                     if (appData.debugMode)
2518                       fprintf(debugFP, "\n<DO ");
2519                     switch (option = (unsigned char) buf[++i]) {
2520                       default:
2521                         /* Whatever this is, we refuse to do it. */
2522                         if (appData.debugMode)
2523                           fprintf(debugFP, "%d ", option);
2524                         TelnetRequest(TN_WONT, option);
2525                         break;
2526                     }
2527                     break;
2528                   case TN_DONT:
2529                     if (appData.debugMode)
2530                       fprintf(debugFP, "\n<DONT ");
2531                     switch (option = (unsigned char) buf[++i]) {
2532                       default:
2533                         if (appData.debugMode)
2534                           fprintf(debugFP, "%d ", option);
2535                         /* Whatever this is, we are already not doing
2536                            it, because we never agree to do anything
2537                            non-default, so according to the protocol
2538                            rules, we don't reply. */
2539                         break;
2540                     }
2541                     break;
2542                   case TN_IAC:
2543                     if (appData.debugMode)
2544                       fprintf(debugFP, "\n<IAC ");
2545                     /* Doubled IAC; pass it through */
2546                     i--;
2547                     break;
2548                   default:
2549                     if (appData.debugMode)
2550                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2551                     /* Drop all other telnet commands on the floor */
2552                     break;
2553                 }
2554                 if (oldi > next_out)
2555                   SendToPlayer(&buf[next_out], oldi - next_out);
2556                 if (++i > next_out)
2557                   next_out = i;
2558                 continue;
2559             }
2560
2561             /* OK, this at least will *usually* work */
2562             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2563                 loggedOn = TRUE;
2564             }
2565
2566             if (loggedOn && !intfSet) {
2567                 if (ics_type == ICS_ICC) {
2568                   sprintf(str,
2569                           "/set-quietly interface %s\n/set-quietly style 12\n",
2570                           programVersion);
2571                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2572                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2573                 } else if (ics_type == ICS_CHESSNET) {
2574                   sprintf(str, "/style 12\n");
2575                 } else {
2576                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2577                   strcat(str, programVersion);
2578                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2579                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2580                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2581 #ifdef WIN32
2582                   strcat(str, "$iset nohighlight 1\n");
2583 #endif
2584                   strcat(str, "$iset lock 1\n$style 12\n");
2585                 }
2586                 SendToICS(str);
2587                 NotifyFrontendLogin();
2588                 intfSet = TRUE;
2589             }
2590
2591             if (started == STARTED_COMMENT) {
2592                 /* Accumulate characters in comment */
2593                 parse[parse_pos++] = buf[i];
2594                 if (buf[i] == '\n') {
2595                     parse[parse_pos] = NULLCHAR;
2596                     if(chattingPartner>=0) {
2597                         char mess[MSG_SIZ];
2598                         sprintf(mess, "%s%s", talker, parse);
2599                         OutputChatMessage(chattingPartner, mess);
2600                         chattingPartner = -1;
2601                         next_out = i+1; // [HGM] suppress printing in ICS window
2602                     } else
2603                     if(!suppressKibitz) // [HGM] kibitz
2604                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2605                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2606                         int nrDigit = 0, nrAlph = 0, j;
2607                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2608                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2609                         parse[parse_pos] = NULLCHAR;
2610                         // try to be smart: if it does not look like search info, it should go to
2611                         // ICS interaction window after all, not to engine-output window.
2612                         for(j=0; j<parse_pos; j++) { // count letters and digits
2613                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2614                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2615                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2616                         }
2617                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2618                             int depth=0; float score;
2619                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2620                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2621                                 pvInfoList[forwardMostMove-1].depth = depth;
2622                                 pvInfoList[forwardMostMove-1].score = 100*score;
2623                             }
2624                             OutputKibitz(suppressKibitz, parse);
2625                         } else {
2626                             char tmp[MSG_SIZ];
2627                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2628                             SendToPlayer(tmp, strlen(tmp));
2629                         }
2630                         next_out = i+1; // [HGM] suppress printing in ICS window
2631                     }
2632                     started = STARTED_NONE;
2633                 } else {
2634                     /* Don't match patterns against characters in comment */
2635                     i++;
2636                     continue;
2637                 }
2638             }
2639             if (started == STARTED_CHATTER) {
2640                 if (buf[i] != '\n') {
2641                     /* Don't match patterns against characters in chatter */
2642                     i++;
2643                     continue;
2644                 }
2645                 started = STARTED_NONE;
2646                 if(suppressKibitz) next_out = i+1;
2647             }
2648
2649             /* Kludge to deal with rcmd protocol */
2650             if (firstTime && looking_at(buf, &i, "\001*")) {
2651                 DisplayFatalError(&buf[1], 0, 1);
2652                 continue;
2653             } else {
2654                 firstTime = FALSE;
2655             }
2656
2657             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2658                 ics_type = ICS_ICC;
2659                 ics_prefix = "/";
2660                 if (appData.debugMode)
2661                   fprintf(debugFP, "ics_type %d\n", ics_type);
2662                 continue;
2663             }
2664             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2665                 ics_type = ICS_FICS;
2666                 ics_prefix = "$";
2667                 if (appData.debugMode)
2668                   fprintf(debugFP, "ics_type %d\n", ics_type);
2669                 continue;
2670             }
2671             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2672                 ics_type = ICS_CHESSNET;
2673                 ics_prefix = "/";
2674                 if (appData.debugMode)
2675                   fprintf(debugFP, "ics_type %d\n", ics_type);
2676                 continue;
2677             }
2678
2679             if (!loggedOn &&
2680                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2681                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2682                  looking_at(buf, &i, "will be \"*\""))) {
2683               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2684               continue;
2685             }
2686
2687             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2688               char buf[MSG_SIZ];
2689               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2690               DisplayIcsInteractionTitle(buf);
2691               have_set_title = TRUE;
2692             }
2693
2694             /* skip finger notes */
2695             if (started == STARTED_NONE &&
2696                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2697                  (buf[i] == '1' && buf[i+1] == '0')) &&
2698                 buf[i+2] == ':' && buf[i+3] == ' ') {
2699               started = STARTED_CHATTER;
2700               i += 3;
2701               continue;
2702             }
2703
2704             oldi = i;
2705             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2706             if(appData.seekGraph) {
2707                 if(soughtPending && MatchSoughtLine(buf+i)) {
2708                     i = strstr(buf+i, "rated") - buf;
2709                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2710                     next_out = leftover_start = i;
2711                     started = STARTED_CHATTER;
2712                     suppressKibitz = TRUE;
2713                     continue;
2714                 }
2715                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2716                         && looking_at(buf, &i, "* ads displayed")) {
2717                     soughtPending = FALSE;
2718                     seekGraphUp = TRUE;
2719                     DrawSeekGraph();
2720                     continue;
2721                 }
2722                 if(appData.autoRefresh) {
2723                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2724                         int s = (ics_type == ICS_ICC); // ICC format differs
2725                         if(seekGraphUp)
2726                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2727                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2728                         looking_at(buf, &i, "*% "); // eat prompt
2729                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2730                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2731                         next_out = i; // suppress
2732                         continue;
2733                     }
2734                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2735                         char *p = star_match[0];
2736                         while(*p) {
2737                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2738                             while(*p && *p++ != ' '); // next
2739                         }
2740                         looking_at(buf, &i, "*% "); // eat prompt
2741                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2742                         next_out = i;
2743                         continue;
2744                     }
2745                 }
2746             }
2747
2748             /* skip formula vars */
2749             if (started == STARTED_NONE &&
2750                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2751               started = STARTED_CHATTER;
2752               i += 3;
2753               continue;
2754             }
2755
2756             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2757             if (appData.autoKibitz && started == STARTED_NONE &&
2758                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2759                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2760                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2761                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2762                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2763                         suppressKibitz = TRUE;
2764                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2765                         next_out = i;
2766                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2767                                 && (gameMode == IcsPlayingWhite)) ||
2768                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2769                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2770                             started = STARTED_CHATTER; // own kibitz we simply discard
2771                         else {
2772                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2773                             parse_pos = 0; parse[0] = NULLCHAR;
2774                             savingComment = TRUE;
2775                             suppressKibitz = gameMode != IcsObserving ? 2 :
2776                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2777                         }
2778                         continue;
2779                 } else
2780                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2781                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2782                          && atoi(star_match[0])) {
2783                     // suppress the acknowledgements of our own autoKibitz
2784                     char *p;
2785                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2786                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2787                     SendToPlayer(star_match[0], strlen(star_match[0]));
2788                     if(looking_at(buf, &i, "*% ")) // eat prompt
2789                         suppressKibitz = FALSE;
2790                     next_out = i;
2791                     continue;
2792                 }
2793             } // [HGM] kibitz: end of patch
2794
2795             // [HGM] chat: intercept tells by users for which we have an open chat window
2796             channel = -1;
2797             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2798                                            looking_at(buf, &i, "* whispers:") ||
2799                                            looking_at(buf, &i, "* kibitzes:") ||
2800                                            looking_at(buf, &i, "* shouts:") ||
2801                                            looking_at(buf, &i, "* c-shouts:") ||
2802                                            looking_at(buf, &i, "--> * ") ||
2803                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2804                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2805                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2806                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2807                 int p;
2808                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2809                 chattingPartner = -1;
2810
2811                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2812                 for(p=0; p<MAX_CHAT; p++) {
2813                     if(channel == atoi(chatPartner[p])) {
2814                     talker[0] = '['; strcat(talker, "] ");
2815                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2816                     chattingPartner = p; break;
2817                     }
2818                 } else
2819                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2820                 for(p=0; p<MAX_CHAT; p++) {
2821                     if(!strcmp("kibitzes", chatPartner[p])) {
2822                         talker[0] = '['; strcat(talker, "] ");
2823                         chattingPartner = p; break;
2824                     }
2825                 } else
2826                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2827                 for(p=0; p<MAX_CHAT; p++) {
2828                     if(!strcmp("whispers", chatPartner[p])) {
2829                         talker[0] = '['; strcat(talker, "] ");
2830                         chattingPartner = p; break;
2831                     }
2832                 } else
2833                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2834                   if(buf[i-8] == '-' && buf[i-3] == 't')
2835                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2836                     if(!strcmp("c-shouts", chatPartner[p])) {
2837                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2838                         chattingPartner = p; break;
2839                     }
2840                   }
2841                   if(chattingPartner < 0)
2842                   for(p=0; p<MAX_CHAT; p++) {
2843                     if(!strcmp("shouts", chatPartner[p])) {
2844                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2845                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2846                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2847                         chattingPartner = p; break;
2848                     }
2849                   }
2850                 }
2851                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2852                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2853                     talker[0] = 0; Colorize(ColorTell, FALSE);
2854                     chattingPartner = p; break;
2855                 }
2856                 if(chattingPartner<0) i = oldi; else {
2857                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2858                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2859                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2860                     started = STARTED_COMMENT;
2861                     parse_pos = 0; parse[0] = NULLCHAR;
2862                     savingComment = 3 + chattingPartner; // counts as TRUE
2863                     suppressKibitz = TRUE;
2864                     continue;
2865                 }
2866             } // [HGM] chat: end of patch
2867
2868             if (appData.zippyTalk || appData.zippyPlay) {
2869                 /* [DM] Backup address for color zippy lines */
2870                 backup = i;
2871 #if ZIPPY
2872                if (loggedOn == TRUE)
2873                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2874                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2875 #endif
2876             } // [DM] 'else { ' deleted
2877                 if (
2878                     /* Regular tells and says */
2879                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2880                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2881                     looking_at(buf, &i, "* says: ") ||
2882                     /* Don't color "message" or "messages" output */
2883                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2884                     looking_at(buf, &i, "*. * at *:*: ") ||
2885                     looking_at(buf, &i, "--* (*:*): ") ||
2886                     /* Message notifications (same color as tells) */
2887                     looking_at(buf, &i, "* has left a message ") ||
2888                     looking_at(buf, &i, "* just sent you a message:\n") ||
2889                     /* Whispers and kibitzes */
2890                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2891                     looking_at(buf, &i, "* kibitzes: ") ||
2892                     /* Channel tells */
2893                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2894
2895                   if (tkind == 1 && strchr(star_match[0], ':')) {
2896                       /* Avoid "tells you:" spoofs in channels */
2897                      tkind = 3;
2898                   }
2899                   if (star_match[0][0] == NULLCHAR ||
2900                       strchr(star_match[0], ' ') ||
2901                       (tkind == 3 && strchr(star_match[1], ' '))) {
2902                     /* Reject bogus matches */
2903                     i = oldi;
2904                   } else {
2905                     if (appData.colorize) {
2906                       if (oldi > next_out) {
2907                         SendToPlayer(&buf[next_out], oldi - next_out);
2908                         next_out = oldi;
2909                       }
2910                       switch (tkind) {
2911                       case 1:
2912                         Colorize(ColorTell, FALSE);
2913                         curColor = ColorTell;
2914                         break;
2915                       case 2:
2916                         Colorize(ColorKibitz, FALSE);
2917                         curColor = ColorKibitz;
2918                         break;
2919                       case 3:
2920                         p = strrchr(star_match[1], '(');
2921                         if (p == NULL) {
2922                           p = star_match[1];
2923                         } else {
2924                           p++;
2925                         }
2926                         if (atoi(p) == 1) {
2927                           Colorize(ColorChannel1, FALSE);
2928                           curColor = ColorChannel1;
2929                         } else {
2930                           Colorize(ColorChannel, FALSE);
2931                           curColor = ColorChannel;
2932                         }
2933                         break;
2934                       case 5:
2935                         curColor = ColorNormal;
2936                         break;
2937                       }
2938                     }
2939                     if (started == STARTED_NONE && appData.autoComment &&
2940                         (gameMode == IcsObserving ||
2941                          gameMode == IcsPlayingWhite ||
2942                          gameMode == IcsPlayingBlack)) {
2943                       parse_pos = i - oldi;
2944                       memcpy(parse, &buf[oldi], parse_pos);
2945                       parse[parse_pos] = NULLCHAR;
2946                       started = STARTED_COMMENT;
2947                       savingComment = TRUE;
2948                     } else {
2949                       started = STARTED_CHATTER;
2950                       savingComment = FALSE;
2951                     }
2952                     loggedOn = TRUE;
2953                     continue;
2954                   }
2955                 }
2956
2957                 if (looking_at(buf, &i, "* s-shouts: ") ||
2958                     looking_at(buf, &i, "* c-shouts: ")) {
2959                     if (appData.colorize) {
2960                         if (oldi > next_out) {
2961                             SendToPlayer(&buf[next_out], oldi - next_out);
2962                             next_out = oldi;
2963                         }
2964                         Colorize(ColorSShout, FALSE);
2965                         curColor = ColorSShout;
2966                     }
2967                     loggedOn = TRUE;
2968                     started = STARTED_CHATTER;
2969                     continue;
2970                 }
2971
2972                 if (looking_at(buf, &i, "--->")) {
2973                     loggedOn = TRUE;
2974                     continue;
2975                 }
2976
2977                 if (looking_at(buf, &i, "* shouts: ") ||
2978                     looking_at(buf, &i, "--> ")) {
2979                     if (appData.colorize) {
2980                         if (oldi > next_out) {
2981                             SendToPlayer(&buf[next_out], oldi - next_out);
2982                             next_out = oldi;
2983                         }
2984                         Colorize(ColorShout, FALSE);
2985                         curColor = ColorShout;
2986                     }
2987                     loggedOn = TRUE;
2988                     started = STARTED_CHATTER;
2989                     continue;
2990                 }
2991
2992                 if (looking_at( buf, &i, "Challenge:")) {
2993                     if (appData.colorize) {
2994                         if (oldi > next_out) {
2995                             SendToPlayer(&buf[next_out], oldi - next_out);
2996                             next_out = oldi;
2997                         }
2998                         Colorize(ColorChallenge, FALSE);
2999                         curColor = ColorChallenge;
3000                     }
3001                     loggedOn = TRUE;
3002                     continue;
3003                 }
3004
3005                 if (looking_at(buf, &i, "* offers you") ||
3006                     looking_at(buf, &i, "* offers to be") ||
3007                     looking_at(buf, &i, "* would like to") ||
3008                     looking_at(buf, &i, "* requests to") ||
3009                     looking_at(buf, &i, "Your opponent offers") ||
3010                     looking_at(buf, &i, "Your opponent requests")) {
3011
3012                     if (appData.colorize) {
3013                         if (oldi > next_out) {
3014                             SendToPlayer(&buf[next_out], oldi - next_out);
3015                             next_out = oldi;
3016                         }
3017                         Colorize(ColorRequest, FALSE);
3018                         curColor = ColorRequest;
3019                     }
3020                     continue;
3021                 }
3022
3023                 if (looking_at(buf, &i, "* (*) seeking")) {
3024                     if (appData.colorize) {
3025                         if (oldi > next_out) {
3026                             SendToPlayer(&buf[next_out], oldi - next_out);
3027                             next_out = oldi;
3028                         }
3029                         Colorize(ColorSeek, FALSE);
3030                         curColor = ColorSeek;
3031                     }
3032                     continue;
3033             }
3034
3035             if (looking_at(buf, &i, "\\   ")) {
3036                 if (prevColor != ColorNormal) {
3037                     if (oldi > next_out) {
3038                         SendToPlayer(&buf[next_out], oldi - next_out);
3039                         next_out = oldi;
3040                     }
3041                     Colorize(prevColor, TRUE);
3042                     curColor = prevColor;
3043                 }
3044                 if (savingComment) {
3045                     parse_pos = i - oldi;
3046                     memcpy(parse, &buf[oldi], parse_pos);
3047                     parse[parse_pos] = NULLCHAR;
3048                     started = STARTED_COMMENT;
3049                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3050                         chattingPartner = savingComment - 3; // kludge to remember the box
3051                 } else {
3052                     started = STARTED_CHATTER;
3053                 }
3054                 continue;
3055             }
3056
3057             if (looking_at(buf, &i, "Black Strength :") ||
3058                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3059                 looking_at(buf, &i, "<10>") ||
3060                 looking_at(buf, &i, "#@#")) {
3061                 /* Wrong board style */
3062                 loggedOn = TRUE;
3063                 SendToICS(ics_prefix);
3064                 SendToICS("set style 12\n");
3065                 SendToICS(ics_prefix);
3066                 SendToICS("refresh\n");
3067                 continue;
3068             }
3069
3070             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3071                 ICSInitScript();
3072                 have_sent_ICS_logon = 1;
3073                 continue;
3074             }
3075
3076             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3077                 (looking_at(buf, &i, "\n<12> ") ||
3078                  looking_at(buf, &i, "<12> "))) {
3079                 loggedOn = TRUE;
3080                 if (oldi > next_out) {
3081                     SendToPlayer(&buf[next_out], oldi - next_out);
3082                 }
3083                 next_out = i;
3084                 started = STARTED_BOARD;
3085                 parse_pos = 0;
3086                 continue;
3087             }
3088
3089             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3090                 looking_at(buf, &i, "<b1> ")) {
3091                 if (oldi > next_out) {
3092                     SendToPlayer(&buf[next_out], oldi - next_out);
3093                 }
3094                 next_out = i;
3095                 started = STARTED_HOLDINGS;
3096                 parse_pos = 0;
3097                 continue;
3098             }
3099
3100             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3101                 loggedOn = TRUE;
3102                 /* Header for a move list -- first line */
3103
3104                 switch (ics_getting_history) {
3105                   case H_FALSE:
3106                     switch (gameMode) {
3107                       case IcsIdle:
3108                       case BeginningOfGame:
3109                         /* User typed "moves" or "oldmoves" while we
3110                            were idle.  Pretend we asked for these
3111                            moves and soak them up so user can step
3112                            through them and/or save them.
3113                            */
3114                         Reset(FALSE, TRUE);
3115                         gameMode = IcsObserving;
3116                         ModeHighlight();
3117                         ics_gamenum = -1;
3118                         ics_getting_history = H_GOT_UNREQ_HEADER;
3119                         break;
3120                       case EditGame: /*?*/
3121                       case EditPosition: /*?*/
3122                         /* Should above feature work in these modes too? */
3123                         /* For now it doesn't */
3124                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3125                         break;
3126                       default:
3127                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3128                         break;
3129                     }
3130                     break;
3131                   case H_REQUESTED:
3132                     /* Is this the right one? */
3133                     if (gameInfo.white && gameInfo.black &&
3134                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3135                         strcmp(gameInfo.black, star_match[2]) == 0) {
3136                         /* All is well */
3137                         ics_getting_history = H_GOT_REQ_HEADER;
3138                     }
3139                     break;
3140                   case H_GOT_REQ_HEADER:
3141                   case H_GOT_UNREQ_HEADER:
3142                   case H_GOT_UNWANTED_HEADER:
3143                   case H_GETTING_MOVES:
3144                     /* Should not happen */
3145                     DisplayError(_("Error gathering move list: two headers"), 0);
3146                     ics_getting_history = H_FALSE;
3147                     break;
3148                 }
3149
3150                 /* Save player ratings into gameInfo if needed */
3151                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3152                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3153                     (gameInfo.whiteRating == -1 ||
3154                      gameInfo.blackRating == -1)) {
3155
3156                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3157                     gameInfo.blackRating = string_to_rating(star_match[3]);
3158                     if (appData.debugMode)
3159                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3160                               gameInfo.whiteRating, gameInfo.blackRating);
3161                 }
3162                 continue;
3163             }
3164
3165             if (looking_at(buf, &i,
3166               "* * match, initial time: * minute*, increment: * second")) {
3167                 /* Header for a move list -- second line */
3168                 /* Initial board will follow if this is a wild game */
3169                 if (gameInfo.event != NULL) free(gameInfo.event);
3170                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3171                 gameInfo.event = StrSave(str);
3172                 /* [HGM] we switched variant. Translate boards if needed. */
3173                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3174                 continue;
3175             }
3176
3177             if (looking_at(buf, &i, "Move  ")) {
3178                 /* Beginning of a move list */
3179                 switch (ics_getting_history) {
3180                   case H_FALSE:
3181                     /* Normally should not happen */
3182                     /* Maybe user hit reset while we were parsing */
3183                     break;
3184                   case H_REQUESTED:
3185                     /* Happens if we are ignoring a move list that is not
3186                      * the one we just requested.  Common if the user
3187                      * tries to observe two games without turning off
3188                      * getMoveList */
3189                     break;
3190                   case H_GETTING_MOVES:
3191                     /* Should not happen */
3192                     DisplayError(_("Error gathering move list: nested"), 0);
3193                     ics_getting_history = H_FALSE;
3194                     break;
3195                   case H_GOT_REQ_HEADER:
3196                     ics_getting_history = H_GETTING_MOVES;
3197                     started = STARTED_MOVES;
3198                     parse_pos = 0;
3199                     if (oldi > next_out) {
3200                         SendToPlayer(&buf[next_out], oldi - next_out);
3201                     }
3202                     break;
3203                   case H_GOT_UNREQ_HEADER:
3204                     ics_getting_history = H_GETTING_MOVES;
3205                     started = STARTED_MOVES_NOHIDE;
3206                     parse_pos = 0;
3207                     break;
3208                   case H_GOT_UNWANTED_HEADER:
3209                     ics_getting_history = H_FALSE;
3210                     break;
3211                 }
3212                 continue;
3213             }
3214
3215             if (looking_at(buf, &i, "% ") ||
3216                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3217                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3218                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3219                     soughtPending = FALSE;
3220                     seekGraphUp = TRUE;
3221                     DrawSeekGraph();
3222                 }
3223                 if(suppressKibitz) next_out = i;
3224                 savingComment = FALSE;
3225                 suppressKibitz = 0;
3226                 switch (started) {
3227                   case STARTED_MOVES:
3228                   case STARTED_MOVES_NOHIDE:
3229                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3230                     parse[parse_pos + i - oldi] = NULLCHAR;
3231                     ParseGameHistory(parse);
3232 #if ZIPPY
3233                     if (appData.zippyPlay && first.initDone) {
3234                         FeedMovesToProgram(&first, forwardMostMove);
3235                         if (gameMode == IcsPlayingWhite) {
3236                             if (WhiteOnMove(forwardMostMove)) {
3237                                 if (first.sendTime) {
3238                                   if (first.useColors) {
3239                                     SendToProgram("black\n", &first);
3240                                   }
3241                                   SendTimeRemaining(&first, TRUE);
3242                                 }
3243                                 if (first.useColors) {
3244                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3245                                 }
3246                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3247                                 first.maybeThinking = TRUE;
3248                             } else {
3249                                 if (first.usePlayother) {
3250                                   if (first.sendTime) {
3251                                     SendTimeRemaining(&first, TRUE);
3252                                   }
3253                                   SendToProgram("playother\n", &first);
3254                                   firstMove = FALSE;
3255                                 } else {
3256                                   firstMove = TRUE;
3257                                 }
3258                             }
3259                         } else if (gameMode == IcsPlayingBlack) {
3260                             if (!WhiteOnMove(forwardMostMove)) {
3261                                 if (first.sendTime) {
3262                                   if (first.useColors) {
3263                                     SendToProgram("white\n", &first);
3264                                   }
3265                                   SendTimeRemaining(&first, FALSE);
3266                                 }
3267                                 if (first.useColors) {
3268                                   SendToProgram("black\n", &first);
3269                                 }
3270                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3271                                 first.maybeThinking = TRUE;
3272                             } else {
3273                                 if (first.usePlayother) {
3274                                   if (first.sendTime) {
3275                                     SendTimeRemaining(&first, FALSE);
3276                                   }
3277                                   SendToProgram("playother\n", &first);
3278                                   firstMove = FALSE;
3279                                 } else {
3280                                   firstMove = TRUE;
3281                                 }
3282                             }
3283                         }
3284                     }
3285 #endif
3286                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3287                         /* Moves came from oldmoves or moves command
3288                            while we weren't doing anything else.
3289                            */
3290                         currentMove = forwardMostMove;
3291                         ClearHighlights();/*!!could figure this out*/
3292                         flipView = appData.flipView;
3293                         DrawPosition(TRUE, boards[currentMove]);
3294                         DisplayBothClocks();
3295                         sprintf(str, "%s vs. %s",
3296                                 gameInfo.white, gameInfo.black);
3297                         DisplayTitle(str);
3298                         gameMode = IcsIdle;
3299                     } else {
3300                         /* Moves were history of an active game */
3301                         if (gameInfo.resultDetails != NULL) {
3302                             free(gameInfo.resultDetails);
3303                             gameInfo.resultDetails = NULL;
3304                         }
3305                     }
3306                     HistorySet(parseList, backwardMostMove,
3307                                forwardMostMove, currentMove-1);
3308                     DisplayMove(currentMove - 1);
3309                     if (started == STARTED_MOVES) next_out = i;
3310                     started = STARTED_NONE;
3311                     ics_getting_history = H_FALSE;
3312                     break;
3313
3314                   case STARTED_OBSERVE:
3315                     started = STARTED_NONE;
3316                     SendToICS(ics_prefix);
3317                     SendToICS("refresh\n");
3318                     break;
3319
3320                   default:
3321                     break;
3322                 }
3323                 if(bookHit) { // [HGM] book: simulate book reply
3324                     static char bookMove[MSG_SIZ]; // a bit generous?
3325
3326                     programStats.nodes = programStats.depth = programStats.time =
3327                     programStats.score = programStats.got_only_move = 0;
3328                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3329
3330                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3331                     strcat(bookMove, bookHit);
3332                     HandleMachineMove(bookMove, &first);
3333                 }
3334                 continue;
3335             }
3336
3337             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3338                  started == STARTED_HOLDINGS ||
3339                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3340                 /* Accumulate characters in move list or board */
3341                 parse[parse_pos++] = buf[i];
3342             }
3343
3344             /* Start of game messages.  Mostly we detect start of game
3345                when the first board image arrives.  On some versions
3346                of the ICS, though, we need to do a "refresh" after starting
3347                to observe in order to get the current board right away. */
3348             if (looking_at(buf, &i, "Adding game * to observation list")) {
3349                 started = STARTED_OBSERVE;
3350                 continue;
3351             }
3352
3353             /* Handle auto-observe */
3354             if (appData.autoObserve &&
3355                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3356                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3357                 char *player;
3358                 /* Choose the player that was highlighted, if any. */
3359                 if (star_match[0][0] == '\033' ||
3360                     star_match[1][0] != '\033') {
3361                     player = star_match[0];
3362                 } else {
3363                     player = star_match[2];
3364                 }
3365                 sprintf(str, "%sobserve %s\n",
3366                         ics_prefix, StripHighlightAndTitle(player));
3367                 SendToICS(str);
3368
3369                 /* Save ratings from notify string */
3370                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3371                 player1Rating = string_to_rating(star_match[1]);
3372                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3373                 player2Rating = string_to_rating(star_match[3]);
3374
3375                 if (appData.debugMode)
3376                   fprintf(debugFP,
3377                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3378                           player1Name, player1Rating,
3379                           player2Name, player2Rating);
3380
3381                 continue;
3382             }
3383
3384             /* Deal with automatic examine mode after a game,
3385                and with IcsObserving -> IcsExamining transition */
3386             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3387                 looking_at(buf, &i, "has made you an examiner of game *")) {
3388
3389                 int gamenum = atoi(star_match[0]);
3390                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3391                     gamenum == ics_gamenum) {
3392                     /* We were already playing or observing this game;
3393                        no need to refetch history */
3394                     gameMode = IcsExamining;
3395                     if (pausing) {
3396                         pauseExamForwardMostMove = forwardMostMove;
3397                     } else if (currentMove < forwardMostMove) {
3398                         ForwardInner(forwardMostMove);
3399                     }
3400                 } else {
3401                     /* I don't think this case really can happen */
3402                     SendToICS(ics_prefix);
3403                     SendToICS("refresh\n");
3404                 }
3405                 continue;
3406             }
3407
3408             /* Error messages */
3409 //          if (ics_user_moved) {
3410             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3411                 if (looking_at(buf, &i, "Illegal move") ||
3412                     looking_at(buf, &i, "Not a legal move") ||
3413                     looking_at(buf, &i, "Your king is in check") ||
3414                     looking_at(buf, &i, "It isn't your turn") ||
3415                     looking_at(buf, &i, "It is not your move")) {
3416                     /* Illegal move */
3417                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3418                         currentMove = forwardMostMove-1;
3419                         DisplayMove(currentMove - 1); /* before DMError */
3420                         DrawPosition(FALSE, boards[currentMove]);
3421                         SwitchClocks(forwardMostMove-1); // [HGM] race
3422                         DisplayBothClocks();
3423                     }
3424                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3425                     ics_user_moved = 0;
3426                     continue;
3427                 }
3428             }
3429
3430             if (looking_at(buf, &i, "still have time") ||
3431                 looking_at(buf, &i, "not out of time") ||
3432                 looking_at(buf, &i, "either player is out of time") ||
3433                 looking_at(buf, &i, "has timeseal; checking")) {
3434                 /* We must have called his flag a little too soon */
3435                 whiteFlag = blackFlag = FALSE;
3436                 continue;
3437             }
3438
3439             if (looking_at(buf, &i, "added * seconds to") ||
3440                 looking_at(buf, &i, "seconds were added to")) {
3441                 /* Update the clocks */
3442                 SendToICS(ics_prefix);
3443                 SendToICS("refresh\n");
3444                 continue;
3445             }
3446
3447             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3448                 ics_clock_paused = TRUE;
3449                 StopClocks();
3450                 continue;
3451             }
3452
3453             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3454                 ics_clock_paused = FALSE;
3455                 StartClocks();
3456                 continue;
3457             }
3458
3459             /* Grab player ratings from the Creating: message.
3460                Note we have to check for the special case when
3461                the ICS inserts things like [white] or [black]. */
3462             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3463                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3464                 /* star_matches:
3465                    0    player 1 name (not necessarily white)
3466                    1    player 1 rating
3467                    2    empty, white, or black (IGNORED)
3468                    3    player 2 name (not necessarily black)
3469                    4    player 2 rating
3470
3471                    The names/ratings are sorted out when the game
3472                    actually starts (below).
3473                 */
3474                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3475                 player1Rating = string_to_rating(star_match[1]);
3476                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3477                 player2Rating = string_to_rating(star_match[4]);
3478
3479                 if (appData.debugMode)
3480                   fprintf(debugFP,
3481                           "Ratings from 'Creating:' %s %d, %s %d\n",
3482                           player1Name, player1Rating,
3483                           player2Name, player2Rating);
3484
3485                 continue;
3486             }
3487
3488             /* Improved generic start/end-of-game messages */
3489             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3490                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3491                 /* If tkind == 0: */
3492                 /* star_match[0] is the game number */
3493                 /*           [1] is the white player's name */
3494                 /*           [2] is the black player's name */
3495                 /* For end-of-game: */
3496                 /*           [3] is the reason for the game end */
3497                 /*           [4] is a PGN end game-token, preceded by " " */
3498                 /* For start-of-game: */
3499                 /*           [3] begins with "Creating" or "Continuing" */
3500                 /*           [4] is " *" or empty (don't care). */
3501                 int gamenum = atoi(star_match[0]);
3502                 char *whitename, *blackname, *why, *endtoken;
3503                 ChessMove endtype = (ChessMove) 0;
3504
3505                 if (tkind == 0) {
3506                   whitename = star_match[1];
3507                   blackname = star_match[2];
3508                   why = star_match[3];
3509                   endtoken = star_match[4];
3510                 } else {
3511                   whitename = star_match[1];
3512                   blackname = star_match[3];
3513                   why = star_match[5];
3514                   endtoken = star_match[6];
3515                 }
3516
3517                 /* Game start messages */
3518                 if (strncmp(why, "Creating ", 9) == 0 ||
3519                     strncmp(why, "Continuing ", 11) == 0) {
3520                     gs_gamenum = gamenum;
3521                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3522                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3523 #if ZIPPY
3524                     if (appData.zippyPlay) {
3525                         ZippyGameStart(whitename, blackname);
3526                     }
3527 #endif /*ZIPPY*/
3528                     partnerBoardValid = FALSE; // [HGM] bughouse
3529                     continue;
3530                 }
3531
3532                 /* Game end messages */
3533                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3534                     ics_gamenum != gamenum) {
3535                     continue;
3536                 }
3537                 while (endtoken[0] == ' ') endtoken++;
3538                 switch (endtoken[0]) {
3539                   case '*':
3540                   default:
3541                     endtype = GameUnfinished;
3542                     break;
3543                   case '0':
3544                     endtype = BlackWins;
3545                     break;
3546                   case '1':
3547                     if (endtoken[1] == '/')
3548                       endtype = GameIsDrawn;
3549                     else
3550                       endtype = WhiteWins;
3551                     break;
3552                 }
3553                 GameEnds(endtype, why, GE_ICS);
3554 #if ZIPPY
3555                 if (appData.zippyPlay && first.initDone) {
3556                     ZippyGameEnd(endtype, why);
3557                     if (first.pr == NULL) {
3558                       /* Start the next process early so that we'll
3559                          be ready for the next challenge */
3560                       StartChessProgram(&first);
3561                     }
3562                     /* Send "new" early, in case this command takes
3563                        a long time to finish, so that we'll be ready
3564                        for the next challenge. */
3565                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3566                     Reset(TRUE, TRUE);
3567                 }
3568 #endif /*ZIPPY*/
3569                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3570                 continue;
3571             }
3572
3573             if (looking_at(buf, &i, "Removing game * from observation") ||
3574                 looking_at(buf, &i, "no longer observing game *") ||
3575                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3576                 if (gameMode == IcsObserving &&
3577                     atoi(star_match[0]) == ics_gamenum)
3578                   {
3579                       /* icsEngineAnalyze */
3580                       if (appData.icsEngineAnalyze) {
3581                             ExitAnalyzeMode();
3582                             ModeHighlight();
3583                       }
3584                       StopClocks();
3585                       gameMode = IcsIdle;
3586                       ics_gamenum = -1;
3587                       ics_user_moved = FALSE;
3588                   }
3589                 continue;
3590             }
3591
3592             if (looking_at(buf, &i, "no longer examining game *")) {
3593                 if (gameMode == IcsExamining &&
3594                     atoi(star_match[0]) == ics_gamenum)
3595                   {
3596                       gameMode = IcsIdle;
3597                       ics_gamenum = -1;
3598                       ics_user_moved = FALSE;
3599                   }
3600                 continue;
3601             }
3602
3603             /* Advance leftover_start past any newlines we find,
3604                so only partial lines can get reparsed */
3605             if (looking_at(buf, &i, "\n")) {
3606                 prevColor = curColor;
3607                 if (curColor != ColorNormal) {
3608                     if (oldi > next_out) {
3609                         SendToPlayer(&buf[next_out], oldi - next_out);
3610                         next_out = oldi;
3611                     }
3612                     Colorize(ColorNormal, FALSE);
3613                     curColor = ColorNormal;
3614                 }
3615                 if (started == STARTED_BOARD) {
3616                     started = STARTED_NONE;
3617                     parse[parse_pos] = NULLCHAR;
3618                     ParseBoard12(parse);
3619                     ics_user_moved = 0;
3620
3621                     /* Send premove here */
3622                     if (appData.premove) {
3623                       char str[MSG_SIZ];
3624                       if (currentMove == 0 &&
3625                           gameMode == IcsPlayingWhite &&
3626                           appData.premoveWhite) {
3627                         sprintf(str, "%s\n", appData.premoveWhiteText);
3628                         if (appData.debugMode)
3629                           fprintf(debugFP, "Sending premove:\n");
3630                         SendToICS(str);
3631                       } else if (currentMove == 1 &&
3632                                  gameMode == IcsPlayingBlack &&
3633                                  appData.premoveBlack) {
3634                         sprintf(str, "%s\n", appData.premoveBlackText);
3635                         if (appData.debugMode)
3636                           fprintf(debugFP, "Sending premove:\n");
3637                         SendToICS(str);
3638                       } else if (gotPremove) {
3639                         gotPremove = 0;
3640                         ClearPremoveHighlights();
3641                         if (appData.debugMode)
3642                           fprintf(debugFP, "Sending premove:\n");
3643                           UserMoveEvent(premoveFromX, premoveFromY,
3644                                         premoveToX, premoveToY,
3645                                         premovePromoChar);
3646                       }
3647                     }
3648
3649                     /* Usually suppress following prompt */
3650                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3651                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3652                         if (looking_at(buf, &i, "*% ")) {
3653                             savingComment = FALSE;
3654                             suppressKibitz = 0;
3655                         }
3656                     }
3657                     next_out = i;
3658                 } else if (started == STARTED_HOLDINGS) {
3659                     int gamenum;
3660                     char new_piece[MSG_SIZ];
3661                     started = STARTED_NONE;
3662                     parse[parse_pos] = NULLCHAR;
3663                     if (appData.debugMode)
3664                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3665                                                         parse, currentMove);
3666                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3667                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3668                         if (gameInfo.variant == VariantNormal) {
3669                           /* [HGM] We seem to switch variant during a game!
3670                            * Presumably no holdings were displayed, so we have
3671                            * to move the position two files to the right to
3672                            * create room for them!
3673                            */
3674                           VariantClass newVariant;
3675                           switch(gameInfo.boardWidth) { // base guess on board width
3676                                 case 9:  newVariant = VariantShogi; break;
3677                                 case 10: newVariant = VariantGreat; break;
3678                                 default: newVariant = VariantCrazyhouse; break;
3679                           }
3680                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3681                           /* Get a move list just to see the header, which
3682                              will tell us whether this is really bug or zh */
3683                           if (ics_getting_history == H_FALSE) {
3684                             ics_getting_history = H_REQUESTED;
3685                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3686                             SendToICS(str);
3687                           }
3688                         }
3689                         new_piece[0] = NULLCHAR;
3690                         sscanf(parse, "game %d white [%s black [%s <- %s",
3691                                &gamenum, white_holding, black_holding,
3692                                new_piece);
3693                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3694                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3695                         /* [HGM] copy holdings to board holdings area */
3696                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3697                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3698                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3699 #if ZIPPY
3700                         if (appData.zippyPlay && first.initDone) {
3701                             ZippyHoldings(white_holding, black_holding,
3702                                           new_piece);
3703                         }
3704 #endif /*ZIPPY*/
3705                         if (tinyLayout || smallLayout) {
3706                             char wh[16], bh[16];
3707                             PackHolding(wh, white_holding);
3708                             PackHolding(bh, black_holding);
3709                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3710                                     gameInfo.white, gameInfo.black);
3711                         } else {
3712                             sprintf(str, "%s [%s] vs. %s [%s]",
3713                                     gameInfo.white, white_holding,
3714                                     gameInfo.black, black_holding);
3715                         }
3716                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3717                         DrawPosition(FALSE, boards[currentMove]);
3718                         DisplayTitle(str);
3719                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3720                         sscanf(parse, "game %d white [%s black [%s <- %s",
3721                                &gamenum, white_holding, black_holding,
3722                                new_piece);
3723                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3724                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3725                         /* [HGM] copy holdings to partner-board holdings area */
3726                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3727                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3728                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3729                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3730                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3731                       }
3732                     }
3733                     /* Suppress following prompt */
3734                     if (looking_at(buf, &i, "*% ")) {
3735                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3736                         savingComment = FALSE;
3737                         suppressKibitz = 0;
3738                     }
3739                     next_out = i;
3740                 }
3741                 continue;
3742             }
3743
3744             i++;                /* skip unparsed character and loop back */
3745         }
3746
3747         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3748 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3749 //          SendToPlayer(&buf[next_out], i - next_out);
3750             started != STARTED_HOLDINGS && leftover_start > next_out) {
3751             SendToPlayer(&buf[next_out], leftover_start - next_out);
3752             next_out = i;
3753         }
3754
3755         leftover_len = buf_len - leftover_start;
3756         /* if buffer ends with something we couldn't parse,
3757            reparse it after appending the next read */
3758
3759     } else if (count == 0) {
3760         RemoveInputSource(isr);
3761         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3762     } else {
3763         DisplayFatalError(_("Error reading from ICS"), error, 1);
3764     }
3765 }
3766
3767
3768 /* Board style 12 looks like this:
3769
3770    <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
3771
3772  * The "<12> " is stripped before it gets to this routine.  The two
3773  * trailing 0's (flip state and clock ticking) are later addition, and
3774  * some chess servers may not have them, or may have only the first.
3775  * Additional trailing fields may be added in the future.
3776  */
3777
3778 #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"
3779
3780 #define RELATION_OBSERVING_PLAYED    0
3781 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3782 #define RELATION_PLAYING_MYMOVE      1
3783 #define RELATION_PLAYING_NOTMYMOVE  -1
3784 #define RELATION_EXAMINING           2
3785 #define RELATION_ISOLATED_BOARD     -3
3786 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3787
3788 void
3789 ParseBoard12(string)
3790      char *string;
3791 {
3792     GameMode newGameMode;
3793     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3794     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3795     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3796     char to_play, board_chars[200];
3797     char move_str[500], str[500], elapsed_time[500];
3798     char black[32], white[32];
3799     Board board;
3800     int prevMove = currentMove;
3801     int ticking = 2;
3802     ChessMove moveType;
3803     int fromX, fromY, toX, toY;
3804     char promoChar;
3805     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3806     char *bookHit = NULL; // [HGM] book
3807     Boolean weird = FALSE, reqFlag = FALSE;
3808
3809     fromX = fromY = toX = toY = -1;
3810
3811     newGame = FALSE;
3812
3813     if (appData.debugMode)
3814       fprintf(debugFP, _("Parsing board: %s\n"), string);
3815
3816     move_str[0] = NULLCHAR;
3817     elapsed_time[0] = NULLCHAR;
3818     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3819         int  i = 0, j;
3820         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3821             if(string[i] == ' ') { ranks++; files = 0; }
3822             else files++;
3823             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3824             i++;
3825         }
3826         for(j = 0; j <i; j++) board_chars[j] = string[j];
3827         board_chars[i] = '\0';
3828         string += i + 1;
3829     }
3830     n = sscanf(string, PATTERN, &to_play, &double_push,
3831                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3832                &gamenum, white, black, &relation, &basetime, &increment,
3833                &white_stren, &black_stren, &white_time, &black_time,
3834                &moveNum, str, elapsed_time, move_str, &ics_flip,
3835                &ticking);
3836
3837     if (n < 21) {
3838         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3839         DisplayError(str, 0);
3840         return;
3841     }
3842
3843     /* Convert the move number to internal form */
3844     moveNum = (moveNum - 1) * 2;
3845     if (to_play == 'B') moveNum++;
3846     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3847       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3848                         0, 1);
3849       return;
3850     }
3851
3852     switch (relation) {
3853       case RELATION_OBSERVING_PLAYED:
3854       case RELATION_OBSERVING_STATIC:
3855         if (gamenum == -1) {
3856             /* Old ICC buglet */
3857             relation = RELATION_OBSERVING_STATIC;
3858         }
3859         newGameMode = IcsObserving;
3860         break;
3861       case RELATION_PLAYING_MYMOVE:
3862       case RELATION_PLAYING_NOTMYMOVE:
3863         newGameMode =
3864           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3865             IcsPlayingWhite : IcsPlayingBlack;
3866         break;
3867       case RELATION_EXAMINING:
3868         newGameMode = IcsExamining;
3869         break;
3870       case RELATION_ISOLATED_BOARD:
3871       default:
3872         /* Just display this board.  If user was doing something else,
3873            we will forget about it until the next board comes. */
3874         newGameMode = IcsIdle;
3875         break;
3876       case RELATION_STARTING_POSITION:
3877         newGameMode = gameMode;
3878         break;
3879     }
3880
3881     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3882          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3883       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3884       char *toSqr;
3885       for (k = 0; k < ranks; k++) {
3886         for (j = 0; j < files; j++)
3887           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3888         if(gameInfo.holdingsWidth > 1) {
3889              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3890              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3891         }
3892       }
3893       CopyBoard(partnerBoard, board);
3894       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3895         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3896         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3897       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3898       if(toSqr = strchr(str, '-')) {
3899         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3900         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3901       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3902       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3903       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3904       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3905       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3906       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3907                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3908       DisplayMessage(partnerStatus, "");
3909         partnerBoardValid = TRUE;
3910       return;
3911     }
3912
3913     /* Modify behavior for initial board display on move listing
3914        of wild games.
3915        */
3916     switch (ics_getting_history) {
3917       case H_FALSE:
3918       case H_REQUESTED:
3919         break;
3920       case H_GOT_REQ_HEADER:
3921       case H_GOT_UNREQ_HEADER:
3922         /* This is the initial position of the current game */
3923         gamenum = ics_gamenum;
3924         moveNum = 0;            /* old ICS bug workaround */
3925         if (to_play == 'B') {
3926           startedFromSetupPosition = TRUE;
3927           blackPlaysFirst = TRUE;
3928           moveNum = 1;
3929           if (forwardMostMove == 0) forwardMostMove = 1;
3930           if (backwardMostMove == 0) backwardMostMove = 1;
3931           if (currentMove == 0) currentMove = 1;
3932         }
3933         newGameMode = gameMode;
3934         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3935         break;
3936       case H_GOT_UNWANTED_HEADER:
3937         /* This is an initial board that we don't want */
3938         return;
3939       case H_GETTING_MOVES:
3940         /* Should not happen */
3941         DisplayError(_("Error gathering move list: extra board"), 0);
3942         ics_getting_history = H_FALSE;
3943         return;
3944     }
3945
3946    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3947                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3948      /* [HGM] We seem to have switched variant unexpectedly
3949       * Try to guess new variant from board size
3950       */
3951           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3952           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3953           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3954           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3955           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3956           if(!weird) newVariant = VariantNormal;
3957           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3958           /* Get a move list just to see the header, which
3959              will tell us whether this is really bug or zh */
3960           if (ics_getting_history == H_FALSE) {
3961             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3962             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3963             SendToICS(str);
3964           }
3965     }
3966
3967     /* Take action if this is the first board of a new game, or of a
3968        different game than is currently being displayed.  */
3969     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3970         relation == RELATION_ISOLATED_BOARD) {
3971
3972         /* Forget the old game and get the history (if any) of the new one */
3973         if (gameMode != BeginningOfGame) {
3974           Reset(TRUE, TRUE);
3975         }
3976         newGame = TRUE;
3977         if (appData.autoRaiseBoard) BoardToTop();
3978         prevMove = -3;
3979         if (gamenum == -1) {
3980             newGameMode = IcsIdle;
3981         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3982                    appData.getMoveList && !reqFlag) {
3983             /* Need to get game history */
3984             ics_getting_history = H_REQUESTED;
3985             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3986             SendToICS(str);
3987         }
3988
3989         /* Initially flip the board to have black on the bottom if playing
3990            black or if the ICS flip flag is set, but let the user change
3991            it with the Flip View button. */
3992         flipView = appData.autoFlipView ?
3993           (newGameMode == IcsPlayingBlack) || ics_flip :
3994           appData.flipView;
3995
3996         /* Done with values from previous mode; copy in new ones */
3997         gameMode = newGameMode;
3998         ModeHighlight();
3999         ics_gamenum = gamenum;
4000         if (gamenum == gs_gamenum) {
4001             int klen = strlen(gs_kind);
4002             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4003             sprintf(str, "ICS %s", gs_kind);
4004             gameInfo.event = StrSave(str);
4005         } else {
4006             gameInfo.event = StrSave("ICS game");
4007         }
4008         gameInfo.site = StrSave(appData.icsHost);
4009         gameInfo.date = PGNDate();
4010         gameInfo.round = StrSave("-");
4011         gameInfo.white = StrSave(white);
4012         gameInfo.black = StrSave(black);
4013         timeControl = basetime * 60 * 1000;
4014         timeControl_2 = 0;
4015         timeIncrement = increment * 1000;
4016         movesPerSession = 0;
4017         gameInfo.timeControl = TimeControlTagValue();
4018         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4019   if (appData.debugMode) {
4020     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4021     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4022     setbuf(debugFP, NULL);
4023   }
4024
4025         gameInfo.outOfBook = NULL;
4026
4027         /* Do we have the ratings? */
4028         if (strcmp(player1Name, white) == 0 &&
4029             strcmp(player2Name, black) == 0) {
4030             if (appData.debugMode)
4031               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4032                       player1Rating, player2Rating);
4033             gameInfo.whiteRating = player1Rating;
4034             gameInfo.blackRating = player2Rating;
4035         } else if (strcmp(player2Name, white) == 0 &&
4036                    strcmp(player1Name, black) == 0) {
4037             if (appData.debugMode)
4038               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4039                       player2Rating, player1Rating);
4040             gameInfo.whiteRating = player2Rating;
4041             gameInfo.blackRating = player1Rating;
4042         }
4043         player1Name[0] = player2Name[0] = NULLCHAR;
4044
4045         /* Silence shouts if requested */
4046         if (appData.quietPlay &&
4047             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4048             SendToICS(ics_prefix);
4049             SendToICS("set shout 0\n");
4050         }
4051     }
4052
4053     /* Deal with midgame name changes */
4054     if (!newGame) {
4055         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4056             if (gameInfo.white) free(gameInfo.white);
4057             gameInfo.white = StrSave(white);
4058         }
4059         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4060             if (gameInfo.black) free(gameInfo.black);
4061             gameInfo.black = StrSave(black);
4062         }
4063     }
4064
4065     /* Throw away game result if anything actually changes in examine mode */
4066     if (gameMode == IcsExamining && !newGame) {
4067         gameInfo.result = GameUnfinished;
4068         if (gameInfo.resultDetails != NULL) {
4069             free(gameInfo.resultDetails);
4070             gameInfo.resultDetails = NULL;
4071         }
4072     }
4073
4074     /* In pausing && IcsExamining mode, we ignore boards coming
4075        in if they are in a different variation than we are. */
4076     if (pauseExamInvalid) return;
4077     if (pausing && gameMode == IcsExamining) {
4078         if (moveNum <= pauseExamForwardMostMove) {
4079             pauseExamInvalid = TRUE;
4080             forwardMostMove = pauseExamForwardMostMove;
4081             return;
4082         }
4083     }
4084
4085   if (appData.debugMode) {
4086     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4087   }
4088     /* Parse the board */
4089     for (k = 0; k < ranks; k++) {
4090       for (j = 0; j < files; j++)
4091         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4092       if(gameInfo.holdingsWidth > 1) {
4093            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4094            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4095       }
4096     }
4097     CopyBoard(boards[moveNum], board);
4098     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4099     if (moveNum == 0) {
4100         startedFromSetupPosition =
4101           !CompareBoards(board, initialPosition);
4102         if(startedFromSetupPosition)
4103             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4104     }
4105
4106     /* [HGM] Set castling rights. Take the outermost Rooks,
4107        to make it also work for FRC opening positions. Note that board12
4108        is really defective for later FRC positions, as it has no way to
4109        indicate which Rook can castle if they are on the same side of King.
4110        For the initial position we grant rights to the outermost Rooks,
4111        and remember thos rights, and we then copy them on positions
4112        later in an FRC game. This means WB might not recognize castlings with
4113        Rooks that have moved back to their original position as illegal,
4114        but in ICS mode that is not its job anyway.
4115     */
4116     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4117     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4118
4119         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4120             if(board[0][i] == WhiteRook) j = i;
4121         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4122         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4123             if(board[0][i] == WhiteRook) j = i;
4124         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4125         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4126             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4127         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4128         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4129             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4130         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4131
4132         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4133         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4134             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4135         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4136             if(board[BOARD_HEIGHT-1][k] == bKing)
4137                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4138         if(gameInfo.variant == VariantTwoKings) {
4139             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4140             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4141             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4142         }
4143     } else { int r;
4144         r = boards[moveNum][CASTLING][0] = initialRights[0];
4145         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4146         r = boards[moveNum][CASTLING][1] = initialRights[1];
4147         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4148         r = boards[moveNum][CASTLING][3] = initialRights[3];
4149         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4150         r = boards[moveNum][CASTLING][4] = initialRights[4];
4151         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4152         /* wildcastle kludge: always assume King has rights */
4153         r = boards[moveNum][CASTLING][2] = initialRights[2];
4154         r = boards[moveNum][CASTLING][5] = initialRights[5];
4155     }
4156     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4157     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4158
4159
4160     if (ics_getting_history == H_GOT_REQ_HEADER ||
4161         ics_getting_history == H_GOT_UNREQ_HEADER) {
4162         /* This was an initial position from a move list, not
4163            the current position */
4164         return;
4165     }
4166
4167     /* Update currentMove and known move number limits */
4168     newMove = newGame || moveNum > forwardMostMove;
4169
4170     if (newGame) {
4171         forwardMostMove = backwardMostMove = currentMove = moveNum;
4172         if (gameMode == IcsExamining && moveNum == 0) {
4173           /* Workaround for ICS limitation: we are not told the wild
4174              type when starting to examine a game.  But if we ask for
4175              the move list, the move list header will tell us */
4176             ics_getting_history = H_REQUESTED;
4177             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4178             SendToICS(str);
4179         }
4180     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4181                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4182 #if ZIPPY
4183         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4184         /* [HGM] applied this also to an engine that is silently watching        */
4185         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4186             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4187             gameInfo.variant == currentlyInitializedVariant) {
4188           takeback = forwardMostMove - moveNum;
4189           for (i = 0; i < takeback; i++) {
4190             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4191             SendToProgram("undo\n", &first);
4192           }
4193         }
4194 #endif
4195
4196         forwardMostMove = moveNum;
4197         if (!pausing || currentMove > forwardMostMove)
4198           currentMove = forwardMostMove;
4199     } else {
4200         /* New part of history that is not contiguous with old part */
4201         if (pausing && gameMode == IcsExamining) {
4202             pauseExamInvalid = TRUE;
4203             forwardMostMove = pauseExamForwardMostMove;
4204             return;
4205         }
4206         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4207 #if ZIPPY
4208             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4209                 // [HGM] when we will receive the move list we now request, it will be
4210                 // fed to the engine from the first move on. So if the engine is not
4211                 // in the initial position now, bring it there.
4212                 InitChessProgram(&first, 0);
4213             }
4214 #endif
4215             ics_getting_history = H_REQUESTED;
4216             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4217             SendToICS(str);
4218         }
4219         forwardMostMove = backwardMostMove = currentMove = moveNum;
4220     }
4221
4222     /* Update the clocks */
4223     if (strchr(elapsed_time, '.')) {
4224       /* Time is in ms */
4225       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4226       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4227     } else {
4228       /* Time is in seconds */
4229       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4230       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4231     }
4232
4233
4234 #if ZIPPY
4235     if (appData.zippyPlay && newGame &&
4236         gameMode != IcsObserving && gameMode != IcsIdle &&
4237         gameMode != IcsExamining)
4238       ZippyFirstBoard(moveNum, basetime, increment);
4239 #endif
4240
4241     /* Put the move on the move list, first converting
4242        to canonical algebraic form. */
4243     if (moveNum > 0) {
4244   if (appData.debugMode) {
4245     if (appData.debugMode) { int f = forwardMostMove;
4246         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4247                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4248                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4249     }
4250     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4251     fprintf(debugFP, "moveNum = %d\n", moveNum);
4252     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4253     setbuf(debugFP, NULL);
4254   }
4255         if (moveNum <= backwardMostMove) {
4256             /* We don't know what the board looked like before
4257                this move.  Punt. */
4258           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4259             strcat(parseList[moveNum - 1], " ");
4260             strcat(parseList[moveNum - 1], elapsed_time);
4261             moveList[moveNum - 1][0] = NULLCHAR;
4262         } else if (strcmp(move_str, "none") == 0) {
4263             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4264             /* Again, we don't know what the board looked like;
4265                this is really the start of the game. */
4266             parseList[moveNum - 1][0] = NULLCHAR;
4267             moveList[moveNum - 1][0] = NULLCHAR;
4268             backwardMostMove = moveNum;
4269             startedFromSetupPosition = TRUE;
4270             fromX = fromY = toX = toY = -1;
4271         } else {
4272           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4273           //                 So we parse the long-algebraic move string in stead of the SAN move
4274           int valid; char buf[MSG_SIZ], *prom;
4275
4276           // str looks something like "Q/a1-a2"; kill the slash
4277           if(str[1] == '/')
4278                 sprintf(buf, "%c%s", str[0], str+2);
4279           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4280           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4281                 strcat(buf, prom); // long move lacks promo specification!
4282           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4283                 if(appData.debugMode)
4284                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4285                 safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
4286           }
4287           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4288                                 &fromX, &fromY, &toX, &toY, &promoChar)
4289                || ParseOneMove(buf, moveNum - 1, &moveType,
4290                                 &fromX, &fromY, &toX, &toY, &promoChar);
4291           // end of long SAN patch
4292           if (valid) {
4293             (void) CoordsToAlgebraic(boards[moveNum - 1],
4294                                      PosFlags(moveNum - 1),
4295                                      fromY, fromX, toY, toX, promoChar,
4296                                      parseList[moveNum-1]);
4297             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4298               case MT_NONE:
4299               case MT_STALEMATE:
4300               default:
4301                 break;
4302               case MT_CHECK:
4303                 if(gameInfo.variant != VariantShogi)
4304                     strcat(parseList[moveNum - 1], "+");
4305                 break;
4306               case MT_CHECKMATE:
4307               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4308                 strcat(parseList[moveNum - 1], "#");
4309                 break;
4310             }
4311             strcat(parseList[moveNum - 1], " ");
4312             strcat(parseList[moveNum - 1], elapsed_time);
4313             /* currentMoveString is set as a side-effect of ParseOneMove */
4314             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4315             strcat(moveList[moveNum - 1], "\n");
4316           } else {
4317             /* Move from ICS was illegal!?  Punt. */
4318             if (appData.debugMode) {
4319               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4320               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4321             }
4322             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4323             strcat(parseList[moveNum - 1], " ");
4324             strcat(parseList[moveNum - 1], elapsed_time);
4325             moveList[moveNum - 1][0] = NULLCHAR;
4326             fromX = fromY = toX = toY = -1;
4327           }
4328         }
4329   if (appData.debugMode) {
4330     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4331     setbuf(debugFP, NULL);
4332   }
4333
4334 #if ZIPPY
4335         /* Send move to chess program (BEFORE animating it). */
4336         if (appData.zippyPlay && !newGame && newMove &&
4337            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4338
4339             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4340                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4341                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4342                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4343                             move_str);
4344                     DisplayError(str, 0);
4345                 } else {
4346                     if (first.sendTime) {
4347                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4348                     }
4349                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4350                     if (firstMove && !bookHit) {
4351                         firstMove = FALSE;
4352                         if (first.useColors) {
4353                           SendToProgram(gameMode == IcsPlayingWhite ?
4354                                         "white\ngo\n" :
4355                                         "black\ngo\n", &first);
4356                         } else {
4357                           SendToProgram("go\n", &first);
4358                         }
4359                         first.maybeThinking = TRUE;
4360                     }
4361                 }
4362             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4363               if (moveList[moveNum - 1][0] == NULLCHAR) {
4364                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4365                 DisplayError(str, 0);
4366               } else {
4367                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4368                 SendMoveToProgram(moveNum - 1, &first);
4369               }
4370             }
4371         }
4372 #endif
4373     }
4374
4375     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4376         /* If move comes from a remote source, animate it.  If it
4377            isn't remote, it will have already been animated. */
4378         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4379             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4380         }
4381         if (!pausing && appData.highlightLastMove) {
4382             SetHighlights(fromX, fromY, toX, toY);
4383         }
4384     }
4385
4386     /* Start the clocks */
4387     whiteFlag = blackFlag = FALSE;
4388     appData.clockMode = !(basetime == 0 && increment == 0);
4389     if (ticking == 0) {
4390       ics_clock_paused = TRUE;
4391       StopClocks();
4392     } else if (ticking == 1) {
4393       ics_clock_paused = FALSE;
4394     }
4395     if (gameMode == IcsIdle ||
4396         relation == RELATION_OBSERVING_STATIC ||
4397         relation == RELATION_EXAMINING ||
4398         ics_clock_paused)
4399       DisplayBothClocks();
4400     else
4401       StartClocks();
4402
4403     /* Display opponents and material strengths */
4404     if (gameInfo.variant != VariantBughouse &&
4405         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4406         if (tinyLayout || smallLayout) {
4407             if(gameInfo.variant == VariantNormal)
4408                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4409                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4410                     basetime, increment);
4411             else
4412                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4413                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4414                     basetime, increment, (int) gameInfo.variant);
4415         } else {
4416             if(gameInfo.variant == VariantNormal)
4417                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4418                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4419                     basetime, increment);
4420             else
4421                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4422                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4423                     basetime, increment, VariantName(gameInfo.variant));
4424         }
4425         DisplayTitle(str);
4426   if (appData.debugMode) {
4427     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4428   }
4429     }
4430
4431
4432     /* Display the board */
4433     if (!pausing && !appData.noGUI) {
4434
4435       if (appData.premove)
4436           if (!gotPremove ||
4437              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4438              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4439               ClearPremoveHighlights();
4440
4441       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4442         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4443       DrawPosition(j, boards[currentMove]);
4444
4445       DisplayMove(moveNum - 1);
4446       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4447             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4448               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4449         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4450       }
4451     }
4452
4453     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4454 #if ZIPPY
4455     if(bookHit) { // [HGM] book: simulate book reply
4456         static char bookMove[MSG_SIZ]; // a bit generous?
4457
4458         programStats.nodes = programStats.depth = programStats.time =
4459         programStats.score = programStats.got_only_move = 0;
4460         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4461
4462         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4463         strcat(bookMove, bookHit);
4464         HandleMachineMove(bookMove, &first);
4465     }
4466 #endif
4467 }
4468
4469 void
4470 GetMoveListEvent()
4471 {
4472     char buf[MSG_SIZ];
4473     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4474         ics_getting_history = H_REQUESTED;
4475         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4476         SendToICS(buf);
4477     }
4478 }
4479
4480 void
4481 AnalysisPeriodicEvent(force)
4482      int force;
4483 {
4484     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4485          && !force) || !appData.periodicUpdates)
4486       return;
4487
4488     /* Send . command to Crafty to collect stats */
4489     SendToProgram(".\n", &first);
4490
4491     /* Don't send another until we get a response (this makes
4492        us stop sending to old Crafty's which don't understand
4493        the "." command (sending illegal cmds resets node count & time,
4494        which looks bad)) */
4495     programStats.ok_to_send = 0;
4496 }
4497
4498 void ics_update_width(new_width)
4499         int new_width;
4500 {
4501         ics_printf("set width %d\n", new_width);
4502 }
4503
4504 void
4505 SendMoveToProgram(moveNum, cps)
4506      int moveNum;
4507      ChessProgramState *cps;
4508 {
4509     char buf[MSG_SIZ];
4510
4511     if (cps->useUsermove) {
4512       SendToProgram("usermove ", cps);
4513     }
4514     if (cps->useSAN) {
4515       char *space;
4516       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4517         int len = space - parseList[moveNum];
4518         memcpy(buf, parseList[moveNum], len);
4519         buf[len++] = '\n';
4520         buf[len] = NULLCHAR;
4521       } else {
4522         sprintf(buf, "%s\n", parseList[moveNum]);
4523       }
4524       SendToProgram(buf, cps);
4525     } else {
4526       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4527         AlphaRank(moveList[moveNum], 4);
4528         SendToProgram(moveList[moveNum], cps);
4529         AlphaRank(moveList[moveNum], 4); // and back
4530       } else
4531       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4532        * the engine. It would be nice to have a better way to identify castle
4533        * moves here. */
4534       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4535                                                                          && cps->useOOCastle) {
4536         int fromX = moveList[moveNum][0] - AAA;
4537         int fromY = moveList[moveNum][1] - ONE;
4538         int toX = moveList[moveNum][2] - AAA;
4539         int toY = moveList[moveNum][3] - ONE;
4540         if((boards[moveNum][fromY][fromX] == WhiteKing
4541             && boards[moveNum][toY][toX] == WhiteRook)
4542            || (boards[moveNum][fromY][fromX] == BlackKing
4543                && boards[moveNum][toY][toX] == BlackRook)) {
4544           if(toX > fromX) SendToProgram("O-O\n", cps);
4545           else SendToProgram("O-O-O\n", cps);
4546         }
4547         else SendToProgram(moveList[moveNum], cps);
4548       }
4549       else SendToProgram(moveList[moveNum], cps);
4550       /* End of additions by Tord */
4551     }
4552
4553     /* [HGM] setting up the opening has brought engine in force mode! */
4554     /*       Send 'go' if we are in a mode where machine should play. */
4555     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4556         (gameMode == TwoMachinesPlay   ||
4557 #if ZIPPY
4558          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4559 #endif
4560          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4561         SendToProgram("go\n", cps);
4562   if (appData.debugMode) {
4563     fprintf(debugFP, "(extra)\n");
4564   }
4565     }
4566     setboardSpoiledMachineBlack = 0;
4567 }
4568
4569 void
4570 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4571      ChessMove moveType;
4572      int fromX, fromY, toX, toY;
4573      char promoChar;
4574 {
4575     char user_move[MSG_SIZ];
4576
4577     switch (moveType) {
4578       default:
4579         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4580                 (int)moveType, fromX, fromY, toX, toY);
4581         DisplayError(user_move + strlen("say "), 0);
4582         break;
4583       case WhiteKingSideCastle:
4584       case BlackKingSideCastle:
4585       case WhiteQueenSideCastleWild:
4586       case BlackQueenSideCastleWild:
4587       /* PUSH Fabien */
4588       case WhiteHSideCastleFR:
4589       case BlackHSideCastleFR:
4590       /* POP Fabien */
4591         sprintf(user_move, "o-o\n");
4592         break;
4593       case WhiteQueenSideCastle:
4594       case BlackQueenSideCastle:
4595       case WhiteKingSideCastleWild:
4596       case BlackKingSideCastleWild:
4597       /* PUSH Fabien */
4598       case WhiteASideCastleFR:
4599       case BlackASideCastleFR:
4600       /* POP Fabien */
4601         sprintf(user_move, "o-o-o\n");
4602         break;
4603       case WhiteNonPromotion:
4604       case BlackNonPromotion:
4605         sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4606         break;
4607       case WhitePromotion:
4608       case BlackPromotion:
4609         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4610             sprintf(user_move, "%c%c%c%c=%c\n",
4611                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4612                 PieceToChar(WhiteFerz));
4613         else if(gameInfo.variant == VariantGreat)
4614             sprintf(user_move, "%c%c%c%c=%c\n",
4615                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4616                 PieceToChar(WhiteMan));
4617         else
4618             sprintf(user_move, "%c%c%c%c=%c\n",
4619                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4620                 promoChar);
4621         break;
4622       case WhiteDrop:
4623       case BlackDrop:
4624         sprintf(user_move, "%c@%c%c\n",
4625                 ToUpper(PieceToChar((ChessSquare) fromX)),
4626                 AAA + toX, ONE + toY);
4627         break;
4628       case NormalMove:
4629       case WhiteCapturesEnPassant:
4630       case BlackCapturesEnPassant:
4631       case IllegalMove:  /* could be a variant we don't quite understand */
4632         sprintf(user_move, "%c%c%c%c\n",
4633                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4634         break;
4635     }
4636     SendToICS(user_move);
4637     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4638         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4639 }
4640
4641 void
4642 UploadGameEvent()
4643 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4644     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4645     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4646     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4647         DisplayError("You cannot do this while you are playing or observing", 0);
4648         return;
4649     }
4650     if(gameMode != IcsExamining) { // is this ever not the case?
4651         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4652
4653         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4654             sprintf(command, "match %s", ics_handle);
4655         } else { // on FICS we must first go to general examine mode
4656           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4657         }
4658         if(gameInfo.variant != VariantNormal) {
4659             // try figure out wild number, as xboard names are not always valid on ICS
4660             for(i=1; i<=36; i++) {
4661                 sprintf(buf, "wild/%d", i);
4662                 if(StringToVariant(buf) == gameInfo.variant) break;
4663             }
4664             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4665             else if(i == 22) sprintf(buf, "%s fr\n", command);
4666             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4667         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4668         SendToICS(ics_prefix);
4669         SendToICS(buf);
4670         if(startedFromSetupPosition || backwardMostMove != 0) {
4671           fen = PositionToFEN(backwardMostMove, NULL);
4672           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4673             sprintf(buf, "loadfen %s\n", fen);
4674             SendToICS(buf);
4675           } else { // FICS: everything has to set by separate bsetup commands
4676             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4677             sprintf(buf, "bsetup fen %s\n", fen);
4678             SendToICS(buf);
4679             if(!WhiteOnMove(backwardMostMove)) {
4680                 SendToICS("bsetup tomove black\n");
4681             }
4682             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4683             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4684             SendToICS(buf);
4685             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4686             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4687             SendToICS(buf);
4688             i = boards[backwardMostMove][EP_STATUS];
4689             if(i >= 0) { // set e.p.
4690                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4691                 SendToICS(buf);
4692             }
4693             bsetup++;
4694           }
4695         }
4696       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4697             SendToICS("bsetup done\n"); // switch to normal examining.
4698     }
4699     for(i = backwardMostMove; i<last; i++) {
4700         char buf[20];
4701         sprintf(buf, "%s\n", parseList[i]);
4702         SendToICS(buf);
4703     }
4704     SendToICS(ics_prefix);
4705     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4706 }
4707
4708 void
4709 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4710      int rf, ff, rt, ft;
4711      char promoChar;
4712      char move[7];
4713 {
4714     if (rf == DROP_RANK) {
4715         sprintf(move, "%c@%c%c\n",
4716                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4717     } else {
4718         if (promoChar == 'x' || promoChar == NULLCHAR) {
4719             sprintf(move, "%c%c%c%c\n",
4720                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4721         } else {
4722             sprintf(move, "%c%c%c%c%c\n",
4723                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4724         }
4725     }
4726 }
4727
4728 void
4729 ProcessICSInitScript(f)
4730      FILE *f;
4731 {
4732     char buf[MSG_SIZ];
4733
4734     while (fgets(buf, MSG_SIZ, f)) {
4735         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4736     }
4737
4738     fclose(f);
4739 }
4740
4741
4742 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4743 void
4744 AlphaRank(char *move, int n)
4745 {
4746 //    char *p = move, c; int x, y;
4747
4748     if (appData.debugMode) {
4749         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4750     }
4751
4752     if(move[1]=='*' &&
4753        move[2]>='0' && move[2]<='9' &&
4754        move[3]>='a' && move[3]<='x'    ) {
4755         move[1] = '@';
4756         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4757         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4758     } else
4759     if(move[0]>='0' && move[0]<='9' &&
4760        move[1]>='a' && move[1]<='x' &&
4761        move[2]>='0' && move[2]<='9' &&
4762        move[3]>='a' && move[3]<='x'    ) {
4763         /* input move, Shogi -> normal */
4764         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4765         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4766         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4767         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4768     } else
4769     if(move[1]=='@' &&
4770        move[3]>='0' && move[3]<='9' &&
4771        move[2]>='a' && move[2]<='x'    ) {
4772         move[1] = '*';
4773         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4774         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4775     } else
4776     if(
4777        move[0]>='a' && move[0]<='x' &&
4778        move[3]>='0' && move[3]<='9' &&
4779        move[2]>='a' && move[2]<='x'    ) {
4780          /* output move, normal -> Shogi */
4781         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4782         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4783         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4784         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4785         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4786     }
4787     if (appData.debugMode) {
4788         fprintf(debugFP, "   out = '%s'\n", move);
4789     }
4790 }
4791
4792 char yy_textstr[8000];
4793
4794 /* Parser for moves from gnuchess, ICS, or user typein box */
4795 Boolean
4796 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4797      char *move;
4798      int moveNum;
4799      ChessMove *moveType;
4800      int *fromX, *fromY, *toX, *toY;
4801      char *promoChar;
4802 {
4803     if (appData.debugMode) {
4804         fprintf(debugFP, "move to parse: %s\n", move);
4805     }
4806     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4807
4808     switch (*moveType) {
4809       case WhitePromotion:
4810       case BlackPromotion:
4811       case WhiteNonPromotion:
4812       case BlackNonPromotion:
4813       case NormalMove:
4814       case WhiteCapturesEnPassant:
4815       case BlackCapturesEnPassant:
4816       case WhiteKingSideCastle:
4817       case WhiteQueenSideCastle:
4818       case BlackKingSideCastle:
4819       case BlackQueenSideCastle:
4820       case WhiteKingSideCastleWild:
4821       case WhiteQueenSideCastleWild:
4822       case BlackKingSideCastleWild:
4823       case BlackQueenSideCastleWild:
4824       /* Code added by Tord: */
4825       case WhiteHSideCastleFR:
4826       case WhiteASideCastleFR:
4827       case BlackHSideCastleFR:
4828       case BlackASideCastleFR:
4829       /* End of code added by Tord */
4830       case IllegalMove:         /* bug or odd chess variant */
4831         *fromX = currentMoveString[0] - AAA;
4832         *fromY = currentMoveString[1] - ONE;
4833         *toX = currentMoveString[2] - AAA;
4834         *toY = currentMoveString[3] - ONE;
4835         *promoChar = currentMoveString[4];
4836         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4837             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4838     if (appData.debugMode) {
4839         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4840     }
4841             *fromX = *fromY = *toX = *toY = 0;
4842             return FALSE;
4843         }
4844         if (appData.testLegality) {
4845           return (*moveType != IllegalMove);
4846         } else {
4847           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4848                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4849         }
4850
4851       case WhiteDrop:
4852       case BlackDrop:
4853         *fromX = *moveType == WhiteDrop ?
4854           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4855           (int) CharToPiece(ToLower(currentMoveString[0]));
4856         *fromY = DROP_RANK;
4857         *toX = currentMoveString[2] - AAA;
4858         *toY = currentMoveString[3] - ONE;
4859         *promoChar = NULLCHAR;
4860         return TRUE;
4861
4862       case AmbiguousMove:
4863       case ImpossibleMove:
4864       case (ChessMove) 0:       /* end of file */
4865       case ElapsedTime:
4866       case Comment:
4867       case PGNTag:
4868       case NAG:
4869       case WhiteWins:
4870       case BlackWins:
4871       case GameIsDrawn:
4872       default:
4873     if (appData.debugMode) {
4874         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4875     }
4876         /* bug? */
4877         *fromX = *fromY = *toX = *toY = 0;
4878         *promoChar = NULLCHAR;
4879         return FALSE;
4880     }
4881 }
4882
4883
4884 void
4885 ParsePV(char *pv, Boolean storeComments)
4886 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4887   int fromX, fromY, toX, toY; char promoChar;
4888   ChessMove moveType;
4889   Boolean valid;
4890   int nr = 0;
4891
4892   endPV = forwardMostMove;
4893   do {
4894     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4895     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4896     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4897 if(appData.debugMode){
4898 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
4899 }
4900     if(!valid && nr == 0 &&
4901        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4902         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4903         // Hande case where played move is different from leading PV move
4904         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4905         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4906         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4907         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4908           endPV += 2; // if position different, keep this
4909           moveList[endPV-1][0] = fromX + AAA;
4910           moveList[endPV-1][1] = fromY + ONE;
4911           moveList[endPV-1][2] = toX + AAA;
4912           moveList[endPV-1][3] = toY + ONE;
4913           parseList[endPV-1][0] = NULLCHAR;
4914           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4915         }
4916       }
4917     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4918     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4919     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4920     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4921         valid++; // allow comments in PV
4922         continue;
4923     }
4924     nr++;
4925     if(endPV+1 > framePtr) break; // no space, truncate
4926     if(!valid) break;
4927     endPV++;
4928     CopyBoard(boards[endPV], boards[endPV-1]);
4929     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4930     moveList[endPV-1][0] = fromX + AAA;
4931     moveList[endPV-1][1] = fromY + ONE;
4932     moveList[endPV-1][2] = toX + AAA;
4933     moveList[endPV-1][3] = toY + ONE;
4934     if(storeComments)
4935         CoordsToAlgebraic(boards[endPV - 1],
4936                              PosFlags(endPV - 1),
4937                              fromY, fromX, toY, toX, promoChar,
4938                              parseList[endPV - 1]);
4939     else
4940         parseList[endPV-1][0] = NULLCHAR;
4941   } while(valid);
4942   currentMove = endPV;
4943   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4944   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4945                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4946   DrawPosition(TRUE, boards[currentMove]);
4947 }
4948
4949 static int lastX, lastY;
4950
4951 Boolean
4952 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4953 {
4954         int startPV;
4955         char *p;
4956
4957         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4958         lastX = x; lastY = y;
4959         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4960         startPV = index;
4961         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4962         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4963         index = startPV;
4964         do{ while(buf[index] && buf[index] != '\n') index++;
4965         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4966         buf[index] = 0;
4967         ParsePV(buf+startPV, FALSE);
4968         *start = startPV; *end = index-1;
4969         return TRUE;
4970 }
4971
4972 Boolean
4973 LoadPV(int x, int y)
4974 { // called on right mouse click to load PV
4975   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4976   lastX = x; lastY = y;
4977   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4978   return TRUE;
4979 }
4980
4981 void
4982 UnLoadPV()
4983 {
4984   if(endPV < 0) return;
4985   endPV = -1;
4986   currentMove = forwardMostMove;
4987   ClearPremoveHighlights();
4988   DrawPosition(TRUE, boards[currentMove]);
4989 }
4990
4991 void
4992 MovePV(int x, int y, int h)
4993 { // step through PV based on mouse coordinates (called on mouse move)
4994   int margin = h>>3, step = 0;
4995
4996   if(endPV < 0) return;
4997   // we must somehow check if right button is still down (might be released off board!)
4998   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4999   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5000   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5001   if(!step) return;
5002   lastX = x; lastY = y;
5003   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5004   currentMove += step;
5005   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5006   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5007                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5008   DrawPosition(FALSE, boards[currentMove]);
5009 }
5010
5011
5012 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5013 // All positions will have equal probability, but the current method will not provide a unique
5014 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5015 #define DARK 1
5016 #define LITE 2
5017 #define ANY 3
5018
5019 int squaresLeft[4];
5020 int piecesLeft[(int)BlackPawn];
5021 int seed, nrOfShuffles;
5022
5023 void GetPositionNumber()
5024 {       // sets global variable seed
5025         int i;
5026
5027         seed = appData.defaultFrcPosition;
5028         if(seed < 0) { // randomize based on time for negative FRC position numbers
5029                 for(i=0; i<50; i++) seed += random();
5030                 seed = random() ^ random() >> 8 ^ random() << 8;
5031                 if(seed<0) seed = -seed;
5032         }
5033 }
5034
5035 int put(Board board, int pieceType, int rank, int n, int shade)
5036 // put the piece on the (n-1)-th empty squares of the given shade
5037 {
5038         int i;
5039
5040         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5041                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5042                         board[rank][i] = (ChessSquare) pieceType;
5043                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5044                         squaresLeft[ANY]--;
5045                         piecesLeft[pieceType]--;
5046                         return i;
5047                 }
5048         }
5049         return -1;
5050 }
5051
5052
5053 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5054 // calculate where the next piece goes, (any empty square), and put it there
5055 {
5056         int i;
5057
5058         i = seed % squaresLeft[shade];
5059         nrOfShuffles *= squaresLeft[shade];
5060         seed /= squaresLeft[shade];
5061         put(board, pieceType, rank, i, shade);
5062 }
5063
5064 void AddTwoPieces(Board board, int pieceType, int rank)
5065 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5066 {
5067         int i, n=squaresLeft[ANY], j=n-1, k;
5068
5069         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5070         i = seed % k;  // pick one
5071         nrOfShuffles *= k;
5072         seed /= k;
5073         while(i >= j) i -= j--;
5074         j = n - 1 - j; i += j;
5075         put(board, pieceType, rank, j, ANY);
5076         put(board, pieceType, rank, i, ANY);
5077 }
5078
5079 void SetUpShuffle(Board board, int number)
5080 {
5081         int i, p, first=1;
5082
5083         GetPositionNumber(); nrOfShuffles = 1;
5084
5085         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5086         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5087         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5088
5089         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5090
5091         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5092             p = (int) board[0][i];
5093             if(p < (int) BlackPawn) piecesLeft[p] ++;
5094             board[0][i] = EmptySquare;
5095         }
5096
5097         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5098             // shuffles restricted to allow normal castling put KRR first
5099             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5100                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5101             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5102                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5103             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5104                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5105             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5106                 put(board, WhiteRook, 0, 0, ANY);
5107             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5108         }
5109
5110         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5111             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5112             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5113                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5114                 while(piecesLeft[p] >= 2) {
5115                     AddOnePiece(board, p, 0, LITE);
5116                     AddOnePiece(board, p, 0, DARK);
5117                 }
5118                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5119             }
5120
5121         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5122             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5123             // but we leave King and Rooks for last, to possibly obey FRC restriction
5124             if(p == (int)WhiteRook) continue;
5125             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5126             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5127         }
5128
5129         // now everything is placed, except perhaps King (Unicorn) and Rooks
5130
5131         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5132             // Last King gets castling rights
5133             while(piecesLeft[(int)WhiteUnicorn]) {
5134                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5135                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5136             }
5137
5138             while(piecesLeft[(int)WhiteKing]) {
5139                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5140                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5141             }
5142
5143
5144         } else {
5145             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5146             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5147         }
5148
5149         // Only Rooks can be left; simply place them all
5150         while(piecesLeft[(int)WhiteRook]) {
5151                 i = put(board, WhiteRook, 0, 0, ANY);
5152                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5153                         if(first) {
5154                                 first=0;
5155                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5156                         }
5157                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5158                 }
5159         }
5160         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5161             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5162         }
5163
5164         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5165 }
5166
5167 int SetCharTable( char *table, const char * map )
5168 /* [HGM] moved here from winboard.c because of its general usefulness */
5169 /*       Basically a safe strcpy that uses the last character as King */
5170 {
5171     int result = FALSE; int NrPieces;
5172
5173     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5174                     && NrPieces >= 12 && !(NrPieces&1)) {
5175         int i; /* [HGM] Accept even length from 12 to 34 */
5176
5177         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5178         for( i=0; i<NrPieces/2-1; i++ ) {
5179             table[i] = map[i];
5180             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5181         }
5182         table[(int) WhiteKing]  = map[NrPieces/2-1];
5183         table[(int) BlackKing]  = map[NrPieces-1];
5184
5185         result = TRUE;
5186     }
5187
5188     return result;
5189 }
5190
5191 void Prelude(Board board)
5192 {       // [HGM] superchess: random selection of exo-pieces
5193         int i, j, k; ChessSquare p;
5194         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5195
5196         GetPositionNumber(); // use FRC position number
5197
5198         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5199             SetCharTable(pieceToChar, appData.pieceToCharTable);
5200             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5201                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5202         }
5203
5204         j = seed%4;                 seed /= 4;
5205         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5206         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5207         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5208         j = seed%3 + (seed%3 >= j); seed /= 3;
5209         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5210         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5211         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5212         j = seed%3;                 seed /= 3;
5213         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5214         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5215         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5216         j = seed%2 + (seed%2 >= j); seed /= 2;
5217         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5218         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5219         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5220         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5221         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5222         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5223         put(board, exoPieces[0],    0, 0, ANY);
5224         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5225 }
5226
5227 void
5228 InitPosition(redraw)
5229      int redraw;
5230 {
5231     ChessSquare (* pieces)[BOARD_FILES];
5232     int i, j, pawnRow, overrule,
5233     oldx = gameInfo.boardWidth,
5234     oldy = gameInfo.boardHeight,
5235     oldh = gameInfo.holdingsWidth,
5236     oldv = gameInfo.variant;
5237
5238     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5239
5240     /* [AS] Initialize pv info list [HGM] and game status */
5241     {
5242         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5243             pvInfoList[i].depth = 0;
5244             boards[i][EP_STATUS] = EP_NONE;
5245             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5246         }
5247
5248         initialRulePlies = 0; /* 50-move counter start */
5249
5250         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5251         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5252     }
5253
5254
5255     /* [HGM] logic here is completely changed. In stead of full positions */
5256     /* the initialized data only consist of the two backranks. The switch */
5257     /* selects which one we will use, which is than copied to the Board   */
5258     /* initialPosition, which for the rest is initialized by Pawns and    */
5259     /* empty squares. This initial position is then copied to boards[0],  */
5260     /* possibly after shuffling, so that it remains available.            */
5261
5262     gameInfo.holdingsWidth = 0; /* default board sizes */
5263     gameInfo.boardWidth    = 8;
5264     gameInfo.boardHeight   = 8;
5265     gameInfo.holdingsSize  = 0;
5266     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5267     for(i=0; i<BOARD_FILES-2; i++)
5268       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5269     initialPosition[EP_STATUS] = EP_NONE;
5270     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5271     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5272          SetCharTable(pieceNickName, appData.pieceNickNames);
5273     else SetCharTable(pieceNickName, "............");
5274
5275     switch (gameInfo.variant) {
5276     case VariantFischeRandom:
5277       shuffleOpenings = TRUE;
5278     default:
5279       pieces = FIDEArray;
5280       break;
5281     case VariantShatranj:
5282       pieces = ShatranjArray;
5283       nrCastlingRights = 0;
5284       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5285       break;
5286     case VariantMakruk:
5287       pieces = makrukArray;
5288       nrCastlingRights = 0;
5289       startedFromSetupPosition = TRUE;
5290       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5291       break;
5292     case VariantTwoKings:
5293       pieces = twoKingsArray;
5294       break;
5295     case VariantCapaRandom:
5296       shuffleOpenings = TRUE;
5297     case VariantCapablanca:
5298       pieces = CapablancaArray;
5299       gameInfo.boardWidth = 10;
5300       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5301       break;
5302     case VariantGothic:
5303       pieces = GothicArray;
5304       gameInfo.boardWidth = 10;
5305       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5306       break;
5307     case VariantJanus:
5308       pieces = JanusArray;
5309       gameInfo.boardWidth = 10;
5310       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5311       nrCastlingRights = 6;
5312         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5313         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5314         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5315         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5316         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5317         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5318       break;
5319     case VariantFalcon:
5320       pieces = FalconArray;
5321       gameInfo.boardWidth = 10;
5322       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5323       break;
5324     case VariantXiangqi:
5325       pieces = XiangqiArray;
5326       gameInfo.boardWidth  = 9;
5327       gameInfo.boardHeight = 10;
5328       nrCastlingRights = 0;
5329       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5330       break;
5331     case VariantShogi:
5332       pieces = ShogiArray;
5333       gameInfo.boardWidth  = 9;
5334       gameInfo.boardHeight = 9;
5335       gameInfo.holdingsSize = 7;
5336       nrCastlingRights = 0;
5337       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5338       break;
5339     case VariantCourier:
5340       pieces = CourierArray;
5341       gameInfo.boardWidth  = 12;
5342       nrCastlingRights = 0;
5343       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5344       break;
5345     case VariantKnightmate:
5346       pieces = KnightmateArray;
5347       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5348       break;
5349     case VariantFairy:
5350       pieces = fairyArray;
5351       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5352       break;
5353     case VariantGreat:
5354       pieces = GreatArray;
5355       gameInfo.boardWidth = 10;
5356       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5357       gameInfo.holdingsSize = 8;
5358       break;
5359     case VariantSuper:
5360       pieces = FIDEArray;
5361       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5362       gameInfo.holdingsSize = 8;
5363       startedFromSetupPosition = TRUE;
5364       break;
5365     case VariantCrazyhouse:
5366     case VariantBughouse:
5367       pieces = FIDEArray;
5368       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5369       gameInfo.holdingsSize = 5;
5370       break;
5371     case VariantWildCastle:
5372       pieces = FIDEArray;
5373       /* !!?shuffle with kings guaranteed to be on d or e file */
5374       shuffleOpenings = 1;
5375       break;
5376     case VariantNoCastle:
5377       pieces = FIDEArray;
5378       nrCastlingRights = 0;
5379       /* !!?unconstrained back-rank shuffle */
5380       shuffleOpenings = 1;
5381       break;
5382     }
5383
5384     overrule = 0;
5385     if(appData.NrFiles >= 0) {
5386         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5387         gameInfo.boardWidth = appData.NrFiles;
5388     }
5389     if(appData.NrRanks >= 0) {
5390         gameInfo.boardHeight = appData.NrRanks;
5391     }
5392     if(appData.holdingsSize >= 0) {
5393         i = appData.holdingsSize;
5394         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5395         gameInfo.holdingsSize = i;
5396     }
5397     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5398     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5399         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5400
5401     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5402     if(pawnRow < 1) pawnRow = 1;
5403     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5404
5405     /* User pieceToChar list overrules defaults */
5406     if(appData.pieceToCharTable != NULL)
5407         SetCharTable(pieceToChar, appData.pieceToCharTable);
5408
5409     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5410
5411         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5412             s = (ChessSquare) 0; /* account holding counts in guard band */
5413         for( i=0; i<BOARD_HEIGHT; i++ )
5414             initialPosition[i][j] = s;
5415
5416         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5417         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5418         initialPosition[pawnRow][j] = WhitePawn;
5419         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5420         if(gameInfo.variant == VariantXiangqi) {
5421             if(j&1) {
5422                 initialPosition[pawnRow][j] =
5423                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5424                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5425                    initialPosition[2][j] = WhiteCannon;
5426                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5427                 }
5428             }
5429         }
5430         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5431     }
5432     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5433
5434             j=BOARD_LEFT+1;
5435             initialPosition[1][j] = WhiteBishop;
5436             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5437             j=BOARD_RGHT-2;
5438             initialPosition[1][j] = WhiteRook;
5439             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5440     }
5441
5442     if( nrCastlingRights == -1) {
5443         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5444         /*       This sets default castling rights from none to normal corners   */
5445         /* Variants with other castling rights must set them themselves above    */
5446         nrCastlingRights = 6;
5447
5448         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5449         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5450         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5451         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5452         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5453         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5454      }
5455
5456      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5457      if(gameInfo.variant == VariantGreat) { // promotion commoners
5458         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5459         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5460         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5461         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5462      }
5463   if (appData.debugMode) {
5464     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5465   }
5466     if(shuffleOpenings) {
5467         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5468         startedFromSetupPosition = TRUE;
5469     }
5470     if(startedFromPositionFile) {
5471       /* [HGM] loadPos: use PositionFile for every new game */
5472       CopyBoard(initialPosition, filePosition);
5473       for(i=0; i<nrCastlingRights; i++)
5474           initialRights[i] = filePosition[CASTLING][i];
5475       startedFromSetupPosition = TRUE;
5476     }
5477
5478     CopyBoard(boards[0], initialPosition);
5479
5480     if(oldx != gameInfo.boardWidth ||
5481        oldy != gameInfo.boardHeight ||
5482        oldh != gameInfo.holdingsWidth
5483 #ifdef GOTHIC
5484        || oldv == VariantGothic ||        // For licensing popups
5485        gameInfo.variant == VariantGothic
5486 #endif
5487 #ifdef FALCON
5488        || oldv == VariantFalcon ||
5489        gameInfo.variant == VariantFalcon
5490 #endif
5491                                          )
5492             InitDrawingSizes(-2 ,0);
5493
5494     if (redraw)
5495       DrawPosition(TRUE, boards[currentMove]);
5496 }
5497
5498 void
5499 SendBoard(cps, moveNum)
5500      ChessProgramState *cps;
5501      int moveNum;
5502 {
5503     char message[MSG_SIZ];
5504
5505     if (cps->useSetboard) {
5506       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5507       sprintf(message, "setboard %s\n", fen);
5508       SendToProgram(message, cps);
5509       free(fen);
5510
5511     } else {
5512       ChessSquare *bp;
5513       int i, j;
5514       /* Kludge to set black to move, avoiding the troublesome and now
5515        * deprecated "black" command.
5516        */
5517       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5518
5519       SendToProgram("edit\n", cps);
5520       SendToProgram("#\n", cps);
5521       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5522         bp = &boards[moveNum][i][BOARD_LEFT];
5523         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5524           if ((int) *bp < (int) BlackPawn) {
5525             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5526                     AAA + j, ONE + i);
5527             if(message[0] == '+' || message[0] == '~') {
5528                 sprintf(message, "%c%c%c+\n",
5529                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5530                         AAA + j, ONE + i);
5531             }
5532             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5533                 message[1] = BOARD_RGHT   - 1 - j + '1';
5534                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5535             }
5536             SendToProgram(message, cps);
5537           }
5538         }
5539       }
5540
5541       SendToProgram("c\n", cps);
5542       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5543         bp = &boards[moveNum][i][BOARD_LEFT];
5544         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5545           if (((int) *bp != (int) EmptySquare)
5546               && ((int) *bp >= (int) BlackPawn)) {
5547             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5548                     AAA + j, ONE + i);
5549             if(message[0] == '+' || message[0] == '~') {
5550                 sprintf(message, "%c%c%c+\n",
5551                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5552                         AAA + j, ONE + i);
5553             }
5554             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5555                 message[1] = BOARD_RGHT   - 1 - j + '1';
5556                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5557             }
5558             SendToProgram(message, cps);
5559           }
5560         }
5561       }
5562
5563       SendToProgram(".\n", cps);
5564     }
5565     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5566 }
5567
5568 static int autoQueen; // [HGM] oneclick
5569
5570 int
5571 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5572 {
5573     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5574     /* [HGM] add Shogi promotions */
5575     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5576     ChessSquare piece;
5577     ChessMove moveType;
5578     Boolean premove;
5579
5580     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5581     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5582
5583     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5584       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5585         return FALSE;
5586
5587     piece = boards[currentMove][fromY][fromX];
5588     if(gameInfo.variant == VariantShogi) {
5589         promotionZoneSize = BOARD_HEIGHT/3;
5590         highestPromotingPiece = (int)WhiteFerz;
5591     } else if(gameInfo.variant == VariantMakruk) {
5592         promotionZoneSize = 3;
5593     }
5594
5595     // next weed out all moves that do not touch the promotion zone at all
5596     if((int)piece >= BlackPawn) {
5597         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5598              return FALSE;
5599         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5600     } else {
5601         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5602            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5603     }
5604
5605     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5606
5607     // weed out mandatory Shogi promotions
5608     if(gameInfo.variant == VariantShogi) {
5609         if(piece >= BlackPawn) {
5610             if(toY == 0 && piece == BlackPawn ||
5611                toY == 0 && piece == BlackQueen ||
5612                toY <= 1 && piece == BlackKnight) {
5613                 *promoChoice = '+';
5614                 return FALSE;
5615             }
5616         } else {
5617             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5618                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5619                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5620                 *promoChoice = '+';
5621                 return FALSE;
5622             }
5623         }
5624     }
5625
5626     // weed out obviously illegal Pawn moves
5627     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5628         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5629         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5630         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5631         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5632         // note we are not allowed to test for valid (non-)capture, due to premove
5633     }
5634
5635     // we either have a choice what to promote to, or (in Shogi) whether to promote
5636     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5637         *promoChoice = PieceToChar(BlackFerz);  // no choice
5638         return FALSE;
5639     }
5640     if(autoQueen) { // predetermined
5641         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5642              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5643         else *promoChoice = PieceToChar(BlackQueen);
5644         return FALSE;
5645     }
5646
5647     // suppress promotion popup on illegal moves that are not premoves
5648     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5649               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5650     if(appData.testLegality && !premove) {
5651         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5652                         fromY, fromX, toY, toX, NULLCHAR);
5653         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5654             return FALSE;
5655     }
5656
5657     return TRUE;
5658 }
5659
5660 int
5661 InPalace(row, column)
5662      int row, column;
5663 {   /* [HGM] for Xiangqi */
5664     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5665          column < (BOARD_WIDTH + 4)/2 &&
5666          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5667     return FALSE;
5668 }
5669
5670 int
5671 PieceForSquare (x, y)
5672      int x;
5673      int y;
5674 {
5675   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5676      return -1;
5677   else
5678      return boards[currentMove][y][x];
5679 }
5680
5681 int
5682 OKToStartUserMove(x, y)
5683      int x, y;
5684 {
5685     ChessSquare from_piece;
5686     int white_piece;
5687
5688     if (matchMode) return FALSE;
5689     if (gameMode == EditPosition) return TRUE;
5690
5691     if (x >= 0 && y >= 0)
5692       from_piece = boards[currentMove][y][x];
5693     else
5694       from_piece = EmptySquare;
5695
5696     if (from_piece == EmptySquare) return FALSE;
5697
5698     white_piece = (int)from_piece >= (int)WhitePawn &&
5699       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5700
5701     switch (gameMode) {
5702       case PlayFromGameFile:
5703       case AnalyzeFile:
5704       case TwoMachinesPlay:
5705       case EndOfGame:
5706         return FALSE;
5707
5708       case IcsObserving:
5709       case IcsIdle:
5710         return FALSE;
5711
5712       case MachinePlaysWhite:
5713       case IcsPlayingBlack:
5714         if (appData.zippyPlay) return FALSE;
5715         if (white_piece) {
5716             DisplayMoveError(_("You are playing Black"));
5717             return FALSE;
5718         }
5719         break;
5720
5721       case MachinePlaysBlack:
5722       case IcsPlayingWhite:
5723         if (appData.zippyPlay) return FALSE;
5724         if (!white_piece) {
5725             DisplayMoveError(_("You are playing White"));
5726             return FALSE;
5727         }
5728         break;
5729
5730       case EditGame:
5731         if (!white_piece && WhiteOnMove(currentMove)) {
5732             DisplayMoveError(_("It is White's turn"));
5733             return FALSE;
5734         }
5735         if (white_piece && !WhiteOnMove(currentMove)) {
5736             DisplayMoveError(_("It is Black's turn"));
5737             return FALSE;
5738         }
5739         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5740             /* Editing correspondence game history */
5741             /* Could disallow this or prompt for confirmation */
5742             cmailOldMove = -1;
5743         }
5744         break;
5745
5746       case BeginningOfGame:
5747         if (appData.icsActive) return FALSE;
5748         if (!appData.noChessProgram) {
5749             if (!white_piece) {
5750                 DisplayMoveError(_("You are playing White"));
5751                 return FALSE;
5752             }
5753         }
5754         break;
5755
5756       case Training:
5757         if (!white_piece && WhiteOnMove(currentMove)) {
5758             DisplayMoveError(_("It is White's turn"));
5759             return FALSE;
5760         }
5761         if (white_piece && !WhiteOnMove(currentMove)) {
5762             DisplayMoveError(_("It is Black's turn"));
5763             return FALSE;
5764         }
5765         break;
5766
5767       default:
5768       case IcsExamining:
5769         break;
5770     }
5771     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5772         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5773         && gameMode != AnalyzeFile && gameMode != Training) {
5774         DisplayMoveError(_("Displayed position is not current"));
5775         return FALSE;
5776     }
5777     return TRUE;
5778 }
5779
5780 Boolean
5781 OnlyMove(int *x, int *y, Boolean captures) {
5782     DisambiguateClosure cl;
5783     if (appData.zippyPlay) return FALSE;
5784     switch(gameMode) {
5785       case MachinePlaysBlack:
5786       case IcsPlayingWhite:
5787       case BeginningOfGame:
5788         if(!WhiteOnMove(currentMove)) return FALSE;
5789         break;
5790       case MachinePlaysWhite:
5791       case IcsPlayingBlack:
5792         if(WhiteOnMove(currentMove)) return FALSE;
5793         break;
5794       default:
5795         return FALSE;
5796     }
5797     cl.pieceIn = EmptySquare;
5798     cl.rfIn = *y;
5799     cl.ffIn = *x;
5800     cl.rtIn = -1;
5801     cl.ftIn = -1;
5802     cl.promoCharIn = NULLCHAR;
5803     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5804     if( cl.kind == NormalMove ||
5805         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5806         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5807         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5808       fromX = cl.ff;
5809       fromY = cl.rf;
5810       *x = cl.ft;
5811       *y = cl.rt;
5812       return TRUE;
5813     }
5814     if(cl.kind != ImpossibleMove) return FALSE;
5815     cl.pieceIn = EmptySquare;
5816     cl.rfIn = -1;
5817     cl.ffIn = -1;
5818     cl.rtIn = *y;
5819     cl.ftIn = *x;
5820     cl.promoCharIn = NULLCHAR;
5821     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5822     if( cl.kind == NormalMove ||
5823         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5824         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5825         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5826       fromX = cl.ff;
5827       fromY = cl.rf;
5828       *x = cl.ft;
5829       *y = cl.rt;
5830       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5831       return TRUE;
5832     }
5833     return FALSE;
5834 }
5835
5836 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5837 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5838 int lastLoadGameUseList = FALSE;
5839 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5840 ChessMove lastLoadGameStart = (ChessMove) 0;
5841
5842 void
5843 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5844      int fromX, fromY, toX, toY;
5845      int promoChar;
5846 {
5847     ChessMove moveType;
5848     ChessSquare pdown, pup;
5849
5850     /* Check if the user is playing in turn.  This is complicated because we
5851        let the user "pick up" a piece before it is his turn.  So the piece he
5852        tried to pick up may have been captured by the time he puts it down!
5853        Therefore we use the color the user is supposed to be playing in this
5854        test, not the color of the piece that is currently on the starting
5855        square---except in EditGame mode, where the user is playing both
5856        sides; fortunately there the capture race can't happen.  (It can
5857        now happen in IcsExamining mode, but that's just too bad.  The user
5858        will get a somewhat confusing message in that case.)
5859        */
5860
5861     switch (gameMode) {
5862       case PlayFromGameFile:
5863       case AnalyzeFile:
5864       case TwoMachinesPlay:
5865       case EndOfGame:
5866       case IcsObserving:
5867       case IcsIdle:
5868         /* We switched into a game mode where moves are not accepted,
5869            perhaps while the mouse button was down. */
5870         return;
5871
5872       case MachinePlaysWhite:
5873         /* User is moving for Black */
5874         if (WhiteOnMove(currentMove)) {
5875             DisplayMoveError(_("It is White's turn"));
5876             return;
5877         }
5878         break;
5879
5880       case MachinePlaysBlack:
5881         /* User is moving for White */
5882         if (!WhiteOnMove(currentMove)) {
5883             DisplayMoveError(_("It is Black's turn"));
5884             return;
5885         }
5886         break;
5887
5888       case EditGame:
5889       case IcsExamining:
5890       case BeginningOfGame:
5891       case AnalyzeMode:
5892       case Training:
5893         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5894             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5895             /* User is moving for Black */
5896             if (WhiteOnMove(currentMove)) {
5897                 DisplayMoveError(_("It is White's turn"));
5898                 return;
5899             }
5900         } else {
5901             /* User is moving for White */
5902             if (!WhiteOnMove(currentMove)) {
5903                 DisplayMoveError(_("It is Black's turn"));
5904                 return;
5905             }
5906         }
5907         break;
5908
5909       case IcsPlayingBlack:
5910         /* User is moving for Black */
5911         if (WhiteOnMove(currentMove)) {
5912             if (!appData.premove) {
5913                 DisplayMoveError(_("It is White's turn"));
5914             } else if (toX >= 0 && toY >= 0) {
5915                 premoveToX = toX;
5916                 premoveToY = toY;
5917                 premoveFromX = fromX;
5918                 premoveFromY = fromY;
5919                 premovePromoChar = promoChar;
5920                 gotPremove = 1;
5921                 if (appData.debugMode)
5922                     fprintf(debugFP, "Got premove: fromX %d,"
5923                             "fromY %d, toX %d, toY %d\n",
5924                             fromX, fromY, toX, toY);
5925             }
5926             return;
5927         }
5928         break;
5929
5930       case IcsPlayingWhite:
5931         /* User is moving for White */
5932         if (!WhiteOnMove(currentMove)) {
5933             if (!appData.premove) {
5934                 DisplayMoveError(_("It is Black's turn"));
5935             } else if (toX >= 0 && toY >= 0) {
5936                 premoveToX = toX;
5937                 premoveToY = toY;
5938                 premoveFromX = fromX;
5939                 premoveFromY = fromY;
5940                 premovePromoChar = promoChar;
5941                 gotPremove = 1;
5942                 if (appData.debugMode)
5943                     fprintf(debugFP, "Got premove: fromX %d,"
5944                             "fromY %d, toX %d, toY %d\n",
5945                             fromX, fromY, toX, toY);
5946             }
5947             return;
5948         }
5949         break;
5950
5951       default:
5952         break;
5953
5954       case EditPosition:
5955         /* EditPosition, empty square, or different color piece;
5956            click-click move is possible */
5957         if (toX == -2 || toY == -2) {
5958             boards[0][fromY][fromX] = EmptySquare;
5959             DrawPosition(FALSE, boards[currentMove]);
5960             return;
5961         } else if (toX >= 0 && toY >= 0) {
5962             boards[0][toY][toX] = boards[0][fromY][fromX];
5963             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5964                 if(boards[0][fromY][0] != EmptySquare) {
5965                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5966                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
5967                 }
5968             } else
5969             if(fromX == BOARD_RGHT+1) {
5970                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5971                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5972                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5973                 }
5974             } else
5975             boards[0][fromY][fromX] = EmptySquare;
5976             DrawPosition(FALSE, boards[currentMove]);
5977             return;
5978         }
5979         return;
5980     }
5981
5982     if(toX < 0 || toY < 0) return;
5983     pdown = boards[currentMove][fromY][fromX];
5984     pup = boards[currentMove][toY][toX];
5985
5986     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
5987     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5988          if( pup != EmptySquare ) return;
5989          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5990            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5991                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5992            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5993            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5994            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5995            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5996          fromY = DROP_RANK;
5997     }
5998
5999     /* [HGM] always test for legality, to get promotion info */
6000     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6001                                          fromY, fromX, toY, toX, promoChar);
6002     /* [HGM] but possibly ignore an IllegalMove result */
6003     if (appData.testLegality) {
6004         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6005             DisplayMoveError(_("Illegal move"));
6006             return;
6007         }
6008     }
6009
6010     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6011 }
6012
6013 /* Common tail of UserMoveEvent and DropMenuEvent */
6014 int
6015 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6016      ChessMove moveType;
6017      int fromX, fromY, toX, toY;
6018      /*char*/int promoChar;
6019 {
6020     char *bookHit = 0;
6021
6022     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6023         // [HGM] superchess: suppress promotions to non-available piece
6024         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6025         if(WhiteOnMove(currentMove)) {
6026             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6027         } else {
6028             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6029         }
6030     }
6031
6032     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6033        move type in caller when we know the move is a legal promotion */
6034     if(moveType == NormalMove && promoChar)
6035         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6036
6037     /* [HGM] <popupFix> The following if has been moved here from
6038        UserMoveEvent(). Because it seemed to belong here (why not allow
6039        piece drops in training games?), and because it can only be
6040        performed after it is known to what we promote. */
6041     if (gameMode == Training) {
6042       /* compare the move played on the board to the next move in the
6043        * game. If they match, display the move and the opponent's response.
6044        * If they don't match, display an error message.
6045        */
6046       int saveAnimate;
6047       Board testBoard;
6048       CopyBoard(testBoard, boards[currentMove]);
6049       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6050
6051       if (CompareBoards(testBoard, boards[currentMove+1])) {
6052         ForwardInner(currentMove+1);
6053
6054         /* Autoplay the opponent's response.
6055          * if appData.animate was TRUE when Training mode was entered,
6056          * the response will be animated.
6057          */
6058         saveAnimate = appData.animate;
6059         appData.animate = animateTraining;
6060         ForwardInner(currentMove+1);
6061         appData.animate = saveAnimate;
6062
6063         /* check for the end of the game */
6064         if (currentMove >= forwardMostMove) {
6065           gameMode = PlayFromGameFile;
6066           ModeHighlight();
6067           SetTrainingModeOff();
6068           DisplayInformation(_("End of game"));
6069         }
6070       } else {
6071         DisplayError(_("Incorrect move"), 0);
6072       }
6073       return 1;
6074     }
6075
6076   /* Ok, now we know that the move is good, so we can kill
6077      the previous line in Analysis Mode */
6078   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6079                                 && currentMove < forwardMostMove) {
6080     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6081   }
6082
6083   /* If we need the chess program but it's dead, restart it */
6084   ResurrectChessProgram();
6085
6086   /* A user move restarts a paused game*/
6087   if (pausing)
6088     PauseEvent();
6089
6090   thinkOutput[0] = NULLCHAR;
6091
6092   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6093
6094   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6095
6096   if (gameMode == BeginningOfGame) {
6097     if (appData.noChessProgram) {
6098       gameMode = EditGame;
6099       SetGameInfo();
6100     } else {
6101       char buf[MSG_SIZ];
6102       gameMode = MachinePlaysBlack;
6103       StartClocks();
6104       SetGameInfo();
6105       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6106       DisplayTitle(buf);
6107       if (first.sendName) {
6108         sprintf(buf, "name %s\n", gameInfo.white);
6109         SendToProgram(buf, &first);
6110       }
6111       StartClocks();
6112     }
6113     ModeHighlight();
6114   }
6115
6116   /* Relay move to ICS or chess engine */
6117   if (appData.icsActive) {
6118     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6119         gameMode == IcsExamining) {
6120       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6121         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6122         SendToICS("draw ");
6123         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6124       }
6125       // also send plain move, in case ICS does not understand atomic claims
6126       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6127       ics_user_moved = 1;
6128     }
6129   } else {
6130     if (first.sendTime && (gameMode == BeginningOfGame ||
6131                            gameMode == MachinePlaysWhite ||
6132                            gameMode == MachinePlaysBlack)) {
6133       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6134     }
6135     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6136          // [HGM] book: if program might be playing, let it use book
6137         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6138         first.maybeThinking = TRUE;
6139     } else SendMoveToProgram(forwardMostMove-1, &first);
6140     if (currentMove == cmailOldMove + 1) {
6141       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6142     }
6143   }
6144
6145   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6146
6147   switch (gameMode) {
6148   case EditGame:
6149     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6150     case MT_NONE:
6151     case MT_CHECK:
6152       break;
6153     case MT_CHECKMATE:
6154     case MT_STAINMATE:
6155       if (WhiteOnMove(currentMove)) {
6156         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6157       } else {
6158         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6159       }
6160       break;
6161     case MT_STALEMATE:
6162       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6163       break;
6164     }
6165     break;
6166
6167   case MachinePlaysBlack:
6168   case MachinePlaysWhite:
6169     /* disable certain menu options while machine is thinking */
6170     SetMachineThinkingEnables();
6171     break;
6172
6173   default:
6174     break;
6175   }
6176
6177   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6178
6179   if(bookHit) { // [HGM] book: simulate book reply
6180         static char bookMove[MSG_SIZ]; // a bit generous?
6181
6182         programStats.nodes = programStats.depth = programStats.time =
6183         programStats.score = programStats.got_only_move = 0;
6184         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6185
6186         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6187         strcat(bookMove, bookHit);
6188         HandleMachineMove(bookMove, &first);
6189   }
6190   return 1;
6191 }
6192
6193 void
6194 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6195      Board board;
6196      int flags;
6197      ChessMove kind;
6198      int rf, ff, rt, ft;
6199      VOIDSTAR closure;
6200 {
6201     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6202     Markers *m = (Markers *) closure;
6203     if(rf == fromY && ff == fromX)
6204         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6205                          || kind == WhiteCapturesEnPassant
6206                          || kind == BlackCapturesEnPassant);
6207     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6208 }
6209
6210 void
6211 MarkTargetSquares(int clear)
6212 {
6213   int x, y;
6214   if(!appData.markers || !appData.highlightDragging ||
6215      !appData.testLegality || gameMode == EditPosition) return;
6216   if(clear) {
6217     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6218   } else {
6219     int capt = 0;
6220     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6221     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6222       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6223       if(capt)
6224       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6225     }
6226   }
6227   DrawPosition(TRUE, NULL);
6228 }
6229
6230 void LeftClick(ClickType clickType, int xPix, int yPix)
6231 {
6232     int x, y;
6233     Boolean saveAnimate;
6234     static int second = 0, promotionChoice = 0, dragging = 0;
6235     char promoChoice = NULLCHAR;
6236
6237     if(appData.seekGraph && appData.icsActive && loggedOn &&
6238         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6239         SeekGraphClick(clickType, xPix, yPix, 0);
6240         return;
6241     }
6242
6243     if (clickType == Press) ErrorPopDown();
6244     MarkTargetSquares(1);
6245
6246     x = EventToSquare(xPix, BOARD_WIDTH);
6247     y = EventToSquare(yPix, BOARD_HEIGHT);
6248     if (!flipView && y >= 0) {
6249         y = BOARD_HEIGHT - 1 - y;
6250     }
6251     if (flipView && x >= 0) {
6252         x = BOARD_WIDTH - 1 - x;
6253     }
6254
6255     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6256         if(clickType == Release) return; // ignore upclick of click-click destination
6257         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6258         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6259         if(gameInfo.holdingsWidth &&
6260                 (WhiteOnMove(currentMove)
6261                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6262                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6263             // click in right holdings, for determining promotion piece
6264             ChessSquare p = boards[currentMove][y][x];
6265             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6266             if(p != EmptySquare) {
6267                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6268                 fromX = fromY = -1;
6269                 return;
6270             }
6271         }
6272         DrawPosition(FALSE, boards[currentMove]);
6273         return;
6274     }
6275
6276     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6277     if(clickType == Press
6278             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6279               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6280               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6281         return;
6282
6283     autoQueen = appData.alwaysPromoteToQueen;
6284
6285     if (fromX == -1) {
6286       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6287         if (clickType == Press) {
6288             /* First square */
6289             if (OKToStartUserMove(x, y)) {
6290                 fromX = x;
6291                 fromY = y;
6292                 second = 0;
6293                 MarkTargetSquares(0);
6294                 DragPieceBegin(xPix, yPix); dragging = 1;
6295                 if (appData.highlightDragging) {
6296                     SetHighlights(x, y, -1, -1);
6297                 }
6298             }
6299         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6300             DragPieceEnd(xPix, yPix); dragging = 0;
6301             DrawPosition(FALSE, NULL);
6302         }
6303         return;
6304       }
6305     }
6306
6307     /* fromX != -1 */
6308     if (clickType == Press && gameMode != EditPosition) {
6309         ChessSquare fromP;
6310         ChessSquare toP;
6311         int frc;
6312
6313         // ignore off-board to clicks
6314         if(y < 0 || x < 0) return;
6315
6316         /* Check if clicking again on the same color piece */
6317         fromP = boards[currentMove][fromY][fromX];
6318         toP = boards[currentMove][y][x];
6319         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6320         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6321              WhitePawn <= toP && toP <= WhiteKing &&
6322              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6323              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6324             (BlackPawn <= fromP && fromP <= BlackKing &&
6325              BlackPawn <= toP && toP <= BlackKing &&
6326              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6327              !(fromP == BlackKing && toP == BlackRook && frc))) {
6328             /* Clicked again on same color piece -- changed his mind */
6329             second = (x == fromX && y == fromY);
6330            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6331             if (appData.highlightDragging) {
6332                 SetHighlights(x, y, -1, -1);
6333             } else {
6334                 ClearHighlights();
6335             }
6336             if (OKToStartUserMove(x, y)) {
6337                 fromX = x;
6338                 fromY = y; dragging = 1;
6339                 MarkTargetSquares(0);
6340                 DragPieceBegin(xPix, yPix);
6341             }
6342             return;
6343            }
6344         }
6345         // ignore clicks on holdings
6346         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6347     }
6348
6349     if (clickType == Release && x == fromX && y == fromY) {
6350         DragPieceEnd(xPix, yPix); dragging = 0;
6351         if (appData.animateDragging) {
6352             /* Undo animation damage if any */
6353             DrawPosition(FALSE, NULL);
6354         }
6355         if (second) {
6356             /* Second up/down in same square; just abort move */
6357             second = 0;
6358             fromX = fromY = -1;
6359             ClearHighlights();
6360             gotPremove = 0;
6361             ClearPremoveHighlights();
6362         } else {
6363             /* First upclick in same square; start click-click mode */
6364             SetHighlights(x, y, -1, -1);
6365         }
6366         return;
6367     }
6368
6369     /* we now have a different from- and (possibly off-board) to-square */
6370     /* Completed move */
6371     toX = x;
6372     toY = y;
6373     saveAnimate = appData.animate;
6374     if (clickType == Press) {
6375         /* Finish clickclick move */
6376         if (appData.animate || appData.highlightLastMove) {
6377             SetHighlights(fromX, fromY, toX, toY);
6378         } else {
6379             ClearHighlights();
6380         }
6381     } else {
6382         /* Finish drag move */
6383         if (appData.highlightLastMove) {
6384             SetHighlights(fromX, fromY, toX, toY);
6385         } else {
6386             ClearHighlights();
6387         }
6388         DragPieceEnd(xPix, yPix); dragging = 0;
6389         /* Don't animate move and drag both */
6390         appData.animate = FALSE;
6391     }
6392
6393     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6394     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6395         ChessSquare piece = boards[currentMove][fromY][fromX];
6396         if(gameMode == EditPosition && piece != EmptySquare &&
6397            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6398             int n;
6399
6400             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6401                 n = PieceToNumber(piece - (int)BlackPawn);
6402                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6403                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6404                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6405             } else
6406             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6407                 n = PieceToNumber(piece);
6408                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6409                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6410                 boards[currentMove][n][BOARD_WIDTH-2]++;
6411             }
6412             boards[currentMove][fromY][fromX] = EmptySquare;
6413         }
6414         ClearHighlights();
6415         fromX = fromY = -1;
6416         DrawPosition(TRUE, boards[currentMove]);
6417         return;
6418     }
6419
6420     // off-board moves should not be highlighted
6421     if(x < 0 || x < 0) ClearHighlights();
6422
6423     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6424         SetHighlights(fromX, fromY, toX, toY);
6425         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6426             // [HGM] super: promotion to captured piece selected from holdings
6427             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6428             promotionChoice = TRUE;
6429             // kludge follows to temporarily execute move on display, without promoting yet
6430             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6431             boards[currentMove][toY][toX] = p;
6432             DrawPosition(FALSE, boards[currentMove]);
6433             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6434             boards[currentMove][toY][toX] = q;
6435             DisplayMessage("Click in holdings to choose piece", "");
6436             return;
6437         }
6438         PromotionPopUp();
6439     } else {
6440         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6441         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6442         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6443         fromX = fromY = -1;
6444     }
6445     appData.animate = saveAnimate;
6446     if (appData.animate || appData.animateDragging) {
6447         /* Undo animation damage if needed */
6448         DrawPosition(FALSE, NULL);
6449     }
6450 }
6451
6452 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6453 {   // front-end-free part taken out of PieceMenuPopup
6454     int whichMenu; int xSqr, ySqr;
6455
6456     if(seekGraphUp) { // [HGM] seekgraph
6457         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6458         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6459         return -2;
6460     }
6461
6462     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6463          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6464         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6465         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6466         if(action == Press)   {
6467             originalFlip = flipView;
6468             flipView = !flipView; // temporarily flip board to see game from partners perspective
6469             DrawPosition(TRUE, partnerBoard);
6470             DisplayMessage(partnerStatus, "");
6471             partnerUp = TRUE;
6472         } else if(action == Release) {
6473             flipView = originalFlip;
6474             DrawPosition(TRUE, boards[currentMove]);
6475             partnerUp = FALSE;
6476         }
6477         return -2;
6478     }
6479
6480     xSqr = EventToSquare(x, BOARD_WIDTH);
6481     ySqr = EventToSquare(y, BOARD_HEIGHT);
6482     if (action == Release) UnLoadPV(); // [HGM] pv
6483     if (action != Press) return -2; // return code to be ignored
6484     switch (gameMode) {
6485       case IcsExamining:
6486         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6487       case EditPosition:
6488         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6489         if (xSqr < 0 || ySqr < 0) return -1;\r
6490         whichMenu = 0; // edit-position menu
6491         break;
6492       case IcsObserving:
6493         if(!appData.icsEngineAnalyze) return -1;
6494       case IcsPlayingWhite:
6495       case IcsPlayingBlack:
6496         if(!appData.zippyPlay) goto noZip;
6497       case AnalyzeMode:
6498       case AnalyzeFile:
6499       case MachinePlaysWhite:
6500       case MachinePlaysBlack:
6501       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6502         if (!appData.dropMenu) {
6503           LoadPV(x, y);
6504           return 2; // flag front-end to grab mouse events
6505         }
6506         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6507            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6508       case EditGame:
6509       noZip:
6510         if (xSqr < 0 || ySqr < 0) return -1;
6511         if (!appData.dropMenu || appData.testLegality &&
6512             gameInfo.variant != VariantBughouse &&
6513             gameInfo.variant != VariantCrazyhouse) return -1;
6514         whichMenu = 1; // drop menu
6515         break;
6516       default:
6517         return -1;
6518     }
6519
6520     if (((*fromX = xSqr) < 0) ||
6521         ((*fromY = ySqr) < 0)) {
6522         *fromX = *fromY = -1;
6523         return -1;
6524     }
6525     if (flipView)
6526       *fromX = BOARD_WIDTH - 1 - *fromX;
6527     else
6528       *fromY = BOARD_HEIGHT - 1 - *fromY;
6529
6530     return whichMenu;
6531 }
6532
6533 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6534 {
6535 //    char * hint = lastHint;
6536     FrontEndProgramStats stats;
6537
6538     stats.which = cps == &first ? 0 : 1;
6539     stats.depth = cpstats->depth;
6540     stats.nodes = cpstats->nodes;
6541     stats.score = cpstats->score;
6542     stats.time = cpstats->time;
6543     stats.pv = cpstats->movelist;
6544     stats.hint = lastHint;
6545     stats.an_move_index = 0;
6546     stats.an_move_count = 0;
6547
6548     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6549         stats.hint = cpstats->move_name;
6550         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6551         stats.an_move_count = cpstats->nr_moves;
6552     }
6553
6554     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
6555
6556     SetProgramStats( &stats );
6557 }
6558
6559 void
6560 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6561 {       // count all piece types
6562         int p, f, r;
6563         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6564         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6565         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6566                 p = board[r][f];
6567                 pCnt[p]++;
6568                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6569                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6570                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6571                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6572                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6573                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6574         }
6575 }
6576
6577 int
6578 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6579 {
6580         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6581         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6582
6583         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6584         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6585         if(myPawns == 2 && nMine == 3) // KPP
6586             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6587         if(myPawns == 1 && nMine == 2) // KP
6588             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6589         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6590             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6591         if(myPawns) return FALSE;
6592         if(pCnt[WhiteRook+side])
6593             return pCnt[BlackRook-side] ||
6594                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6595                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6596                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6597         if(pCnt[WhiteCannon+side]) {
6598             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6599             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6600         }
6601         if(pCnt[WhiteKnight+side])
6602             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6603         return FALSE;
6604 }
6605
6606 int
6607 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6608 {
6609         VariantClass v = gameInfo.variant;
6610
6611         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6612         if(v == VariantShatranj) return TRUE; // always winnable through baring
6613         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6614         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6615
6616         if(v == VariantXiangqi) {
6617                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6618
6619                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6620                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6621                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6622                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6623                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6624                 if(stale) // we have at least one last-rank P plus perhaps C
6625                     return majors // KPKX
6626                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6627                 else // KCA*E*
6628                     return pCnt[WhiteFerz+side] // KCAK
6629                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6630                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6631                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6632
6633         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6634                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6635
6636                 if(nMine == 1) return FALSE; // bare King
6637                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
6638                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6639                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6640                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6641                 if(pCnt[WhiteKnight+side])
6642                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6643                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6644                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6645                 if(nBishops)
6646                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6647                 if(pCnt[WhiteAlfil+side])
6648                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6649                 if(pCnt[WhiteWazir+side])
6650                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6651         }
6652
6653         return TRUE;
6654 }
6655
6656 int
6657 Adjudicate(ChessProgramState *cps)
6658 {       // [HGM] some adjudications useful with buggy engines
6659         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6660         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6661         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6662         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6663         int k, count = 0; static int bare = 1;
6664         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6665         Boolean canAdjudicate = !appData.icsActive;
6666
6667         // most tests only when we understand the game, i.e. legality-checking on
6668             if( appData.testLegality )
6669             {   /* [HGM] Some more adjudications for obstinate engines */
6670                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6671                 static int moveCount = 6;
6672                 ChessMove result;
6673                 char *reason = NULL;
6674
6675                 /* Count what is on board. */
6676                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6677
6678                 /* Some material-based adjudications that have to be made before stalemate test */
6679                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6680                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6681                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6682                      if(canAdjudicate && appData.checkMates) {
6683                          if(engineOpponent)
6684                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6685                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6686                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6687                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6688                          return 1;
6689                      }
6690                 }
6691
6692                 /* Bare King in Shatranj (loses) or Losers (wins) */
6693                 if( nrW == 1 || nrB == 1) {
6694                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6695                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6696                      if(canAdjudicate && appData.checkMates) {
6697                          if(engineOpponent)
6698                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6699                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6700                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6701                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6702                          return 1;
6703                      }
6704                   } else
6705                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6706                   {    /* bare King */
6707                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6708                         if(canAdjudicate && appData.checkMates) {
6709                             /* but only adjudicate if adjudication enabled */
6710                             if(engineOpponent)
6711                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6712                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6713                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6714                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6715                             return 1;
6716                         }
6717                   }
6718                 } else bare = 1;
6719
6720
6721             // don't wait for engine to announce game end if we can judge ourselves
6722             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6723               case MT_CHECK:
6724                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6725                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6726                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6727                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6728                             checkCnt++;
6729                         if(checkCnt >= 2) {
6730                             reason = "Xboard adjudication: 3rd check";
6731                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6732                             break;
6733                         }
6734                     }
6735                 }
6736               case MT_NONE:
6737               default:
6738                 break;
6739               case MT_STALEMATE:
6740               case MT_STAINMATE:
6741                 reason = "Xboard adjudication: Stalemate";
6742                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6743                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6744                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6745                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6746                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6747                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6748                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6749                                                                         EP_CHECKMATE : EP_WINS);
6750                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6751                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6752                 }
6753                 break;
6754               case MT_CHECKMATE:
6755                 reason = "Xboard adjudication: Checkmate";
6756                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6757                 break;
6758             }
6759
6760                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6761                     case EP_STALEMATE:
6762                         result = GameIsDrawn; break;
6763                     case EP_CHECKMATE:
6764                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6765                     case EP_WINS:
6766                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6767                     default:
6768                         result = (ChessMove) 0;
6769                 }
6770                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6771                     if(engineOpponent)
6772                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6773                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6774                     GameEnds( result, reason, GE_XBOARD );
6775                     return 1;
6776                 }
6777
6778                 /* Next absolutely insufficient mating material. */
6779                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6780                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6781                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6782
6783                      /* always flag draws, for judging claims */
6784                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6785
6786                      if(canAdjudicate && appData.materialDraws) {
6787                          /* but only adjudicate them if adjudication enabled */
6788                          if(engineOpponent) {
6789                            SendToProgram("force\n", engineOpponent); // suppress reply
6790                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6791                          }
6792                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6793                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6794                          return 1;
6795                      }
6796                 }
6797
6798                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6799                 if(gameInfo.variant == VariantXiangqi ?
6800                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6801                  : nrW + nrB == 4 &&
6802                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6803                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6804                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6805                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6806                    ) ) {
6807                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6808                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6809                           if(engineOpponent) {
6810                             SendToProgram("force\n", engineOpponent); // suppress reply
6811                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6812                           }
6813                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6814                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6815                           return 1;
6816                      }
6817                 } else moveCount = 6;
6818             }
6819         if (appData.debugMode) { int i;
6820             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6821                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6822                     appData.drawRepeats);
6823             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6824               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6825
6826         }
6827
6828         // Repetition draws and 50-move rule can be applied independently of legality testing
6829
6830                 /* Check for rep-draws */
6831                 count = 0;
6832                 for(k = forwardMostMove-2;
6833                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6834                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6835                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6836                     k-=2)
6837                 {   int rights=0;
6838                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6839                         /* compare castling rights */
6840                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6841                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6842                                 rights++; /* King lost rights, while rook still had them */
6843                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6844                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6845                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6846                                    rights++; /* but at least one rook lost them */
6847                         }
6848                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6849                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6850                                 rights++;
6851                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6852                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6853                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6854                                    rights++;
6855                         }
6856                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6857                             && appData.drawRepeats > 1) {
6858                              /* adjudicate after user-specified nr of repeats */
6859                              int result = GameIsDrawn;
6860                              char *details = "XBoard adjudication: repetition draw";
6861                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6862                                 // [HGM] xiangqi: check for forbidden perpetuals
6863                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6864                                 for(m=forwardMostMove; m>k; m-=2) {
6865                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6866                                         ourPerpetual = 0; // the current mover did not always check
6867                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6868                                         hisPerpetual = 0; // the opponent did not always check
6869                                 }
6870                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6871                                                                         ourPerpetual, hisPerpetual);
6872                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6873                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6874                                     details = "Xboard adjudication: perpetual checking";
6875                                 } else
6876                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6877                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6878                                 } else
6879                                 // Now check for perpetual chases
6880                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6881                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6882                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6883                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6884                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6885                                         details = "Xboard adjudication: perpetual chasing";
6886                                     } else
6887                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6888                                         break; // Abort repetition-checking loop.
6889                                 }
6890                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6891                              }
6892                              if(engineOpponent) {
6893                                SendToProgram("force\n", engineOpponent); // suppress reply
6894                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6895                              }
6896                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6897                              GameEnds( result, details, GE_XBOARD );
6898                              return 1;
6899                         }
6900                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6901                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6902                     }
6903                 }
6904
6905                 /* Now we test for 50-move draws. Determine ply count */
6906                 count = forwardMostMove;
6907                 /* look for last irreversble move */
6908                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6909                     count--;
6910                 /* if we hit starting position, add initial plies */
6911                 if( count == backwardMostMove )
6912                     count -= initialRulePlies;
6913                 count = forwardMostMove - count;
6914                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6915                         // adjust reversible move counter for checks in Xiangqi
6916                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6917                         if(i < backwardMostMove) i = backwardMostMove;
6918                         while(i <= forwardMostMove) {
6919                                 lastCheck = inCheck; // check evasion does not count
6920                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6921                                 if(inCheck || lastCheck) count--; // check does not count
6922                                 i++;
6923                         }
6924                 }
6925                 if( count >= 100)
6926                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6927                          /* this is used to judge if draw claims are legal */
6928                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6929                          if(engineOpponent) {
6930                            SendToProgram("force\n", engineOpponent); // suppress reply
6931                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6932                          }
6933                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6934                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6935                          return 1;
6936                 }
6937
6938                 /* if draw offer is pending, treat it as a draw claim
6939                  * when draw condition present, to allow engines a way to
6940                  * claim draws before making their move to avoid a race
6941                  * condition occurring after their move
6942                  */
6943                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6944                          char *p = NULL;
6945                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6946                              p = "Draw claim: 50-move rule";
6947                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6948                              p = "Draw claim: 3-fold repetition";
6949                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6950                              p = "Draw claim: insufficient mating material";
6951                          if( p != NULL && canAdjudicate) {
6952                              if(engineOpponent) {
6953                                SendToProgram("force\n", engineOpponent); // suppress reply
6954                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6955                              }
6956                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6957                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6958                              return 1;
6959                          }
6960                 }
6961
6962                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6963                     if(engineOpponent) {
6964                       SendToProgram("force\n", engineOpponent); // suppress reply
6965                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6966                     }
6967                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6968                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6969                     return 1;
6970                 }
6971         return 0;
6972 }
6973
6974 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6975 {   // [HGM] book: this routine intercepts moves to simulate book replies
6976     char *bookHit = NULL;
6977
6978     //first determine if the incoming move brings opponent into his book
6979     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6980         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6981     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6982     if(bookHit != NULL && !cps->bookSuspend) {
6983         // make sure opponent is not going to reply after receiving move to book position
6984         SendToProgram("force\n", cps);
6985         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6986     }
6987     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6988     // now arrange restart after book miss
6989     if(bookHit) {
6990         // after a book hit we never send 'go', and the code after the call to this routine
6991         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6992         char buf[MSG_SIZ];
6993         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
6994         SendToProgram(buf, cps);
6995         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6996     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6997         SendToProgram("go\n", cps);
6998         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6999     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7000         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7001             SendToProgram("go\n", cps);
7002         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7003     }
7004     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7005 }
7006
7007 char *savedMessage;
7008 ChessProgramState *savedState;
7009 void DeferredBookMove(void)
7010 {
7011         if(savedState->lastPing != savedState->lastPong)
7012                     ScheduleDelayedEvent(DeferredBookMove, 10);
7013         else
7014         HandleMachineMove(savedMessage, savedState);
7015 }
7016
7017 void
7018 HandleMachineMove(message, cps)
7019      char *message;
7020      ChessProgramState *cps;
7021 {
7022     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7023     char realname[MSG_SIZ];
7024     int fromX, fromY, toX, toY;
7025     ChessMove moveType;
7026     char promoChar;
7027     char *p;
7028     int machineWhite;
7029     char *bookHit;
7030
7031     cps->userError = 0;
7032
7033 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7034     /*
7035      * Kludge to ignore BEL characters
7036      */
7037     while (*message == '\007') message++;
7038
7039     /*
7040      * [HGM] engine debug message: ignore lines starting with '#' character
7041      */
7042     if(cps->debug && *message == '#') return;
7043
7044     /*
7045      * Look for book output
7046      */
7047     if (cps == &first && bookRequested) {
7048         if (message[0] == '\t' || message[0] == ' ') {
7049             /* Part of the book output is here; append it */
7050             strcat(bookOutput, message);
7051             strcat(bookOutput, "  \n");
7052             return;
7053         } else if (bookOutput[0] != NULLCHAR) {
7054             /* All of book output has arrived; display it */
7055             char *p = bookOutput;
7056             while (*p != NULLCHAR) {
7057                 if (*p == '\t') *p = ' ';
7058                 p++;
7059             }
7060             DisplayInformation(bookOutput);
7061             bookRequested = FALSE;
7062             /* Fall through to parse the current output */
7063         }
7064     }
7065
7066     /*
7067      * Look for machine move.
7068      */
7069     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7070         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7071     {
7072         /* This method is only useful on engines that support ping */
7073         if (cps->lastPing != cps->lastPong) {
7074           if (gameMode == BeginningOfGame) {
7075             /* Extra move from before last new; ignore */
7076             if (appData.debugMode) {
7077                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7078             }
7079           } else {
7080             if (appData.debugMode) {
7081                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7082                         cps->which, gameMode);
7083             }
7084
7085             SendToProgram("undo\n", cps);
7086           }
7087           return;
7088         }
7089
7090         switch (gameMode) {
7091           case BeginningOfGame:
7092             /* Extra move from before last reset; ignore */
7093             if (appData.debugMode) {
7094                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7095             }
7096             return;
7097
7098           case EndOfGame:
7099           case IcsIdle:
7100           default:
7101             /* Extra move after we tried to stop.  The mode test is
7102                not a reliable way of detecting this problem, but it's
7103                the best we can do on engines that don't support ping.
7104             */
7105             if (appData.debugMode) {
7106                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7107                         cps->which, gameMode);
7108             }
7109             SendToProgram("undo\n", cps);
7110             return;
7111
7112           case MachinePlaysWhite:
7113           case IcsPlayingWhite:
7114             machineWhite = TRUE;
7115             break;
7116
7117           case MachinePlaysBlack:
7118           case IcsPlayingBlack:
7119             machineWhite = FALSE;
7120             break;
7121
7122           case TwoMachinesPlay:
7123             machineWhite = (cps->twoMachinesColor[0] == 'w');
7124             break;
7125         }
7126         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7127             if (appData.debugMode) {
7128                 fprintf(debugFP,
7129                         "Ignoring move out of turn by %s, gameMode %d"
7130                         ", forwardMost %d\n",
7131                         cps->which, gameMode, forwardMostMove);
7132             }
7133             return;
7134         }
7135
7136     if (appData.debugMode) { int f = forwardMostMove;
7137         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7138                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7139                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7140     }
7141         if(cps->alphaRank) AlphaRank(machineMove, 4);
7142         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7143                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7144             /* Machine move could not be parsed; ignore it. */
7145             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7146                     machineMove, cps->which);
7147             DisplayError(buf1, 0);
7148             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7149                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7150             if (gameMode == TwoMachinesPlay) {
7151               GameEnds(machineWhite ? BlackWins : WhiteWins,
7152                        buf1, GE_XBOARD);
7153             }
7154             return;
7155         }
7156
7157         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7158         /* So we have to redo legality test with true e.p. status here,  */
7159         /* to make sure an illegal e.p. capture does not slip through,   */
7160         /* to cause a forfeit on a justified illegal-move complaint      */
7161         /* of the opponent.                                              */
7162         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7163            ChessMove moveType;
7164            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7165                              fromY, fromX, toY, toX, promoChar);
7166             if (appData.debugMode) {
7167                 int i;
7168                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7169                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7170                 fprintf(debugFP, "castling rights\n");
7171             }
7172             if(moveType == IllegalMove) {
7173                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7174                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7175                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7176                            buf1, GE_XBOARD);
7177                 return;
7178            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7179            /* [HGM] Kludge to handle engines that send FRC-style castling
7180               when they shouldn't (like TSCP-Gothic) */
7181            switch(moveType) {
7182              case WhiteASideCastleFR:
7183              case BlackASideCastleFR:
7184                toX+=2;
7185                currentMoveString[2]++;
7186                break;
7187              case WhiteHSideCastleFR:
7188              case BlackHSideCastleFR:
7189                toX--;
7190                currentMoveString[2]--;
7191                break;
7192              default: ; // nothing to do, but suppresses warning of pedantic compilers
7193            }
7194         }
7195         hintRequested = FALSE;
7196         lastHint[0] = NULLCHAR;
7197         bookRequested = FALSE;
7198         /* Program may be pondering now */
7199         cps->maybeThinking = TRUE;
7200         if (cps->sendTime == 2) cps->sendTime = 1;
7201         if (cps->offeredDraw) cps->offeredDraw--;
7202
7203         /* currentMoveString is set as a side-effect of ParseOneMove */
7204         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7205         strcat(machineMove, "\n");
7206         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7207
7208         /* [AS] Save move info*/
7209         pvInfoList[ forwardMostMove ].score = programStats.score;
7210         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7211         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7212
7213         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7214
7215         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7216         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7217             int count = 0;
7218
7219             while( count < adjudicateLossPlies ) {
7220                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7221
7222                 if( count & 1 ) {
7223                     score = -score; /* Flip score for winning side */
7224                 }
7225
7226                 if( score > adjudicateLossThreshold ) {
7227                     break;
7228                 }
7229
7230                 count++;
7231             }
7232
7233             if( count >= adjudicateLossPlies ) {
7234                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7235
7236                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7237                     "Xboard adjudication",
7238                     GE_XBOARD );
7239
7240                 return;
7241             }
7242         }
7243
7244         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7245
7246 #if ZIPPY
7247         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7248             first.initDone) {
7249           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7250                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7251                 SendToICS("draw ");
7252                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7253           }
7254           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7255           ics_user_moved = 1;
7256           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7257                 char buf[3*MSG_SIZ];
7258
7259                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7260                         programStats.score / 100.,
7261                         programStats.depth,
7262                         programStats.time / 100.,
7263                         (unsigned int)programStats.nodes,
7264                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7265                         programStats.movelist);
7266                 SendToICS(buf);
7267 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7268           }
7269         }
7270 #endif
7271
7272         /* [AS] Clear stats for next move */
7273         ClearProgramStats();
7274         thinkOutput[0] = NULLCHAR;
7275         hiddenThinkOutputState = 0;
7276
7277         bookHit = NULL;
7278         if (gameMode == TwoMachinesPlay) {
7279             /* [HGM] relaying draw offers moved to after reception of move */
7280             /* and interpreting offer as claim if it brings draw condition */
7281             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7282                 SendToProgram("draw\n", cps->other);
7283             }
7284             if (cps->other->sendTime) {
7285                 SendTimeRemaining(cps->other,
7286                                   cps->other->twoMachinesColor[0] == 'w');
7287             }
7288             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7289             if (firstMove && !bookHit) {
7290                 firstMove = FALSE;
7291                 if (cps->other->useColors) {
7292                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7293                 }
7294                 SendToProgram("go\n", cps->other);
7295             }
7296             cps->other->maybeThinking = TRUE;
7297         }
7298
7299         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7300
7301         if (!pausing && appData.ringBellAfterMoves) {
7302             RingBell();
7303         }
7304
7305         /*
7306          * Reenable menu items that were disabled while
7307          * machine was thinking
7308          */
7309         if (gameMode != TwoMachinesPlay)
7310             SetUserThinkingEnables();
7311
7312         // [HGM] book: after book hit opponent has received move and is now in force mode
7313         // force the book reply into it, and then fake that it outputted this move by jumping
7314         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7315         if(bookHit) {
7316                 static char bookMove[MSG_SIZ]; // a bit generous?
7317
7318                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7319                 strcat(bookMove, bookHit);
7320                 message = bookMove;
7321                 cps = cps->other;
7322                 programStats.nodes = programStats.depth = programStats.time =
7323                 programStats.score = programStats.got_only_move = 0;
7324                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7325
7326                 if(cps->lastPing != cps->lastPong) {
7327                     savedMessage = message; // args for deferred call
7328                     savedState = cps;
7329                     ScheduleDelayedEvent(DeferredBookMove, 10);
7330                     return;
7331                 }
7332                 goto FakeBookMove;
7333         }
7334
7335         return;
7336     }
7337
7338     /* Set special modes for chess engines.  Later something general
7339      *  could be added here; for now there is just one kludge feature,
7340      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7341      *  when "xboard" is given as an interactive command.
7342      */
7343     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7344         cps->useSigint = FALSE;
7345         cps->useSigterm = FALSE;
7346     }
7347     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7348       ParseFeatures(message+8, cps);
7349       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7350     }
7351
7352     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7353      * want this, I was asked to put it in, and obliged.
7354      */
7355     if (!strncmp(message, "setboard ", 9)) {
7356         Board initial_position;
7357
7358         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7359
7360         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7361             DisplayError(_("Bad FEN received from engine"), 0);
7362             return ;
7363         } else {
7364            Reset(TRUE, FALSE);
7365            CopyBoard(boards[0], initial_position);
7366            initialRulePlies = FENrulePlies;
7367            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7368            else gameMode = MachinePlaysBlack;
7369            DrawPosition(FALSE, boards[currentMove]);
7370         }
7371         return;
7372     }
7373
7374     /*
7375      * Look for communication commands
7376      */
7377     if (!strncmp(message, "telluser ", 9)) {
7378         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7379         DisplayNote(message + 9);
7380         return;
7381     }
7382     if (!strncmp(message, "tellusererror ", 14)) {
7383         cps->userError = 1;
7384         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7385         DisplayError(message + 14, 0);
7386         return;
7387     }
7388     if (!strncmp(message, "tellopponent ", 13)) {
7389       if (appData.icsActive) {
7390         if (loggedOn) {
7391           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7392           SendToICS(buf1);
7393         }
7394       } else {
7395         DisplayNote(message + 13);
7396       }
7397       return;
7398     }
7399     if (!strncmp(message, "tellothers ", 11)) {
7400       if (appData.icsActive) {
7401         if (loggedOn) {
7402           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7403           SendToICS(buf1);
7404         }
7405       }
7406       return;
7407     }
7408     if (!strncmp(message, "tellall ", 8)) {
7409       if (appData.icsActive) {
7410         if (loggedOn) {
7411           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7412           SendToICS(buf1);
7413         }
7414       } else {
7415         DisplayNote(message + 8);
7416       }
7417       return;
7418     }
7419     if (strncmp(message, "warning", 7) == 0) {
7420         /* Undocumented feature, use tellusererror in new code */
7421         DisplayError(message, 0);
7422         return;
7423     }
7424     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7425         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7426         strcat(realname, " query");
7427         AskQuestion(realname, buf2, buf1, cps->pr);
7428         return;
7429     }
7430     /* Commands from the engine directly to ICS.  We don't allow these to be
7431      *  sent until we are logged on. Crafty kibitzes have been known to
7432      *  interfere with the login process.
7433      */
7434     if (loggedOn) {
7435         if (!strncmp(message, "tellics ", 8)) {
7436             SendToICS(message + 8);
7437             SendToICS("\n");
7438             return;
7439         }
7440         if (!strncmp(message, "tellicsnoalias ", 15)) {
7441             SendToICS(ics_prefix);
7442             SendToICS(message + 15);
7443             SendToICS("\n");
7444             return;
7445         }
7446         /* The following are for backward compatibility only */
7447         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7448             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7449             SendToICS(ics_prefix);
7450             SendToICS(message);
7451             SendToICS("\n");
7452             return;
7453         }
7454     }
7455     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7456         return;
7457     }
7458     /*
7459      * If the move is illegal, cancel it and redraw the board.
7460      * Also deal with other error cases.  Matching is rather loose
7461      * here to accommodate engines written before the spec.
7462      */
7463     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7464         strncmp(message, "Error", 5) == 0) {
7465         if (StrStr(message, "name") ||
7466             StrStr(message, "rating") || StrStr(message, "?") ||
7467             StrStr(message, "result") || StrStr(message, "board") ||
7468             StrStr(message, "bk") || StrStr(message, "computer") ||
7469             StrStr(message, "variant") || StrStr(message, "hint") ||
7470             StrStr(message, "random") || StrStr(message, "depth") ||
7471             StrStr(message, "accepted")) {
7472             return;
7473         }
7474         if (StrStr(message, "protover")) {
7475           /* Program is responding to input, so it's apparently done
7476              initializing, and this error message indicates it is
7477              protocol version 1.  So we don't need to wait any longer
7478              for it to initialize and send feature commands. */
7479           FeatureDone(cps, 1);
7480           cps->protocolVersion = 1;
7481           return;
7482         }
7483         cps->maybeThinking = FALSE;
7484
7485         if (StrStr(message, "draw")) {
7486             /* Program doesn't have "draw" command */
7487             cps->sendDrawOffers = 0;
7488             return;
7489         }
7490         if (cps->sendTime != 1 &&
7491             (StrStr(message, "time") || StrStr(message, "otim"))) {
7492           /* Program apparently doesn't have "time" or "otim" command */
7493           cps->sendTime = 0;
7494           return;
7495         }
7496         if (StrStr(message, "analyze")) {
7497             cps->analysisSupport = FALSE;
7498             cps->analyzing = FALSE;
7499             Reset(FALSE, TRUE);
7500             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7501             DisplayError(buf2, 0);
7502             return;
7503         }
7504         if (StrStr(message, "(no matching move)st")) {
7505           /* Special kludge for GNU Chess 4 only */
7506           cps->stKludge = TRUE;
7507           SendTimeControl(cps, movesPerSession, timeControl,
7508                           timeIncrement, appData.searchDepth,
7509                           searchTime);
7510           return;
7511         }
7512         if (StrStr(message, "(no matching move)sd")) {
7513           /* Special kludge for GNU Chess 4 only */
7514           cps->sdKludge = TRUE;
7515           SendTimeControl(cps, movesPerSession, timeControl,
7516                           timeIncrement, appData.searchDepth,
7517                           searchTime);
7518           return;
7519         }
7520         if (!StrStr(message, "llegal")) {
7521             return;
7522         }
7523         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7524             gameMode == IcsIdle) return;
7525         if (forwardMostMove <= backwardMostMove) return;
7526         if (pausing) PauseEvent();
7527       if(appData.forceIllegal) {
7528             // [HGM] illegal: machine refused move; force position after move into it
7529           SendToProgram("force\n", cps);
7530           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7531                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7532                 // when black is to move, while there might be nothing on a2 or black
7533                 // might already have the move. So send the board as if white has the move.
7534                 // But first we must change the stm of the engine, as it refused the last move
7535                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7536                 if(WhiteOnMove(forwardMostMove)) {
7537                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7538                     SendBoard(cps, forwardMostMove); // kludgeless board
7539                 } else {
7540                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7541                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7542                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7543                 }
7544           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7545             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7546                  gameMode == TwoMachinesPlay)
7547               SendToProgram("go\n", cps);
7548             return;
7549       } else
7550         if (gameMode == PlayFromGameFile) {
7551             /* Stop reading this game file */
7552             gameMode = EditGame;
7553             ModeHighlight();
7554         }
7555         currentMove = forwardMostMove-1;
7556         DisplayMove(currentMove-1); /* before DisplayMoveError */
7557         SwitchClocks(forwardMostMove-1); // [HGM] race
7558         DisplayBothClocks();
7559         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7560                 parseList[currentMove], cps->which);
7561         DisplayMoveError(buf1);
7562         DrawPosition(FALSE, boards[currentMove]);
7563
7564         /* [HGM] illegal-move claim should forfeit game when Xboard */
7565         /* only passes fully legal moves                            */
7566         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7567             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7568                                 "False illegal-move claim", GE_XBOARD );
7569         }
7570         return;
7571     }
7572     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7573         /* Program has a broken "time" command that
7574            outputs a string not ending in newline.
7575            Don't use it. */
7576         cps->sendTime = 0;
7577     }
7578
7579     /*
7580      * If chess program startup fails, exit with an error message.
7581      * Attempts to recover here are futile.
7582      */
7583     if ((StrStr(message, "unknown host") != NULL)
7584         || (StrStr(message, "No remote directory") != NULL)
7585         || (StrStr(message, "not found") != NULL)
7586         || (StrStr(message, "No such file") != NULL)
7587         || (StrStr(message, "can't alloc") != NULL)
7588         || (StrStr(message, "Permission denied") != NULL)) {
7589
7590         cps->maybeThinking = FALSE;
7591         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7592                 cps->which, cps->program, cps->host, message);
7593         RemoveInputSource(cps->isr);
7594         DisplayFatalError(buf1, 0, 1);
7595         return;
7596     }
7597
7598     /*
7599      * Look for hint output
7600      */
7601     if (sscanf(message, "Hint: %s", buf1) == 1) {
7602         if (cps == &first && hintRequested) {
7603             hintRequested = FALSE;
7604             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7605                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7606                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7607                                     PosFlags(forwardMostMove),
7608                                     fromY, fromX, toY, toX, promoChar, buf1);
7609                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7610                 DisplayInformation(buf2);
7611             } else {
7612                 /* Hint move could not be parsed!? */
7613               snprintf(buf2, sizeof(buf2),
7614                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7615                         buf1, cps->which);
7616                 DisplayError(buf2, 0);
7617             }
7618         } else {
7619           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7620         }
7621         return;
7622     }
7623
7624     /*
7625      * Ignore other messages if game is not in progress
7626      */
7627     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7628         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7629
7630     /*
7631      * look for win, lose, draw, or draw offer
7632      */
7633     if (strncmp(message, "1-0", 3) == 0) {
7634         char *p, *q, *r = "";
7635         p = strchr(message, '{');
7636         if (p) {
7637             q = strchr(p, '}');
7638             if (q) {
7639                 *q = NULLCHAR;
7640                 r = p + 1;
7641             }
7642         }
7643         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7644         return;
7645     } else if (strncmp(message, "0-1", 3) == 0) {
7646         char *p, *q, *r = "";
7647         p = strchr(message, '{');
7648         if (p) {
7649             q = strchr(p, '}');
7650             if (q) {
7651                 *q = NULLCHAR;
7652                 r = p + 1;
7653             }
7654         }
7655         /* Kludge for Arasan 4.1 bug */
7656         if (strcmp(r, "Black resigns") == 0) {
7657             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7658             return;
7659         }
7660         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7661         return;
7662     } else if (strncmp(message, "1/2", 3) == 0) {
7663         char *p, *q, *r = "";
7664         p = strchr(message, '{');
7665         if (p) {
7666             q = strchr(p, '}');
7667             if (q) {
7668                 *q = NULLCHAR;
7669                 r = p + 1;
7670             }
7671         }
7672
7673         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7674         return;
7675
7676     } else if (strncmp(message, "White resign", 12) == 0) {
7677         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7678         return;
7679     } else if (strncmp(message, "Black resign", 12) == 0) {
7680         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7681         return;
7682     } else if (strncmp(message, "White matches", 13) == 0 ||
7683                strncmp(message, "Black matches", 13) == 0   ) {
7684         /* [HGM] ignore GNUShogi noises */
7685         return;
7686     } else if (strncmp(message, "White", 5) == 0 &&
7687                message[5] != '(' &&
7688                StrStr(message, "Black") == NULL) {
7689         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7690         return;
7691     } else if (strncmp(message, "Black", 5) == 0 &&
7692                message[5] != '(') {
7693         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7694         return;
7695     } else if (strcmp(message, "resign") == 0 ||
7696                strcmp(message, "computer resigns") == 0) {
7697         switch (gameMode) {
7698           case MachinePlaysBlack:
7699           case IcsPlayingBlack:
7700             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7701             break;
7702           case MachinePlaysWhite:
7703           case IcsPlayingWhite:
7704             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7705             break;
7706           case TwoMachinesPlay:
7707             if (cps->twoMachinesColor[0] == 'w')
7708               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7709             else
7710               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7711             break;
7712           default:
7713             /* can't happen */
7714             break;
7715         }
7716         return;
7717     } else if (strncmp(message, "opponent mates", 14) == 0) {
7718         switch (gameMode) {
7719           case MachinePlaysBlack:
7720           case IcsPlayingBlack:
7721             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7722             break;
7723           case MachinePlaysWhite:
7724           case IcsPlayingWhite:
7725             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7726             break;
7727           case TwoMachinesPlay:
7728             if (cps->twoMachinesColor[0] == 'w')
7729               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7730             else
7731               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7732             break;
7733           default:
7734             /* can't happen */
7735             break;
7736         }
7737         return;
7738     } else if (strncmp(message, "computer mates", 14) == 0) {
7739         switch (gameMode) {
7740           case MachinePlaysBlack:
7741           case IcsPlayingBlack:
7742             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7743             break;
7744           case MachinePlaysWhite:
7745           case IcsPlayingWhite:
7746             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7747             break;
7748           case TwoMachinesPlay:
7749             if (cps->twoMachinesColor[0] == 'w')
7750               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7751             else
7752               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7753             break;
7754           default:
7755             /* can't happen */
7756             break;
7757         }
7758         return;
7759     } else if (strncmp(message, "checkmate", 9) == 0) {
7760         if (WhiteOnMove(forwardMostMove)) {
7761             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7762         } else {
7763             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7764         }
7765         return;
7766     } else if (strstr(message, "Draw") != NULL ||
7767                strstr(message, "game is a draw") != NULL) {
7768         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7769         return;
7770     } else if (strstr(message, "offer") != NULL &&
7771                strstr(message, "draw") != NULL) {
7772 #if ZIPPY
7773         if (appData.zippyPlay && first.initDone) {
7774             /* Relay offer to ICS */
7775             SendToICS(ics_prefix);
7776             SendToICS("draw\n");
7777         }
7778 #endif
7779         cps->offeredDraw = 2; /* valid until this engine moves twice */
7780         if (gameMode == TwoMachinesPlay) {
7781             if (cps->other->offeredDraw) {
7782                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7783             /* [HGM] in two-machine mode we delay relaying draw offer      */
7784             /* until after we also have move, to see if it is really claim */
7785             }
7786         } else if (gameMode == MachinePlaysWhite ||
7787                    gameMode == MachinePlaysBlack) {
7788           if (userOfferedDraw) {
7789             DisplayInformation(_("Machine accepts your draw offer"));
7790             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7791           } else {
7792             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7793           }
7794         }
7795     }
7796
7797
7798     /*
7799      * Look for thinking output
7800      */
7801     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7802           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7803                                 ) {
7804         int plylev, mvleft, mvtot, curscore, time;
7805         char mvname[MOVE_LEN];
7806         u64 nodes; // [DM]
7807         char plyext;
7808         int ignore = FALSE;
7809         int prefixHint = FALSE;
7810         mvname[0] = NULLCHAR;
7811
7812         switch (gameMode) {
7813           case MachinePlaysBlack:
7814           case IcsPlayingBlack:
7815             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7816             break;
7817           case MachinePlaysWhite:
7818           case IcsPlayingWhite:
7819             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7820             break;
7821           case AnalyzeMode:
7822           case AnalyzeFile:
7823             break;
7824           case IcsObserving: /* [DM] icsEngineAnalyze */
7825             if (!appData.icsEngineAnalyze) ignore = TRUE;
7826             break;
7827           case TwoMachinesPlay:
7828             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7829                 ignore = TRUE;
7830             }
7831             break;
7832           default:
7833             ignore = TRUE;
7834             break;
7835         }
7836
7837         if (!ignore) {
7838             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7839             buf1[0] = NULLCHAR;
7840             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7841                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7842
7843                 if (plyext != ' ' && plyext != '\t') {
7844                     time *= 100;
7845                 }
7846
7847                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7848                 if( cps->scoreIsAbsolute &&
7849                     ( gameMode == MachinePlaysBlack ||
7850                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7851                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7852                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7853                      !WhiteOnMove(currentMove)
7854                     ) )
7855                 {
7856                     curscore = -curscore;
7857                 }
7858
7859
7860                 tempStats.depth = plylev;
7861                 tempStats.nodes = nodes;
7862                 tempStats.time = time;
7863                 tempStats.score = curscore;
7864                 tempStats.got_only_move = 0;
7865
7866                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7867                         int ticklen;
7868
7869                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7870                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7871                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7872                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7873                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7874                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7875                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7876                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7877                 }
7878
7879                 /* Buffer overflow protection */
7880                 if (buf1[0] != NULLCHAR) {
7881                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7882                         && appData.debugMode) {
7883                         fprintf(debugFP,
7884                                 "PV is too long; using the first %u bytes.\n",
7885                                 (unsigned) sizeof(tempStats.movelist) - 1);
7886                     }
7887
7888                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
7889                 } else {
7890                     sprintf(tempStats.movelist, " no PV\n");
7891                 }
7892
7893                 if (tempStats.seen_stat) {
7894                     tempStats.ok_to_send = 1;
7895                 }
7896
7897                 if (strchr(tempStats.movelist, '(') != NULL) {
7898                     tempStats.line_is_book = 1;
7899                     tempStats.nr_moves = 0;
7900                     tempStats.moves_left = 0;
7901                 } else {
7902                     tempStats.line_is_book = 0;
7903                 }
7904
7905                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7906                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7907
7908                 SendProgramStatsToFrontend( cps, &tempStats );
7909
7910                 /*
7911                     [AS] Protect the thinkOutput buffer from overflow... this
7912                     is only useful if buf1 hasn't overflowed first!
7913                 */
7914                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7915                         plylev,
7916                         (gameMode == TwoMachinesPlay ?
7917                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7918                         ((double) curscore) / 100.0,
7919                         prefixHint ? lastHint : "",
7920                         prefixHint ? " " : "" );
7921
7922                 if( buf1[0] != NULLCHAR ) {
7923                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7924
7925                     if( strlen(buf1) > max_len ) {
7926                         if( appData.debugMode) {
7927                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7928                         }
7929                         buf1[max_len+1] = '\0';
7930                     }
7931
7932                     strcat( thinkOutput, buf1 );
7933                 }
7934
7935                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7936                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7937                     DisplayMove(currentMove - 1);
7938                 }
7939                 return;
7940
7941             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7942                 /* crafty (9.25+) says "(only move) <move>"
7943                  * if there is only 1 legal move
7944                  */
7945                 sscanf(p, "(only move) %s", buf1);
7946                 sprintf(thinkOutput, "%s (only move)", buf1);
7947                 sprintf(programStats.movelist, "%s (only move)", buf1);
7948                 programStats.depth = 1;
7949                 programStats.nr_moves = 1;
7950                 programStats.moves_left = 1;
7951                 programStats.nodes = 1;
7952                 programStats.time = 1;
7953                 programStats.got_only_move = 1;
7954
7955                 /* Not really, but we also use this member to
7956                    mean "line isn't going to change" (Crafty
7957                    isn't searching, so stats won't change) */
7958                 programStats.line_is_book = 1;
7959
7960                 SendProgramStatsToFrontend( cps, &programStats );
7961
7962                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7963                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7964                     DisplayMove(currentMove - 1);
7965                 }
7966                 return;
7967             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7968                               &time, &nodes, &plylev, &mvleft,
7969                               &mvtot, mvname) >= 5) {
7970                 /* The stat01: line is from Crafty (9.29+) in response
7971                    to the "." command */
7972                 programStats.seen_stat = 1;
7973                 cps->maybeThinking = TRUE;
7974
7975                 if (programStats.got_only_move || !appData.periodicUpdates)
7976                   return;
7977
7978                 programStats.depth = plylev;
7979                 programStats.time = time;
7980                 programStats.nodes = nodes;
7981                 programStats.moves_left = mvleft;
7982                 programStats.nr_moves = mvtot;
7983                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
7984                 programStats.ok_to_send = 1;
7985                 programStats.movelist[0] = '\0';
7986
7987                 SendProgramStatsToFrontend( cps, &programStats );
7988
7989                 return;
7990
7991             } else if (strncmp(message,"++",2) == 0) {
7992                 /* Crafty 9.29+ outputs this */
7993                 programStats.got_fail = 2;
7994                 return;
7995
7996             } else if (strncmp(message,"--",2) == 0) {
7997                 /* Crafty 9.29+ outputs this */
7998                 programStats.got_fail = 1;
7999                 return;
8000
8001             } else if (thinkOutput[0] != NULLCHAR &&
8002                        strncmp(message, "    ", 4) == 0) {
8003                 unsigned message_len;
8004
8005                 p = message;
8006                 while (*p && *p == ' ') p++;
8007
8008                 message_len = strlen( p );
8009
8010                 /* [AS] Avoid buffer overflow */
8011                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8012                     strcat(thinkOutput, " ");
8013                     strcat(thinkOutput, p);
8014                 }
8015
8016                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8017                     strcat(programStats.movelist, " ");
8018                     strcat(programStats.movelist, p);
8019                 }
8020
8021                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8022                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8023                     DisplayMove(currentMove - 1);
8024                 }
8025                 return;
8026             }
8027         }
8028         else {
8029             buf1[0] = NULLCHAR;
8030
8031             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8032                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8033             {
8034                 ChessProgramStats cpstats;
8035
8036                 if (plyext != ' ' && plyext != '\t') {
8037                     time *= 100;
8038                 }
8039
8040                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8041                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8042                     curscore = -curscore;
8043                 }
8044
8045                 cpstats.depth = plylev;
8046                 cpstats.nodes = nodes;
8047                 cpstats.time = time;
8048                 cpstats.score = curscore;
8049                 cpstats.got_only_move = 0;
8050                 cpstats.movelist[0] = '\0';
8051
8052                 if (buf1[0] != NULLCHAR) {
8053                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8054                 }
8055
8056                 cpstats.ok_to_send = 0;
8057                 cpstats.line_is_book = 0;
8058                 cpstats.nr_moves = 0;
8059                 cpstats.moves_left = 0;
8060
8061                 SendProgramStatsToFrontend( cps, &cpstats );
8062             }
8063         }
8064     }
8065 }
8066
8067
8068 /* Parse a game score from the character string "game", and
8069    record it as the history of the current game.  The game
8070    score is NOT assumed to start from the standard position.
8071    The display is not updated in any way.
8072    */
8073 void
8074 ParseGameHistory(game)
8075      char *game;
8076 {
8077     ChessMove moveType;
8078     int fromX, fromY, toX, toY, boardIndex;
8079     char promoChar;
8080     char *p, *q;
8081     char buf[MSG_SIZ];
8082
8083     if (appData.debugMode)
8084       fprintf(debugFP, "Parsing game history: %s\n", game);
8085
8086     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8087     gameInfo.site = StrSave(appData.icsHost);
8088     gameInfo.date = PGNDate();
8089     gameInfo.round = StrSave("-");
8090
8091     /* Parse out names of players */
8092     while (*game == ' ') game++;
8093     p = buf;
8094     while (*game != ' ') *p++ = *game++;
8095     *p = NULLCHAR;
8096     gameInfo.white = StrSave(buf);
8097     while (*game == ' ') game++;
8098     p = buf;
8099     while (*game != ' ' && *game != '\n') *p++ = *game++;
8100     *p = NULLCHAR;
8101     gameInfo.black = StrSave(buf);
8102
8103     /* Parse moves */
8104     boardIndex = blackPlaysFirst ? 1 : 0;
8105     yynewstr(game);
8106     for (;;) {
8107         yyboardindex = boardIndex;
8108         moveType = (ChessMove) yylex();
8109         switch (moveType) {
8110           case IllegalMove:             /* maybe suicide chess, etc. */
8111   if (appData.debugMode) {
8112     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8113     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8114     setbuf(debugFP, NULL);
8115   }
8116           case WhitePromotion:
8117           case BlackPromotion:
8118           case WhiteNonPromotion:
8119           case BlackNonPromotion:
8120           case NormalMove:
8121           case WhiteCapturesEnPassant:
8122           case BlackCapturesEnPassant:
8123           case WhiteKingSideCastle:
8124           case WhiteQueenSideCastle:
8125           case BlackKingSideCastle:
8126           case BlackQueenSideCastle:
8127           case WhiteKingSideCastleWild:
8128           case WhiteQueenSideCastleWild:
8129           case BlackKingSideCastleWild:
8130           case BlackQueenSideCastleWild:
8131           /* PUSH Fabien */
8132           case WhiteHSideCastleFR:
8133           case WhiteASideCastleFR:
8134           case BlackHSideCastleFR:
8135           case BlackASideCastleFR:
8136           /* POP Fabien */
8137             fromX = currentMoveString[0] - AAA;
8138             fromY = currentMoveString[1] - ONE;
8139             toX = currentMoveString[2] - AAA;
8140             toY = currentMoveString[3] - ONE;
8141             promoChar = currentMoveString[4];
8142             break;
8143           case WhiteDrop:
8144           case BlackDrop:
8145             fromX = moveType == WhiteDrop ?
8146               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8147             (int) CharToPiece(ToLower(currentMoveString[0]));
8148             fromY = DROP_RANK;
8149             toX = currentMoveString[2] - AAA;
8150             toY = currentMoveString[3] - ONE;
8151             promoChar = NULLCHAR;
8152             break;
8153           case AmbiguousMove:
8154             /* bug? */
8155             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8156   if (appData.debugMode) {
8157     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8158     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8159     setbuf(debugFP, NULL);
8160   }
8161             DisplayError(buf, 0);
8162             return;
8163           case ImpossibleMove:
8164             /* bug? */
8165             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8166   if (appData.debugMode) {
8167     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8168     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8169     setbuf(debugFP, NULL);
8170   }
8171             DisplayError(buf, 0);
8172             return;
8173           case (ChessMove) 0:   /* end of file */
8174             if (boardIndex < backwardMostMove) {
8175                 /* Oops, gap.  How did that happen? */
8176                 DisplayError(_("Gap in move list"), 0);
8177                 return;
8178             }
8179             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8180             if (boardIndex > forwardMostMove) {
8181                 forwardMostMove = boardIndex;
8182             }
8183             return;
8184           case ElapsedTime:
8185             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8186                 strcat(parseList[boardIndex-1], " ");
8187                 strcat(parseList[boardIndex-1], yy_text);
8188             }
8189             continue;
8190           case Comment:
8191           case PGNTag:
8192           case NAG:
8193           default:
8194             /* ignore */
8195             continue;
8196           case WhiteWins:
8197           case BlackWins:
8198           case GameIsDrawn:
8199           case GameUnfinished:
8200             if (gameMode == IcsExamining) {
8201                 if (boardIndex < backwardMostMove) {
8202                     /* Oops, gap.  How did that happen? */
8203                     return;
8204                 }
8205                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8206                 return;
8207             }
8208             gameInfo.result = moveType;
8209             p = strchr(yy_text, '{');
8210             if (p == NULL) p = strchr(yy_text, '(');
8211             if (p == NULL) {
8212                 p = yy_text;
8213                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8214             } else {
8215                 q = strchr(p, *p == '{' ? '}' : ')');
8216                 if (q != NULL) *q = NULLCHAR;
8217                 p++;
8218             }
8219             gameInfo.resultDetails = StrSave(p);
8220             continue;
8221         }
8222         if (boardIndex >= forwardMostMove &&
8223             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8224             backwardMostMove = blackPlaysFirst ? 1 : 0;
8225             return;
8226         }
8227         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8228                                  fromY, fromX, toY, toX, promoChar,
8229                                  parseList[boardIndex]);
8230         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8231         /* currentMoveString is set as a side-effect of yylex */
8232         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8233         strcat(moveList[boardIndex], "\n");
8234         boardIndex++;
8235         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8236         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8237           case MT_NONE:
8238           case MT_STALEMATE:
8239           default:
8240             break;
8241           case MT_CHECK:
8242             if(gameInfo.variant != VariantShogi)
8243                 strcat(parseList[boardIndex - 1], "+");
8244             break;
8245           case MT_CHECKMATE:
8246           case MT_STAINMATE:
8247             strcat(parseList[boardIndex - 1], "#");
8248             break;
8249         }
8250     }
8251 }
8252
8253
8254 /* Apply a move to the given board  */
8255 void
8256 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8257      int fromX, fromY, toX, toY;
8258      int promoChar;
8259      Board board;
8260 {
8261   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8262   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8263
8264     /* [HGM] compute & store e.p. status and castling rights for new position */
8265     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8266
8267       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8268       oldEP = (signed char)board[EP_STATUS];
8269       board[EP_STATUS] = EP_NONE;
8270
8271       if( board[toY][toX] != EmptySquare )
8272            board[EP_STATUS] = EP_CAPTURE;
8273
8274   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8275   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8276        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8277          
8278   if (fromY == DROP_RANK) {
8279         /* must be first */
8280         piece = board[toY][toX] = (ChessSquare) fromX;
8281   } else {
8282       int i;
8283
8284       if( board[fromY][fromX] == WhitePawn ) {
8285            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8286                board[EP_STATUS] = EP_PAWN_MOVE;
8287            if( toY-fromY==2) {
8288                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8289                         gameInfo.variant != VariantBerolina || toX < fromX)
8290                       board[EP_STATUS] = toX | berolina;
8291                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8292                         gameInfo.variant != VariantBerolina || toX > fromX)
8293                       board[EP_STATUS] = toX;
8294            }
8295       } else
8296       if( board[fromY][fromX] == BlackPawn ) {
8297            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8298                board[EP_STATUS] = EP_PAWN_MOVE;
8299            if( toY-fromY== -2) {
8300                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8301                         gameInfo.variant != VariantBerolina || toX < fromX)
8302                       board[EP_STATUS] = toX | berolina;
8303                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8304                         gameInfo.variant != VariantBerolina || toX > fromX)
8305                       board[EP_STATUS] = toX;
8306            }
8307        }
8308
8309        for(i=0; i<nrCastlingRights; i++) {
8310            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8311               board[CASTLING][i] == toX   && castlingRank[i] == toY
8312              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8313        }
8314
8315      if (fromX == toX && fromY == toY) return;
8316
8317      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8318      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8319      if(gameInfo.variant == VariantKnightmate)
8320          king += (int) WhiteUnicorn - (int) WhiteKing;
8321
8322     /* Code added by Tord: */
8323     /* FRC castling assumed when king captures friendly rook. */
8324     if (board[fromY][fromX] == WhiteKing &&
8325              board[toY][toX] == WhiteRook) {
8326       board[fromY][fromX] = EmptySquare;
8327       board[toY][toX] = EmptySquare;
8328       if(toX > fromX) {
8329         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8330       } else {
8331         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8332       }
8333     } else if (board[fromY][fromX] == BlackKing &&
8334                board[toY][toX] == BlackRook) {
8335       board[fromY][fromX] = EmptySquare;
8336       board[toY][toX] = EmptySquare;
8337       if(toX > fromX) {
8338         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8339       } else {
8340         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8341       }
8342     /* End of code added by Tord */
8343
8344     } else if (board[fromY][fromX] == king
8345         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8346         && toY == fromY && toX > fromX+1) {
8347         board[fromY][fromX] = EmptySquare;
8348         board[toY][toX] = king;
8349         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8350         board[fromY][BOARD_RGHT-1] = EmptySquare;
8351     } else if (board[fromY][fromX] == king
8352         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8353                && toY == fromY && toX < fromX-1) {
8354         board[fromY][fromX] = EmptySquare;
8355         board[toY][toX] = king;
8356         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8357         board[fromY][BOARD_LEFT] = EmptySquare;
8358     } else if (board[fromY][fromX] == WhitePawn
8359                && toY >= BOARD_HEIGHT-promoRank
8360                && gameInfo.variant != VariantXiangqi
8361                ) {
8362         /* white pawn promotion */
8363         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8364         if (board[toY][toX] == EmptySquare) {
8365             board[toY][toX] = WhiteQueen;
8366         }
8367         if(gameInfo.variant==VariantBughouse ||
8368            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8369             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8370         board[fromY][fromX] = EmptySquare;
8371     } else if ((fromY == BOARD_HEIGHT-4)
8372                && (toX != fromX)
8373                && gameInfo.variant != VariantXiangqi
8374                && gameInfo.variant != VariantBerolina
8375                && (board[fromY][fromX] == WhitePawn)
8376                && (board[toY][toX] == EmptySquare)) {
8377         board[fromY][fromX] = EmptySquare;
8378         board[toY][toX] = WhitePawn;
8379         captured = board[toY - 1][toX];
8380         board[toY - 1][toX] = EmptySquare;
8381     } else if ((fromY == BOARD_HEIGHT-4)
8382                && (toX == fromX)
8383                && gameInfo.variant == VariantBerolina
8384                && (board[fromY][fromX] == WhitePawn)
8385                && (board[toY][toX] == EmptySquare)) {
8386         board[fromY][fromX] = EmptySquare;
8387         board[toY][toX] = WhitePawn;
8388         if(oldEP & EP_BEROLIN_A) {
8389                 captured = board[fromY][fromX-1];
8390                 board[fromY][fromX-1] = EmptySquare;
8391         }else{  captured = board[fromY][fromX+1];
8392                 board[fromY][fromX+1] = EmptySquare;
8393         }
8394     } else if (board[fromY][fromX] == king
8395         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8396                && toY == fromY && toX > fromX+1) {
8397         board[fromY][fromX] = EmptySquare;
8398         board[toY][toX] = king;
8399         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8400         board[fromY][BOARD_RGHT-1] = EmptySquare;
8401     } else if (board[fromY][fromX] == king
8402         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8403                && toY == fromY && toX < fromX-1) {
8404         board[fromY][fromX] = EmptySquare;
8405         board[toY][toX] = king;
8406         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8407         board[fromY][BOARD_LEFT] = EmptySquare;
8408     } else if (fromY == 7 && fromX == 3
8409                && board[fromY][fromX] == BlackKing
8410                && toY == 7 && toX == 5) {
8411         board[fromY][fromX] = EmptySquare;
8412         board[toY][toX] = BlackKing;
8413         board[fromY][7] = EmptySquare;
8414         board[toY][4] = BlackRook;
8415     } else if (fromY == 7 && fromX == 3
8416                && board[fromY][fromX] == BlackKing
8417                && toY == 7 && toX == 1) {
8418         board[fromY][fromX] = EmptySquare;
8419         board[toY][toX] = BlackKing;
8420         board[fromY][0] = EmptySquare;
8421         board[toY][2] = BlackRook;
8422     } else if (board[fromY][fromX] == BlackPawn
8423                && toY < promoRank
8424                && gameInfo.variant != VariantXiangqi
8425                ) {
8426         /* black pawn promotion */
8427         board[toY][toX] = CharToPiece(ToLower(promoChar));
8428         if (board[toY][toX] == EmptySquare) {
8429             board[toY][toX] = BlackQueen;
8430         }
8431         if(gameInfo.variant==VariantBughouse ||
8432            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8433             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8434         board[fromY][fromX] = EmptySquare;
8435     } else if ((fromY == 3)
8436                && (toX != fromX)
8437                && gameInfo.variant != VariantXiangqi
8438                && gameInfo.variant != VariantBerolina
8439                && (board[fromY][fromX] == BlackPawn)
8440                && (board[toY][toX] == EmptySquare)) {
8441         board[fromY][fromX] = EmptySquare;
8442         board[toY][toX] = BlackPawn;
8443         captured = board[toY + 1][toX];
8444         board[toY + 1][toX] = EmptySquare;
8445     } else if ((fromY == 3)
8446                && (toX == fromX)
8447                && gameInfo.variant == VariantBerolina
8448                && (board[fromY][fromX] == BlackPawn)
8449                && (board[toY][toX] == EmptySquare)) {
8450         board[fromY][fromX] = EmptySquare;
8451         board[toY][toX] = BlackPawn;
8452         if(oldEP & EP_BEROLIN_A) {
8453                 captured = board[fromY][fromX-1];
8454                 board[fromY][fromX-1] = EmptySquare;
8455         }else{  captured = board[fromY][fromX+1];
8456                 board[fromY][fromX+1] = EmptySquare;
8457         }
8458     } else {
8459         board[toY][toX] = board[fromY][fromX];
8460         board[fromY][fromX] = EmptySquare;
8461     }
8462   }
8463
8464     if (gameInfo.holdingsWidth != 0) {
8465
8466       /* !!A lot more code needs to be written to support holdings  */
8467       /* [HGM] OK, so I have written it. Holdings are stored in the */
8468       /* penultimate board files, so they are automaticlly stored   */
8469       /* in the game history.                                       */
8470       if (fromY == DROP_RANK) {
8471         /* Delete from holdings, by decreasing count */
8472         /* and erasing image if necessary            */
8473         p = (int) fromX;
8474         if(p < (int) BlackPawn) { /* white drop */
8475              p -= (int)WhitePawn;
8476                  p = PieceToNumber((ChessSquare)p);
8477              if(p >= gameInfo.holdingsSize) p = 0;
8478              if(--board[p][BOARD_WIDTH-2] <= 0)
8479                   board[p][BOARD_WIDTH-1] = EmptySquare;
8480              if((int)board[p][BOARD_WIDTH-2] < 0)
8481                         board[p][BOARD_WIDTH-2] = 0;
8482         } else {                  /* black drop */
8483              p -= (int)BlackPawn;
8484                  p = PieceToNumber((ChessSquare)p);
8485              if(p >= gameInfo.holdingsSize) p = 0;
8486              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8487                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8488              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8489                         board[BOARD_HEIGHT-1-p][1] = 0;
8490         }
8491       }
8492       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8493           && gameInfo.variant != VariantBughouse        ) {
8494         /* [HGM] holdings: Add to holdings, if holdings exist */
8495         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8496                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8497                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8498         }
8499         p = (int) captured;
8500         if (p >= (int) BlackPawn) {
8501           p -= (int)BlackPawn;
8502           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8503                   /* in Shogi restore piece to its original  first */
8504                   captured = (ChessSquare) (DEMOTED captured);
8505                   p = DEMOTED p;
8506           }
8507           p = PieceToNumber((ChessSquare)p);
8508           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8509           board[p][BOARD_WIDTH-2]++;
8510           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8511         } else {
8512           p -= (int)WhitePawn;
8513           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8514                   captured = (ChessSquare) (DEMOTED captured);
8515                   p = DEMOTED p;
8516           }
8517           p = PieceToNumber((ChessSquare)p);
8518           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8519           board[BOARD_HEIGHT-1-p][1]++;
8520           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8521         }
8522       }
8523     } else if (gameInfo.variant == VariantAtomic) {
8524       if (captured != EmptySquare) {
8525         int y, x;
8526         for (y = toY-1; y <= toY+1; y++) {
8527           for (x = toX-1; x <= toX+1; x++) {
8528             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8529                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8530               board[y][x] = EmptySquare;
8531             }
8532           }
8533         }
8534         board[toY][toX] = EmptySquare;
8535       }
8536     }
8537     if(promoChar == '+') {
8538         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8539         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8540     } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
8541         board[toY][toX] = CharToPiece(promoChar);
8542     }
8543     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8544                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8545         // [HGM] superchess: take promotion piece out of holdings
8546         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8547         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8548             if(!--board[k][BOARD_WIDTH-2])
8549                 board[k][BOARD_WIDTH-1] = EmptySquare;
8550         } else {
8551             if(!--board[BOARD_HEIGHT-1-k][1])
8552                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8553         }
8554     }
8555
8556 }
8557
8558 /* Updates forwardMostMove */
8559 void
8560 MakeMove(fromX, fromY, toX, toY, promoChar)
8561      int fromX, fromY, toX, toY;
8562      int promoChar;
8563 {
8564 //    forwardMostMove++; // [HGM] bare: moved downstream
8565
8566     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8567         int timeLeft; static int lastLoadFlag=0; int king, piece;
8568         piece = boards[forwardMostMove][fromY][fromX];
8569         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8570         if(gameInfo.variant == VariantKnightmate)
8571             king += (int) WhiteUnicorn - (int) WhiteKing;
8572         if(forwardMostMove == 0) {
8573             if(blackPlaysFirst)
8574                 fprintf(serverMoves, "%s;", second.tidy);
8575             fprintf(serverMoves, "%s;", first.tidy);
8576             if(!blackPlaysFirst)
8577                 fprintf(serverMoves, "%s;", second.tidy);
8578         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8579         lastLoadFlag = loadFlag;
8580         // print base move
8581         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8582         // print castling suffix
8583         if( toY == fromY && piece == king ) {
8584             if(toX-fromX > 1)
8585                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8586             if(fromX-toX >1)
8587                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8588         }
8589         // e.p. suffix
8590         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8591              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8592              boards[forwardMostMove][toY][toX] == EmptySquare
8593              && fromX != toX && fromY != toY)
8594                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8595         // promotion suffix
8596         if(promoChar != NULLCHAR)
8597                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8598         if(!loadFlag) {
8599             fprintf(serverMoves, "/%d/%d",
8600                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8601             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8602             else                      timeLeft = blackTimeRemaining/1000;
8603             fprintf(serverMoves, "/%d", timeLeft);
8604         }
8605         fflush(serverMoves);
8606     }
8607
8608     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8609       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8610                         0, 1);
8611       return;
8612     }
8613     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8614     if (commentList[forwardMostMove+1] != NULL) {
8615         free(commentList[forwardMostMove+1]);
8616         commentList[forwardMostMove+1] = NULL;
8617     }
8618     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8619     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8620     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8621     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8622     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8623     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8624     gameInfo.result = GameUnfinished;
8625     if (gameInfo.resultDetails != NULL) {
8626         free(gameInfo.resultDetails);
8627         gameInfo.resultDetails = NULL;
8628     }
8629     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8630                               moveList[forwardMostMove - 1]);
8631     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8632                              PosFlags(forwardMostMove - 1),
8633                              fromY, fromX, toY, toX, promoChar,
8634                              parseList[forwardMostMove - 1]);
8635     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8636       case MT_NONE:
8637       case MT_STALEMATE:
8638       default:
8639         break;
8640       case MT_CHECK:
8641         if(gameInfo.variant != VariantShogi)
8642             strcat(parseList[forwardMostMove - 1], "+");
8643         break;
8644       case MT_CHECKMATE:
8645       case MT_STAINMATE:
8646         strcat(parseList[forwardMostMove - 1], "#");
8647         break;
8648     }
8649     if (appData.debugMode) {
8650         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8651     }
8652
8653 }
8654
8655 /* Updates currentMove if not pausing */
8656 void
8657 ShowMove(fromX, fromY, toX, toY)
8658 {
8659     int instant = (gameMode == PlayFromGameFile) ?
8660         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8661     if(appData.noGUI) return;
8662     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8663         if (!instant) {
8664             if (forwardMostMove == currentMove + 1) {
8665                 AnimateMove(boards[forwardMostMove - 1],
8666                             fromX, fromY, toX, toY);
8667             }
8668             if (appData.highlightLastMove) {
8669                 SetHighlights(fromX, fromY, toX, toY);
8670             }
8671         }
8672         currentMove = forwardMostMove;
8673     }
8674
8675     if (instant) return;
8676
8677     DisplayMove(currentMove - 1);
8678     DrawPosition(FALSE, boards[currentMove]);
8679     DisplayBothClocks();
8680     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8681 }
8682
8683 void SendEgtPath(ChessProgramState *cps)
8684 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8685         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8686
8687         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8688
8689         while(*p) {
8690             char c, *q = name+1, *r, *s;
8691
8692             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8693             while(*p && *p != ',') *q++ = *p++;
8694             *q++ = ':'; *q = 0;
8695             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8696                 strcmp(name, ",nalimov:") == 0 ) {
8697                 // take nalimov path from the menu-changeable option first, if it is defined
8698                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8699                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8700             } else
8701             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8702                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8703                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8704                 s = r = StrStr(s, ":") + 1; // beginning of path info
8705                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8706                 c = *r; *r = 0;             // temporarily null-terminate path info
8707                     *--q = 0;               // strip of trailig ':' from name
8708                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8709                 *r = c;
8710                 SendToProgram(buf,cps);     // send egtbpath command for this format
8711             }
8712             if(*p == ',') p++; // read away comma to position for next format name
8713         }
8714 }
8715
8716 void
8717 InitChessProgram(cps, setup)
8718      ChessProgramState *cps;
8719      int setup; /* [HGM] needed to setup FRC opening position */
8720 {
8721     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8722     if (appData.noChessProgram) return;
8723     hintRequested = FALSE;
8724     bookRequested = FALSE;
8725
8726     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8727     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8728     if(cps->memSize) { /* [HGM] memory */
8729         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8730         SendToProgram(buf, cps);
8731     }
8732     SendEgtPath(cps); /* [HGM] EGT */
8733     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8734         sprintf(buf, "cores %d\n", appData.smpCores);
8735         SendToProgram(buf, cps);
8736     }
8737
8738     SendToProgram(cps->initString, cps);
8739     if (gameInfo.variant != VariantNormal &&
8740         gameInfo.variant != VariantLoadable
8741         /* [HGM] also send variant if board size non-standard */
8742         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8743                                             ) {
8744       char *v = VariantName(gameInfo.variant);
8745       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8746         /* [HGM] in protocol 1 we have to assume all variants valid */
8747         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8748         DisplayFatalError(buf, 0, 1);
8749         return;
8750       }
8751
8752       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8753       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8754       if( gameInfo.variant == VariantXiangqi )
8755            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8756       if( gameInfo.variant == VariantShogi )
8757            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8758       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8759            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8760       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8761                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8762            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8763       if( gameInfo.variant == VariantCourier )
8764            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8765       if( gameInfo.variant == VariantSuper )
8766            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8767       if( gameInfo.variant == VariantGreat )
8768            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8769
8770       if(overruled) {
8771            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8772                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8773            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8774            if(StrStr(cps->variants, b) == NULL) {
8775                // specific sized variant not known, check if general sizing allowed
8776                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8777                    if(StrStr(cps->variants, "boardsize") == NULL) {
8778                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8779                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8780                        DisplayFatalError(buf, 0, 1);
8781                        return;
8782                    }
8783                    /* [HGM] here we really should compare with the maximum supported board size */
8784                }
8785            }
8786       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8787       sprintf(buf, "variant %s\n", b);
8788       SendToProgram(buf, cps);
8789     }
8790     currentlyInitializedVariant = gameInfo.variant;
8791
8792     /* [HGM] send opening position in FRC to first engine */
8793     if(setup) {
8794           SendToProgram("force\n", cps);
8795           SendBoard(cps, 0);
8796           /* engine is now in force mode! Set flag to wake it up after first move. */
8797           setboardSpoiledMachineBlack = 1;
8798     }
8799
8800     if (cps->sendICS) {
8801       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8802       SendToProgram(buf, cps);
8803     }
8804     cps->maybeThinking = FALSE;
8805     cps->offeredDraw = 0;
8806     if (!appData.icsActive) {
8807         SendTimeControl(cps, movesPerSession, timeControl,
8808                         timeIncrement, appData.searchDepth,
8809                         searchTime);
8810     }
8811     if (appData.showThinking
8812         // [HGM] thinking: four options require thinking output to be sent
8813         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8814                                 ) {
8815         SendToProgram("post\n", cps);
8816     }
8817     SendToProgram("hard\n", cps);
8818     if (!appData.ponderNextMove) {
8819         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8820            it without being sure what state we are in first.  "hard"
8821            is not a toggle, so that one is OK.
8822          */
8823         SendToProgram("easy\n", cps);
8824     }
8825     if (cps->usePing) {
8826       sprintf(buf, "ping %d\n", ++cps->lastPing);
8827       SendToProgram(buf, cps);
8828     }
8829     cps->initDone = TRUE;
8830 }
8831
8832
8833 void
8834 StartChessProgram(cps)
8835      ChessProgramState *cps;
8836 {
8837     char buf[MSG_SIZ];
8838     int err;
8839
8840     if (appData.noChessProgram) return;
8841     cps->initDone = FALSE;
8842
8843     if (strcmp(cps->host, "localhost") == 0) {
8844         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8845     } else if (*appData.remoteShell == NULLCHAR) {
8846         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8847     } else {
8848         if (*appData.remoteUser == NULLCHAR) {
8849           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8850                     cps->program);
8851         } else {
8852           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8853                     cps->host, appData.remoteUser, cps->program);
8854         }
8855         err = StartChildProcess(buf, "", &cps->pr);
8856     }
8857
8858     if (err != 0) {
8859         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8860         DisplayFatalError(buf, err, 1);
8861         cps->pr = NoProc;
8862         cps->isr = NULL;
8863         return;
8864     }
8865
8866     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8867     if (cps->protocolVersion > 1) {
8868       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8869       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8870       cps->comboCnt = 0;  //                and values of combo boxes
8871       SendToProgram(buf, cps);
8872     } else {
8873       SendToProgram("xboard\n", cps);
8874     }
8875 }
8876
8877
8878 void
8879 TwoMachinesEventIfReady P((void))
8880 {
8881   if (first.lastPing != first.lastPong) {
8882     DisplayMessage("", _("Waiting for first chess program"));
8883     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8884     return;
8885   }
8886   if (second.lastPing != second.lastPong) {
8887     DisplayMessage("", _("Waiting for second chess program"));
8888     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8889     return;
8890   }
8891   ThawUI();
8892   TwoMachinesEvent();
8893 }
8894
8895 void
8896 NextMatchGame P((void))
8897 {
8898     int index; /* [HGM] autoinc: step load index during match */
8899     Reset(FALSE, TRUE);
8900     if (*appData.loadGameFile != NULLCHAR) {
8901         index = appData.loadGameIndex;
8902         if(index < 0) { // [HGM] autoinc
8903             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8904             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8905         }
8906         LoadGameFromFile(appData.loadGameFile,
8907                          index,
8908                          appData.loadGameFile, FALSE);
8909     } else if (*appData.loadPositionFile != NULLCHAR) {
8910         index = appData.loadPositionIndex;
8911         if(index < 0) { // [HGM] autoinc
8912             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8913             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8914         }
8915         LoadPositionFromFile(appData.loadPositionFile,
8916                              index,
8917                              appData.loadPositionFile);
8918     }
8919     TwoMachinesEventIfReady();
8920 }
8921
8922 void UserAdjudicationEvent( int result )
8923 {
8924     ChessMove gameResult = GameIsDrawn;
8925
8926     if( result > 0 ) {
8927         gameResult = WhiteWins;
8928     }
8929     else if( result < 0 ) {
8930         gameResult = BlackWins;
8931     }
8932
8933     if( gameMode == TwoMachinesPlay ) {
8934         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8935     }
8936 }
8937
8938
8939 // [HGM] save: calculate checksum of game to make games easily identifiable
8940 int StringCheckSum(char *s)
8941 {
8942         int i = 0;
8943         if(s==NULL) return 0;
8944         while(*s) i = i*259 + *s++;
8945         return i;
8946 }
8947
8948 int GameCheckSum()
8949 {
8950         int i, sum=0;
8951         for(i=backwardMostMove; i<forwardMostMove; i++) {
8952                 sum += pvInfoList[i].depth;
8953                 sum += StringCheckSum(parseList[i]);
8954                 sum += StringCheckSum(commentList[i]);
8955                 sum *= 261;
8956         }
8957         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8958         return sum + StringCheckSum(commentList[i]);
8959 } // end of save patch
8960
8961 void
8962 GameEnds(result, resultDetails, whosays)
8963      ChessMove result;
8964      char *resultDetails;
8965      int whosays;
8966 {
8967     GameMode nextGameMode;
8968     int isIcsGame;
8969     char buf[MSG_SIZ], popupRequested = 0;
8970
8971     if(endingGame) return; /* [HGM] crash: forbid recursion */
8972     endingGame = 1;
8973     if(twoBoards) { // [HGM] dual: switch back to one board
8974         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8975         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8976     }
8977     if (appData.debugMode) {
8978       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8979               result, resultDetails ? resultDetails : "(null)", whosays);
8980     }
8981
8982     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8983
8984     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8985         /* If we are playing on ICS, the server decides when the
8986            game is over, but the engine can offer to draw, claim
8987            a draw, or resign.
8988          */
8989 #if ZIPPY
8990         if (appData.zippyPlay && first.initDone) {
8991             if (result == GameIsDrawn) {
8992                 /* In case draw still needs to be claimed */
8993                 SendToICS(ics_prefix);
8994                 SendToICS("draw\n");
8995             } else if (StrCaseStr(resultDetails, "resign")) {
8996                 SendToICS(ics_prefix);
8997                 SendToICS("resign\n");
8998             }
8999         }
9000 #endif
9001         endingGame = 0; /* [HGM] crash */
9002         return;
9003     }
9004
9005     /* If we're loading the game from a file, stop */
9006     if (whosays == GE_FILE) {
9007       (void) StopLoadGameTimer();
9008       gameFileFP = NULL;
9009     }
9010
9011     /* Cancel draw offers */
9012     first.offeredDraw = second.offeredDraw = 0;
9013
9014     /* If this is an ICS game, only ICS can really say it's done;
9015        if not, anyone can. */
9016     isIcsGame = (gameMode == IcsPlayingWhite ||
9017                  gameMode == IcsPlayingBlack ||
9018                  gameMode == IcsObserving    ||
9019                  gameMode == IcsExamining);
9020
9021     if (!isIcsGame || whosays == GE_ICS) {
9022         /* OK -- not an ICS game, or ICS said it was done */
9023         StopClocks();
9024         if (!isIcsGame && !appData.noChessProgram)
9025           SetUserThinkingEnables();
9026
9027         /* [HGM] if a machine claims the game end we verify this claim */
9028         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9029             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9030                 char claimer;
9031                 ChessMove trueResult = (ChessMove) -1;
9032
9033                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9034                                             first.twoMachinesColor[0] :
9035                                             second.twoMachinesColor[0] ;
9036
9037                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9038                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9039                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9040                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9041                 } else
9042                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9043                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9044                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9045                 } else
9046                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9047                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9048                 }
9049
9050                 // now verify win claims, but not in drop games, as we don't understand those yet
9051                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9052                                                  || gameInfo.variant == VariantGreat) &&
9053                     (result == WhiteWins && claimer == 'w' ||
9054                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9055                       if (appData.debugMode) {
9056                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9057                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9058                       }
9059                       if(result != trueResult) {
9060                               sprintf(buf, "False win claim: '%s'", resultDetails);
9061                               result = claimer == 'w' ? BlackWins : WhiteWins;
9062                               resultDetails = buf;
9063                       }
9064                 } else
9065                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9066                     && (forwardMostMove <= backwardMostMove ||
9067                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9068                         (claimer=='b')==(forwardMostMove&1))
9069                                                                                   ) {
9070                       /* [HGM] verify: draws that were not flagged are false claims */
9071                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9072                       result = claimer == 'w' ? BlackWins : WhiteWins;
9073                       resultDetails = buf;
9074                 }
9075                 /* (Claiming a loss is accepted no questions asked!) */
9076             }
9077             /* [HGM] bare: don't allow bare King to win */
9078             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9079                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9080                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9081                && result != GameIsDrawn)
9082             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9083                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9084                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9085                         if(p >= 0 && p <= (int)WhiteKing) k++;
9086                 }
9087                 if (appData.debugMode) {
9088                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9089                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9090                 }
9091                 if(k <= 1) {
9092                         result = GameIsDrawn;
9093                         sprintf(buf, "%s but bare king", resultDetails);
9094                         resultDetails = buf;
9095                 }
9096             }
9097         }
9098
9099
9100         if(serverMoves != NULL && !loadFlag) { char c = '=';
9101             if(result==WhiteWins) c = '+';
9102             if(result==BlackWins) c = '-';
9103             if(resultDetails != NULL)
9104                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9105         }
9106         if (resultDetails != NULL) {
9107             gameInfo.result = result;
9108             gameInfo.resultDetails = StrSave(resultDetails);
9109
9110             /* display last move only if game was not loaded from file */
9111             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9112                 DisplayMove(currentMove - 1);
9113
9114             if (forwardMostMove != 0) {
9115                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9116                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9117                                                                 ) {
9118                     if (*appData.saveGameFile != NULLCHAR) {
9119                         SaveGameToFile(appData.saveGameFile, TRUE);
9120                     } else if (appData.autoSaveGames) {
9121                         AutoSaveGame();
9122                     }
9123                     if (*appData.savePositionFile != NULLCHAR) {
9124                         SavePositionToFile(appData.savePositionFile);
9125                     }
9126                 }
9127             }
9128
9129             /* Tell program how game ended in case it is learning */
9130             /* [HGM] Moved this to after saving the PGN, just in case */
9131             /* engine died and we got here through time loss. In that */
9132             /* case we will get a fatal error writing the pipe, which */
9133             /* would otherwise lose us the PGN.                       */
9134             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9135             /* output during GameEnds should never be fatal anymore   */
9136             if (gameMode == MachinePlaysWhite ||
9137                 gameMode == MachinePlaysBlack ||
9138                 gameMode == TwoMachinesPlay ||
9139                 gameMode == IcsPlayingWhite ||
9140                 gameMode == IcsPlayingBlack ||
9141                 gameMode == BeginningOfGame) {
9142                 char buf[MSG_SIZ];
9143                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9144                         resultDetails);
9145                 if (first.pr != NoProc) {
9146                     SendToProgram(buf, &first);
9147                 }
9148                 if (second.pr != NoProc &&
9149                     gameMode == TwoMachinesPlay) {
9150                     SendToProgram(buf, &second);
9151                 }
9152             }
9153         }
9154
9155         if (appData.icsActive) {
9156             if (appData.quietPlay &&
9157                 (gameMode == IcsPlayingWhite ||
9158                  gameMode == IcsPlayingBlack)) {
9159                 SendToICS(ics_prefix);
9160                 SendToICS("set shout 1\n");
9161             }
9162             nextGameMode = IcsIdle;
9163             ics_user_moved = FALSE;
9164             /* clean up premove.  It's ugly when the game has ended and the
9165              * premove highlights are still on the board.
9166              */
9167             if (gotPremove) {
9168               gotPremove = FALSE;
9169               ClearPremoveHighlights();
9170               DrawPosition(FALSE, boards[currentMove]);
9171             }
9172             if (whosays == GE_ICS) {
9173                 switch (result) {
9174                 case WhiteWins:
9175                     if (gameMode == IcsPlayingWhite)
9176                         PlayIcsWinSound();
9177                     else if(gameMode == IcsPlayingBlack)
9178                         PlayIcsLossSound();
9179                     break;
9180                 case BlackWins:
9181                     if (gameMode == IcsPlayingBlack)
9182                         PlayIcsWinSound();
9183                     else if(gameMode == IcsPlayingWhite)
9184                         PlayIcsLossSound();
9185                     break;
9186                 case GameIsDrawn:
9187                     PlayIcsDrawSound();
9188                     break;
9189                 default:
9190                     PlayIcsUnfinishedSound();
9191                 }
9192             }
9193         } else if (gameMode == EditGame ||
9194                    gameMode == PlayFromGameFile ||
9195                    gameMode == AnalyzeMode ||
9196                    gameMode == AnalyzeFile) {
9197             nextGameMode = gameMode;
9198         } else {
9199             nextGameMode = EndOfGame;
9200         }
9201         pausing = FALSE;
9202         ModeHighlight();
9203     } else {
9204         nextGameMode = gameMode;
9205     }
9206
9207     if (appData.noChessProgram) {
9208         gameMode = nextGameMode;
9209         ModeHighlight();
9210         endingGame = 0; /* [HGM] crash */
9211         return;
9212     }
9213
9214     if (first.reuse) {
9215         /* Put first chess program into idle state */
9216         if (first.pr != NoProc &&
9217             (gameMode == MachinePlaysWhite ||
9218              gameMode == MachinePlaysBlack ||
9219              gameMode == TwoMachinesPlay ||
9220              gameMode == IcsPlayingWhite ||
9221              gameMode == IcsPlayingBlack ||
9222              gameMode == BeginningOfGame)) {
9223             SendToProgram("force\n", &first);
9224             if (first.usePing) {
9225               char buf[MSG_SIZ];
9226               sprintf(buf, "ping %d\n", ++first.lastPing);
9227               SendToProgram(buf, &first);
9228             }
9229         }
9230     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9231         /* Kill off first chess program */
9232         if (first.isr != NULL)
9233           RemoveInputSource(first.isr);
9234         first.isr = NULL;
9235
9236         if (first.pr != NoProc) {
9237             ExitAnalyzeMode();
9238             DoSleep( appData.delayBeforeQuit );
9239             SendToProgram("quit\n", &first);
9240             DoSleep( appData.delayAfterQuit );
9241             DestroyChildProcess(first.pr, first.useSigterm);
9242         }
9243         first.pr = NoProc;
9244     }
9245     if (second.reuse) {
9246         /* Put second chess program into idle state */
9247         if (second.pr != NoProc &&
9248             gameMode == TwoMachinesPlay) {
9249             SendToProgram("force\n", &second);
9250             if (second.usePing) {
9251               char buf[MSG_SIZ];
9252               sprintf(buf, "ping %d\n", ++second.lastPing);
9253               SendToProgram(buf, &second);
9254             }
9255         }
9256     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9257         /* Kill off second chess program */
9258         if (second.isr != NULL)
9259           RemoveInputSource(second.isr);
9260         second.isr = NULL;
9261
9262         if (second.pr != NoProc) {
9263             DoSleep( appData.delayBeforeQuit );
9264             SendToProgram("quit\n", &second);
9265             DoSleep( appData.delayAfterQuit );
9266             DestroyChildProcess(second.pr, second.useSigterm);
9267         }
9268         second.pr = NoProc;
9269     }
9270
9271     if (matchMode && gameMode == TwoMachinesPlay) {
9272         switch (result) {
9273         case WhiteWins:
9274           if (first.twoMachinesColor[0] == 'w') {
9275             first.matchWins++;
9276           } else {
9277             second.matchWins++;
9278           }
9279           break;
9280         case BlackWins:
9281           if (first.twoMachinesColor[0] == 'b') {
9282             first.matchWins++;
9283           } else {
9284             second.matchWins++;
9285           }
9286           break;
9287         default:
9288           break;
9289         }
9290         if (matchGame < appData.matchGames) {
9291             char *tmp;
9292             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9293                 tmp = first.twoMachinesColor;
9294                 first.twoMachinesColor = second.twoMachinesColor;
9295                 second.twoMachinesColor = tmp;
9296             }
9297             gameMode = nextGameMode;
9298             matchGame++;
9299             if(appData.matchPause>10000 || appData.matchPause<10)
9300                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9301             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9302             endingGame = 0; /* [HGM] crash */
9303             return;
9304         } else {
9305             gameMode = nextGameMode;
9306             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9307                     first.tidy, second.tidy,
9308                     first.matchWins, second.matchWins,
9309                     appData.matchGames - (first.matchWins + second.matchWins));
9310             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9311         }
9312     }
9313     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9314         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9315       ExitAnalyzeMode();
9316     gameMode = nextGameMode;
9317     ModeHighlight();
9318     endingGame = 0;  /* [HGM] crash */
9319     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9320       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9321         matchMode = FALSE; appData.matchGames = matchGame = 0;
9322         DisplayNote(buf);
9323       }
9324     }
9325 }
9326
9327 /* Assumes program was just initialized (initString sent).
9328    Leaves program in force mode. */
9329 void
9330 FeedMovesToProgram(cps, upto)
9331      ChessProgramState *cps;
9332      int upto;
9333 {
9334     int i;
9335
9336     if (appData.debugMode)
9337       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9338               startedFromSetupPosition ? "position and " : "",
9339               backwardMostMove, upto, cps->which);
9340     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9341         // [HGM] variantswitch: make engine aware of new variant
9342         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9343                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9344         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9345         SendToProgram(buf, cps);
9346         currentlyInitializedVariant = gameInfo.variant;
9347     }
9348     SendToProgram("force\n", cps);
9349     if (startedFromSetupPosition) {
9350         SendBoard(cps, backwardMostMove);
9351     if (appData.debugMode) {
9352         fprintf(debugFP, "feedMoves\n");
9353     }
9354     }
9355     for (i = backwardMostMove; i < upto; i++) {
9356         SendMoveToProgram(i, cps);
9357     }
9358 }
9359
9360
9361 void
9362 ResurrectChessProgram()
9363 {
9364      /* The chess program may have exited.
9365         If so, restart it and feed it all the moves made so far. */
9366
9367     if (appData.noChessProgram || first.pr != NoProc) return;
9368
9369     StartChessProgram(&first);
9370     InitChessProgram(&first, FALSE);
9371     FeedMovesToProgram(&first, currentMove);
9372
9373     if (!first.sendTime) {
9374         /* can't tell gnuchess what its clock should read,
9375            so we bow to its notion. */
9376         ResetClocks();
9377         timeRemaining[0][currentMove] = whiteTimeRemaining;
9378         timeRemaining[1][currentMove] = blackTimeRemaining;
9379     }
9380
9381     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9382                 appData.icsEngineAnalyze) && first.analysisSupport) {
9383       SendToProgram("analyze\n", &first);
9384       first.analyzing = TRUE;
9385     }
9386 }
9387
9388 /*
9389  * Button procedures
9390  */
9391 void
9392 Reset(redraw, init)
9393      int redraw, init;
9394 {
9395     int i;
9396
9397     if (appData.debugMode) {
9398         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9399                 redraw, init, gameMode);
9400     }
9401     CleanupTail(); // [HGM] vari: delete any stored variations
9402     pausing = pauseExamInvalid = FALSE;
9403     startedFromSetupPosition = blackPlaysFirst = FALSE;
9404     firstMove = TRUE;
9405     whiteFlag = blackFlag = FALSE;
9406     userOfferedDraw = FALSE;
9407     hintRequested = bookRequested = FALSE;
9408     first.maybeThinking = FALSE;
9409     second.maybeThinking = FALSE;
9410     first.bookSuspend = FALSE; // [HGM] book
9411     second.bookSuspend = FALSE;
9412     thinkOutput[0] = NULLCHAR;
9413     lastHint[0] = NULLCHAR;
9414     ClearGameInfo(&gameInfo);
9415     gameInfo.variant = StringToVariant(appData.variant);
9416     ics_user_moved = ics_clock_paused = FALSE;
9417     ics_getting_history = H_FALSE;
9418     ics_gamenum = -1;
9419     white_holding[0] = black_holding[0] = NULLCHAR;
9420     ClearProgramStats();
9421     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9422
9423     ResetFrontEnd();
9424     ClearHighlights();
9425     flipView = appData.flipView;
9426     ClearPremoveHighlights();
9427     gotPremove = FALSE;
9428     alarmSounded = FALSE;
9429
9430     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9431     if(appData.serverMovesName != NULL) {
9432         /* [HGM] prepare to make moves file for broadcasting */
9433         clock_t t = clock();
9434         if(serverMoves != NULL) fclose(serverMoves);
9435         serverMoves = fopen(appData.serverMovesName, "r");
9436         if(serverMoves != NULL) {
9437             fclose(serverMoves);
9438             /* delay 15 sec before overwriting, so all clients can see end */
9439             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9440         }
9441         serverMoves = fopen(appData.serverMovesName, "w");
9442     }
9443
9444     ExitAnalyzeMode();
9445     gameMode = BeginningOfGame;
9446     ModeHighlight();
9447     if(appData.icsActive) gameInfo.variant = VariantNormal;
9448     currentMove = forwardMostMove = backwardMostMove = 0;
9449     InitPosition(redraw);
9450     for (i = 0; i < MAX_MOVES; i++) {
9451         if (commentList[i] != NULL) {
9452             free(commentList[i]);
9453             commentList[i] = NULL;
9454         }
9455     }
9456     ResetClocks();
9457     timeRemaining[0][0] = whiteTimeRemaining;
9458     timeRemaining[1][0] = blackTimeRemaining;
9459     if (first.pr == NULL) {
9460         StartChessProgram(&first);
9461     }
9462     if (init) {
9463             InitChessProgram(&first, startedFromSetupPosition);
9464     }
9465     DisplayTitle("");
9466     DisplayMessage("", "");
9467     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9468     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9469 }
9470
9471 void
9472 AutoPlayGameLoop()
9473 {
9474     for (;;) {
9475         if (!AutoPlayOneMove())
9476           return;
9477         if (matchMode || appData.timeDelay == 0)
9478           continue;
9479         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9480           return;
9481         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9482         break;
9483     }
9484 }
9485
9486
9487 int
9488 AutoPlayOneMove()
9489 {
9490     int fromX, fromY, toX, toY;
9491
9492     if (appData.debugMode) {
9493       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9494     }
9495
9496     if (gameMode != PlayFromGameFile)
9497       return FALSE;
9498
9499     if (currentMove >= forwardMostMove) {
9500       gameMode = EditGame;
9501       ModeHighlight();
9502
9503       /* [AS] Clear current move marker at the end of a game */
9504       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9505
9506       return FALSE;
9507     }
9508
9509     toX = moveList[currentMove][2] - AAA;
9510     toY = moveList[currentMove][3] - ONE;
9511
9512     if (moveList[currentMove][1] == '@') {
9513         if (appData.highlightLastMove) {
9514             SetHighlights(-1, -1, toX, toY);
9515         }
9516     } else {
9517         fromX = moveList[currentMove][0] - AAA;
9518         fromY = moveList[currentMove][1] - ONE;
9519
9520         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9521
9522         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9523
9524         if (appData.highlightLastMove) {
9525             SetHighlights(fromX, fromY, toX, toY);
9526         }
9527     }
9528     DisplayMove(currentMove);
9529     SendMoveToProgram(currentMove++, &first);
9530     DisplayBothClocks();
9531     DrawPosition(FALSE, boards[currentMove]);
9532     // [HGM] PV info: always display, routine tests if empty
9533     DisplayComment(currentMove - 1, commentList[currentMove]);
9534     return TRUE;
9535 }
9536
9537
9538 int
9539 LoadGameOneMove(readAhead)
9540      ChessMove readAhead;
9541 {
9542     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9543     char promoChar = NULLCHAR;
9544     ChessMove moveType;
9545     char move[MSG_SIZ];
9546     char *p, *q;
9547
9548     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9549         gameMode != AnalyzeMode && gameMode != Training) {
9550         gameFileFP = NULL;
9551         return FALSE;
9552     }
9553
9554     yyboardindex = forwardMostMove;
9555     if (readAhead != (ChessMove)0) {
9556       moveType = readAhead;
9557     } else {
9558       if (gameFileFP == NULL)
9559           return FALSE;
9560       moveType = (ChessMove) yylex();
9561     }
9562
9563     done = FALSE;
9564     switch (moveType) {
9565       case Comment:
9566         if (appData.debugMode)
9567           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9568         p = yy_text;
9569
9570         /* append the comment but don't display it */
9571         AppendComment(currentMove, p, FALSE);
9572         return TRUE;
9573
9574       case WhiteCapturesEnPassant:
9575       case BlackCapturesEnPassant:
9576       case WhitePromotion:
9577       case BlackPromotion:
9578       case WhiteNonPromotion:
9579       case BlackNonPromotion:
9580       case NormalMove:
9581       case WhiteKingSideCastle:
9582       case WhiteQueenSideCastle:
9583       case BlackKingSideCastle:
9584       case BlackQueenSideCastle:
9585       case WhiteKingSideCastleWild:
9586       case WhiteQueenSideCastleWild:
9587       case BlackKingSideCastleWild:
9588       case BlackQueenSideCastleWild:
9589       /* PUSH Fabien */
9590       case WhiteHSideCastleFR:
9591       case WhiteASideCastleFR:
9592       case BlackHSideCastleFR:
9593       case BlackASideCastleFR:
9594       /* POP Fabien */
9595         if (appData.debugMode)
9596           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9597         fromX = currentMoveString[0] - AAA;
9598         fromY = currentMoveString[1] - ONE;
9599         toX = currentMoveString[2] - AAA;
9600         toY = currentMoveString[3] - ONE;
9601         promoChar = currentMoveString[4];
9602         break;
9603
9604       case WhiteDrop:
9605       case BlackDrop:
9606         if (appData.debugMode)
9607           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9608         fromX = moveType == WhiteDrop ?
9609           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9610         (int) CharToPiece(ToLower(currentMoveString[0]));
9611         fromY = DROP_RANK;
9612         toX = currentMoveString[2] - AAA;
9613         toY = currentMoveString[3] - ONE;
9614         break;
9615
9616       case WhiteWins:
9617       case BlackWins:
9618       case GameIsDrawn:
9619       case GameUnfinished:
9620         if (appData.debugMode)
9621           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9622         p = strchr(yy_text, '{');
9623         if (p == NULL) p = strchr(yy_text, '(');
9624         if (p == NULL) {
9625             p = yy_text;
9626             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9627         } else {
9628             q = strchr(p, *p == '{' ? '}' : ')');
9629             if (q != NULL) *q = NULLCHAR;
9630             p++;
9631         }
9632         GameEnds(moveType, p, GE_FILE);
9633         done = TRUE;
9634         if (cmailMsgLoaded) {
9635             ClearHighlights();
9636             flipView = WhiteOnMove(currentMove);
9637             if (moveType == GameUnfinished) flipView = !flipView;
9638             if (appData.debugMode)
9639               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9640         }
9641         break;
9642
9643       case (ChessMove) 0:       /* end of file */
9644         if (appData.debugMode)
9645           fprintf(debugFP, "Parser hit end of file\n");
9646         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9647           case MT_NONE:
9648           case MT_CHECK:
9649             break;
9650           case MT_CHECKMATE:
9651           case MT_STAINMATE:
9652             if (WhiteOnMove(currentMove)) {
9653                 GameEnds(BlackWins, "Black mates", GE_FILE);
9654             } else {
9655                 GameEnds(WhiteWins, "White mates", GE_FILE);
9656             }
9657             break;
9658           case MT_STALEMATE:
9659             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9660             break;
9661         }
9662         done = TRUE;
9663         break;
9664
9665       case MoveNumberOne:
9666         if (lastLoadGameStart == GNUChessGame) {
9667             /* GNUChessGames have numbers, but they aren't move numbers */
9668             if (appData.debugMode)
9669               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9670                       yy_text, (int) moveType);
9671             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9672         }
9673         /* else fall thru */
9674
9675       case XBoardGame:
9676       case GNUChessGame:
9677       case PGNTag:
9678         /* Reached start of next game in file */
9679         if (appData.debugMode)
9680           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9681         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9682           case MT_NONE:
9683           case MT_CHECK:
9684             break;
9685           case MT_CHECKMATE:
9686           case MT_STAINMATE:
9687             if (WhiteOnMove(currentMove)) {
9688                 GameEnds(BlackWins, "Black mates", GE_FILE);
9689             } else {
9690                 GameEnds(WhiteWins, "White mates", GE_FILE);
9691             }
9692             break;
9693           case MT_STALEMATE:
9694             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9695             break;
9696         }
9697         done = TRUE;
9698         break;
9699
9700       case PositionDiagram:     /* should not happen; ignore */
9701       case ElapsedTime:         /* ignore */
9702       case NAG:                 /* ignore */
9703         if (appData.debugMode)
9704           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9705                   yy_text, (int) moveType);
9706         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9707
9708       case IllegalMove:
9709         if (appData.testLegality) {
9710             if (appData.debugMode)
9711               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9712             sprintf(move, _("Illegal move: %d.%s%s"),
9713                     (forwardMostMove / 2) + 1,
9714                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9715             DisplayError(move, 0);
9716             done = TRUE;
9717         } else {
9718             if (appData.debugMode)
9719               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9720                       yy_text, currentMoveString);
9721             fromX = currentMoveString[0] - AAA;
9722             fromY = currentMoveString[1] - ONE;
9723             toX = currentMoveString[2] - AAA;
9724             toY = currentMoveString[3] - ONE;
9725             promoChar = currentMoveString[4];
9726         }
9727         break;
9728
9729       case AmbiguousMove:
9730         if (appData.debugMode)
9731           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9732         sprintf(move, _("Ambiguous move: %d.%s%s"),
9733                 (forwardMostMove / 2) + 1,
9734                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9735         DisplayError(move, 0);
9736         done = TRUE;
9737         break;
9738
9739       default:
9740       case ImpossibleMove:
9741         if (appData.debugMode)
9742           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9743         sprintf(move, _("Illegal move: %d.%s%s"),
9744                 (forwardMostMove / 2) + 1,
9745                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9746         DisplayError(move, 0);
9747         done = TRUE;
9748         break;
9749     }
9750
9751     if (done) {
9752         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9753             DrawPosition(FALSE, boards[currentMove]);
9754             DisplayBothClocks();
9755             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9756               DisplayComment(currentMove - 1, commentList[currentMove]);
9757         }
9758         (void) StopLoadGameTimer();
9759         gameFileFP = NULL;
9760         cmailOldMove = forwardMostMove;
9761         return FALSE;
9762     } else {
9763         /* currentMoveString is set as a side-effect of yylex */
9764         strcat(currentMoveString, "\n");
9765         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9766
9767         thinkOutput[0] = NULLCHAR;
9768         MakeMove(fromX, fromY, toX, toY, promoChar);
9769         currentMove = forwardMostMove;
9770         return TRUE;
9771     }
9772 }
9773
9774 /* Load the nth game from the given file */
9775 int
9776 LoadGameFromFile(filename, n, title, useList)
9777      char *filename;
9778      int n;
9779      char *title;
9780      /*Boolean*/ int useList;
9781 {
9782     FILE *f;
9783     char buf[MSG_SIZ];
9784
9785     if (strcmp(filename, "-") == 0) {
9786         f = stdin;
9787         title = "stdin";
9788     } else {
9789         f = fopen(filename, "rb");
9790         if (f == NULL) {
9791           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9792             DisplayError(buf, errno);
9793             return FALSE;
9794         }
9795     }
9796     if (fseek(f, 0, 0) == -1) {
9797         /* f is not seekable; probably a pipe */
9798         useList = FALSE;
9799     }
9800     if (useList && n == 0) {
9801         int error = GameListBuild(f);
9802         if (error) {
9803             DisplayError(_("Cannot build game list"), error);
9804         } else if (!ListEmpty(&gameList) &&
9805                    ((ListGame *) gameList.tailPred)->number > 1) {
9806             GameListPopUp(f, title);
9807             return TRUE;
9808         }
9809         GameListDestroy();
9810         n = 1;
9811     }
9812     if (n == 0) n = 1;
9813     return LoadGame(f, n, title, FALSE);
9814 }
9815
9816
9817 void
9818 MakeRegisteredMove()
9819 {
9820     int fromX, fromY, toX, toY;
9821     char promoChar;
9822     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9823         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9824           case CMAIL_MOVE:
9825           case CMAIL_DRAW:
9826             if (appData.debugMode)
9827               fprintf(debugFP, "Restoring %s for game %d\n",
9828                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9829
9830             thinkOutput[0] = NULLCHAR;
9831             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9832             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9833             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9834             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9835             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9836             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9837             MakeMove(fromX, fromY, toX, toY, promoChar);
9838             ShowMove(fromX, fromY, toX, toY);
9839
9840             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9841               case MT_NONE:
9842               case MT_CHECK:
9843                 break;
9844
9845               case MT_CHECKMATE:
9846               case MT_STAINMATE:
9847                 if (WhiteOnMove(currentMove)) {
9848                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9849                 } else {
9850                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9851                 }
9852                 break;
9853
9854               case MT_STALEMATE:
9855                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9856                 break;
9857             }
9858
9859             break;
9860
9861           case CMAIL_RESIGN:
9862             if (WhiteOnMove(currentMove)) {
9863                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9864             } else {
9865                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9866             }
9867             break;
9868
9869           case CMAIL_ACCEPT:
9870             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9871             break;
9872
9873           default:
9874             break;
9875         }
9876     }
9877
9878     return;
9879 }
9880
9881 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9882 int
9883 CmailLoadGame(f, gameNumber, title, useList)
9884      FILE *f;
9885      int gameNumber;
9886      char *title;
9887      int useList;
9888 {
9889     int retVal;
9890
9891     if (gameNumber > nCmailGames) {
9892         DisplayError(_("No more games in this message"), 0);
9893         return FALSE;
9894     }
9895     if (f == lastLoadGameFP) {
9896         int offset = gameNumber - lastLoadGameNumber;
9897         if (offset == 0) {
9898             cmailMsg[0] = NULLCHAR;
9899             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9900                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9901                 nCmailMovesRegistered--;
9902             }
9903             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9904             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9905                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9906             }
9907         } else {
9908             if (! RegisterMove()) return FALSE;
9909         }
9910     }
9911
9912     retVal = LoadGame(f, gameNumber, title, useList);
9913
9914     /* Make move registered during previous look at this game, if any */
9915     MakeRegisteredMove();
9916
9917     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9918         commentList[currentMove]
9919           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9920         DisplayComment(currentMove - 1, commentList[currentMove]);
9921     }
9922
9923     return retVal;
9924 }
9925
9926 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9927 int
9928 ReloadGame(offset)
9929      int offset;
9930 {
9931     int gameNumber = lastLoadGameNumber + offset;
9932     if (lastLoadGameFP == NULL) {
9933         DisplayError(_("No game has been loaded yet"), 0);
9934         return FALSE;
9935     }
9936     if (gameNumber <= 0) {
9937         DisplayError(_("Can't back up any further"), 0);
9938         return FALSE;
9939     }
9940     if (cmailMsgLoaded) {
9941         return CmailLoadGame(lastLoadGameFP, gameNumber,
9942                              lastLoadGameTitle, lastLoadGameUseList);
9943     } else {
9944         return LoadGame(lastLoadGameFP, gameNumber,
9945                         lastLoadGameTitle, lastLoadGameUseList);
9946     }
9947 }
9948
9949
9950
9951 /* Load the nth game from open file f */
9952 int
9953 LoadGame(f, gameNumber, title, useList)
9954      FILE *f;
9955      int gameNumber;
9956      char *title;
9957      int useList;
9958 {
9959     ChessMove cm;
9960     char buf[MSG_SIZ];
9961     int gn = gameNumber;
9962     ListGame *lg = NULL;
9963     int numPGNTags = 0;
9964     int err;
9965     GameMode oldGameMode;
9966     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9967
9968     if (appData.debugMode)
9969         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9970
9971     if (gameMode == Training )
9972         SetTrainingModeOff();
9973
9974     oldGameMode = gameMode;
9975     if (gameMode != BeginningOfGame) {
9976       Reset(FALSE, TRUE);
9977     }
9978
9979     gameFileFP = f;
9980     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9981         fclose(lastLoadGameFP);
9982     }
9983
9984     if (useList) {
9985         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9986
9987         if (lg) {
9988             fseek(f, lg->offset, 0);
9989             GameListHighlight(gameNumber);
9990             gn = 1;
9991         }
9992         else {
9993             DisplayError(_("Game number out of range"), 0);
9994             return FALSE;
9995         }
9996     } else {
9997         GameListDestroy();
9998         if (fseek(f, 0, 0) == -1) {
9999             if (f == lastLoadGameFP ?
10000                 gameNumber == lastLoadGameNumber + 1 :
10001                 gameNumber == 1) {
10002                 gn = 1;
10003             } else {
10004                 DisplayError(_("Can't seek on game file"), 0);
10005                 return FALSE;
10006             }
10007         }
10008     }
10009     lastLoadGameFP = f;
10010     lastLoadGameNumber = gameNumber;
10011     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10012     lastLoadGameUseList = useList;
10013
10014     yynewfile(f);
10015
10016     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10017       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10018                 lg->gameInfo.black);
10019             DisplayTitle(buf);
10020     } else if (*title != NULLCHAR) {
10021         if (gameNumber > 1) {
10022             sprintf(buf, "%s %d", title, gameNumber);
10023             DisplayTitle(buf);
10024         } else {
10025             DisplayTitle(title);
10026         }
10027     }
10028
10029     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10030         gameMode = PlayFromGameFile;
10031         ModeHighlight();
10032     }
10033
10034     currentMove = forwardMostMove = backwardMostMove = 0;
10035     CopyBoard(boards[0], initialPosition);
10036     StopClocks();
10037
10038     /*
10039      * Skip the first gn-1 games in the file.
10040      * Also skip over anything that precedes an identifiable
10041      * start of game marker, to avoid being confused by
10042      * garbage at the start of the file.  Currently
10043      * recognized start of game markers are the move number "1",
10044      * the pattern "gnuchess .* game", the pattern
10045      * "^[#;%] [^ ]* game file", and a PGN tag block.
10046      * A game that starts with one of the latter two patterns
10047      * will also have a move number 1, possibly
10048      * following a position diagram.
10049      * 5-4-02: Let's try being more lenient and allowing a game to
10050      * start with an unnumbered move.  Does that break anything?
10051      */
10052     cm = lastLoadGameStart = (ChessMove) 0;
10053     while (gn > 0) {
10054         yyboardindex = forwardMostMove;
10055         cm = (ChessMove) yylex();
10056         switch (cm) {
10057           case (ChessMove) 0:
10058             if (cmailMsgLoaded) {
10059                 nCmailGames = CMAIL_MAX_GAMES - gn;
10060             } else {
10061                 Reset(TRUE, TRUE);
10062                 DisplayError(_("Game not found in file"), 0);
10063             }
10064             return FALSE;
10065
10066           case GNUChessGame:
10067           case XBoardGame:
10068             gn--;
10069             lastLoadGameStart = cm;
10070             break;
10071
10072           case MoveNumberOne:
10073             switch (lastLoadGameStart) {
10074               case GNUChessGame:
10075               case XBoardGame:
10076               case PGNTag:
10077                 break;
10078               case MoveNumberOne:
10079               case (ChessMove) 0:
10080                 gn--;           /* count this game */
10081                 lastLoadGameStart = cm;
10082                 break;
10083               default:
10084                 /* impossible */
10085                 break;
10086             }
10087             break;
10088
10089           case PGNTag:
10090             switch (lastLoadGameStart) {
10091               case GNUChessGame:
10092               case PGNTag:
10093               case MoveNumberOne:
10094               case (ChessMove) 0:
10095                 gn--;           /* count this game */
10096                 lastLoadGameStart = cm;
10097                 break;
10098               case XBoardGame:
10099                 lastLoadGameStart = cm; /* game counted already */
10100                 break;
10101               default:
10102                 /* impossible */
10103                 break;
10104             }
10105             if (gn > 0) {
10106                 do {
10107                     yyboardindex = forwardMostMove;
10108                     cm = (ChessMove) yylex();
10109                 } while (cm == PGNTag || cm == Comment);
10110             }
10111             break;
10112
10113           case WhiteWins:
10114           case BlackWins:
10115           case GameIsDrawn:
10116             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10117                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10118                     != CMAIL_OLD_RESULT) {
10119                     nCmailResults ++ ;
10120                     cmailResult[  CMAIL_MAX_GAMES
10121                                 - gn - 1] = CMAIL_OLD_RESULT;
10122                 }
10123             }
10124             break;
10125
10126           case NormalMove:
10127             /* Only a NormalMove can be at the start of a game
10128              * without a position diagram. */
10129             if (lastLoadGameStart == (ChessMove) 0) {
10130               gn--;
10131               lastLoadGameStart = MoveNumberOne;
10132             }
10133             break;
10134
10135           default:
10136             break;
10137         }
10138     }
10139
10140     if (appData.debugMode)
10141       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10142
10143     if (cm == XBoardGame) {
10144         /* Skip any header junk before position diagram and/or move 1 */
10145         for (;;) {
10146             yyboardindex = forwardMostMove;
10147             cm = (ChessMove) yylex();
10148
10149             if (cm == (ChessMove) 0 ||
10150                 cm == GNUChessGame || cm == XBoardGame) {
10151                 /* Empty game; pretend end-of-file and handle later */
10152                 cm = (ChessMove) 0;
10153                 break;
10154             }
10155
10156             if (cm == MoveNumberOne || cm == PositionDiagram ||
10157                 cm == PGNTag || cm == Comment)
10158               break;
10159         }
10160     } else if (cm == GNUChessGame) {
10161         if (gameInfo.event != NULL) {
10162             free(gameInfo.event);
10163         }
10164         gameInfo.event = StrSave(yy_text);
10165     }
10166
10167     startedFromSetupPosition = FALSE;
10168     while (cm == PGNTag) {
10169         if (appData.debugMode)
10170           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10171         err = ParsePGNTag(yy_text, &gameInfo);
10172         if (!err) numPGNTags++;
10173
10174         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10175         if(gameInfo.variant != oldVariant) {
10176             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10177             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10178             InitPosition(TRUE);
10179             oldVariant = gameInfo.variant;
10180             if (appData.debugMode)
10181               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10182         }
10183
10184
10185         if (gameInfo.fen != NULL) {
10186           Board initial_position;
10187           startedFromSetupPosition = TRUE;
10188           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10189             Reset(TRUE, TRUE);
10190             DisplayError(_("Bad FEN position in file"), 0);
10191             return FALSE;
10192           }
10193           CopyBoard(boards[0], initial_position);
10194           if (blackPlaysFirst) {
10195             currentMove = forwardMostMove = backwardMostMove = 1;
10196             CopyBoard(boards[1], initial_position);
10197             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10198             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10199             timeRemaining[0][1] = whiteTimeRemaining;
10200             timeRemaining[1][1] = blackTimeRemaining;
10201             if (commentList[0] != NULL) {
10202               commentList[1] = commentList[0];
10203               commentList[0] = NULL;
10204             }
10205           } else {
10206             currentMove = forwardMostMove = backwardMostMove = 0;
10207           }
10208           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10209           {   int i;
10210               initialRulePlies = FENrulePlies;
10211               for( i=0; i< nrCastlingRights; i++ )
10212                   initialRights[i] = initial_position[CASTLING][i];
10213           }
10214           yyboardindex = forwardMostMove;
10215           free(gameInfo.fen);
10216           gameInfo.fen = NULL;
10217         }
10218
10219         yyboardindex = forwardMostMove;
10220         cm = (ChessMove) yylex();
10221
10222         /* Handle comments interspersed among the tags */
10223         while (cm == Comment) {
10224             char *p;
10225             if (appData.debugMode)
10226               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10227             p = yy_text;
10228             AppendComment(currentMove, p, FALSE);
10229             yyboardindex = forwardMostMove;
10230             cm = (ChessMove) yylex();
10231         }
10232     }
10233
10234     /* don't rely on existence of Event tag since if game was
10235      * pasted from clipboard the Event tag may not exist
10236      */
10237     if (numPGNTags > 0){
10238         char *tags;
10239         if (gameInfo.variant == VariantNormal) {
10240           VariantClass v = StringToVariant(gameInfo.event);
10241           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10242           if(v < VariantShogi) gameInfo.variant = v;
10243         }
10244         if (!matchMode) {
10245           if( appData.autoDisplayTags ) {
10246             tags = PGNTags(&gameInfo);
10247             TagsPopUp(tags, CmailMsg());
10248             free(tags);
10249           }
10250         }
10251     } else {
10252         /* Make something up, but don't display it now */
10253         SetGameInfo();
10254         TagsPopDown();
10255     }
10256
10257     if (cm == PositionDiagram) {
10258         int i, j;
10259         char *p;
10260         Board initial_position;
10261
10262         if (appData.debugMode)
10263           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10264
10265         if (!startedFromSetupPosition) {
10266             p = yy_text;
10267             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10268               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10269                 switch (*p) {
10270                   case '[':
10271                   case '-':
10272                   case ' ':
10273                   case '\t':
10274                   case '\n':
10275                   case '\r':
10276                     break;
10277                   default:
10278                     initial_position[i][j++] = CharToPiece(*p);
10279                     break;
10280                 }
10281             while (*p == ' ' || *p == '\t' ||
10282                    *p == '\n' || *p == '\r') p++;
10283
10284             if (strncmp(p, "black", strlen("black"))==0)
10285               blackPlaysFirst = TRUE;
10286             else
10287               blackPlaysFirst = FALSE;
10288             startedFromSetupPosition = TRUE;
10289
10290             CopyBoard(boards[0], initial_position);
10291             if (blackPlaysFirst) {
10292                 currentMove = forwardMostMove = backwardMostMove = 1;
10293                 CopyBoard(boards[1], initial_position);
10294                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10295                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10296                 timeRemaining[0][1] = whiteTimeRemaining;
10297                 timeRemaining[1][1] = blackTimeRemaining;
10298                 if (commentList[0] != NULL) {
10299                     commentList[1] = commentList[0];
10300                     commentList[0] = NULL;
10301                 }
10302             } else {
10303                 currentMove = forwardMostMove = backwardMostMove = 0;
10304             }
10305         }
10306         yyboardindex = forwardMostMove;
10307         cm = (ChessMove) yylex();
10308     }
10309
10310     if (first.pr == NoProc) {
10311         StartChessProgram(&first);
10312     }
10313     InitChessProgram(&first, FALSE);
10314     SendToProgram("force\n", &first);
10315     if (startedFromSetupPosition) {
10316         SendBoard(&first, forwardMostMove);
10317     if (appData.debugMode) {
10318         fprintf(debugFP, "Load Game\n");
10319     }
10320         DisplayBothClocks();
10321     }
10322
10323     /* [HGM] server: flag to write setup moves in broadcast file as one */
10324     loadFlag = appData.suppressLoadMoves;
10325
10326     while (cm == Comment) {
10327         char *p;
10328         if (appData.debugMode)
10329           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10330         p = yy_text;
10331         AppendComment(currentMove, p, FALSE);
10332         yyboardindex = forwardMostMove;
10333         cm = (ChessMove) yylex();
10334     }
10335
10336     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10337         cm == WhiteWins || cm == BlackWins ||
10338         cm == GameIsDrawn || cm == GameUnfinished) {
10339         DisplayMessage("", _("No moves in game"));
10340         if (cmailMsgLoaded) {
10341             if (appData.debugMode)
10342               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10343             ClearHighlights();
10344             flipView = FALSE;
10345         }
10346         DrawPosition(FALSE, boards[currentMove]);
10347         DisplayBothClocks();
10348         gameMode = EditGame;
10349         ModeHighlight();
10350         gameFileFP = NULL;
10351         cmailOldMove = 0;
10352         return TRUE;
10353     }
10354
10355     // [HGM] PV info: routine tests if comment empty
10356     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10357         DisplayComment(currentMove - 1, commentList[currentMove]);
10358     }
10359     if (!matchMode && appData.timeDelay != 0)
10360       DrawPosition(FALSE, boards[currentMove]);
10361
10362     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10363       programStats.ok_to_send = 1;
10364     }
10365
10366     /* if the first token after the PGN tags is a move
10367      * and not move number 1, retrieve it from the parser
10368      */
10369     if (cm != MoveNumberOne)
10370         LoadGameOneMove(cm);
10371
10372     /* load the remaining moves from the file */
10373     while (LoadGameOneMove((ChessMove)0)) {
10374       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10375       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10376     }
10377
10378     /* rewind to the start of the game */
10379     currentMove = backwardMostMove;
10380
10381     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10382
10383     if (oldGameMode == AnalyzeFile ||
10384         oldGameMode == AnalyzeMode) {
10385       AnalyzeFileEvent();
10386     }
10387
10388     if (matchMode || appData.timeDelay == 0) {
10389       ToEndEvent();
10390       gameMode = EditGame;
10391       ModeHighlight();
10392     } else if (appData.timeDelay > 0) {
10393       AutoPlayGameLoop();
10394     }
10395
10396     if (appData.debugMode)
10397         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10398
10399     loadFlag = 0; /* [HGM] true game starts */
10400     return TRUE;
10401 }
10402
10403 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10404 int
10405 ReloadPosition(offset)
10406      int offset;
10407 {
10408     int positionNumber = lastLoadPositionNumber + offset;
10409     if (lastLoadPositionFP == NULL) {
10410         DisplayError(_("No position has been loaded yet"), 0);
10411         return FALSE;
10412     }
10413     if (positionNumber <= 0) {
10414         DisplayError(_("Can't back up any further"), 0);
10415         return FALSE;
10416     }
10417     return LoadPosition(lastLoadPositionFP, positionNumber,
10418                         lastLoadPositionTitle);
10419 }
10420
10421 /* Load the nth position from the given file */
10422 int
10423 LoadPositionFromFile(filename, n, title)
10424      char *filename;
10425      int n;
10426      char *title;
10427 {
10428     FILE *f;
10429     char buf[MSG_SIZ];
10430
10431     if (strcmp(filename, "-") == 0) {
10432         return LoadPosition(stdin, n, "stdin");
10433     } else {
10434         f = fopen(filename, "rb");
10435         if (f == NULL) {
10436             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10437             DisplayError(buf, errno);
10438             return FALSE;
10439         } else {
10440             return LoadPosition(f, n, title);
10441         }
10442     }
10443 }
10444
10445 /* Load the nth position from the given open file, and close it */
10446 int
10447 LoadPosition(f, positionNumber, title)
10448      FILE *f;
10449      int positionNumber;
10450      char *title;
10451 {
10452     char *p, line[MSG_SIZ];
10453     Board initial_position;
10454     int i, j, fenMode, pn;
10455
10456     if (gameMode == Training )
10457         SetTrainingModeOff();
10458
10459     if (gameMode != BeginningOfGame) {
10460         Reset(FALSE, TRUE);
10461     }
10462     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10463         fclose(lastLoadPositionFP);
10464     }
10465     if (positionNumber == 0) positionNumber = 1;
10466     lastLoadPositionFP = f;
10467     lastLoadPositionNumber = positionNumber;
10468     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10469     if (first.pr == NoProc) {
10470       StartChessProgram(&first);
10471       InitChessProgram(&first, FALSE);
10472     }
10473     pn = positionNumber;
10474     if (positionNumber < 0) {
10475         /* Negative position number means to seek to that byte offset */
10476         if (fseek(f, -positionNumber, 0) == -1) {
10477             DisplayError(_("Can't seek on position file"), 0);
10478             return FALSE;
10479         };
10480         pn = 1;
10481     } else {
10482         if (fseek(f, 0, 0) == -1) {
10483             if (f == lastLoadPositionFP ?
10484                 positionNumber == lastLoadPositionNumber + 1 :
10485                 positionNumber == 1) {
10486                 pn = 1;
10487             } else {
10488                 DisplayError(_("Can't seek on position file"), 0);
10489                 return FALSE;
10490             }
10491         }
10492     }
10493     /* See if this file is FEN or old-style xboard */
10494     if (fgets(line, MSG_SIZ, f) == NULL) {
10495         DisplayError(_("Position not found in file"), 0);
10496         return FALSE;
10497     }
10498     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10499     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10500
10501     if (pn >= 2) {
10502         if (fenMode || line[0] == '#') pn--;
10503         while (pn > 0) {
10504             /* skip positions before number pn */
10505             if (fgets(line, MSG_SIZ, f) == NULL) {
10506                 Reset(TRUE, TRUE);
10507                 DisplayError(_("Position not found in file"), 0);
10508                 return FALSE;
10509             }
10510             if (fenMode || line[0] == '#') pn--;
10511         }
10512     }
10513
10514     if (fenMode) {
10515         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10516             DisplayError(_("Bad FEN position in file"), 0);
10517             return FALSE;
10518         }
10519     } else {
10520         (void) fgets(line, MSG_SIZ, f);
10521         (void) fgets(line, MSG_SIZ, f);
10522
10523         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10524             (void) fgets(line, MSG_SIZ, f);
10525             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10526                 if (*p == ' ')
10527                   continue;
10528                 initial_position[i][j++] = CharToPiece(*p);
10529             }
10530         }
10531
10532         blackPlaysFirst = FALSE;
10533         if (!feof(f)) {
10534             (void) fgets(line, MSG_SIZ, f);
10535             if (strncmp(line, "black", strlen("black"))==0)
10536               blackPlaysFirst = TRUE;
10537         }
10538     }
10539     startedFromSetupPosition = TRUE;
10540
10541     SendToProgram("force\n", &first);
10542     CopyBoard(boards[0], initial_position);
10543     if (blackPlaysFirst) {
10544         currentMove = forwardMostMove = backwardMostMove = 1;
10545         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10546         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10547         CopyBoard(boards[1], initial_position);
10548         DisplayMessage("", _("Black to play"));
10549     } else {
10550         currentMove = forwardMostMove = backwardMostMove = 0;
10551         DisplayMessage("", _("White to play"));
10552     }
10553     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10554     SendBoard(&first, forwardMostMove);
10555     if (appData.debugMode) {
10556 int i, j;
10557   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10558   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10559         fprintf(debugFP, "Load Position\n");
10560     }
10561
10562     if (positionNumber > 1) {
10563         sprintf(line, "%s %d", title, positionNumber);
10564         DisplayTitle(line);
10565     } else {
10566         DisplayTitle(title);
10567     }
10568     gameMode = EditGame;
10569     ModeHighlight();
10570     ResetClocks();
10571     timeRemaining[0][1] = whiteTimeRemaining;
10572     timeRemaining[1][1] = blackTimeRemaining;
10573     DrawPosition(FALSE, boards[currentMove]);
10574
10575     return TRUE;
10576 }
10577
10578
10579 void
10580 CopyPlayerNameIntoFileName(dest, src)
10581      char **dest, *src;
10582 {
10583     while (*src != NULLCHAR && *src != ',') {
10584         if (*src == ' ') {
10585             *(*dest)++ = '_';
10586             src++;
10587         } else {
10588             *(*dest)++ = *src++;
10589         }
10590     }
10591 }
10592
10593 char *DefaultFileName(ext)
10594      char *ext;
10595 {
10596     static char def[MSG_SIZ];
10597     char *p;
10598
10599     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10600         p = def;
10601         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10602         *p++ = '-';
10603         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10604         *p++ = '.';
10605         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10606     } else {
10607         def[0] = NULLCHAR;
10608     }
10609     return def;
10610 }
10611
10612 /* Save the current game to the given file */
10613 int
10614 SaveGameToFile(filename, append)
10615      char *filename;
10616      int append;
10617 {
10618     FILE *f;
10619     char buf[MSG_SIZ];
10620
10621     if (strcmp(filename, "-") == 0) {
10622         return SaveGame(stdout, 0, NULL);
10623     } else {
10624         f = fopen(filename, append ? "a" : "w");
10625         if (f == NULL) {
10626             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10627             DisplayError(buf, errno);
10628             return FALSE;
10629         } else {
10630             return SaveGame(f, 0, NULL);
10631         }
10632     }
10633 }
10634
10635 char *
10636 SavePart(str)
10637      char *str;
10638 {
10639     static char buf[MSG_SIZ];
10640     char *p;
10641
10642     p = strchr(str, ' ');
10643     if (p == NULL) return str;
10644     strncpy(buf, str, p - str);
10645     buf[p - str] = NULLCHAR;
10646     return buf;
10647 }
10648
10649 #define PGN_MAX_LINE 75
10650
10651 #define PGN_SIDE_WHITE  0
10652 #define PGN_SIDE_BLACK  1
10653
10654 /* [AS] */
10655 static int FindFirstMoveOutOfBook( int side )
10656 {
10657     int result = -1;
10658
10659     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10660         int index = backwardMostMove;
10661         int has_book_hit = 0;
10662
10663         if( (index % 2) != side ) {
10664             index++;
10665         }
10666
10667         while( index < forwardMostMove ) {
10668             /* Check to see if engine is in book */
10669             int depth = pvInfoList[index].depth;
10670             int score = pvInfoList[index].score;
10671             int in_book = 0;
10672
10673             if( depth <= 2 ) {
10674                 in_book = 1;
10675             }
10676             else if( score == 0 && depth == 63 ) {
10677                 in_book = 1; /* Zappa */
10678             }
10679             else if( score == 2 && depth == 99 ) {
10680                 in_book = 1; /* Abrok */
10681             }
10682
10683             has_book_hit += in_book;
10684
10685             if( ! in_book ) {
10686                 result = index;
10687
10688                 break;
10689             }
10690
10691             index += 2;
10692         }
10693     }
10694
10695     return result;
10696 }
10697
10698 /* [AS] */
10699 void GetOutOfBookInfo( char * buf )
10700 {
10701     int oob[2];
10702     int i;
10703     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10704
10705     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10706     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10707
10708     *buf = '\0';
10709
10710     if( oob[0] >= 0 || oob[1] >= 0 ) {
10711         for( i=0; i<2; i++ ) {
10712             int idx = oob[i];
10713
10714             if( idx >= 0 ) {
10715                 if( i > 0 && oob[0] >= 0 ) {
10716                     strcat( buf, "   " );
10717                 }
10718
10719                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10720                 sprintf( buf+strlen(buf), "%s%.2f",
10721                     pvInfoList[idx].score >= 0 ? "+" : "",
10722                     pvInfoList[idx].score / 100.0 );
10723             }
10724         }
10725     }
10726 }
10727
10728 /* Save game in PGN style and close the file */
10729 int
10730 SaveGamePGN(f)
10731      FILE *f;
10732 {
10733     int i, offset, linelen, newblock;
10734     time_t tm;
10735 //    char *movetext;
10736     char numtext[32];
10737     int movelen, numlen, blank;
10738     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10739
10740     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10741
10742     tm = time((time_t *) NULL);
10743
10744     PrintPGNTags(f, &gameInfo);
10745
10746     if (backwardMostMove > 0 || startedFromSetupPosition) {
10747         char *fen = PositionToFEN(backwardMostMove, NULL);
10748         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10749         fprintf(f, "\n{--------------\n");
10750         PrintPosition(f, backwardMostMove);
10751         fprintf(f, "--------------}\n");
10752         free(fen);
10753     }
10754     else {
10755         /* [AS] Out of book annotation */
10756         if( appData.saveOutOfBookInfo ) {
10757             char buf[64];
10758
10759             GetOutOfBookInfo( buf );
10760
10761             if( buf[0] != '\0' ) {
10762                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10763             }
10764         }
10765
10766         fprintf(f, "\n");
10767     }
10768
10769     i = backwardMostMove;
10770     linelen = 0;
10771     newblock = TRUE;
10772
10773     while (i < forwardMostMove) {
10774         /* Print comments preceding this move */
10775         if (commentList[i] != NULL) {
10776             if (linelen > 0) fprintf(f, "\n");
10777             fprintf(f, "%s", commentList[i]);
10778             linelen = 0;
10779             newblock = TRUE;
10780         }
10781
10782         /* Format move number */
10783         if ((i % 2) == 0) {
10784             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10785         } else {
10786             if (newblock) {
10787                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10788             } else {
10789                 numtext[0] = NULLCHAR;
10790             }
10791         }
10792         numlen = strlen(numtext);
10793         newblock = FALSE;
10794
10795         /* Print move number */
10796         blank = linelen > 0 && numlen > 0;
10797         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10798             fprintf(f, "\n");
10799             linelen = 0;
10800             blank = 0;
10801         }
10802         if (blank) {
10803             fprintf(f, " ");
10804             linelen++;
10805         }
10806         fprintf(f, "%s", numtext);
10807         linelen += numlen;
10808
10809         /* Get move */
10810         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10811         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10812
10813         /* Print move */
10814         blank = linelen > 0 && movelen > 0;
10815         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10816             fprintf(f, "\n");
10817             linelen = 0;
10818             blank = 0;
10819         }
10820         if (blank) {
10821             fprintf(f, " ");
10822             linelen++;
10823         }
10824         fprintf(f, "%s", move_buffer);
10825         linelen += movelen;
10826
10827         /* [AS] Add PV info if present */
10828         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10829             /* [HGM] add time */
10830             char buf[MSG_SIZ]; int seconds;
10831
10832             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10833
10834             if( seconds <= 0) buf[0] = 0; else
10835             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10836                 seconds = (seconds + 4)/10; // round to full seconds
10837                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10838                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10839             }
10840
10841             sprintf( move_buffer, "{%s%.2f/%d%s}",
10842                 pvInfoList[i].score >= 0 ? "+" : "",
10843                 pvInfoList[i].score / 100.0,
10844                 pvInfoList[i].depth,
10845                 buf );
10846
10847             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10848
10849             /* Print score/depth */
10850             blank = linelen > 0 && movelen > 0;
10851             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10852                 fprintf(f, "\n");
10853                 linelen = 0;
10854                 blank = 0;
10855             }
10856             if (blank) {
10857                 fprintf(f, " ");
10858                 linelen++;
10859             }
10860             fprintf(f, "%s", move_buffer);
10861             linelen += movelen;
10862         }
10863
10864         i++;
10865     }
10866
10867     /* Start a new line */
10868     if (linelen > 0) fprintf(f, "\n");
10869
10870     /* Print comments after last move */
10871     if (commentList[i] != NULL) {
10872         fprintf(f, "%s\n", commentList[i]);
10873     }
10874
10875     /* Print result */
10876     if (gameInfo.resultDetails != NULL &&
10877         gameInfo.resultDetails[0] != NULLCHAR) {
10878         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10879                 PGNResult(gameInfo.result));
10880     } else {
10881         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10882     }
10883
10884     fclose(f);
10885     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10886     return TRUE;
10887 }
10888
10889 /* Save game in old style and close the file */
10890 int
10891 SaveGameOldStyle(f)
10892      FILE *f;
10893 {
10894     int i, offset;
10895     time_t tm;
10896
10897     tm = time((time_t *) NULL);
10898
10899     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10900     PrintOpponents(f);
10901
10902     if (backwardMostMove > 0 || startedFromSetupPosition) {
10903         fprintf(f, "\n[--------------\n");
10904         PrintPosition(f, backwardMostMove);
10905         fprintf(f, "--------------]\n");
10906     } else {
10907         fprintf(f, "\n");
10908     }
10909
10910     i = backwardMostMove;
10911     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10912
10913     while (i < forwardMostMove) {
10914         if (commentList[i] != NULL) {
10915             fprintf(f, "[%s]\n", commentList[i]);
10916         }
10917
10918         if ((i % 2) == 1) {
10919             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10920             i++;
10921         } else {
10922             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10923             i++;
10924             if (commentList[i] != NULL) {
10925                 fprintf(f, "\n");
10926                 continue;
10927             }
10928             if (i >= forwardMostMove) {
10929                 fprintf(f, "\n");
10930                 break;
10931             }
10932             fprintf(f, "%s\n", parseList[i]);
10933             i++;
10934         }
10935     }
10936
10937     if (commentList[i] != NULL) {
10938         fprintf(f, "[%s]\n", commentList[i]);
10939     }
10940
10941     /* This isn't really the old style, but it's close enough */
10942     if (gameInfo.resultDetails != NULL &&
10943         gameInfo.resultDetails[0] != NULLCHAR) {
10944         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10945                 gameInfo.resultDetails);
10946     } else {
10947         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10948     }
10949
10950     fclose(f);
10951     return TRUE;
10952 }
10953
10954 /* Save the current game to open file f and close the file */
10955 int
10956 SaveGame(f, dummy, dummy2)
10957      FILE *f;
10958      int dummy;
10959      char *dummy2;
10960 {
10961     if (gameMode == EditPosition) EditPositionDone(TRUE);
10962     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10963     if (appData.oldSaveStyle)
10964       return SaveGameOldStyle(f);
10965     else
10966       return SaveGamePGN(f);
10967 }
10968
10969 /* Save the current position to the given file */
10970 int
10971 SavePositionToFile(filename)
10972      char *filename;
10973 {
10974     FILE *f;
10975     char buf[MSG_SIZ];
10976
10977     if (strcmp(filename, "-") == 0) {
10978         return SavePosition(stdout, 0, NULL);
10979     } else {
10980         f = fopen(filename, "a");
10981         if (f == NULL) {
10982             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10983             DisplayError(buf, errno);
10984             return FALSE;
10985         } else {
10986             SavePosition(f, 0, NULL);
10987             return TRUE;
10988         }
10989     }
10990 }
10991
10992 /* Save the current position to the given open file and close the file */
10993 int
10994 SavePosition(f, dummy, dummy2)
10995      FILE *f;
10996      int dummy;
10997      char *dummy2;
10998 {
10999     time_t tm;
11000     char *fen;
11001
11002     if (gameMode == EditPosition) EditPositionDone(TRUE);
11003     if (appData.oldSaveStyle) {
11004         tm = time((time_t *) NULL);
11005
11006         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11007         PrintOpponents(f);
11008         fprintf(f, "[--------------\n");
11009         PrintPosition(f, currentMove);
11010         fprintf(f, "--------------]\n");
11011     } else {
11012         fen = PositionToFEN(currentMove, NULL);
11013         fprintf(f, "%s\n", fen);
11014         free(fen);
11015     }
11016     fclose(f);
11017     return TRUE;
11018 }
11019
11020 void
11021 ReloadCmailMsgEvent(unregister)
11022      int unregister;
11023 {
11024 #if !WIN32
11025     static char *inFilename = NULL;
11026     static char *outFilename;
11027     int i;
11028     struct stat inbuf, outbuf;
11029     int status;
11030
11031     /* Any registered moves are unregistered if unregister is set, */
11032     /* i.e. invoked by the signal handler */
11033     if (unregister) {
11034         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11035             cmailMoveRegistered[i] = FALSE;
11036             if (cmailCommentList[i] != NULL) {
11037                 free(cmailCommentList[i]);
11038                 cmailCommentList[i] = NULL;
11039             }
11040         }
11041         nCmailMovesRegistered = 0;
11042     }
11043
11044     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11045         cmailResult[i] = CMAIL_NOT_RESULT;
11046     }
11047     nCmailResults = 0;
11048
11049     if (inFilename == NULL) {
11050         /* Because the filenames are static they only get malloced once  */
11051         /* and they never get freed                                      */
11052         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11053         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11054
11055         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11056         sprintf(outFilename, "%s.out", appData.cmailGameName);
11057     }
11058
11059     status = stat(outFilename, &outbuf);
11060     if (status < 0) {
11061         cmailMailedMove = FALSE;
11062     } else {
11063         status = stat(inFilename, &inbuf);
11064         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11065     }
11066
11067     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11068        counts the games, notes how each one terminated, etc.
11069
11070        It would be nice to remove this kludge and instead gather all
11071        the information while building the game list.  (And to keep it
11072        in the game list nodes instead of having a bunch of fixed-size
11073        parallel arrays.)  Note this will require getting each game's
11074        termination from the PGN tags, as the game list builder does
11075        not process the game moves.  --mann
11076        */
11077     cmailMsgLoaded = TRUE;
11078     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11079
11080     /* Load first game in the file or popup game menu */
11081     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11082
11083 #endif /* !WIN32 */
11084     return;
11085 }
11086
11087 int
11088 RegisterMove()
11089 {
11090     FILE *f;
11091     char string[MSG_SIZ];
11092
11093     if (   cmailMailedMove
11094         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11095         return TRUE;            /* Allow free viewing  */
11096     }
11097
11098     /* Unregister move to ensure that we don't leave RegisterMove        */
11099     /* with the move registered when the conditions for registering no   */
11100     /* longer hold                                                       */
11101     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11102         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11103         nCmailMovesRegistered --;
11104
11105         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11106           {
11107               free(cmailCommentList[lastLoadGameNumber - 1]);
11108               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11109           }
11110     }
11111
11112     if (cmailOldMove == -1) {
11113         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11114         return FALSE;
11115     }
11116
11117     if (currentMove > cmailOldMove + 1) {
11118         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11119         return FALSE;
11120     }
11121
11122     if (currentMove < cmailOldMove) {
11123         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11124         return FALSE;
11125     }
11126
11127     if (forwardMostMove > currentMove) {
11128         /* Silently truncate extra moves */
11129         TruncateGame();
11130     }
11131
11132     if (   (currentMove == cmailOldMove + 1)
11133         || (   (currentMove == cmailOldMove)
11134             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11135                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11136         if (gameInfo.result != GameUnfinished) {
11137             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11138         }
11139
11140         if (commentList[currentMove] != NULL) {
11141             cmailCommentList[lastLoadGameNumber - 1]
11142               = StrSave(commentList[currentMove]);
11143         }
11144         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11145
11146         if (appData.debugMode)
11147           fprintf(debugFP, "Saving %s for game %d\n",
11148                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11149
11150         sprintf(string,
11151                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11152
11153         f = fopen(string, "w");
11154         if (appData.oldSaveStyle) {
11155             SaveGameOldStyle(f); /* also closes the file */
11156
11157             sprintf(string, "%s.pos.out", appData.cmailGameName);
11158             f = fopen(string, "w");
11159             SavePosition(f, 0, NULL); /* also closes the file */
11160         } else {
11161             fprintf(f, "{--------------\n");
11162             PrintPosition(f, currentMove);
11163             fprintf(f, "--------------}\n\n");
11164
11165             SaveGame(f, 0, NULL); /* also closes the file*/
11166         }
11167
11168         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11169         nCmailMovesRegistered ++;
11170     } else if (nCmailGames == 1) {
11171         DisplayError(_("You have not made a move yet"), 0);
11172         return FALSE;
11173     }
11174
11175     return TRUE;
11176 }
11177
11178 void
11179 MailMoveEvent()
11180 {
11181 #if !WIN32
11182     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11183     FILE *commandOutput;
11184     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11185     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11186     int nBuffers;
11187     int i;
11188     int archived;
11189     char *arcDir;
11190
11191     if (! cmailMsgLoaded) {
11192         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11193         return;
11194     }
11195
11196     if (nCmailGames == nCmailResults) {
11197         DisplayError(_("No unfinished games"), 0);
11198         return;
11199     }
11200
11201 #if CMAIL_PROHIBIT_REMAIL
11202     if (cmailMailedMove) {
11203         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);
11204         DisplayError(msg, 0);
11205         return;
11206     }
11207 #endif
11208
11209     if (! (cmailMailedMove || RegisterMove())) return;
11210
11211     if (   cmailMailedMove
11212         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11213         sprintf(string, partCommandString,
11214                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11215         commandOutput = popen(string, "r");
11216
11217         if (commandOutput == NULL) {
11218             DisplayError(_("Failed to invoke cmail"), 0);
11219         } else {
11220             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11221                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11222             }
11223             if (nBuffers > 1) {
11224                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11225                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11226                 nBytes = MSG_SIZ - 1;
11227             } else {
11228                 (void) memcpy(msg, buffer, nBytes);
11229             }
11230             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11231
11232             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11233                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11234
11235                 archived = TRUE;
11236                 for (i = 0; i < nCmailGames; i ++) {
11237                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11238                         archived = FALSE;
11239                     }
11240                 }
11241                 if (   archived
11242                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11243                         != NULL)) {
11244                     sprintf(buffer, "%s/%s.%s.archive",
11245                             arcDir,
11246                             appData.cmailGameName,
11247                             gameInfo.date);
11248                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11249                     cmailMsgLoaded = FALSE;
11250                 }
11251             }
11252
11253             DisplayInformation(msg);
11254             pclose(commandOutput);
11255         }
11256     } else {
11257         if ((*cmailMsg) != '\0') {
11258             DisplayInformation(cmailMsg);
11259         }
11260     }
11261
11262     return;
11263 #endif /* !WIN32 */
11264 }
11265
11266 char *
11267 CmailMsg()
11268 {
11269 #if WIN32
11270     return NULL;
11271 #else
11272     int  prependComma = 0;
11273     char number[5];
11274     char string[MSG_SIZ];       /* Space for game-list */
11275     int  i;
11276
11277     if (!cmailMsgLoaded) return "";
11278
11279     if (cmailMailedMove) {
11280         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11281     } else {
11282         /* Create a list of games left */
11283         sprintf(string, "[");
11284         for (i = 0; i < nCmailGames; i ++) {
11285             if (! (   cmailMoveRegistered[i]
11286                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11287                 if (prependComma) {
11288                     sprintf(number, ",%d", i + 1);
11289                 } else {
11290                     sprintf(number, "%d", i + 1);
11291                     prependComma = 1;
11292                 }
11293
11294                 strcat(string, number);
11295             }
11296         }
11297         strcat(string, "]");
11298
11299         if (nCmailMovesRegistered + nCmailResults == 0) {
11300             switch (nCmailGames) {
11301               case 1:
11302                 sprintf(cmailMsg,
11303                         _("Still need to make move for game\n"));
11304                 break;
11305
11306               case 2:
11307                 sprintf(cmailMsg,
11308                         _("Still need to make moves for both games\n"));
11309                 break;
11310
11311               default:
11312                 sprintf(cmailMsg,
11313                         _("Still need to make moves for all %d games\n"),
11314                         nCmailGames);
11315                 break;
11316             }
11317         } else {
11318             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11319               case 1:
11320                 sprintf(cmailMsg,
11321                         _("Still need to make a move for game %s\n"),
11322                         string);
11323                 break;
11324
11325               case 0:
11326                 if (nCmailResults == nCmailGames) {
11327                     sprintf(cmailMsg, _("No unfinished games\n"));
11328                 } else {
11329                     sprintf(cmailMsg, _("Ready to send mail\n"));
11330                 }
11331                 break;
11332
11333               default:
11334                 sprintf(cmailMsg,
11335                         _("Still need to make moves for games %s\n"),
11336                         string);
11337             }
11338         }
11339     }
11340     return cmailMsg;
11341 #endif /* WIN32 */
11342 }
11343
11344 void
11345 ResetGameEvent()
11346 {
11347     if (gameMode == Training)
11348       SetTrainingModeOff();
11349
11350     Reset(TRUE, TRUE);
11351     cmailMsgLoaded = FALSE;
11352     if (appData.icsActive) {
11353       SendToICS(ics_prefix);
11354       SendToICS("refresh\n");
11355     }
11356 }
11357
11358 void
11359 ExitEvent(status)
11360      int status;
11361 {
11362     exiting++;
11363     if (exiting > 2) {
11364       /* Give up on clean exit */
11365       exit(status);
11366     }
11367     if (exiting > 1) {
11368       /* Keep trying for clean exit */
11369       return;
11370     }
11371
11372     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11373
11374     if (telnetISR != NULL) {
11375       RemoveInputSource(telnetISR);
11376     }
11377     if (icsPR != NoProc) {
11378       DestroyChildProcess(icsPR, TRUE);
11379     }
11380
11381     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11382     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11383
11384     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11385     /* make sure this other one finishes before killing it!                  */
11386     if(endingGame) { int count = 0;
11387         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11388         while(endingGame && count++ < 10) DoSleep(1);
11389         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11390     }
11391
11392     /* Kill off chess programs */
11393     if (first.pr != NoProc) {
11394         ExitAnalyzeMode();
11395
11396         DoSleep( appData.delayBeforeQuit );
11397         SendToProgram("quit\n", &first);
11398         DoSleep( appData.delayAfterQuit );
11399         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11400     }
11401     if (second.pr != NoProc) {
11402         DoSleep( appData.delayBeforeQuit );
11403         SendToProgram("quit\n", &second);
11404         DoSleep( appData.delayAfterQuit );
11405         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11406     }
11407     if (first.isr != NULL) {
11408         RemoveInputSource(first.isr);
11409     }
11410     if (second.isr != NULL) {
11411         RemoveInputSource(second.isr);
11412     }
11413
11414     ShutDownFrontEnd();
11415     exit(status);
11416 }
11417
11418 void
11419 PauseEvent()
11420 {
11421     if (appData.debugMode)
11422         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11423     if (pausing) {
11424         pausing = FALSE;
11425         ModeHighlight();
11426         if (gameMode == MachinePlaysWhite ||
11427             gameMode == MachinePlaysBlack) {
11428             StartClocks();
11429         } else {
11430             DisplayBothClocks();
11431         }
11432         if (gameMode == PlayFromGameFile) {
11433             if (appData.timeDelay >= 0)
11434                 AutoPlayGameLoop();
11435         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11436             Reset(FALSE, TRUE);
11437             SendToICS(ics_prefix);
11438             SendToICS("refresh\n");
11439         } else if (currentMove < forwardMostMove) {
11440             ForwardInner(forwardMostMove);
11441         }
11442         pauseExamInvalid = FALSE;
11443     } else {
11444         switch (gameMode) {
11445           default:
11446             return;
11447           case IcsExamining:
11448             pauseExamForwardMostMove = forwardMostMove;
11449             pauseExamInvalid = FALSE;
11450             /* fall through */
11451           case IcsObserving:
11452           case IcsPlayingWhite:
11453           case IcsPlayingBlack:
11454             pausing = TRUE;
11455             ModeHighlight();
11456             return;
11457           case PlayFromGameFile:
11458             (void) StopLoadGameTimer();
11459             pausing = TRUE;
11460             ModeHighlight();
11461             break;
11462           case BeginningOfGame:
11463             if (appData.icsActive) return;
11464             /* else fall through */
11465           case MachinePlaysWhite:
11466           case MachinePlaysBlack:
11467           case TwoMachinesPlay:
11468             if (forwardMostMove == 0)
11469               return;           /* don't pause if no one has moved */
11470             if ((gameMode == MachinePlaysWhite &&
11471                  !WhiteOnMove(forwardMostMove)) ||
11472                 (gameMode == MachinePlaysBlack &&
11473                  WhiteOnMove(forwardMostMove))) {
11474                 StopClocks();
11475             }
11476             pausing = TRUE;
11477             ModeHighlight();
11478             break;
11479         }
11480     }
11481 }
11482
11483 void
11484 EditCommentEvent()
11485 {
11486     char title[MSG_SIZ];
11487
11488     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11489       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11490     } else {
11491       sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11492               WhiteOnMove(currentMove - 1) ? " " : ".. ",
11493               parseList[currentMove - 1]);
11494     }
11495
11496     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11497 }
11498
11499
11500 void
11501 EditTagsEvent()
11502 {
11503     char *tags = PGNTags(&gameInfo);
11504     EditTagsPopUp(tags);
11505     free(tags);
11506 }
11507
11508 void
11509 AnalyzeModeEvent()
11510 {
11511     if (appData.noChessProgram || gameMode == AnalyzeMode)
11512       return;
11513
11514     if (gameMode != AnalyzeFile) {
11515         if (!appData.icsEngineAnalyze) {
11516                EditGameEvent();
11517                if (gameMode != EditGame) return;
11518         }
11519         ResurrectChessProgram();
11520         SendToProgram("analyze\n", &first);
11521         first.analyzing = TRUE;
11522         /*first.maybeThinking = TRUE;*/
11523         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11524         EngineOutputPopUp();
11525     }
11526     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11527     pausing = FALSE;
11528     ModeHighlight();
11529     SetGameInfo();
11530
11531     StartAnalysisClock();
11532     GetTimeMark(&lastNodeCountTime);
11533     lastNodeCount = 0;
11534 }
11535
11536 void
11537 AnalyzeFileEvent()
11538 {
11539     if (appData.noChessProgram || gameMode == AnalyzeFile)
11540       return;
11541
11542     if (gameMode != AnalyzeMode) {
11543         EditGameEvent();
11544         if (gameMode != EditGame) return;
11545         ResurrectChessProgram();
11546         SendToProgram("analyze\n", &first);
11547         first.analyzing = TRUE;
11548         /*first.maybeThinking = TRUE;*/
11549         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11550         EngineOutputPopUp();
11551     }
11552     gameMode = AnalyzeFile;
11553     pausing = FALSE;
11554     ModeHighlight();
11555     SetGameInfo();
11556
11557     StartAnalysisClock();
11558     GetTimeMark(&lastNodeCountTime);
11559     lastNodeCount = 0;
11560 }
11561
11562 void
11563 MachineWhiteEvent()
11564 {
11565     char buf[MSG_SIZ];
11566     char *bookHit = NULL;
11567
11568     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11569       return;
11570
11571
11572     if (gameMode == PlayFromGameFile ||
11573         gameMode == TwoMachinesPlay  ||
11574         gameMode == Training         ||
11575         gameMode == AnalyzeMode      ||
11576         gameMode == EndOfGame)
11577         EditGameEvent();
11578
11579     if (gameMode == EditPosition)
11580         EditPositionDone(TRUE);
11581
11582     if (!WhiteOnMove(currentMove)) {
11583         DisplayError(_("It is not White's turn"), 0);
11584         return;
11585     }
11586
11587     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11588       ExitAnalyzeMode();
11589
11590     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11591         gameMode == AnalyzeFile)
11592         TruncateGame();
11593
11594     ResurrectChessProgram();    /* in case it isn't running */
11595     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11596         gameMode = MachinePlaysWhite;
11597         ResetClocks();
11598     } else
11599     gameMode = MachinePlaysWhite;
11600     pausing = FALSE;
11601     ModeHighlight();
11602     SetGameInfo();
11603     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11604     DisplayTitle(buf);
11605     if (first.sendName) {
11606       sprintf(buf, "name %s\n", gameInfo.black);
11607       SendToProgram(buf, &first);
11608     }
11609     if (first.sendTime) {
11610       if (first.useColors) {
11611         SendToProgram("black\n", &first); /*gnu kludge*/
11612       }
11613       SendTimeRemaining(&first, TRUE);
11614     }
11615     if (first.useColors) {
11616       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11617     }
11618     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11619     SetMachineThinkingEnables();
11620     first.maybeThinking = TRUE;
11621     StartClocks();
11622     firstMove = FALSE;
11623
11624     if (appData.autoFlipView && !flipView) {
11625       flipView = !flipView;
11626       DrawPosition(FALSE, NULL);
11627       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11628     }
11629
11630     if(bookHit) { // [HGM] book: simulate book reply
11631         static char bookMove[MSG_SIZ]; // a bit generous?
11632
11633         programStats.nodes = programStats.depth = programStats.time =
11634         programStats.score = programStats.got_only_move = 0;
11635         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11636
11637         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11638         strcat(bookMove, bookHit);
11639         HandleMachineMove(bookMove, &first);
11640     }
11641 }
11642
11643 void
11644 MachineBlackEvent()
11645 {
11646     char buf[MSG_SIZ];
11647    char *bookHit = NULL;
11648
11649     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11650         return;
11651
11652
11653     if (gameMode == PlayFromGameFile ||
11654         gameMode == TwoMachinesPlay  ||
11655         gameMode == Training         ||
11656         gameMode == AnalyzeMode      ||
11657         gameMode == EndOfGame)
11658         EditGameEvent();
11659
11660     if (gameMode == EditPosition)
11661         EditPositionDone(TRUE);
11662
11663     if (WhiteOnMove(currentMove)) {
11664         DisplayError(_("It is not Black's turn"), 0);
11665         return;
11666     }
11667
11668     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11669       ExitAnalyzeMode();
11670
11671     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11672         gameMode == AnalyzeFile)
11673         TruncateGame();
11674
11675     ResurrectChessProgram();    /* in case it isn't running */
11676     gameMode = MachinePlaysBlack;
11677     pausing = FALSE;
11678     ModeHighlight();
11679     SetGameInfo();
11680     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11681     DisplayTitle(buf);
11682     if (first.sendName) {
11683       sprintf(buf, "name %s\n", gameInfo.white);
11684       SendToProgram(buf, &first);
11685     }
11686     if (first.sendTime) {
11687       if (first.useColors) {
11688         SendToProgram("white\n", &first); /*gnu kludge*/
11689       }
11690       SendTimeRemaining(&first, FALSE);
11691     }
11692     if (first.useColors) {
11693       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11694     }
11695     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11696     SetMachineThinkingEnables();
11697     first.maybeThinking = TRUE;
11698     StartClocks();
11699
11700     if (appData.autoFlipView && flipView) {
11701       flipView = !flipView;
11702       DrawPosition(FALSE, NULL);
11703       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11704     }
11705     if(bookHit) { // [HGM] book: simulate book reply
11706         static char bookMove[MSG_SIZ]; // a bit generous?
11707
11708         programStats.nodes = programStats.depth = programStats.time =
11709         programStats.score = programStats.got_only_move = 0;
11710         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11711
11712         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11713         strcat(bookMove, bookHit);
11714         HandleMachineMove(bookMove, &first);
11715     }
11716 }
11717
11718
11719 void
11720 DisplayTwoMachinesTitle()
11721 {
11722     char buf[MSG_SIZ];
11723     if (appData.matchGames > 0) {
11724         if (first.twoMachinesColor[0] == 'w') {
11725             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11726                     gameInfo.white, gameInfo.black,
11727                     first.matchWins, second.matchWins,
11728                     matchGame - 1 - (first.matchWins + second.matchWins));
11729         } else {
11730             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11731                     gameInfo.white, gameInfo.black,
11732                     second.matchWins, first.matchWins,
11733                     matchGame - 1 - (first.matchWins + second.matchWins));
11734         }
11735     } else {
11736         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11737     }
11738     DisplayTitle(buf);
11739 }
11740
11741 void
11742 TwoMachinesEvent P((void))
11743 {
11744     int i;
11745     char buf[MSG_SIZ];
11746     ChessProgramState *onmove;
11747     char *bookHit = NULL;
11748
11749     if (appData.noChessProgram) return;
11750
11751     switch (gameMode) {
11752       case TwoMachinesPlay:
11753         return;
11754       case MachinePlaysWhite:
11755       case MachinePlaysBlack:
11756         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11757             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11758             return;
11759         }
11760         /* fall through */
11761       case BeginningOfGame:
11762       case PlayFromGameFile:
11763       case EndOfGame:
11764         EditGameEvent();
11765         if (gameMode != EditGame) return;
11766         break;
11767       case EditPosition:
11768         EditPositionDone(TRUE);
11769         break;
11770       case AnalyzeMode:
11771       case AnalyzeFile:
11772         ExitAnalyzeMode();
11773         break;
11774       case EditGame:
11775       default:
11776         break;
11777     }
11778
11779 //    forwardMostMove = currentMove;
11780     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11781     ResurrectChessProgram();    /* in case first program isn't running */
11782
11783     if (second.pr == NULL) {
11784         StartChessProgram(&second);
11785         if (second.protocolVersion == 1) {
11786           TwoMachinesEventIfReady();
11787         } else {
11788           /* kludge: allow timeout for initial "feature" command */
11789           FreezeUI();
11790           DisplayMessage("", _("Starting second chess program"));
11791           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11792         }
11793         return;
11794     }
11795     DisplayMessage("", "");
11796     InitChessProgram(&second, FALSE);
11797     SendToProgram("force\n", &second);
11798     if (startedFromSetupPosition) {
11799         SendBoard(&second, backwardMostMove);
11800     if (appData.debugMode) {
11801         fprintf(debugFP, "Two Machines\n");
11802     }
11803     }
11804     for (i = backwardMostMove; i < forwardMostMove; i++) {
11805         SendMoveToProgram(i, &second);
11806     }
11807
11808     gameMode = TwoMachinesPlay;
11809     pausing = FALSE;
11810     ModeHighlight();
11811     SetGameInfo();
11812     DisplayTwoMachinesTitle();
11813     firstMove = TRUE;
11814     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11815         onmove = &first;
11816     } else {
11817         onmove = &second;
11818     }
11819
11820     SendToProgram(first.computerString, &first);
11821     if (first.sendName) {
11822       sprintf(buf, "name %s\n", second.tidy);
11823       SendToProgram(buf, &first);
11824     }
11825     SendToProgram(second.computerString, &second);
11826     if (second.sendName) {
11827       sprintf(buf, "name %s\n", first.tidy);
11828       SendToProgram(buf, &second);
11829     }
11830
11831     ResetClocks();
11832     if (!first.sendTime || !second.sendTime) {
11833         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11834         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11835     }
11836     if (onmove->sendTime) {
11837       if (onmove->useColors) {
11838         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11839       }
11840       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11841     }
11842     if (onmove->useColors) {
11843       SendToProgram(onmove->twoMachinesColor, onmove);
11844     }
11845     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11846 //    SendToProgram("go\n", onmove);
11847     onmove->maybeThinking = TRUE;
11848     SetMachineThinkingEnables();
11849
11850     StartClocks();
11851
11852     if(bookHit) { // [HGM] book: simulate book reply
11853         static char bookMove[MSG_SIZ]; // a bit generous?
11854
11855         programStats.nodes = programStats.depth = programStats.time =
11856         programStats.score = programStats.got_only_move = 0;
11857         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11858
11859         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11860         strcat(bookMove, bookHit);
11861         savedMessage = bookMove; // args for deferred call
11862         savedState = onmove;
11863         ScheduleDelayedEvent(DeferredBookMove, 1);
11864     }
11865 }
11866
11867 void
11868 TrainingEvent()
11869 {
11870     if (gameMode == Training) {
11871       SetTrainingModeOff();
11872       gameMode = PlayFromGameFile;
11873       DisplayMessage("", _("Training mode off"));
11874     } else {
11875       gameMode = Training;
11876       animateTraining = appData.animate;
11877
11878       /* make sure we are not already at the end of the game */
11879       if (currentMove < forwardMostMove) {
11880         SetTrainingModeOn();
11881         DisplayMessage("", _("Training mode on"));
11882       } else {
11883         gameMode = PlayFromGameFile;
11884         DisplayError(_("Already at end of game"), 0);
11885       }
11886     }
11887     ModeHighlight();
11888 }
11889
11890 void
11891 IcsClientEvent()
11892 {
11893     if (!appData.icsActive) return;
11894     switch (gameMode) {
11895       case IcsPlayingWhite:
11896       case IcsPlayingBlack:
11897       case IcsObserving:
11898       case IcsIdle:
11899       case BeginningOfGame:
11900       case IcsExamining:
11901         return;
11902
11903       case EditGame:
11904         break;
11905
11906       case EditPosition:
11907         EditPositionDone(TRUE);
11908         break;
11909
11910       case AnalyzeMode:
11911       case AnalyzeFile:
11912         ExitAnalyzeMode();
11913         break;
11914
11915       default:
11916         EditGameEvent();
11917         break;
11918     }
11919
11920     gameMode = IcsIdle;
11921     ModeHighlight();
11922     return;
11923 }
11924
11925
11926 void
11927 EditGameEvent()
11928 {
11929     int i;
11930
11931     switch (gameMode) {
11932       case Training:
11933         SetTrainingModeOff();
11934         break;
11935       case MachinePlaysWhite:
11936       case MachinePlaysBlack:
11937       case BeginningOfGame:
11938         SendToProgram("force\n", &first);
11939         SetUserThinkingEnables();
11940         break;
11941       case PlayFromGameFile:
11942         (void) StopLoadGameTimer();
11943         if (gameFileFP != NULL) {
11944             gameFileFP = NULL;
11945         }
11946         break;
11947       case EditPosition:
11948         EditPositionDone(TRUE);
11949         break;
11950       case AnalyzeMode:
11951       case AnalyzeFile:
11952         ExitAnalyzeMode();
11953         SendToProgram("force\n", &first);
11954         break;
11955       case TwoMachinesPlay:
11956         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11957         ResurrectChessProgram();
11958         SetUserThinkingEnables();
11959         break;
11960       case EndOfGame:
11961         ResurrectChessProgram();
11962         break;
11963       case IcsPlayingBlack:
11964       case IcsPlayingWhite:
11965         DisplayError(_("Warning: You are still playing a game"), 0);
11966         break;
11967       case IcsObserving:
11968         DisplayError(_("Warning: You are still observing a game"), 0);
11969         break;
11970       case IcsExamining:
11971         DisplayError(_("Warning: You are still examining a game"), 0);
11972         break;
11973       case IcsIdle:
11974         break;
11975       case EditGame:
11976       default:
11977         return;
11978     }
11979
11980     pausing = FALSE;
11981     StopClocks();
11982     first.offeredDraw = second.offeredDraw = 0;
11983
11984     if (gameMode == PlayFromGameFile) {
11985         whiteTimeRemaining = timeRemaining[0][currentMove];
11986         blackTimeRemaining = timeRemaining[1][currentMove];
11987         DisplayTitle("");
11988     }
11989
11990     if (gameMode == MachinePlaysWhite ||
11991         gameMode == MachinePlaysBlack ||
11992         gameMode == TwoMachinesPlay ||
11993         gameMode == EndOfGame) {
11994         i = forwardMostMove;
11995         while (i > currentMove) {
11996             SendToProgram("undo\n", &first);
11997             i--;
11998         }
11999         whiteTimeRemaining = timeRemaining[0][currentMove];
12000         blackTimeRemaining = timeRemaining[1][currentMove];
12001         DisplayBothClocks();
12002         if (whiteFlag || blackFlag) {
12003             whiteFlag = blackFlag = 0;
12004         }
12005         DisplayTitle("");
12006     }
12007
12008     gameMode = EditGame;
12009     ModeHighlight();
12010     SetGameInfo();
12011 }
12012
12013
12014 void
12015 EditPositionEvent()
12016 {
12017     if (gameMode == EditPosition) {
12018         EditGameEvent();
12019         return;
12020     }
12021
12022     EditGameEvent();
12023     if (gameMode != EditGame) return;
12024
12025     gameMode = EditPosition;
12026     ModeHighlight();
12027     SetGameInfo();
12028     if (currentMove > 0)
12029       CopyBoard(boards[0], boards[currentMove]);
12030
12031     blackPlaysFirst = !WhiteOnMove(currentMove);
12032     ResetClocks();
12033     currentMove = forwardMostMove = backwardMostMove = 0;
12034     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12035     DisplayMove(-1);
12036 }
12037
12038 void
12039 ExitAnalyzeMode()
12040 {
12041     /* [DM] icsEngineAnalyze - possible call from other functions */
12042     if (appData.icsEngineAnalyze) {
12043         appData.icsEngineAnalyze = FALSE;
12044
12045         DisplayMessage("",_("Close ICS engine analyze..."));
12046     }
12047     if (first.analysisSupport && first.analyzing) {
12048       SendToProgram("exit\n", &first);
12049       first.analyzing = FALSE;
12050     }
12051     thinkOutput[0] = NULLCHAR;
12052 }
12053
12054 void
12055 EditPositionDone(Boolean fakeRights)
12056 {
12057     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12058
12059     startedFromSetupPosition = TRUE;
12060     InitChessProgram(&first, FALSE);
12061     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12062       boards[0][EP_STATUS] = EP_NONE;
12063       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12064     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12065         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12066         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12067       } else boards[0][CASTLING][2] = NoRights;
12068     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12069         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12070         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12071       } else boards[0][CASTLING][5] = NoRights;
12072     }
12073     SendToProgram("force\n", &first);
12074     if (blackPlaysFirst) {
12075         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12076         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12077         currentMove = forwardMostMove = backwardMostMove = 1;
12078         CopyBoard(boards[1], boards[0]);
12079     } else {
12080         currentMove = forwardMostMove = backwardMostMove = 0;
12081     }
12082     SendBoard(&first, forwardMostMove);
12083     if (appData.debugMode) {
12084         fprintf(debugFP, "EditPosDone\n");
12085     }
12086     DisplayTitle("");
12087     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12088     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12089     gameMode = EditGame;
12090     ModeHighlight();
12091     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12092     ClearHighlights(); /* [AS] */
12093 }
12094
12095 /* Pause for `ms' milliseconds */
12096 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12097 void
12098 TimeDelay(ms)
12099      long ms;
12100 {
12101     TimeMark m1, m2;
12102
12103     GetTimeMark(&m1);
12104     do {
12105         GetTimeMark(&m2);
12106     } while (SubtractTimeMarks(&m2, &m1) < ms);
12107 }
12108
12109 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12110 void
12111 SendMultiLineToICS(buf)
12112      char *buf;
12113 {
12114     char temp[MSG_SIZ+1], *p;
12115     int len;
12116
12117     len = strlen(buf);
12118     if (len > MSG_SIZ)
12119       len = MSG_SIZ;
12120
12121     strncpy(temp, buf, len);
12122     temp[len] = 0;
12123
12124     p = temp;
12125     while (*p) {
12126         if (*p == '\n' || *p == '\r')
12127           *p = ' ';
12128         ++p;
12129     }
12130
12131     strcat(temp, "\n");
12132     SendToICS(temp);
12133     SendToPlayer(temp, strlen(temp));
12134 }
12135
12136 void
12137 SetWhiteToPlayEvent()
12138 {
12139     if (gameMode == EditPosition) {
12140         blackPlaysFirst = FALSE;
12141         DisplayBothClocks();    /* works because currentMove is 0 */
12142     } else if (gameMode == IcsExamining) {
12143         SendToICS(ics_prefix);
12144         SendToICS("tomove white\n");
12145     }
12146 }
12147
12148 void
12149 SetBlackToPlayEvent()
12150 {
12151     if (gameMode == EditPosition) {
12152         blackPlaysFirst = TRUE;
12153         currentMove = 1;        /* kludge */
12154         DisplayBothClocks();
12155         currentMove = 0;
12156     } else if (gameMode == IcsExamining) {
12157         SendToICS(ics_prefix);
12158         SendToICS("tomove black\n");
12159     }
12160 }
12161
12162 void
12163 EditPositionMenuEvent(selection, x, y)
12164      ChessSquare selection;
12165      int x, y;
12166 {
12167     char buf[MSG_SIZ];
12168     ChessSquare piece = boards[0][y][x];
12169
12170     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12171
12172     switch (selection) {
12173       case ClearBoard:
12174         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12175             SendToICS(ics_prefix);
12176             SendToICS("bsetup clear\n");
12177         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12178             SendToICS(ics_prefix);
12179             SendToICS("clearboard\n");
12180         } else {
12181             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12182                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12183                 for (y = 0; y < BOARD_HEIGHT; y++) {
12184                     if (gameMode == IcsExamining) {
12185                         if (boards[currentMove][y][x] != EmptySquare) {
12186                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12187                                     AAA + x, ONE + y);
12188                             SendToICS(buf);
12189                         }
12190                     } else {
12191                         boards[0][y][x] = p;
12192                     }
12193                 }
12194             }
12195         }
12196         if (gameMode == EditPosition) {
12197             DrawPosition(FALSE, boards[0]);
12198         }
12199         break;
12200
12201       case WhitePlay:
12202         SetWhiteToPlayEvent();
12203         break;
12204
12205       case BlackPlay:
12206         SetBlackToPlayEvent();
12207         break;
12208
12209       case EmptySquare:
12210         if (gameMode == IcsExamining) {
12211             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12212             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12213             SendToICS(buf);
12214         } else {
12215             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12216                 if(x == BOARD_LEFT-2) {
12217                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12218                     boards[0][y][1] = 0;
12219                 } else
12220                 if(x == BOARD_RGHT+1) {
12221                     if(y >= gameInfo.holdingsSize) break;
12222                     boards[0][y][BOARD_WIDTH-2] = 0;
12223                 } else break;
12224             }
12225             boards[0][y][x] = EmptySquare;
12226             DrawPosition(FALSE, boards[0]);
12227         }
12228         break;
12229
12230       case PromotePiece:
12231         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12232            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12233             selection = (ChessSquare) (PROMOTED piece);
12234         } else if(piece == EmptySquare) selection = WhiteSilver;
12235         else selection = (ChessSquare)((int)piece - 1);
12236         goto defaultlabel;
12237
12238       case DemotePiece:
12239         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12240            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12241             selection = (ChessSquare) (DEMOTED piece);
12242         } else if(piece == EmptySquare) selection = BlackSilver;
12243         else selection = (ChessSquare)((int)piece + 1);
12244         goto defaultlabel;
12245
12246       case WhiteQueen:
12247       case BlackQueen:
12248         if(gameInfo.variant == VariantShatranj ||
12249            gameInfo.variant == VariantXiangqi  ||
12250            gameInfo.variant == VariantCourier  ||
12251            gameInfo.variant == VariantMakruk     )
12252             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12253         goto defaultlabel;
12254
12255       case WhiteKing:
12256       case BlackKing:
12257         if(gameInfo.variant == VariantXiangqi)
12258             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12259         if(gameInfo.variant == VariantKnightmate)
12260             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12261       default:
12262         defaultlabel:
12263         if (gameMode == IcsExamining) {
12264             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12265             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12266                     PieceToChar(selection), AAA + x, ONE + y);
12267             SendToICS(buf);
12268         } else {
12269             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12270                 int n;
12271                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12272                     n = PieceToNumber(selection - BlackPawn);
12273                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12274                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12275                     boards[0][BOARD_HEIGHT-1-n][1]++;
12276                 } else
12277                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12278                     n = PieceToNumber(selection);
12279                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12280                     boards[0][n][BOARD_WIDTH-1] = selection;
12281                     boards[0][n][BOARD_WIDTH-2]++;
12282                 }
12283             } else
12284             boards[0][y][x] = selection;
12285             DrawPosition(TRUE, boards[0]);
12286         }
12287         break;
12288     }
12289 }
12290
12291
12292 void
12293 DropMenuEvent(selection, x, y)
12294      ChessSquare selection;
12295      int x, y;
12296 {
12297     ChessMove moveType;
12298
12299     switch (gameMode) {
12300       case IcsPlayingWhite:
12301       case MachinePlaysBlack:
12302         if (!WhiteOnMove(currentMove)) {
12303             DisplayMoveError(_("It is Black's turn"));
12304             return;
12305         }
12306         moveType = WhiteDrop;
12307         break;
12308       case IcsPlayingBlack:
12309       case MachinePlaysWhite:
12310         if (WhiteOnMove(currentMove)) {
12311             DisplayMoveError(_("It is White's turn"));
12312             return;
12313         }
12314         moveType = BlackDrop;
12315         break;
12316       case EditGame:
12317         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12318         break;
12319       default:
12320         return;
12321     }
12322
12323     if (moveType == BlackDrop && selection < BlackPawn) {
12324       selection = (ChessSquare) ((int) selection
12325                                  + (int) BlackPawn - (int) WhitePawn);
12326     }
12327     if (boards[currentMove][y][x] != EmptySquare) {
12328         DisplayMoveError(_("That square is occupied"));
12329         return;
12330     }
12331
12332     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12333 }
12334
12335 void
12336 AcceptEvent()
12337 {
12338     /* Accept a pending offer of any kind from opponent */
12339
12340     if (appData.icsActive) {
12341         SendToICS(ics_prefix);
12342         SendToICS("accept\n");
12343     } else if (cmailMsgLoaded) {
12344         if (currentMove == cmailOldMove &&
12345             commentList[cmailOldMove] != NULL &&
12346             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12347                    "Black offers a draw" : "White offers a draw")) {
12348             TruncateGame();
12349             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12350             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12351         } else {
12352             DisplayError(_("There is no pending offer on this move"), 0);
12353             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12354         }
12355     } else {
12356         /* Not used for offers from chess program */
12357     }
12358 }
12359
12360 void
12361 DeclineEvent()
12362 {
12363     /* Decline a pending offer of any kind from opponent */
12364
12365     if (appData.icsActive) {
12366         SendToICS(ics_prefix);
12367         SendToICS("decline\n");
12368     } else if (cmailMsgLoaded) {
12369         if (currentMove == cmailOldMove &&
12370             commentList[cmailOldMove] != NULL &&
12371             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12372                    "Black offers a draw" : "White offers a draw")) {
12373 #ifdef NOTDEF
12374             AppendComment(cmailOldMove, "Draw declined", TRUE);
12375             DisplayComment(cmailOldMove - 1, "Draw declined");
12376 #endif /*NOTDEF*/
12377         } else {
12378             DisplayError(_("There is no pending offer on this move"), 0);
12379         }
12380     } else {
12381         /* Not used for offers from chess program */
12382     }
12383 }
12384
12385 void
12386 RematchEvent()
12387 {
12388     /* Issue ICS rematch command */
12389     if (appData.icsActive) {
12390         SendToICS(ics_prefix);
12391         SendToICS("rematch\n");
12392     }
12393 }
12394
12395 void
12396 CallFlagEvent()
12397 {
12398     /* Call your opponent's flag (claim a win on time) */
12399     if (appData.icsActive) {
12400         SendToICS(ics_prefix);
12401         SendToICS("flag\n");
12402     } else {
12403         switch (gameMode) {
12404           default:
12405             return;
12406           case MachinePlaysWhite:
12407             if (whiteFlag) {
12408                 if (blackFlag)
12409                   GameEnds(GameIsDrawn, "Both players ran out of time",
12410                            GE_PLAYER);
12411                 else
12412                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12413             } else {
12414                 DisplayError(_("Your opponent is not out of time"), 0);
12415             }
12416             break;
12417           case MachinePlaysBlack:
12418             if (blackFlag) {
12419                 if (whiteFlag)
12420                   GameEnds(GameIsDrawn, "Both players ran out of time",
12421                            GE_PLAYER);
12422                 else
12423                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12424             } else {
12425                 DisplayError(_("Your opponent is not out of time"), 0);
12426             }
12427             break;
12428         }
12429     }
12430 }
12431
12432 void
12433 DrawEvent()
12434 {
12435     /* Offer draw or accept pending draw offer from opponent */
12436
12437     if (appData.icsActive) {
12438         /* Note: tournament rules require draw offers to be
12439            made after you make your move but before you punch
12440            your clock.  Currently ICS doesn't let you do that;
12441            instead, you immediately punch your clock after making
12442            a move, but you can offer a draw at any time. */
12443
12444         SendToICS(ics_prefix);
12445         SendToICS("draw\n");
12446         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12447     } else if (cmailMsgLoaded) {
12448         if (currentMove == cmailOldMove &&
12449             commentList[cmailOldMove] != NULL &&
12450             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12451                    "Black offers a draw" : "White offers a draw")) {
12452             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12453             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12454         } else if (currentMove == cmailOldMove + 1) {
12455             char *offer = WhiteOnMove(cmailOldMove) ?
12456               "White offers a draw" : "Black offers a draw";
12457             AppendComment(currentMove, offer, TRUE);
12458             DisplayComment(currentMove - 1, offer);
12459             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12460         } else {
12461             DisplayError(_("You must make your move before offering a draw"), 0);
12462             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12463         }
12464     } else if (first.offeredDraw) {
12465         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12466     } else {
12467         if (first.sendDrawOffers) {
12468             SendToProgram("draw\n", &first);
12469             userOfferedDraw = TRUE;
12470         }
12471     }
12472 }
12473
12474 void
12475 AdjournEvent()
12476 {
12477     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12478
12479     if (appData.icsActive) {
12480         SendToICS(ics_prefix);
12481         SendToICS("adjourn\n");
12482     } else {
12483         /* Currently GNU Chess doesn't offer or accept Adjourns */
12484     }
12485 }
12486
12487
12488 void
12489 AbortEvent()
12490 {
12491     /* Offer Abort or accept pending Abort offer from opponent */
12492
12493     if (appData.icsActive) {
12494         SendToICS(ics_prefix);
12495         SendToICS("abort\n");
12496     } else {
12497         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12498     }
12499 }
12500
12501 void
12502 ResignEvent()
12503 {
12504     /* Resign.  You can do this even if it's not your turn. */
12505
12506     if (appData.icsActive) {
12507         SendToICS(ics_prefix);
12508         SendToICS("resign\n");
12509     } else {
12510         switch (gameMode) {
12511           case MachinePlaysWhite:
12512             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12513             break;
12514           case MachinePlaysBlack:
12515             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12516             break;
12517           case EditGame:
12518             if (cmailMsgLoaded) {
12519                 TruncateGame();
12520                 if (WhiteOnMove(cmailOldMove)) {
12521                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12522                 } else {
12523                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12524                 }
12525                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12526             }
12527             break;
12528           default:
12529             break;
12530         }
12531     }
12532 }
12533
12534
12535 void
12536 StopObservingEvent()
12537 {
12538     /* Stop observing current games */
12539     SendToICS(ics_prefix);
12540     SendToICS("unobserve\n");
12541 }
12542
12543 void
12544 StopExaminingEvent()
12545 {
12546     /* Stop observing current game */
12547     SendToICS(ics_prefix);
12548     SendToICS("unexamine\n");
12549 }
12550
12551 void
12552 ForwardInner(target)
12553      int target;
12554 {
12555     int limit;
12556
12557     if (appData.debugMode)
12558         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12559                 target, currentMove, forwardMostMove);
12560
12561     if (gameMode == EditPosition)
12562       return;
12563
12564     if (gameMode == PlayFromGameFile && !pausing)
12565       PauseEvent();
12566
12567     if (gameMode == IcsExamining && pausing)
12568       limit = pauseExamForwardMostMove;
12569     else
12570       limit = forwardMostMove;
12571
12572     if (target > limit) target = limit;
12573
12574     if (target > 0 && moveList[target - 1][0]) {
12575         int fromX, fromY, toX, toY;
12576         toX = moveList[target - 1][2] - AAA;
12577         toY = moveList[target - 1][3] - ONE;
12578         if (moveList[target - 1][1] == '@') {
12579             if (appData.highlightLastMove) {
12580                 SetHighlights(-1, -1, toX, toY);
12581             }
12582         } else {
12583             fromX = moveList[target - 1][0] - AAA;
12584             fromY = moveList[target - 1][1] - ONE;
12585             if (target == currentMove + 1) {
12586                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12587             }
12588             if (appData.highlightLastMove) {
12589                 SetHighlights(fromX, fromY, toX, toY);
12590             }
12591         }
12592     }
12593     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12594         gameMode == Training || gameMode == PlayFromGameFile ||
12595         gameMode == AnalyzeFile) {
12596         while (currentMove < target) {
12597             SendMoveToProgram(currentMove++, &first);
12598         }
12599     } else {
12600         currentMove = target;
12601     }
12602
12603     if (gameMode == EditGame || gameMode == EndOfGame) {
12604         whiteTimeRemaining = timeRemaining[0][currentMove];
12605         blackTimeRemaining = timeRemaining[1][currentMove];
12606     }
12607     DisplayBothClocks();
12608     DisplayMove(currentMove - 1);
12609     DrawPosition(FALSE, boards[currentMove]);
12610     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12611     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12612         DisplayComment(currentMove - 1, commentList[currentMove]);
12613     }
12614 }
12615
12616
12617 void
12618 ForwardEvent()
12619 {
12620     if (gameMode == IcsExamining && !pausing) {
12621         SendToICS(ics_prefix);
12622         SendToICS("forward\n");
12623     } else {
12624         ForwardInner(currentMove + 1);
12625     }
12626 }
12627
12628 void
12629 ToEndEvent()
12630 {
12631     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12632         /* to optimze, we temporarily turn off analysis mode while we feed
12633          * the remaining moves to the engine. Otherwise we get analysis output
12634          * after each move.
12635          */
12636         if (first.analysisSupport) {
12637           SendToProgram("exit\nforce\n", &first);
12638           first.analyzing = FALSE;
12639         }
12640     }
12641
12642     if (gameMode == IcsExamining && !pausing) {
12643         SendToICS(ics_prefix);
12644         SendToICS("forward 999999\n");
12645     } else {
12646         ForwardInner(forwardMostMove);
12647     }
12648
12649     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12650         /* we have fed all the moves, so reactivate analysis mode */
12651         SendToProgram("analyze\n", &first);
12652         first.analyzing = TRUE;
12653         /*first.maybeThinking = TRUE;*/
12654         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12655     }
12656 }
12657
12658 void
12659 BackwardInner(target)
12660      int target;
12661 {
12662     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12663
12664     if (appData.debugMode)
12665         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12666                 target, currentMove, forwardMostMove);
12667
12668     if (gameMode == EditPosition) return;
12669     if (currentMove <= backwardMostMove) {
12670         ClearHighlights();
12671         DrawPosition(full_redraw, boards[currentMove]);
12672         return;
12673     }
12674     if (gameMode == PlayFromGameFile && !pausing)
12675       PauseEvent();
12676
12677     if (moveList[target][0]) {
12678         int fromX, fromY, toX, toY;
12679         toX = moveList[target][2] - AAA;
12680         toY = moveList[target][3] - ONE;
12681         if (moveList[target][1] == '@') {
12682             if (appData.highlightLastMove) {
12683                 SetHighlights(-1, -1, toX, toY);
12684             }
12685         } else {
12686             fromX = moveList[target][0] - AAA;
12687             fromY = moveList[target][1] - ONE;
12688             if (target == currentMove - 1) {
12689                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12690             }
12691             if (appData.highlightLastMove) {
12692                 SetHighlights(fromX, fromY, toX, toY);
12693             }
12694         }
12695     }
12696     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12697         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12698         while (currentMove > target) {
12699             SendToProgram("undo\n", &first);
12700             currentMove--;
12701         }
12702     } else {
12703         currentMove = target;
12704     }
12705
12706     if (gameMode == EditGame || gameMode == EndOfGame) {
12707         whiteTimeRemaining = timeRemaining[0][currentMove];
12708         blackTimeRemaining = timeRemaining[1][currentMove];
12709     }
12710     DisplayBothClocks();
12711     DisplayMove(currentMove - 1);
12712     DrawPosition(full_redraw, boards[currentMove]);
12713     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12714     // [HGM] PV info: routine tests if comment empty
12715     DisplayComment(currentMove - 1, commentList[currentMove]);
12716 }
12717
12718 void
12719 BackwardEvent()
12720 {
12721     if (gameMode == IcsExamining && !pausing) {
12722         SendToICS(ics_prefix);
12723         SendToICS("backward\n");
12724     } else {
12725         BackwardInner(currentMove - 1);
12726     }
12727 }
12728
12729 void
12730 ToStartEvent()
12731 {
12732     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12733         /* to optimize, we temporarily turn off analysis mode while we undo
12734          * all the moves. Otherwise we get analysis output after each undo.
12735          */
12736         if (first.analysisSupport) {
12737           SendToProgram("exit\nforce\n", &first);
12738           first.analyzing = FALSE;
12739         }
12740     }
12741
12742     if (gameMode == IcsExamining && !pausing) {
12743         SendToICS(ics_prefix);
12744         SendToICS("backward 999999\n");
12745     } else {
12746         BackwardInner(backwardMostMove);
12747     }
12748
12749     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12750         /* we have fed all the moves, so reactivate analysis mode */
12751         SendToProgram("analyze\n", &first);
12752         first.analyzing = TRUE;
12753         /*first.maybeThinking = TRUE;*/
12754         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12755     }
12756 }
12757
12758 void
12759 ToNrEvent(int to)
12760 {
12761   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12762   if (to >= forwardMostMove) to = forwardMostMove;
12763   if (to <= backwardMostMove) to = backwardMostMove;
12764   if (to < currentMove) {
12765     BackwardInner(to);
12766   } else {
12767     ForwardInner(to);
12768   }
12769 }
12770
12771 void
12772 RevertEvent(Boolean annotate)
12773 {
12774     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12775         return;
12776     }
12777     if (gameMode != IcsExamining) {
12778         DisplayError(_("You are not examining a game"), 0);
12779         return;
12780     }
12781     if (pausing) {
12782         DisplayError(_("You can't revert while pausing"), 0);
12783         return;
12784     }
12785     SendToICS(ics_prefix);
12786     SendToICS("revert\n");
12787 }
12788
12789 void
12790 RetractMoveEvent()
12791 {
12792     switch (gameMode) {
12793       case MachinePlaysWhite:
12794       case MachinePlaysBlack:
12795         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12796             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12797             return;
12798         }
12799         if (forwardMostMove < 2) return;
12800         currentMove = forwardMostMove = forwardMostMove - 2;
12801         whiteTimeRemaining = timeRemaining[0][currentMove];
12802         blackTimeRemaining = timeRemaining[1][currentMove];
12803         DisplayBothClocks();
12804         DisplayMove(currentMove - 1);
12805         ClearHighlights();/*!! could figure this out*/
12806         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12807         SendToProgram("remove\n", &first);
12808         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12809         break;
12810
12811       case BeginningOfGame:
12812       default:
12813         break;
12814
12815       case IcsPlayingWhite:
12816       case IcsPlayingBlack:
12817         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12818             SendToICS(ics_prefix);
12819             SendToICS("takeback 2\n");
12820         } else {
12821             SendToICS(ics_prefix);
12822             SendToICS("takeback 1\n");
12823         }
12824         break;
12825     }
12826 }
12827
12828 void
12829 MoveNowEvent()
12830 {
12831     ChessProgramState *cps;
12832
12833     switch (gameMode) {
12834       case MachinePlaysWhite:
12835         if (!WhiteOnMove(forwardMostMove)) {
12836             DisplayError(_("It is your turn"), 0);
12837             return;
12838         }
12839         cps = &first;
12840         break;
12841       case MachinePlaysBlack:
12842         if (WhiteOnMove(forwardMostMove)) {
12843             DisplayError(_("It is your turn"), 0);
12844             return;
12845         }
12846         cps = &first;
12847         break;
12848       case TwoMachinesPlay:
12849         if (WhiteOnMove(forwardMostMove) ==
12850             (first.twoMachinesColor[0] == 'w')) {
12851             cps = &first;
12852         } else {
12853             cps = &second;
12854         }
12855         break;
12856       case BeginningOfGame:
12857       default:
12858         return;
12859     }
12860     SendToProgram("?\n", cps);
12861 }
12862
12863 void
12864 TruncateGameEvent()
12865 {
12866     EditGameEvent();
12867     if (gameMode != EditGame) return;
12868     TruncateGame();
12869 }
12870
12871 void
12872 TruncateGame()
12873 {
12874     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12875     if (forwardMostMove > currentMove) {
12876         if (gameInfo.resultDetails != NULL) {
12877             free(gameInfo.resultDetails);
12878             gameInfo.resultDetails = NULL;
12879             gameInfo.result = GameUnfinished;
12880         }
12881         forwardMostMove = currentMove;
12882         HistorySet(parseList, backwardMostMove, forwardMostMove,
12883                    currentMove-1);
12884     }
12885 }
12886
12887 void
12888 HintEvent()
12889 {
12890     if (appData.noChessProgram) return;
12891     switch (gameMode) {
12892       case MachinePlaysWhite:
12893         if (WhiteOnMove(forwardMostMove)) {
12894             DisplayError(_("Wait until your turn"), 0);
12895             return;
12896         }
12897         break;
12898       case BeginningOfGame:
12899       case MachinePlaysBlack:
12900         if (!WhiteOnMove(forwardMostMove)) {
12901             DisplayError(_("Wait until your turn"), 0);
12902             return;
12903         }
12904         break;
12905       default:
12906         DisplayError(_("No hint available"), 0);
12907         return;
12908     }
12909     SendToProgram("hint\n", &first);
12910     hintRequested = TRUE;
12911 }
12912
12913 void
12914 BookEvent()
12915 {
12916     if (appData.noChessProgram) return;
12917     switch (gameMode) {
12918       case MachinePlaysWhite:
12919         if (WhiteOnMove(forwardMostMove)) {
12920             DisplayError(_("Wait until your turn"), 0);
12921             return;
12922         }
12923         break;
12924       case BeginningOfGame:
12925       case MachinePlaysBlack:
12926         if (!WhiteOnMove(forwardMostMove)) {
12927             DisplayError(_("Wait until your turn"), 0);
12928             return;
12929         }
12930         break;
12931       case EditPosition:
12932         EditPositionDone(TRUE);
12933         break;
12934       case TwoMachinesPlay:
12935         return;
12936       default:
12937         break;
12938     }
12939     SendToProgram("bk\n", &first);
12940     bookOutput[0] = NULLCHAR;
12941     bookRequested = TRUE;
12942 }
12943
12944 void
12945 AboutGameEvent()
12946 {
12947     char *tags = PGNTags(&gameInfo);
12948     TagsPopUp(tags, CmailMsg());
12949     free(tags);
12950 }
12951
12952 /* end button procedures */
12953
12954 void
12955 PrintPosition(fp, move)
12956      FILE *fp;
12957      int move;
12958 {
12959     int i, j;
12960
12961     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12962         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12963             char c = PieceToChar(boards[move][i][j]);
12964             fputc(c == 'x' ? '.' : c, fp);
12965             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12966         }
12967     }
12968     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12969       fprintf(fp, "white to play\n");
12970     else
12971       fprintf(fp, "black to play\n");
12972 }
12973
12974 void
12975 PrintOpponents(fp)
12976      FILE *fp;
12977 {
12978     if (gameInfo.white != NULL) {
12979         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12980     } else {
12981         fprintf(fp, "\n");
12982     }
12983 }
12984
12985 /* Find last component of program's own name, using some heuristics */
12986 void
12987 TidyProgramName(prog, host, buf)
12988      char *prog, *host, buf[MSG_SIZ];
12989 {
12990     char *p, *q;
12991     int local = (strcmp(host, "localhost") == 0);
12992     while (!local && (p = strchr(prog, ';')) != NULL) {
12993         p++;
12994         while (*p == ' ') p++;
12995         prog = p;
12996     }
12997     if (*prog == '"' || *prog == '\'') {
12998         q = strchr(prog + 1, *prog);
12999     } else {
13000         q = strchr(prog, ' ');
13001     }
13002     if (q == NULL) q = prog + strlen(prog);
13003     p = q;
13004     while (p >= prog && *p != '/' && *p != '\\') p--;
13005     p++;
13006     if(p == prog && *p == '"') p++;
13007     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13008     memcpy(buf, p, q - p);
13009     buf[q - p] = NULLCHAR;
13010     if (!local) {
13011         strcat(buf, "@");
13012         strcat(buf, host);
13013     }
13014 }
13015
13016 char *
13017 TimeControlTagValue()
13018 {
13019     char buf[MSG_SIZ];
13020     if (!appData.clockMode) {
13021       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13022     } else if (movesPerSession > 0) {
13023       sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13024     } else if (timeIncrement == 0) {
13025       sprintf(buf, "%ld", timeControl/1000);
13026     } else {
13027       sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13028     }
13029     return StrSave(buf);
13030 }
13031
13032 void
13033 SetGameInfo()
13034 {
13035     /* This routine is used only for certain modes */
13036     VariantClass v = gameInfo.variant;
13037     ChessMove r = GameUnfinished;
13038     char *p = NULL;
13039
13040     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13041         r = gameInfo.result;
13042         p = gameInfo.resultDetails;
13043         gameInfo.resultDetails = NULL;
13044     }
13045     ClearGameInfo(&gameInfo);
13046     gameInfo.variant = v;
13047
13048     switch (gameMode) {
13049       case MachinePlaysWhite:
13050         gameInfo.event = StrSave( appData.pgnEventHeader );
13051         gameInfo.site = StrSave(HostName());
13052         gameInfo.date = PGNDate();
13053         gameInfo.round = StrSave("-");
13054         gameInfo.white = StrSave(first.tidy);
13055         gameInfo.black = StrSave(UserName());
13056         gameInfo.timeControl = TimeControlTagValue();
13057         break;
13058
13059       case MachinePlaysBlack:
13060         gameInfo.event = StrSave( appData.pgnEventHeader );
13061         gameInfo.site = StrSave(HostName());
13062         gameInfo.date = PGNDate();
13063         gameInfo.round = StrSave("-");
13064         gameInfo.white = StrSave(UserName());
13065         gameInfo.black = StrSave(first.tidy);
13066         gameInfo.timeControl = TimeControlTagValue();
13067         break;
13068
13069       case TwoMachinesPlay:
13070         gameInfo.event = StrSave( appData.pgnEventHeader );
13071         gameInfo.site = StrSave(HostName());
13072         gameInfo.date = PGNDate();
13073         if (matchGame > 0) {
13074             char buf[MSG_SIZ];
13075             sprintf(buf, "%d", matchGame);
13076             gameInfo.round = StrSave(buf);
13077         } else {
13078             gameInfo.round = StrSave("-");
13079         }
13080         if (first.twoMachinesColor[0] == 'w') {
13081             gameInfo.white = StrSave(first.tidy);
13082             gameInfo.black = StrSave(second.tidy);
13083         } else {
13084             gameInfo.white = StrSave(second.tidy);
13085             gameInfo.black = StrSave(first.tidy);
13086         }
13087         gameInfo.timeControl = TimeControlTagValue();
13088         break;
13089
13090       case EditGame:
13091         gameInfo.event = StrSave("Edited game");
13092         gameInfo.site = StrSave(HostName());
13093         gameInfo.date = PGNDate();
13094         gameInfo.round = StrSave("-");
13095         gameInfo.white = StrSave("-");
13096         gameInfo.black = StrSave("-");
13097         gameInfo.result = r;
13098         gameInfo.resultDetails = p;
13099         break;
13100
13101       case EditPosition:
13102         gameInfo.event = StrSave("Edited position");
13103         gameInfo.site = StrSave(HostName());
13104         gameInfo.date = PGNDate();
13105         gameInfo.round = StrSave("-");
13106         gameInfo.white = StrSave("-");
13107         gameInfo.black = StrSave("-");
13108         break;
13109
13110       case IcsPlayingWhite:
13111       case IcsPlayingBlack:
13112       case IcsObserving:
13113       case IcsExamining:
13114         break;
13115
13116       case PlayFromGameFile:
13117         gameInfo.event = StrSave("Game from non-PGN file");
13118         gameInfo.site = StrSave(HostName());
13119         gameInfo.date = PGNDate();
13120         gameInfo.round = StrSave("-");
13121         gameInfo.white = StrSave("?");
13122         gameInfo.black = StrSave("?");
13123         break;
13124
13125       default:
13126         break;
13127     }
13128 }
13129
13130 void
13131 ReplaceComment(index, text)
13132      int index;
13133      char *text;
13134 {
13135     int len;
13136
13137     while (*text == '\n') text++;
13138     len = strlen(text);
13139     while (len > 0 && text[len - 1] == '\n') len--;
13140
13141     if (commentList[index] != NULL)
13142       free(commentList[index]);
13143
13144     if (len == 0) {
13145         commentList[index] = NULL;
13146         return;
13147     }
13148   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13149       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13150       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13151     commentList[index] = (char *) malloc(len + 2);
13152     strncpy(commentList[index], text, len);
13153     commentList[index][len] = '\n';
13154     commentList[index][len + 1] = NULLCHAR;
13155   } else {
13156     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13157     char *p;
13158     commentList[index] = (char *) malloc(len + 7);
13159     safeStrCpy(commentList[index], "{\n", 3);
13160     safeStrCpy(commentList[index]+2, text, len+1);
13161     commentList[index][len+2] = NULLCHAR;
13162     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13163     strcat(commentList[index], "\n}\n");
13164   }
13165 }
13166
13167 void
13168 CrushCRs(text)
13169      char *text;
13170 {
13171   char *p = text;
13172   char *q = text;
13173   char ch;
13174
13175   do {
13176     ch = *p++;
13177     if (ch == '\r') continue;
13178     *q++ = ch;
13179   } while (ch != '\0');
13180 }
13181
13182 void
13183 AppendComment(index, text, addBraces)
13184      int index;
13185      char *text;
13186      Boolean addBraces; // [HGM] braces: tells if we should add {}
13187 {
13188     int oldlen, len;
13189     char *old;
13190
13191 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13192     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13193
13194     CrushCRs(text);
13195     while (*text == '\n') text++;
13196     len = strlen(text);
13197     while (len > 0 && text[len - 1] == '\n') len--;
13198
13199     if (len == 0) return;
13200
13201     if (commentList[index] != NULL) {
13202         old = commentList[index];
13203         oldlen = strlen(old);
13204         while(commentList[index][oldlen-1] ==  '\n')
13205           commentList[index][--oldlen] = NULLCHAR;
13206         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13207         safeStrCpy(commentList[index], old, oldlen);
13208         free(old);
13209         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13210         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13211           if(addBraces) addBraces = FALSE; else { text++; len--; }
13212           while (*text == '\n') { text++; len--; }
13213           commentList[index][--oldlen] = NULLCHAR;
13214       }
13215         if(addBraces) strcat(commentList[index], "\n{\n");
13216         else          strcat(commentList[index], "\n");
13217         strcat(commentList[index], text);
13218         if(addBraces) strcat(commentList[index], "\n}\n");
13219         else          strcat(commentList[index], "\n");
13220     } else {
13221         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13222         if(addBraces)
13223           safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0]));
13224         else commentList[index][0] = NULLCHAR;
13225         strcat(commentList[index], text);
13226         strcat(commentList[index], "\n");
13227         if(addBraces) strcat(commentList[index], "}\n");
13228     }
13229 }
13230
13231 static char * FindStr( char * text, char * sub_text )
13232 {
13233     char * result = strstr( text, sub_text );
13234
13235     if( result != NULL ) {
13236         result += strlen( sub_text );
13237     }
13238
13239     return result;
13240 }
13241
13242 /* [AS] Try to extract PV info from PGN comment */
13243 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13244 char *GetInfoFromComment( int index, char * text )
13245 {
13246     char * sep = text;
13247
13248     if( text != NULL && index > 0 ) {
13249         int score = 0;
13250         int depth = 0;
13251         int time = -1, sec = 0, deci;
13252         char * s_eval = FindStr( text, "[%eval " );
13253         char * s_emt = FindStr( text, "[%emt " );
13254
13255         if( s_eval != NULL || s_emt != NULL ) {
13256             /* New style */
13257             char delim;
13258
13259             if( s_eval != NULL ) {
13260                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13261                     return text;
13262                 }
13263
13264                 if( delim != ']' ) {
13265                     return text;
13266                 }
13267             }
13268
13269             if( s_emt != NULL ) {
13270             }
13271                 return text;
13272         }
13273         else {
13274             /* We expect something like: [+|-]nnn.nn/dd */
13275             int score_lo = 0;
13276
13277             if(*text != '{') return text; // [HGM] braces: must be normal comment
13278
13279             sep = strchr( text, '/' );
13280             if( sep == NULL || sep < (text+4) ) {
13281                 return text;
13282             }
13283
13284             time = -1; sec = -1; deci = -1;
13285             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13286                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13287                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13288                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13289                 return text;
13290             }
13291
13292             if( score_lo < 0 || score_lo >= 100 ) {
13293                 return text;
13294             }
13295
13296             if(sec >= 0) time = 600*time + 10*sec; else
13297             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13298
13299             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13300
13301             /* [HGM] PV time: now locate end of PV info */
13302             while( *++sep >= '0' && *sep <= '9'); // strip depth
13303             if(time >= 0)
13304             while( *++sep >= '0' && *sep <= '9'); // strip time
13305             if(sec >= 0)
13306             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13307             if(deci >= 0)
13308             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13309             while(*sep == ' ') sep++;
13310         }
13311
13312         if( depth <= 0 ) {
13313             return text;
13314         }
13315
13316         if( time < 0 ) {
13317             time = -1;
13318         }
13319
13320         pvInfoList[index-1].depth = depth;
13321         pvInfoList[index-1].score = score;
13322         pvInfoList[index-1].time  = 10*time; // centi-sec
13323         if(*sep == '}') *sep = 0; else *--sep = '{';
13324     }
13325     return sep;
13326 }
13327
13328 void
13329 SendToProgram(message, cps)
13330      char *message;
13331      ChessProgramState *cps;
13332 {
13333     int count, outCount, error;
13334     char buf[MSG_SIZ];
13335
13336     if (cps->pr == NULL) return;
13337     Attention(cps);
13338
13339     if (appData.debugMode) {
13340         TimeMark now;
13341         GetTimeMark(&now);
13342         fprintf(debugFP, "%ld >%-6s: %s",
13343                 SubtractTimeMarks(&now, &programStartTime),
13344                 cps->which, message);
13345     }
13346
13347     count = strlen(message);
13348     outCount = OutputToProcess(cps->pr, message, count, &error);
13349     if (outCount < count && !exiting
13350                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13351         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13352         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13353             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13354                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13355                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13356             } else {
13357                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13358             }
13359             gameInfo.resultDetails = StrSave(buf);
13360         }
13361         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13362     }
13363 }
13364
13365 void
13366 ReceiveFromProgram(isr, closure, message, count, error)
13367      InputSourceRef isr;
13368      VOIDSTAR closure;
13369      char *message;
13370      int count;
13371      int error;
13372 {
13373     char *end_str;
13374     char buf[MSG_SIZ];
13375     ChessProgramState *cps = (ChessProgramState *)closure;
13376
13377     if (isr != cps->isr) return; /* Killed intentionally */
13378     if (count <= 0) {
13379         if (count == 0) {
13380             sprintf(buf,
13381                     _("Error: %s chess program (%s) exited unexpectedly"),
13382                     cps->which, cps->program);
13383         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13384                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13385                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13386                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13387                 } else {
13388                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13389                 }
13390                 gameInfo.resultDetails = StrSave(buf);
13391             }
13392             RemoveInputSource(cps->isr);
13393             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13394         } else {
13395             sprintf(buf,
13396                     _("Error reading from %s chess program (%s)"),
13397                     cps->which, cps->program);
13398             RemoveInputSource(cps->isr);
13399
13400             /* [AS] Program is misbehaving badly... kill it */
13401             if( count == -2 ) {
13402                 DestroyChildProcess( cps->pr, 9 );
13403                 cps->pr = NoProc;
13404             }
13405
13406             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13407         }
13408         return;
13409     }
13410
13411     if ((end_str = strchr(message, '\r')) != NULL)
13412       *end_str = NULLCHAR;
13413     if ((end_str = strchr(message, '\n')) != NULL)
13414       *end_str = NULLCHAR;
13415
13416     if (appData.debugMode) {
13417         TimeMark now; int print = 1;
13418         char *quote = ""; char c; int i;
13419
13420         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13421                 char start = message[0];
13422                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13423                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13424                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13425                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13426                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13427                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13428                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13429                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13430                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13431                     print = (appData.engineComments >= 2);
13432                 }
13433                 message[0] = start; // restore original message
13434         }
13435         if(print) {
13436                 GetTimeMark(&now);
13437                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13438                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13439                         quote,
13440                         message);
13441         }
13442     }
13443
13444     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13445     if (appData.icsEngineAnalyze) {
13446         if (strstr(message, "whisper") != NULL ||
13447              strstr(message, "kibitz") != NULL ||
13448             strstr(message, "tellics") != NULL) return;
13449     }
13450
13451     HandleMachineMove(message, cps);
13452 }
13453
13454
13455 void
13456 SendTimeControl(cps, mps, tc, inc, sd, st)
13457      ChessProgramState *cps;
13458      int mps, inc, sd, st;
13459      long tc;
13460 {
13461     char buf[MSG_SIZ];
13462     int seconds;
13463
13464     if( timeControl_2 > 0 ) {
13465         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13466             tc = timeControl_2;
13467         }
13468     }
13469     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13470     inc /= cps->timeOdds;
13471     st  /= cps->timeOdds;
13472
13473     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13474
13475     if (st > 0) {
13476       /* Set exact time per move, normally using st command */
13477       if (cps->stKludge) {
13478         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13479         seconds = st % 60;
13480         if (seconds == 0) {
13481           sprintf(buf, "level 1 %d\n", st/60);
13482         } else {
13483           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13484         }
13485       } else {
13486         sprintf(buf, "st %d\n", st);
13487       }
13488     } else {
13489       /* Set conventional or incremental time control, using level command */
13490       if (seconds == 0) {
13491         /* Note old gnuchess bug -- minutes:seconds used to not work.
13492            Fixed in later versions, but still avoid :seconds
13493            when seconds is 0. */
13494         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13495       } else {
13496         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13497                 seconds, inc/1000);
13498       }
13499     }
13500     SendToProgram(buf, cps);
13501
13502     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13503     /* Orthogonally, limit search to given depth */
13504     if (sd > 0) {
13505       if (cps->sdKludge) {
13506         sprintf(buf, "depth\n%d\n", sd);
13507       } else {
13508         sprintf(buf, "sd %d\n", sd);
13509       }
13510       SendToProgram(buf, cps);
13511     }
13512
13513     if(cps->nps > 0) { /* [HGM] nps */
13514         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13515         else {
13516                 sprintf(buf, "nps %d\n", cps->nps);
13517               SendToProgram(buf, cps);
13518         }
13519     }
13520 }
13521
13522 ChessProgramState *WhitePlayer()
13523 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13524 {
13525     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13526        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13527         return &second;
13528     return &first;
13529 }
13530
13531 void
13532 SendTimeRemaining(cps, machineWhite)
13533      ChessProgramState *cps;
13534      int /*boolean*/ machineWhite;
13535 {
13536     char message[MSG_SIZ];
13537     long time, otime;
13538
13539     /* Note: this routine must be called when the clocks are stopped
13540        or when they have *just* been set or switched; otherwise
13541        it will be off by the time since the current tick started.
13542     */
13543     if (machineWhite) {
13544         time = whiteTimeRemaining / 10;
13545         otime = blackTimeRemaining / 10;
13546     } else {
13547         time = blackTimeRemaining / 10;
13548         otime = whiteTimeRemaining / 10;
13549     }
13550     /* [HGM] translate opponent's time by time-odds factor */
13551     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13552     if (appData.debugMode) {
13553         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13554     }
13555
13556     if (time <= 0) time = 1;
13557     if (otime <= 0) otime = 1;
13558
13559     sprintf(message, "time %ld\n", time);
13560     SendToProgram(message, cps);
13561
13562     sprintf(message, "otim %ld\n", otime);
13563     SendToProgram(message, cps);
13564 }
13565
13566 int
13567 BoolFeature(p, name, loc, cps)
13568      char **p;
13569      char *name;
13570      int *loc;
13571      ChessProgramState *cps;
13572 {
13573   char buf[MSG_SIZ];
13574   int len = strlen(name);
13575   int val;
13576   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13577     (*p) += len + 1;
13578     sscanf(*p, "%d", &val);
13579     *loc = (val != 0);
13580     while (**p && **p != ' ') (*p)++;
13581     sprintf(buf, "accepted %s\n", name);
13582     SendToProgram(buf, cps);
13583     return TRUE;
13584   }
13585   return FALSE;
13586 }
13587
13588 int
13589 IntFeature(p, name, loc, cps)
13590      char **p;
13591      char *name;
13592      int *loc;
13593      ChessProgramState *cps;
13594 {
13595   char buf[MSG_SIZ];
13596   int len = strlen(name);
13597   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13598     (*p) += len + 1;
13599     sscanf(*p, "%d", loc);
13600     while (**p && **p != ' ') (*p)++;
13601     sprintf(buf, "accepted %s\n", name);
13602     SendToProgram(buf, cps);
13603     return TRUE;
13604   }
13605   return FALSE;
13606 }
13607
13608 int
13609 StringFeature(p, name, loc, cps)
13610      char **p;
13611      char *name;
13612      char loc[];
13613      ChessProgramState *cps;
13614 {
13615   char buf[MSG_SIZ];
13616   int len = strlen(name);
13617   if (strncmp((*p), name, len) == 0
13618       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13619     (*p) += len + 2;
13620     sscanf(*p, "%[^\"]", loc);
13621     while (**p && **p != '\"') (*p)++;
13622     if (**p == '\"') (*p)++;
13623     sprintf(buf, "accepted %s\n", name);
13624     SendToProgram(buf, cps);
13625     return TRUE;
13626   }
13627   return FALSE;
13628 }
13629
13630 int
13631 ParseOption(Option *opt, ChessProgramState *cps)
13632 // [HGM] options: process the string that defines an engine option, and determine
13633 // name, type, default value, and allowed value range
13634 {
13635         char *p, *q, buf[MSG_SIZ];
13636         int n, min = (-1)<<31, max = 1<<31, def;
13637
13638         if(p = strstr(opt->name, " -spin ")) {
13639             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13640             if(max < min) max = min; // enforce consistency
13641             if(def < min) def = min;
13642             if(def > max) def = max;
13643             opt->value = def;
13644             opt->min = min;
13645             opt->max = max;
13646             opt->type = Spin;
13647         } else if((p = strstr(opt->name, " -slider "))) {
13648             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13649             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13650             if(max < min) max = min; // enforce consistency
13651             if(def < min) def = min;
13652             if(def > max) def = max;
13653             opt->value = def;
13654             opt->min = min;
13655             opt->max = max;
13656             opt->type = Spin; // Slider;
13657         } else if((p = strstr(opt->name, " -string "))) {
13658             opt->textValue = p+9;
13659             opt->type = TextBox;
13660         } else if((p = strstr(opt->name, " -file "))) {
13661             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13662             opt->textValue = p+7;
13663             opt->type = TextBox; // FileName;
13664         } else if((p = strstr(opt->name, " -path "))) {
13665             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13666             opt->textValue = p+7;
13667             opt->type = TextBox; // PathName;
13668         } else if(p = strstr(opt->name, " -check ")) {
13669             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13670             opt->value = (def != 0);
13671             opt->type = CheckBox;
13672         } else if(p = strstr(opt->name, " -combo ")) {
13673             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13674             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13675             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13676             opt->value = n = 0;
13677             while(q = StrStr(q, " /// ")) {
13678                 n++; *q = 0;    // count choices, and null-terminate each of them
13679                 q += 5;
13680                 if(*q == '*') { // remember default, which is marked with * prefix
13681                     q++;
13682                     opt->value = n;
13683                 }
13684                 cps->comboList[cps->comboCnt++] = q;
13685             }
13686             cps->comboList[cps->comboCnt++] = NULL;
13687             opt->max = n + 1;
13688             opt->type = ComboBox;
13689         } else if(p = strstr(opt->name, " -button")) {
13690             opt->type = Button;
13691         } else if(p = strstr(opt->name, " -save")) {
13692             opt->type = SaveButton;
13693         } else return FALSE;
13694         *p = 0; // terminate option name
13695         // now look if the command-line options define a setting for this engine option.
13696         if(cps->optionSettings && cps->optionSettings[0])
13697             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13698         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13699                 sprintf(buf, "option %s", p);
13700                 if(p = strstr(buf, ",")) *p = 0;
13701                 strcat(buf, "\n");
13702                 SendToProgram(buf, cps);
13703         }
13704         return TRUE;
13705 }
13706
13707 void
13708 FeatureDone(cps, val)
13709      ChessProgramState* cps;
13710      int val;
13711 {
13712   DelayedEventCallback cb = GetDelayedEvent();
13713   if ((cb == InitBackEnd3 && cps == &first) ||
13714       (cb == TwoMachinesEventIfReady && cps == &second)) {
13715     CancelDelayedEvent();
13716     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13717   }
13718   cps->initDone = val;
13719 }
13720
13721 /* Parse feature command from engine */
13722 void
13723 ParseFeatures(args, cps)
13724      char* args;
13725      ChessProgramState *cps;
13726 {
13727   char *p = args;
13728   char *q;
13729   int val;
13730   char buf[MSG_SIZ];
13731
13732   for (;;) {
13733     while (*p == ' ') p++;
13734     if (*p == NULLCHAR) return;
13735
13736     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13737     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13738     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13739     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13740     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13741     if (BoolFeature(&p, "reuse", &val, cps)) {
13742       /* Engine can disable reuse, but can't enable it if user said no */
13743       if (!val) cps->reuse = FALSE;
13744       continue;
13745     }
13746     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13747     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13748       if (gameMode == TwoMachinesPlay) {
13749         DisplayTwoMachinesTitle();
13750       } else {
13751         DisplayTitle("");
13752       }
13753       continue;
13754     }
13755     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13756     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13757     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13758     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13759     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13760     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13761     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13762     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13763     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13764     if (IntFeature(&p, "done", &val, cps)) {
13765       FeatureDone(cps, val);
13766       continue;
13767     }
13768     /* Added by Tord: */
13769     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13770     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13771     /* End of additions by Tord */
13772
13773     /* [HGM] added features: */
13774     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13775     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13776     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13777     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13778     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13779     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13780     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13781         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13782             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13783             SendToProgram(buf, cps);
13784             continue;
13785         }
13786         if(cps->nrOptions >= MAX_OPTIONS) {
13787             cps->nrOptions--;
13788             sprintf(buf, "%s engine has too many options\n", cps->which);
13789             DisplayError(buf, 0);
13790         }
13791         continue;
13792     }
13793     /* End of additions by HGM */
13794
13795     /* unknown feature: complain and skip */
13796     q = p;
13797     while (*q && *q != '=') q++;
13798     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13799     SendToProgram(buf, cps);
13800     p = q;
13801     if (*p == '=') {
13802       p++;
13803       if (*p == '\"') {
13804         p++;
13805         while (*p && *p != '\"') p++;
13806         if (*p == '\"') p++;
13807       } else {
13808         while (*p && *p != ' ') p++;
13809       }
13810     }
13811   }
13812
13813 }
13814
13815 void
13816 PeriodicUpdatesEvent(newState)
13817      int newState;
13818 {
13819     if (newState == appData.periodicUpdates)
13820       return;
13821
13822     appData.periodicUpdates=newState;
13823
13824     /* Display type changes, so update it now */
13825 //    DisplayAnalysis();
13826
13827     /* Get the ball rolling again... */
13828     if (newState) {
13829         AnalysisPeriodicEvent(1);
13830         StartAnalysisClock();
13831     }
13832 }
13833
13834 void
13835 PonderNextMoveEvent(newState)
13836      int newState;
13837 {
13838     if (newState == appData.ponderNextMove) return;
13839     if (gameMode == EditPosition) EditPositionDone(TRUE);
13840     if (newState) {
13841         SendToProgram("hard\n", &first);
13842         if (gameMode == TwoMachinesPlay) {
13843             SendToProgram("hard\n", &second);
13844         }
13845     } else {
13846         SendToProgram("easy\n", &first);
13847         thinkOutput[0] = NULLCHAR;
13848         if (gameMode == TwoMachinesPlay) {
13849             SendToProgram("easy\n", &second);
13850         }
13851     }
13852     appData.ponderNextMove = newState;
13853 }
13854
13855 void
13856 NewSettingEvent(option, feature, command, value)
13857      char *command;
13858      int option, value, *feature;
13859 {
13860     char buf[MSG_SIZ];
13861
13862     if (gameMode == EditPosition) EditPositionDone(TRUE);
13863     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13864     if(feature == NULL || *feature) SendToProgram(buf, &first);
13865     if (gameMode == TwoMachinesPlay) {
13866         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13867     }
13868 }
13869
13870 void
13871 ShowThinkingEvent()
13872 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13873 {
13874     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13875     int newState = appData.showThinking
13876         // [HGM] thinking: other features now need thinking output as well
13877         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13878
13879     if (oldState == newState) return;
13880     oldState = newState;
13881     if (gameMode == EditPosition) EditPositionDone(TRUE);
13882     if (oldState) {
13883         SendToProgram("post\n", &first);
13884         if (gameMode == TwoMachinesPlay) {
13885             SendToProgram("post\n", &second);
13886         }
13887     } else {
13888         SendToProgram("nopost\n", &first);
13889         thinkOutput[0] = NULLCHAR;
13890         if (gameMode == TwoMachinesPlay) {
13891             SendToProgram("nopost\n", &second);
13892         }
13893     }
13894 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13895 }
13896
13897 void
13898 AskQuestionEvent(title, question, replyPrefix, which)
13899      char *title; char *question; char *replyPrefix; char *which;
13900 {
13901   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13902   if (pr == NoProc) return;
13903   AskQuestion(title, question, replyPrefix, pr);
13904 }
13905
13906 void
13907 DisplayMove(moveNumber)
13908      int moveNumber;
13909 {
13910     char message[MSG_SIZ];
13911     char res[MSG_SIZ];
13912     char cpThinkOutput[MSG_SIZ];
13913
13914     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13915
13916     if (moveNumber == forwardMostMove - 1 ||
13917         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13918
13919         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
13920
13921         if (strchr(cpThinkOutput, '\n')) {
13922             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13923         }
13924     } else {
13925         *cpThinkOutput = NULLCHAR;
13926     }
13927
13928     /* [AS] Hide thinking from human user */
13929     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13930         *cpThinkOutput = NULLCHAR;
13931         if( thinkOutput[0] != NULLCHAR ) {
13932             int i;
13933
13934             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13935                 cpThinkOutput[i] = '.';
13936             }
13937             cpThinkOutput[i] = NULLCHAR;
13938             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13939         }
13940     }
13941
13942     if (moveNumber == forwardMostMove - 1 &&
13943         gameInfo.resultDetails != NULL) {
13944         if (gameInfo.resultDetails[0] == NULLCHAR) {
13945             sprintf(res, " %s", PGNResult(gameInfo.result));
13946         } else {
13947             sprintf(res, " {%s} %s",
13948                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
13949         }
13950     } else {
13951         res[0] = NULLCHAR;
13952     }
13953
13954     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13955         DisplayMessage(res, cpThinkOutput);
13956     } else {
13957         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13958                 WhiteOnMove(moveNumber) ? " " : ".. ",
13959                 parseList[moveNumber], res);
13960         DisplayMessage(message, cpThinkOutput);
13961     }
13962 }
13963
13964 void
13965 DisplayComment(moveNumber, text)
13966      int moveNumber;
13967      char *text;
13968 {
13969     char title[MSG_SIZ];
13970     char buf[8000]; // comment can be long!
13971     int score, depth;
13972
13973     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13974       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
13975     } else {
13976       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13977               WhiteOnMove(moveNumber) ? " " : ".. ",
13978               parseList[moveNumber]);
13979     }
13980     // [HGM] PV info: display PV info together with (or as) comment
13981     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13982       if(text == NULL) text = "";
13983       score = pvInfoList[moveNumber].score;
13984       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13985               depth, (pvInfoList[moveNumber].time+50)/100, text);
13986       text = buf;
13987     }
13988     if (text != NULL && (appData.autoDisplayComment || commentUp))
13989         CommentPopUp(title, text);
13990 }
13991
13992 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13993  * might be busy thinking or pondering.  It can be omitted if your
13994  * gnuchess is configured to stop thinking immediately on any user
13995  * input.  However, that gnuchess feature depends on the FIONREAD
13996  * ioctl, which does not work properly on some flavors of Unix.
13997  */
13998 void
13999 Attention(cps)
14000      ChessProgramState *cps;
14001 {
14002 #if ATTENTION
14003     if (!cps->useSigint) return;
14004     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14005     switch (gameMode) {
14006       case MachinePlaysWhite:
14007       case MachinePlaysBlack:
14008       case TwoMachinesPlay:
14009       case IcsPlayingWhite:
14010       case IcsPlayingBlack:
14011       case AnalyzeMode:
14012       case AnalyzeFile:
14013         /* Skip if we know it isn't thinking */
14014         if (!cps->maybeThinking) return;
14015         if (appData.debugMode)
14016           fprintf(debugFP, "Interrupting %s\n", cps->which);
14017         InterruptChildProcess(cps->pr);
14018         cps->maybeThinking = FALSE;
14019         break;
14020       default:
14021         break;
14022     }
14023 #endif /*ATTENTION*/
14024 }
14025
14026 int
14027 CheckFlags()
14028 {
14029     if (whiteTimeRemaining <= 0) {
14030         if (!whiteFlag) {
14031             whiteFlag = TRUE;
14032             if (appData.icsActive) {
14033                 if (appData.autoCallFlag &&
14034                     gameMode == IcsPlayingBlack && !blackFlag) {
14035                   SendToICS(ics_prefix);
14036                   SendToICS("flag\n");
14037                 }
14038             } else {
14039                 if (blackFlag) {
14040                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14041                 } else {
14042                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14043                     if (appData.autoCallFlag) {
14044                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14045                         return TRUE;
14046                     }
14047                 }
14048             }
14049         }
14050     }
14051     if (blackTimeRemaining <= 0) {
14052         if (!blackFlag) {
14053             blackFlag = TRUE;
14054             if (appData.icsActive) {
14055                 if (appData.autoCallFlag &&
14056                     gameMode == IcsPlayingWhite && !whiteFlag) {
14057                   SendToICS(ics_prefix);
14058                   SendToICS("flag\n");
14059                 }
14060             } else {
14061                 if (whiteFlag) {
14062                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14063                 } else {
14064                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14065                     if (appData.autoCallFlag) {
14066                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14067                         return TRUE;
14068                     }
14069                 }
14070             }
14071         }
14072     }
14073     return FALSE;
14074 }
14075
14076 void
14077 CheckTimeControl()
14078 {
14079     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14080         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14081
14082     /*
14083      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14084      */
14085     if ( !WhiteOnMove(forwardMostMove) ) {
14086         /* White made time control */
14087         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14088         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14089         /* [HGM] time odds: correct new time quota for time odds! */
14090                                             / WhitePlayer()->timeOdds;
14091         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14092     } else {
14093         lastBlack -= blackTimeRemaining;
14094         /* Black made time control */
14095         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14096                                             / WhitePlayer()->other->timeOdds;
14097         lastWhite = whiteTimeRemaining;
14098     }
14099 }
14100
14101 void
14102 DisplayBothClocks()
14103 {
14104     int wom = gameMode == EditPosition ?
14105       !blackPlaysFirst : WhiteOnMove(currentMove);
14106     DisplayWhiteClock(whiteTimeRemaining, wom);
14107     DisplayBlackClock(blackTimeRemaining, !wom);
14108 }
14109
14110
14111 /* Timekeeping seems to be a portability nightmare.  I think everyone
14112    has ftime(), but I'm really not sure, so I'm including some ifdefs
14113    to use other calls if you don't.  Clocks will be less accurate if
14114    you have neither ftime nor gettimeofday.
14115 */
14116
14117 /* VS 2008 requires the #include outside of the function */
14118 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14119 #include <sys/timeb.h>
14120 #endif
14121
14122 /* Get the current time as a TimeMark */
14123 void
14124 GetTimeMark(tm)
14125      TimeMark *tm;
14126 {
14127 #if HAVE_GETTIMEOFDAY
14128
14129     struct timeval timeVal;
14130     struct timezone timeZone;
14131
14132     gettimeofday(&timeVal, &timeZone);
14133     tm->sec = (long) timeVal.tv_sec;
14134     tm->ms = (int) (timeVal.tv_usec / 1000L);
14135
14136 #else /*!HAVE_GETTIMEOFDAY*/
14137 #if HAVE_FTIME
14138
14139 // include <sys/timeb.h> / moved to just above start of function
14140     struct timeb timeB;
14141
14142     ftime(&timeB);
14143     tm->sec = (long) timeB.time;
14144     tm->ms = (int) timeB.millitm;
14145
14146 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14147     tm->sec = (long) time(NULL);
14148     tm->ms = 0;
14149 #endif
14150 #endif
14151 }
14152
14153 /* Return the difference in milliseconds between two
14154    time marks.  We assume the difference will fit in a long!
14155 */
14156 long
14157 SubtractTimeMarks(tm2, tm1)
14158      TimeMark *tm2, *tm1;
14159 {
14160     return 1000L*(tm2->sec - tm1->sec) +
14161            (long) (tm2->ms - tm1->ms);
14162 }
14163
14164
14165 /*
14166  * Code to manage the game clocks.
14167  *
14168  * In tournament play, black starts the clock and then white makes a move.
14169  * We give the human user a slight advantage if he is playing white---the
14170  * clocks don't run until he makes his first move, so it takes zero time.
14171  * Also, we don't account for network lag, so we could get out of sync
14172  * with GNU Chess's clock -- but then, referees are always right.
14173  */
14174
14175 static TimeMark tickStartTM;
14176 static long intendedTickLength;
14177
14178 long
14179 NextTickLength(timeRemaining)
14180      long timeRemaining;
14181 {
14182     long nominalTickLength, nextTickLength;
14183
14184     if (timeRemaining > 0L && timeRemaining <= 10000L)
14185       nominalTickLength = 100L;
14186     else
14187       nominalTickLength = 1000L;
14188     nextTickLength = timeRemaining % nominalTickLength;
14189     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14190
14191     return nextTickLength;
14192 }
14193
14194 /* Adjust clock one minute up or down */
14195 void
14196 AdjustClock(Boolean which, int dir)
14197 {
14198     if(which) blackTimeRemaining += 60000*dir;
14199     else      whiteTimeRemaining += 60000*dir;
14200     DisplayBothClocks();
14201 }
14202
14203 /* Stop clocks and reset to a fresh time control */
14204 void
14205 ResetClocks()
14206 {
14207     (void) StopClockTimer();
14208     if (appData.icsActive) {
14209         whiteTimeRemaining = blackTimeRemaining = 0;
14210     } else if (searchTime) {
14211         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14212         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14213     } else { /* [HGM] correct new time quote for time odds */
14214         whiteTC = blackTC = fullTimeControlString;
14215         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14216         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14217     }
14218     if (whiteFlag || blackFlag) {
14219         DisplayTitle("");
14220         whiteFlag = blackFlag = FALSE;
14221     }
14222     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14223     DisplayBothClocks();
14224 }
14225
14226 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14227
14228 /* Decrement running clock by amount of time that has passed */
14229 void
14230 DecrementClocks()
14231 {
14232     long timeRemaining;
14233     long lastTickLength, fudge;
14234     TimeMark now;
14235
14236     if (!appData.clockMode) return;
14237     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14238
14239     GetTimeMark(&now);
14240
14241     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14242
14243     /* Fudge if we woke up a little too soon */
14244     fudge = intendedTickLength - lastTickLength;
14245     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14246
14247     if (WhiteOnMove(forwardMostMove)) {
14248         if(whiteNPS >= 0) lastTickLength = 0;
14249         timeRemaining = whiteTimeRemaining -= lastTickLength;
14250         if(timeRemaining < 0) {
14251             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14252             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14253                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14254                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14255             }
14256         }
14257         DisplayWhiteClock(whiteTimeRemaining - fudge,
14258                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14259     } else {
14260         if(blackNPS >= 0) lastTickLength = 0;
14261         timeRemaining = blackTimeRemaining -= lastTickLength;
14262         if(timeRemaining < 0) { // [HGM] if we run out of a non-last incremental session, go to the next
14263             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14264             if(suddenDeath) {
14265                 blackStartMove = forwardMostMove;
14266                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14267             }
14268         }
14269         DisplayBlackClock(blackTimeRemaining - fudge,
14270                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14271     }
14272     if (CheckFlags()) return;
14273
14274     tickStartTM = now;
14275     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14276     StartClockTimer(intendedTickLength);
14277
14278     /* if the time remaining has fallen below the alarm threshold, sound the
14279      * alarm. if the alarm has sounded and (due to a takeback or time control
14280      * with increment) the time remaining has increased to a level above the
14281      * threshold, reset the alarm so it can sound again.
14282      */
14283
14284     if (appData.icsActive && appData.icsAlarm) {
14285
14286         /* make sure we are dealing with the user's clock */
14287         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14288                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14289            )) return;
14290
14291         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14292             alarmSounded = FALSE;
14293         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14294             PlayAlarmSound();
14295             alarmSounded = TRUE;
14296         }
14297     }
14298 }
14299
14300
14301 /* A player has just moved, so stop the previously running
14302    clock and (if in clock mode) start the other one.
14303    We redisplay both clocks in case we're in ICS mode, because
14304    ICS gives us an update to both clocks after every move.
14305    Note that this routine is called *after* forwardMostMove
14306    is updated, so the last fractional tick must be subtracted
14307    from the color that is *not* on move now.
14308 */
14309 void
14310 SwitchClocks(int newMoveNr)
14311 {
14312     long lastTickLength;
14313     TimeMark now;
14314     int flagged = FALSE;
14315
14316     GetTimeMark(&now);
14317
14318     if (StopClockTimer() && appData.clockMode) {
14319         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14320         if (!WhiteOnMove(forwardMostMove)) {
14321             if(blackNPS >= 0) lastTickLength = 0;
14322             blackTimeRemaining -= lastTickLength;
14323            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14324 //         if(pvInfoList[forwardMostMove-1].time == -1)
14325                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14326                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14327         } else {
14328            if(whiteNPS >= 0) lastTickLength = 0;
14329            whiteTimeRemaining -= lastTickLength;
14330            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14331 //         if(pvInfoList[forwardMostMove-1].time == -1)
14332                  pvInfoList[forwardMostMove-1].time =
14333                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14334         }
14335         flagged = CheckFlags();
14336     }
14337     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14338     CheckTimeControl();
14339
14340     if (flagged || !appData.clockMode) return;
14341
14342     switch (gameMode) {
14343       case MachinePlaysBlack:
14344       case MachinePlaysWhite:
14345       case BeginningOfGame:
14346         if (pausing) return;
14347         break;
14348
14349       case EditGame:
14350       case PlayFromGameFile:
14351       case IcsExamining:
14352         return;
14353
14354       default:
14355         break;
14356     }
14357
14358     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14359         if(WhiteOnMove(forwardMostMove))
14360              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14361         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14362     }
14363
14364     tickStartTM = now;
14365     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14366       whiteTimeRemaining : blackTimeRemaining);
14367     StartClockTimer(intendedTickLength);
14368 }
14369
14370
14371 /* Stop both clocks */
14372 void
14373 StopClocks()
14374 {
14375     long lastTickLength;
14376     TimeMark now;
14377
14378     if (!StopClockTimer()) return;
14379     if (!appData.clockMode) return;
14380
14381     GetTimeMark(&now);
14382
14383     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14384     if (WhiteOnMove(forwardMostMove)) {
14385         if(whiteNPS >= 0) lastTickLength = 0;
14386         whiteTimeRemaining -= lastTickLength;
14387         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14388     } else {
14389         if(blackNPS >= 0) lastTickLength = 0;
14390         blackTimeRemaining -= lastTickLength;
14391         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14392     }
14393     CheckFlags();
14394 }
14395
14396 /* Start clock of player on move.  Time may have been reset, so
14397    if clock is already running, stop and restart it. */
14398 void
14399 StartClocks()
14400 {
14401     (void) StopClockTimer(); /* in case it was running already */
14402     DisplayBothClocks();
14403     if (CheckFlags()) return;
14404
14405     if (!appData.clockMode) return;
14406     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14407
14408     GetTimeMark(&tickStartTM);
14409     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14410       whiteTimeRemaining : blackTimeRemaining);
14411
14412    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14413     whiteNPS = blackNPS = -1;
14414     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14415        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14416         whiteNPS = first.nps;
14417     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14418        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14419         blackNPS = first.nps;
14420     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14421         whiteNPS = second.nps;
14422     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14423         blackNPS = second.nps;
14424     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14425
14426     StartClockTimer(intendedTickLength);
14427 }
14428
14429 char *
14430 TimeString(ms)
14431      long ms;
14432 {
14433     long second, minute, hour, day;
14434     char *sign = "";
14435     static char buf[32];
14436
14437     if (ms > 0 && ms <= 9900) {
14438       /* convert milliseconds to tenths, rounding up */
14439       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14440
14441       sprintf(buf, " %03.1f ", tenths/10.0);
14442       return buf;
14443     }
14444
14445     /* convert milliseconds to seconds, rounding up */
14446     /* use floating point to avoid strangeness of integer division
14447        with negative dividends on many machines */
14448     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14449
14450     if (second < 0) {
14451         sign = "-";
14452         second = -second;
14453     }
14454
14455     day = second / (60 * 60 * 24);
14456     second = second % (60 * 60 * 24);
14457     hour = second / (60 * 60);
14458     second = second % (60 * 60);
14459     minute = second / 60;
14460     second = second % 60;
14461
14462     if (day > 0)
14463       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14464               sign, day, hour, minute, second);
14465     else if (hour > 0)
14466       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14467     else
14468       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14469
14470     return buf;
14471 }
14472
14473
14474 /*
14475  * This is necessary because some C libraries aren't ANSI C compliant yet.
14476  */
14477 char *
14478 StrStr(string, match)
14479      char *string, *match;
14480 {
14481     int i, length;
14482
14483     length = strlen(match);
14484
14485     for (i = strlen(string) - length; i >= 0; i--, string++)
14486       if (!strncmp(match, string, length))
14487         return string;
14488
14489     return NULL;
14490 }
14491
14492 char *
14493 StrCaseStr(string, match)
14494      char *string, *match;
14495 {
14496     int i, j, length;
14497
14498     length = strlen(match);
14499
14500     for (i = strlen(string) - length; i >= 0; i--, string++) {
14501         for (j = 0; j < length; j++) {
14502             if (ToLower(match[j]) != ToLower(string[j]))
14503               break;
14504         }
14505         if (j == length) return string;
14506     }
14507
14508     return NULL;
14509 }
14510
14511 #ifndef _amigados
14512 int
14513 StrCaseCmp(s1, s2)
14514      char *s1, *s2;
14515 {
14516     char c1, c2;
14517
14518     for (;;) {
14519         c1 = ToLower(*s1++);
14520         c2 = ToLower(*s2++);
14521         if (c1 > c2) return 1;
14522         if (c1 < c2) return -1;
14523         if (c1 == NULLCHAR) return 0;
14524     }
14525 }
14526
14527
14528 int
14529 ToLower(c)
14530      int c;
14531 {
14532     return isupper(c) ? tolower(c) : c;
14533 }
14534
14535
14536 int
14537 ToUpper(c)
14538      int c;
14539 {
14540     return islower(c) ? toupper(c) : c;
14541 }
14542 #endif /* !_amigados    */
14543
14544 char *
14545 StrSave(s)
14546      char *s;
14547 {
14548   char *ret;
14549
14550   if ((ret = (char *) malloc(strlen(s) + 1)))
14551     {
14552       safeStrCpy(ret, s, strlen(s)+1);
14553     }
14554   return ret;
14555 }
14556
14557 char *
14558 StrSavePtr(s, savePtr)
14559      char *s, **savePtr;
14560 {
14561     if (*savePtr) {
14562         free(*savePtr);
14563     }
14564     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14565       safeStrCpy(*savePtr, s, strlen(s)+1);
14566     }
14567     return(*savePtr);
14568 }
14569
14570 char *
14571 PGNDate()
14572 {
14573     time_t clock;
14574     struct tm *tm;
14575     char buf[MSG_SIZ];
14576
14577     clock = time((time_t *)NULL);
14578     tm = localtime(&clock);
14579     sprintf(buf, "%04d.%02d.%02d",
14580             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14581     return StrSave(buf);
14582 }
14583
14584
14585 char *
14586 PositionToFEN(move, overrideCastling)
14587      int move;
14588      char *overrideCastling;
14589 {
14590     int i, j, fromX, fromY, toX, toY;
14591     int whiteToPlay;
14592     char buf[128];
14593     char *p, *q;
14594     int emptycount;
14595     ChessSquare piece;
14596
14597     whiteToPlay = (gameMode == EditPosition) ?
14598       !blackPlaysFirst : (move % 2 == 0);
14599     p = buf;
14600
14601     /* Piece placement data */
14602     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14603         emptycount = 0;
14604         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14605             if (boards[move][i][j] == EmptySquare) {
14606                 emptycount++;
14607             } else { ChessSquare piece = boards[move][i][j];
14608                 if (emptycount > 0) {
14609                     if(emptycount<10) /* [HGM] can be >= 10 */
14610                         *p++ = '0' + emptycount;
14611                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14612                     emptycount = 0;
14613                 }
14614                 if(PieceToChar(piece) == '+') {
14615                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14616                     *p++ = '+';
14617                     piece = (ChessSquare)(DEMOTED piece);
14618                 }
14619                 *p++ = PieceToChar(piece);
14620                 if(p[-1] == '~') {
14621                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14622                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14623                     *p++ = '~';
14624                 }
14625             }
14626         }
14627         if (emptycount > 0) {
14628             if(emptycount<10) /* [HGM] can be >= 10 */
14629                 *p++ = '0' + emptycount;
14630             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14631             emptycount = 0;
14632         }
14633         *p++ = '/';
14634     }
14635     *(p - 1) = ' ';
14636
14637     /* [HGM] print Crazyhouse or Shogi holdings */
14638     if( gameInfo.holdingsWidth ) {
14639         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14640         q = p;
14641         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14642             piece = boards[move][i][BOARD_WIDTH-1];
14643             if( piece != EmptySquare )
14644               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14645                   *p++ = PieceToChar(piece);
14646         }
14647         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14648             piece = boards[move][BOARD_HEIGHT-i-1][0];
14649             if( piece != EmptySquare )
14650               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14651                   *p++ = PieceToChar(piece);
14652         }
14653
14654         if( q == p ) *p++ = '-';
14655         *p++ = ']';
14656         *p++ = ' ';
14657     }
14658
14659     /* Active color */
14660     *p++ = whiteToPlay ? 'w' : 'b';
14661     *p++ = ' ';
14662
14663   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14664     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14665   } else {
14666   if(nrCastlingRights) {
14667      q = p;
14668      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14669        /* [HGM] write directly from rights */
14670            if(boards[move][CASTLING][2] != NoRights &&
14671               boards[move][CASTLING][0] != NoRights   )
14672                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14673            if(boards[move][CASTLING][2] != NoRights &&
14674               boards[move][CASTLING][1] != NoRights   )
14675                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14676            if(boards[move][CASTLING][5] != NoRights &&
14677               boards[move][CASTLING][3] != NoRights   )
14678                 *p++ = boards[move][CASTLING][3] + AAA;
14679            if(boards[move][CASTLING][5] != NoRights &&
14680               boards[move][CASTLING][4] != NoRights   )
14681                 *p++ = boards[move][CASTLING][4] + AAA;
14682      } else {
14683
14684         /* [HGM] write true castling rights */
14685         if( nrCastlingRights == 6 ) {
14686             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14687                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14688             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14689                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14690             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14691                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14692             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14693                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14694         }
14695      }
14696      if (q == p) *p++ = '-'; /* No castling rights */
14697      *p++ = ' ';
14698   }
14699
14700   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14701      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14702     /* En passant target square */
14703     if (move > backwardMostMove) {
14704         fromX = moveList[move - 1][0] - AAA;
14705         fromY = moveList[move - 1][1] - ONE;
14706         toX = moveList[move - 1][2] - AAA;
14707         toY = moveList[move - 1][3] - ONE;
14708         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14709             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14710             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14711             fromX == toX) {
14712             /* 2-square pawn move just happened */
14713             *p++ = toX + AAA;
14714             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14715         } else {
14716             *p++ = '-';
14717         }
14718     } else if(move == backwardMostMove) {
14719         // [HGM] perhaps we should always do it like this, and forget the above?
14720         if((signed char)boards[move][EP_STATUS] >= 0) {
14721             *p++ = boards[move][EP_STATUS] + AAA;
14722             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14723         } else {
14724             *p++ = '-';
14725         }
14726     } else {
14727         *p++ = '-';
14728     }
14729     *p++ = ' ';
14730   }
14731   }
14732
14733     /* [HGM] find reversible plies */
14734     {   int i = 0, j=move;
14735
14736         if (appData.debugMode) { int k;
14737             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14738             for(k=backwardMostMove; k<=forwardMostMove; k++)
14739                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14740
14741         }
14742
14743         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14744         if( j == backwardMostMove ) i += initialRulePlies;
14745         sprintf(p, "%d ", i);
14746         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14747     }
14748     /* Fullmove number */
14749     sprintf(p, "%d", (move / 2) + 1);
14750
14751     return StrSave(buf);
14752 }
14753
14754 Boolean
14755 ParseFEN(board, blackPlaysFirst, fen)
14756     Board board;
14757      int *blackPlaysFirst;
14758      char *fen;
14759 {
14760     int i, j;
14761     char *p, c;
14762     int emptycount;
14763     ChessSquare piece;
14764
14765     p = fen;
14766
14767     /* [HGM] by default clear Crazyhouse holdings, if present */
14768     if(gameInfo.holdingsWidth) {
14769        for(i=0; i<BOARD_HEIGHT; i++) {
14770            board[i][0]             = EmptySquare; /* black holdings */
14771            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14772            board[i][1]             = (ChessSquare) 0; /* black counts */
14773            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14774        }
14775     }
14776
14777     /* Piece placement data */
14778     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14779         j = 0;
14780         for (;;) {
14781             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14782                 if (*p == '/') p++;
14783                 emptycount = gameInfo.boardWidth - j;
14784                 while (emptycount--)
14785                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14786                 break;
14787 #if(BOARD_FILES >= 10)
14788             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14789                 p++; emptycount=10;
14790                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14791                 while (emptycount--)
14792                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14793 #endif
14794             } else if (isdigit(*p)) {
14795                 emptycount = *p++ - '0';
14796                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14797                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14798                 while (emptycount--)
14799                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14800             } else if (*p == '+' || isalpha(*p)) {
14801                 if (j >= gameInfo.boardWidth) return FALSE;
14802                 if(*p=='+') {
14803                     piece = CharToPiece(*++p);
14804                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14805                     piece = (ChessSquare) (PROMOTED piece ); p++;
14806                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14807                 } else piece = CharToPiece(*p++);
14808
14809                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14810                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14811                     piece = (ChessSquare) (PROMOTED piece);
14812                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14813                     p++;
14814                 }
14815                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14816             } else {
14817                 return FALSE;
14818             }
14819         }
14820     }
14821     while (*p == '/' || *p == ' ') p++;
14822
14823     /* [HGM] look for Crazyhouse holdings here */
14824     while(*p==' ') p++;
14825     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14826         if(*p == '[') p++;
14827         if(*p == '-' ) *p++; /* empty holdings */ else {
14828             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14829             /* if we would allow FEN reading to set board size, we would   */
14830             /* have to add holdings and shift the board read so far here   */
14831             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14832                 *p++;
14833                 if((int) piece >= (int) BlackPawn ) {
14834                     i = (int)piece - (int)BlackPawn;
14835                     i = PieceToNumber((ChessSquare)i);
14836                     if( i >= gameInfo.holdingsSize ) return FALSE;
14837                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14838                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14839                 } else {
14840                     i = (int)piece - (int)WhitePawn;
14841                     i = PieceToNumber((ChessSquare)i);
14842                     if( i >= gameInfo.holdingsSize ) return FALSE;
14843                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14844                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14845                 }
14846             }
14847         }
14848         if(*p == ']') *p++;
14849     }
14850
14851     while(*p == ' ') p++;
14852
14853     /* Active color */
14854     c = *p++;
14855     if(appData.colorNickNames) {
14856       if( c == appData.colorNickNames[0] ) c = 'w'; else
14857       if( c == appData.colorNickNames[1] ) c = 'b';
14858     }
14859     switch (c) {
14860       case 'w':
14861         *blackPlaysFirst = FALSE;
14862         break;
14863       case 'b':
14864         *blackPlaysFirst = TRUE;
14865         break;
14866       default:
14867         return FALSE;
14868     }
14869
14870     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14871     /* return the extra info in global variiables             */
14872
14873     /* set defaults in case FEN is incomplete */
14874     board[EP_STATUS] = EP_UNKNOWN;
14875     for(i=0; i<nrCastlingRights; i++ ) {
14876         board[CASTLING][i] =
14877             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14878     }   /* assume possible unless obviously impossible */
14879     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14880     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14881     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14882                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14883     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14884     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14885     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14886                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14887     FENrulePlies = 0;
14888
14889     while(*p==' ') p++;
14890     if(nrCastlingRights) {
14891       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14892           /* castling indicator present, so default becomes no castlings */
14893           for(i=0; i<nrCastlingRights; i++ ) {
14894                  board[CASTLING][i] = NoRights;
14895           }
14896       }
14897       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14898              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14899              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14900              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14901         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14902
14903         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14904             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14905             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14906         }
14907         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14908             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14909         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14910                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14911         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14912                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14913         switch(c) {
14914           case'K':
14915               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14916               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14917               board[CASTLING][2] = whiteKingFile;
14918               break;
14919           case'Q':
14920               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14921               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14922               board[CASTLING][2] = whiteKingFile;
14923               break;
14924           case'k':
14925               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14926               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14927               board[CASTLING][5] = blackKingFile;
14928               break;
14929           case'q':
14930               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14931               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14932               board[CASTLING][5] = blackKingFile;
14933           case '-':
14934               break;
14935           default: /* FRC castlings */
14936               if(c >= 'a') { /* black rights */
14937                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14938                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14939                   if(i == BOARD_RGHT) break;
14940                   board[CASTLING][5] = i;
14941                   c -= AAA;
14942                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14943                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14944                   if(c > i)
14945                       board[CASTLING][3] = c;
14946                   else
14947                       board[CASTLING][4] = c;
14948               } else { /* white rights */
14949                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14950                     if(board[0][i] == WhiteKing) break;
14951                   if(i == BOARD_RGHT) break;
14952                   board[CASTLING][2] = i;
14953                   c -= AAA - 'a' + 'A';
14954                   if(board[0][c] >= WhiteKing) break;
14955                   if(c > i)
14956                       board[CASTLING][0] = c;
14957                   else
14958                       board[CASTLING][1] = c;
14959               }
14960         }
14961       }
14962       for(i=0; i<nrCastlingRights; i++)
14963         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14964     if (appData.debugMode) {
14965         fprintf(debugFP, "FEN castling rights:");
14966         for(i=0; i<nrCastlingRights; i++)
14967         fprintf(debugFP, " %d", board[CASTLING][i]);
14968         fprintf(debugFP, "\n");
14969     }
14970
14971       while(*p==' ') p++;
14972     }
14973
14974     /* read e.p. field in games that know e.p. capture */
14975     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14976        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14977       if(*p=='-') {
14978         p++; board[EP_STATUS] = EP_NONE;
14979       } else {
14980          char c = *p++ - AAA;
14981
14982          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14983          if(*p >= '0' && *p <='9') *p++;
14984          board[EP_STATUS] = c;
14985       }
14986     }
14987
14988
14989     if(sscanf(p, "%d", &i) == 1) {
14990         FENrulePlies = i; /* 50-move ply counter */
14991         /* (The move number is still ignored)    */
14992     }
14993
14994     return TRUE;
14995 }
14996
14997 void
14998 EditPositionPasteFEN(char *fen)
14999 {
15000   if (fen != NULL) {
15001     Board initial_position;
15002
15003     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15004       DisplayError(_("Bad FEN position in clipboard"), 0);
15005       return ;
15006     } else {
15007       int savedBlackPlaysFirst = blackPlaysFirst;
15008       EditPositionEvent();
15009       blackPlaysFirst = savedBlackPlaysFirst;
15010       CopyBoard(boards[0], initial_position);
15011       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15012       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15013       DisplayBothClocks();
15014       DrawPosition(FALSE, boards[currentMove]);
15015     }
15016   }
15017 }
15018
15019 static char cseq[12] = "\\   ";
15020
15021 Boolean set_cont_sequence(char *new_seq)
15022 {
15023     int len;
15024     Boolean ret;
15025
15026     // handle bad attempts to set the sequence
15027         if (!new_seq)
15028                 return 0; // acceptable error - no debug
15029
15030     len = strlen(new_seq);
15031     ret = (len > 0) && (len < sizeof(cseq));
15032     if (ret)
15033       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15034     else if (appData.debugMode)
15035       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15036     return ret;
15037 }
15038
15039 /*
15040     reformat a source message so words don't cross the width boundary.  internal
15041     newlines are not removed.  returns the wrapped size (no null character unless
15042     included in source message).  If dest is NULL, only calculate the size required
15043     for the dest buffer.  lp argument indicats line position upon entry, and it's
15044     passed back upon exit.
15045 */
15046 int wrap(char *dest, char *src, int count, int width, int *lp)
15047 {
15048     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15049
15050     cseq_len = strlen(cseq);
15051     old_line = line = *lp;
15052     ansi = len = clen = 0;
15053
15054     for (i=0; i < count; i++)
15055     {
15056         if (src[i] == '\033')
15057             ansi = 1;
15058
15059         // if we hit the width, back up
15060         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15061         {
15062             // store i & len in case the word is too long
15063             old_i = i, old_len = len;
15064
15065             // find the end of the last word
15066             while (i && src[i] != ' ' && src[i] != '\n')
15067             {
15068                 i--;
15069                 len--;
15070             }
15071
15072             // word too long?  restore i & len before splitting it
15073             if ((old_i-i+clen) >= width)
15074             {
15075                 i = old_i;
15076                 len = old_len;
15077             }
15078
15079             // extra space?
15080             if (i && src[i-1] == ' ')
15081                 len--;
15082
15083             if (src[i] != ' ' && src[i] != '\n')
15084             {
15085                 i--;
15086                 if (len)
15087                     len--;
15088             }
15089
15090             // now append the newline and continuation sequence
15091             if (dest)
15092                 dest[len] = '\n';
15093             len++;
15094             if (dest)
15095                 strncpy(dest+len, cseq, cseq_len);
15096             len += cseq_len;
15097             line = cseq_len;
15098             clen = cseq_len;
15099             continue;
15100         }
15101
15102         if (dest)
15103             dest[len] = src[i];
15104         len++;
15105         if (!ansi)
15106             line++;
15107         if (src[i] == '\n')
15108             line = 0;
15109         if (src[i] == 'm')
15110             ansi = 0;
15111     }
15112     if (dest && appData.debugMode)
15113     {
15114         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15115             count, width, line, len, *lp);
15116         show_bytes(debugFP, src, count);
15117         fprintf(debugFP, "\ndest: ");
15118         show_bytes(debugFP, dest, len);
15119         fprintf(debugFP, "\n");
15120     }
15121     *lp = dest ? line : old_line;
15122
15123     return len;
15124 }
15125
15126 // [HGM] vari: routines for shelving variations
15127
15128 void
15129 PushTail(int firstMove, int lastMove)
15130 {
15131         int i, j, nrMoves = lastMove - firstMove;
15132
15133         if(appData.icsActive) { // only in local mode
15134                 forwardMostMove = currentMove; // mimic old ICS behavior
15135                 return;
15136         }
15137         if(storedGames >= MAX_VARIATIONS-1) return;
15138
15139         // push current tail of game on stack
15140         savedResult[storedGames] = gameInfo.result;
15141         savedDetails[storedGames] = gameInfo.resultDetails;
15142         gameInfo.resultDetails = NULL;
15143         savedFirst[storedGames] = firstMove;
15144         savedLast [storedGames] = lastMove;
15145         savedFramePtr[storedGames] = framePtr;
15146         framePtr -= nrMoves; // reserve space for the boards
15147         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15148             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15149             for(j=0; j<MOVE_LEN; j++)
15150                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15151             for(j=0; j<2*MOVE_LEN; j++)
15152                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15153             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15154             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15155             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15156             pvInfoList[firstMove+i-1].depth = 0;
15157             commentList[framePtr+i] = commentList[firstMove+i];
15158             commentList[firstMove+i] = NULL;
15159         }
15160
15161         storedGames++;
15162         forwardMostMove = firstMove; // truncate game so we can start variation
15163         if(storedGames == 1) GreyRevert(FALSE);
15164 }
15165
15166 Boolean
15167 PopTail(Boolean annotate)
15168 {
15169         int i, j, nrMoves;
15170         char buf[8000], moveBuf[20];
15171
15172         if(appData.icsActive) return FALSE; // only in local mode
15173         if(!storedGames) return FALSE; // sanity
15174         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15175
15176         storedGames--;
15177         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15178         nrMoves = savedLast[storedGames] - currentMove;
15179         if(annotate) {
15180                 int cnt = 10;
15181                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15182                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15183                 for(i=currentMove; i<forwardMostMove; i++) {
15184                         if(WhiteOnMove(i))
15185                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15186                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15187                         strcat(buf, moveBuf);
15188                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15189                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15190                 }
15191                 strcat(buf, ")");
15192         }
15193         for(i=1; i<=nrMoves; i++) { // copy last variation back
15194             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15195             for(j=0; j<MOVE_LEN; j++)
15196                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15197             for(j=0; j<2*MOVE_LEN; j++)
15198                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15199             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15200             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15201             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15202             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15203             commentList[currentMove+i] = commentList[framePtr+i];
15204             commentList[framePtr+i] = NULL;
15205         }
15206         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15207         framePtr = savedFramePtr[storedGames];
15208         gameInfo.result = savedResult[storedGames];
15209         if(gameInfo.resultDetails != NULL) {
15210             free(gameInfo.resultDetails);
15211       }
15212         gameInfo.resultDetails = savedDetails[storedGames];
15213         forwardMostMove = currentMove + nrMoves;
15214         if(storedGames == 0) GreyRevert(TRUE);
15215         return TRUE;
15216 }
15217
15218 void
15219 CleanupTail()
15220 {       // remove all shelved variations
15221         int i;
15222         for(i=0; i<storedGames; i++) {
15223             if(savedDetails[i])
15224                 free(savedDetails[i]);
15225             savedDetails[i] = NULL;
15226         }
15227         for(i=framePtr; i<MAX_MOVES; i++) {
15228                 if(commentList[i]) free(commentList[i]);
15229                 commentList[i] = NULL;
15230         }
15231         framePtr = MAX_MOVES-1;
15232         storedGames = 0;
15233 }
15234
15235 void
15236 LoadVariation(int index, char *text)
15237 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15238         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15239         int level = 0, move;
15240
15241         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15242         // first find outermost bracketing variation
15243         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15244             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15245                 if(*p == '{') wait = '}'; else
15246                 if(*p == '[') wait = ']'; else
15247                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15248                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15249             }
15250             if(*p == wait) wait = NULLCHAR; // closing ]} found
15251             p++;
15252         }
15253         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15254         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15255         end[1] = NULLCHAR; // clip off comment beyond variation
15256         ToNrEvent(currentMove-1);
15257         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15258         // kludge: use ParsePV() to append variation to game
15259         move = currentMove;
15260         ParsePV(start, TRUE);
15261         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15262         ClearPremoveHighlights();
15263         CommentPopDown();
15264         ToNrEvent(currentMove+1);
15265 }
15266