eed8bff3bad356e92d318fef1afda6356f51f2c4
[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   assert( dst != NULL );
321   assert( src != NULL );
322   assert( count > 0 );
323
324   strncpy( dst, src, count );
325   if(  dst[ count-1 ] != '\0' )
326     {
327       if(appData.debugMode)
328       printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
329     }
330   dst[ count-1 ] = '\0';
331
332   return dst;
333 }
334
335 /* Some compiler can't cast u64 to double
336  * This function do the job for us:
337
338  * We use the highest bit for cast, this only
339  * works if the highest bit is not
340  * in use (This should not happen)
341  *
342  * We used this for all compiler
343  */
344 double
345 u64ToDouble(u64 value)
346 {
347   double r;
348   u64 tmp = value & u64Const(0x7fffffffffffffff);
349   r = (double)(s64)tmp;
350   if (value & u64Const(0x8000000000000000))
351        r +=  9.2233720368547758080e18; /* 2^63 */
352  return r;
353 }
354
355 /* Fake up flags for now, as we aren't keeping track of castling
356    availability yet. [HGM] Change of logic: the flag now only
357    indicates the type of castlings allowed by the rule of the game.
358    The actual rights themselves are maintained in the array
359    castlingRights, as part of the game history, and are not probed
360    by this function.
361  */
362 int
363 PosFlags(index)
364 {
365   int flags = F_ALL_CASTLE_OK;
366   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
367   switch (gameInfo.variant) {
368   case VariantSuicide:
369     flags &= ~F_ALL_CASTLE_OK;
370   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
371     flags |= F_IGNORE_CHECK;
372   case VariantLosers:
373     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374     break;
375   case VariantAtomic:
376     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377     break;
378   case VariantKriegspiel:
379     flags |= F_KRIEGSPIEL_CAPTURE;
380     break;
381   case VariantCapaRandom:
382   case VariantFischeRandom:
383     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
384   case VariantNoCastle:
385   case VariantShatranj:
386   case VariantCourier:
387   case VariantMakruk:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP;
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452
453 int have_sent_ICS_logon = 0;
454 int sending_ICS_login    = 0;
455 int sending_ICS_password = 0;
456
457 int movesPerSession;
458 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
459 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0;
464 TimeMark programStartTime;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 int shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void CleanupTail P((void));
503
504 ChessSquare  FIDEArray[2][BOARD_FILES] = {
505     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
506         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
507     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
508         BlackKing, BlackBishop, BlackKnight, BlackRook }
509 };
510
511 ChessSquare twoKingsArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515         BlackKing, BlackKing, BlackKnight, BlackRook }
516 };
517
518 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
520         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
521     { BlackRook, BlackMan, BlackBishop, BlackQueen,
522         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
523 };
524
525 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
526     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
527         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
528     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
529         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
530 };
531
532 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
533     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
534         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
536         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
537 };
538
539 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
540     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
541         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackMan, BlackFerz,
543         BlackKing, BlackMan, BlackKnight, BlackRook }
544 };
545
546
547 #if (BOARD_FILES>=10)
548 ChessSquare ShogiArray[2][BOARD_FILES] = {
549     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
550         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
551     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
552         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
553 };
554
555 ChessSquare XiangqiArray[2][BOARD_FILES] = {
556     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
557         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
559         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
560 };
561
562 ChessSquare CapablancaArray[2][BOARD_FILES] = {
563     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
564         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
566         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
567 };
568
569 ChessSquare GreatArray[2][BOARD_FILES] = {
570     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
571         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
572     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
573         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
574 };
575
576 ChessSquare JanusArray[2][BOARD_FILES] = {
577     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
578         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
579     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
580         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
581 };
582
583 #ifdef GOTHIC
584 ChessSquare GothicArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
586         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
588         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
589 };
590 #else // !GOTHIC
591 #define GothicArray CapablancaArray
592 #endif // !GOTHIC
593
594 #ifdef FALCON
595 ChessSquare FalconArray[2][BOARD_FILES] = {
596     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
597         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
598     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
599         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
600 };
601 #else // !FALCON
602 #define FalconArray CapablancaArray
603 #endif // !FALCON
604
605 #else // !(BOARD_FILES>=10)
606 #define XiangqiPosition FIDEArray
607 #define CapablancaArray FIDEArray
608 #define GothicArray FIDEArray
609 #define GreatArray FIDEArray
610 #endif // !(BOARD_FILES>=10)
611
612 #if (BOARD_FILES>=12)
613 ChessSquare CourierArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
615         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
617         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
618 };
619 #else // !(BOARD_FILES>=12)
620 #define CourierArray CapablancaArray
621 #endif // !(BOARD_FILES>=12)
622
623
624 Board initialPosition;
625
626
627 /* Convert str to a rating. Checks for special cases of "----",
628
629    "++++", etc. Also strips ()'s */
630 int
631 string_to_rating(str)
632   char *str;
633 {
634   while(*str && !isdigit(*str)) ++str;
635   if (!*str)
636     return 0;   /* One of the special "no rating" cases */
637   else
638     return atoi(str);
639 }
640
641 void
642 ClearProgramStats()
643 {
644     /* Init programStats */
645     programStats.movelist[0] = 0;
646     programStats.depth = 0;
647     programStats.nr_moves = 0;
648     programStats.moves_left = 0;
649     programStats.nodes = 0;
650     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
651     programStats.score = 0;
652     programStats.got_only_move = 0;
653     programStats.got_fail = 0;
654     programStats.line_is_book = 0;
655 }
656
657 void
658 InitBackEnd1()
659 {
660     int matched, min, sec;
661
662     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
663     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
664
665     GetTimeMark(&programStartTime);
666     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
667
668     ClearProgramStats();
669     programStats.ok_to_send = 1;
670     programStats.seen_stat = 0;
671
672     /*
673      * Initialize game list
674      */
675     ListNew(&gameList);
676
677
678     /*
679      * Internet chess server status
680      */
681     if (appData.icsActive) {
682         appData.matchMode = FALSE;
683         appData.matchGames = 0;
684 #if ZIPPY
685         appData.noChessProgram = !appData.zippyPlay;
686 #else
687         appData.zippyPlay = FALSE;
688         appData.zippyTalk = FALSE;
689         appData.noChessProgram = TRUE;
690 #endif
691         if (*appData.icsHelper != NULLCHAR) {
692             appData.useTelnet = TRUE;
693             appData.telnetProgram = appData.icsHelper;
694         }
695     } else {
696         appData.zippyTalk = appData.zippyPlay = FALSE;
697     }
698
699     /* [AS] Initialize pv info list [HGM] and game state */
700     {
701         int i, j;
702
703         for( i=0; i<=framePtr; i++ ) {
704             pvInfoList[i].depth = -1;
705             boards[i][EP_STATUS] = EP_NONE;
706             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
707         }
708     }
709
710     /*
711      * Parse timeControl resource
712      */
713     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
714                           appData.movesPerSession)) {
715         char buf[MSG_SIZ];
716         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
717         DisplayFatalError(buf, 0, 2);
718     }
719
720     /*
721      * Parse searchTime resource
722      */
723     if (*appData.searchTime != NULLCHAR) {
724         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
725         if (matched == 1) {
726             searchTime = min * 60;
727         } else if (matched == 2) {
728             searchTime = min * 60 + sec;
729         } else {
730             char buf[MSG_SIZ];
731             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
732             DisplayFatalError(buf, 0, 2);
733         }
734     }
735
736     /* [AS] Adjudication threshold */
737     adjudicateLossThreshold = appData.adjudicateLossThreshold;
738
739     first.which = _("first");
740     second.which = _("second");
741     first.maybeThinking = second.maybeThinking = FALSE;
742     first.pr = second.pr = NoProc;
743     first.isr = second.isr = NULL;
744     first.sendTime = second.sendTime = 2;
745     first.sendDrawOffers = 1;
746     if (appData.firstPlaysBlack) {
747         first.twoMachinesColor = "black\n";
748         second.twoMachinesColor = "white\n";
749     } else {
750         first.twoMachinesColor = "white\n";
751         second.twoMachinesColor = "black\n";
752     }
753     first.program = appData.firstChessProgram;
754     second.program = appData.secondChessProgram;
755     first.host = appData.firstHost;
756     second.host = appData.secondHost;
757     first.dir = appData.firstDirectory;
758     second.dir = appData.secondDirectory;
759     first.other = &second;
760     second.other = &first;
761     first.initString = appData.initString;
762     second.initString = appData.secondInitString;
763     first.computerString = appData.firstComputerString;
764     second.computerString = appData.secondComputerString;
765     first.useSigint = second.useSigint = TRUE;
766     first.useSigterm = second.useSigterm = TRUE;
767     first.reuse = appData.reuseFirst;
768     second.reuse = appData.reuseSecond;
769     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
770     second.nps = appData.secondNPS;
771     first.useSetboard = second.useSetboard = FALSE;
772     first.useSAN = second.useSAN = FALSE;
773     first.usePing = second.usePing = FALSE;
774     first.lastPing = second.lastPing = 0;
775     first.lastPong = second.lastPong = 0;
776     first.usePlayother = second.usePlayother = FALSE;
777     first.useColors = second.useColors = TRUE;
778     first.useUsermove = second.useUsermove = FALSE;
779     first.sendICS = second.sendICS = FALSE;
780     first.sendName = second.sendName = appData.icsActive;
781     first.sdKludge = second.sdKludge = FALSE;
782     first.stKludge = second.stKludge = FALSE;
783     TidyProgramName(first.program, first.host, first.tidy);
784     TidyProgramName(second.program, second.host, second.tidy);
785     first.matchWins = second.matchWins = 0;
786     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
787     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
788     first.analysisSupport = second.analysisSupport = 2; /* detect */
789     first.analyzing = second.analyzing = FALSE;
790     first.initDone = second.initDone = FALSE;
791
792     /* New features added by Tord: */
793     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
794     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
795     /* End of new features added by Tord. */
796     first.fenOverride  = appData.fenOverride1;
797     second.fenOverride = appData.fenOverride2;
798
799     /* [HGM] time odds: set factor for each machine */
800     first.timeOdds  = appData.firstTimeOdds;
801     second.timeOdds = appData.secondTimeOdds;
802     { float norm = 1;
803         if(appData.timeOddsMode) {
804             norm = first.timeOdds;
805             if(norm > second.timeOdds) norm = second.timeOdds;
806         }
807         first.timeOdds /= norm;
808         second.timeOdds /= norm;
809     }
810
811     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
812     first.accumulateTC = appData.firstAccumulateTC;
813     second.accumulateTC = appData.secondAccumulateTC;
814     first.maxNrOfSessions = second.maxNrOfSessions = 1;
815
816     /* [HGM] debug */
817     first.debug = second.debug = FALSE;
818     first.supportsNPS = second.supportsNPS = UNKNOWN;
819
820     /* [HGM] options */
821     first.optionSettings  = appData.firstOptions;
822     second.optionSettings = appData.secondOptions;
823
824     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
825     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
826     first.isUCI = appData.firstIsUCI; /* [AS] */
827     second.isUCI = appData.secondIsUCI; /* [AS] */
828     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
829     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
830
831     if (appData.firstProtocolVersion > PROTOVER
832         || appData.firstProtocolVersion < 1)
833       {
834         char buf[MSG_SIZ];
835         int len;
836
837         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
838                        appData.firstProtocolVersion);
839         if( (len > MSG_SIZ) && appData.debugMode )
840           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
841
842         DisplayFatalError(buf, 0, 2);
843       }
844     else
845       {
846         first.protocolVersion = appData.firstProtocolVersion;
847       }
848
849     if (appData.secondProtocolVersion > PROTOVER
850         || appData.secondProtocolVersion < 1)
851       {
852         char buf[MSG_SIZ];
853         int len;
854
855         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
856                        appData.secondProtocolVersion);
857         if( (len > MSG_SIZ) && appData.debugMode )
858           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
859
860         DisplayFatalError(buf, 0, 2);
861       }
862     else
863       {
864         second.protocolVersion = appData.secondProtocolVersion;
865       }
866
867     if (appData.icsActive) {
868         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
869 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
870     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
871         appData.clockMode = FALSE;
872         first.sendTime = second.sendTime = 0;
873     }
874
875 #if ZIPPY
876     /* Override some settings from environment variables, for backward
877        compatibility.  Unfortunately it's not feasible to have the env
878        vars just set defaults, at least in xboard.  Ugh.
879     */
880     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
881       ZippyInit();
882     }
883 #endif
884
885     if (appData.noChessProgram) {
886         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
887         sprintf(programVersion, "%s", PACKAGE_STRING);
888     } else {
889       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
890       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
891       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
892     }
893
894     if (!appData.icsActive) {
895       char buf[MSG_SIZ];
896       int len;
897
898       /* Check for variants that are supported only in ICS mode,
899          or not at all.  Some that are accepted here nevertheless
900          have bugs; see comments below.
901       */
902       VariantClass variant = StringToVariant(appData.variant);
903       switch (variant) {
904       case VariantBughouse:     /* need four players and two boards */
905       case VariantKriegspiel:   /* need to hide pieces and move details */
906         /* case VariantFischeRandom: (Fabien: moved below) */
907         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
908         if( (len > MSG_SIZ) && appData.debugMode )
909           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
910
911         DisplayFatalError(buf, 0, 2);
912         return;
913
914       case VariantUnknown:
915       case VariantLoadable:
916       case Variant29:
917       case Variant30:
918       case Variant31:
919       case Variant32:
920       case Variant33:
921       case Variant34:
922       case Variant35:
923       case Variant36:
924       default:
925         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
926         if( (len > MSG_SIZ) && appData.debugMode )
927           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
928
929         DisplayFatalError(buf, 0, 2);
930         return;
931
932       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
933       case VariantFairy:      /* [HGM] TestLegality definitely off! */
934       case VariantGothic:     /* [HGM] should work */
935       case VariantCapablanca: /* [HGM] should work */
936       case VariantCourier:    /* [HGM] initial forced moves not implemented */
937       case VariantShogi:      /* [HGM] could still mate with pawn drop */
938       case VariantKnightmate: /* [HGM] should work */
939       case VariantCylinder:   /* [HGM] untested */
940       case VariantFalcon:     /* [HGM] untested */
941       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
942                                  offboard interposition not understood */
943       case VariantNormal:     /* definitely works! */
944       case VariantWildCastle: /* pieces not automatically shuffled */
945       case VariantNoCastle:   /* pieces not automatically shuffled */
946       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
947       case VariantLosers:     /* should work except for win condition,
948                                  and doesn't know captures are mandatory */
949       case VariantSuicide:    /* should work except for win condition,
950                                  and doesn't know captures are mandatory */
951       case VariantGiveaway:   /* should work except for win condition,
952                                  and doesn't know captures are mandatory */
953       case VariantTwoKings:   /* should work */
954       case VariantAtomic:     /* should work except for win condition */
955       case Variant3Check:     /* should work except for win condition */
956       case VariantShatranj:   /* should work except for all win conditions */
957       case VariantMakruk:     /* should work except for daw countdown */
958       case VariantBerolina:   /* might work if TestLegality is off */
959       case VariantCapaRandom: /* should work */
960       case VariantJanus:      /* should work */
961       case VariantSuper:      /* experimental */
962       case VariantGreat:      /* experimental, requires legality testing to be off */
963         break;
964       }
965     }
966
967     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
968     InitEngineUCI( installDir, &second );
969 }
970
971 int NextIntegerFromString( char ** str, long * value )
972 {
973     int result = -1;
974     char * s = *str;
975
976     while( *s == ' ' || *s == '\t' ) {
977         s++;
978     }
979
980     *value = 0;
981
982     if( *s >= '0' && *s <= '9' ) {
983         while( *s >= '0' && *s <= '9' ) {
984             *value = *value * 10 + (*s - '0');
985             s++;
986         }
987
988         result = 0;
989     }
990
991     *str = s;
992
993     return result;
994 }
995
996 int NextTimeControlFromString( char ** str, long * value )
997 {
998     long temp;
999     int result = NextIntegerFromString( str, &temp );
1000
1001     if( result == 0 ) {
1002         *value = temp * 60; /* Minutes */
1003         if( **str == ':' ) {
1004             (*str)++;
1005             result = NextIntegerFromString( str, &temp );
1006             *value += temp; /* Seconds */
1007         }
1008     }
1009
1010     return result;
1011 }
1012
1013 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1014 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1015     int result = -1, type = 0; long temp, temp2;
1016
1017     if(**str != ':') return -1; // old params remain in force!
1018     (*str)++;
1019     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1020     if( NextIntegerFromString( str, &temp ) ) return -1;
1021     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1022
1023     if(**str != '/') {
1024         /* time only: incremental or sudden-death time control */
1025         if(**str == '+') { /* increment follows; read it */
1026             (*str)++;
1027             if(**str == '!') type = *(*str)++; // Bronstein TC
1028             if(result = NextIntegerFromString( str, &temp2)) return -1;
1029             *inc = temp2 * 1000;
1030         } else *inc = 0;
1031         *moves = 0; *tc = temp * 1000; *incType = type;
1032         return 0;
1033     }
1034
1035     (*str)++; /* classical time control */
1036     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1037
1038     if(result == 0) {
1039         *moves = temp;
1040         *tc    = temp2 * 1000;
1041         *inc   = 0;
1042         *incType = type;
1043     }
1044     return result;
1045 }
1046
1047 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1048 {   /* [HGM] get time to add from the multi-session time-control string */
1049     int incType, moves=1; /* kludge to force reading of first session */
1050     long time, increment;
1051     char *s = tcString;
1052
1053     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1054     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1055     do {
1056         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1057         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1058         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1059         if(movenr == -1) return time;    /* last move before new session     */
1060         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1061         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1062         if(!moves) return increment;     /* current session is incremental   */
1063         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1064     } while(movenr >= -1);               /* try again for next session       */
1065
1066     return 0; // no new time quota on this move
1067 }
1068
1069 int
1070 ParseTimeControl(tc, ti, mps)
1071      char *tc;
1072      int ti;
1073      int mps;
1074 {
1075   long tc1;
1076   long tc2;
1077   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1078   int min, sec=0;
1079   int len;
1080
1081   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1082   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1083       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1084   if(ti > 0) {
1085
1086     if(mps)
1087       snprintf(buf, MSG_SIZ, ":%d/%s+%d", mps, mytc, ti);
1088     else 
1089       snprintf(buf, MSG_SIZ, ":%s+%d", mytc, ti);
1090   } else {
1091     if(mps)
1092       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1093     else 
1094       snprintf(buf, MSG_SIZ, ":%s", mytc);
1095   }
1096   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1097   
1098   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1099     return FALSE;
1100   }
1101
1102   if( *tc == '/' ) {
1103     /* Parse second time control */
1104     tc++;
1105
1106     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1107       return FALSE;
1108     }
1109
1110     if( tc2 == 0 ) {
1111       return FALSE;
1112     }
1113
1114     timeControl_2 = tc2 * 1000;
1115   }
1116   else {
1117     timeControl_2 = 0;
1118   }
1119
1120   if( tc1 == 0 ) {
1121     return FALSE;
1122   }
1123
1124   timeControl = tc1 * 1000;
1125
1126   if (ti >= 0) {
1127     timeIncrement = ti * 1000;  /* convert to ms */
1128     movesPerSession = 0;
1129   } else {
1130     timeIncrement = 0;
1131     movesPerSession = mps;
1132   }
1133   return TRUE;
1134 }
1135
1136 void
1137 InitBackEnd2()
1138 {
1139     if (appData.debugMode) {
1140         fprintf(debugFP, "%s\n", programVersion);
1141     }
1142
1143     set_cont_sequence(appData.wrapContSeq);
1144     if (appData.matchGames > 0) {
1145         appData.matchMode = TRUE;
1146     } else if (appData.matchMode) {
1147         appData.matchGames = 1;
1148     }
1149     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1150         appData.matchGames = appData.sameColorGames;
1151     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1152         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1153         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1154     }
1155     Reset(TRUE, FALSE);
1156     if (appData.noChessProgram || first.protocolVersion == 1) {
1157       InitBackEnd3();
1158     } else {
1159       /* kludge: allow timeout for initial "feature" commands */
1160       FreezeUI();
1161       DisplayMessage("", _("Starting chess program"));
1162       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1163     }
1164 }
1165
1166 void
1167 InitBackEnd3 P((void))
1168 {
1169     GameMode initialMode;
1170     char buf[MSG_SIZ];
1171     int err, len;
1172
1173     InitChessProgram(&first, startedFromSetupPosition);
1174
1175     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1176         free(programVersion);
1177         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1178         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1179     }
1180
1181     if (appData.icsActive) {
1182 #ifdef WIN32
1183         /* [DM] Make a console window if needed [HGM] merged ifs */
1184         ConsoleCreate();
1185 #endif
1186         err = establish();
1187         if (err != 0)
1188           {
1189             if (*appData.icsCommPort != NULLCHAR)
1190               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1191                              appData.icsCommPort);
1192             else
1193               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1194                         appData.icsHost, appData.icsPort);
1195
1196             if( (len > MSG_SIZ) && appData.debugMode )
1197               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1198
1199             DisplayFatalError(buf, err, 1);
1200             return;
1201         }
1202         SetICSMode();
1203         telnetISR =
1204           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1205         fromUserISR =
1206           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1207         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1208             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1209     } else if (appData.noChessProgram) {
1210         SetNCPMode();
1211     } else {
1212         SetGNUMode();
1213     }
1214
1215     if (*appData.cmailGameName != NULLCHAR) {
1216         SetCmailMode();
1217         OpenLoopback(&cmailPR);
1218         cmailISR =
1219           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1220     }
1221
1222     ThawUI();
1223     DisplayMessage("", "");
1224     if (StrCaseCmp(appData.initialMode, "") == 0) {
1225       initialMode = BeginningOfGame;
1226     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1227       initialMode = TwoMachinesPlay;
1228     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1229       initialMode = AnalyzeFile;
1230     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1231       initialMode = AnalyzeMode;
1232     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1233       initialMode = MachinePlaysWhite;
1234     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1235       initialMode = MachinePlaysBlack;
1236     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1237       initialMode = EditGame;
1238     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1239       initialMode = EditPosition;
1240     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1241       initialMode = Training;
1242     } else {
1243       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1244       if( (len > MSG_SIZ) && appData.debugMode )
1245         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1246
1247       DisplayFatalError(buf, 0, 2);
1248       return;
1249     }
1250
1251     if (appData.matchMode) {
1252         /* Set up machine vs. machine match */
1253         if (appData.noChessProgram) {
1254             DisplayFatalError(_("Can't have a match with no chess programs"),
1255                               0, 2);
1256             return;
1257         }
1258         matchMode = TRUE;
1259         matchGame = 1;
1260         if (*appData.loadGameFile != NULLCHAR) {
1261             int index = appData.loadGameIndex; // [HGM] autoinc
1262             if(index<0) lastIndex = index = 1;
1263             if (!LoadGameFromFile(appData.loadGameFile,
1264                                   index,
1265                                   appData.loadGameFile, FALSE)) {
1266                 DisplayFatalError(_("Bad game file"), 0, 1);
1267                 return;
1268             }
1269         } else if (*appData.loadPositionFile != NULLCHAR) {
1270             int index = appData.loadPositionIndex; // [HGM] autoinc
1271             if(index<0) lastIndex = index = 1;
1272             if (!LoadPositionFromFile(appData.loadPositionFile,
1273                                       index,
1274                                       appData.loadPositionFile)) {
1275                 DisplayFatalError(_("Bad position file"), 0, 1);
1276                 return;
1277             }
1278         }
1279         TwoMachinesEvent();
1280     } else if (*appData.cmailGameName != NULLCHAR) {
1281         /* Set up cmail mode */
1282         ReloadCmailMsgEvent(TRUE);
1283     } else {
1284         /* Set up other modes */
1285         if (initialMode == AnalyzeFile) {
1286           if (*appData.loadGameFile == NULLCHAR) {
1287             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1288             return;
1289           }
1290         }
1291         if (*appData.loadGameFile != NULLCHAR) {
1292             (void) LoadGameFromFile(appData.loadGameFile,
1293                                     appData.loadGameIndex,
1294                                     appData.loadGameFile, TRUE);
1295         } else if (*appData.loadPositionFile != NULLCHAR) {
1296             (void) LoadPositionFromFile(appData.loadPositionFile,
1297                                         appData.loadPositionIndex,
1298                                         appData.loadPositionFile);
1299             /* [HGM] try to make self-starting even after FEN load */
1300             /* to allow automatic setup of fairy variants with wtm */
1301             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1302                 gameMode = BeginningOfGame;
1303                 setboardSpoiledMachineBlack = 1;
1304             }
1305             /* [HGM] loadPos: make that every new game uses the setup */
1306             /* from file as long as we do not switch variant          */
1307             if(!blackPlaysFirst) {
1308                 startedFromPositionFile = TRUE;
1309                 CopyBoard(filePosition, boards[0]);
1310             }
1311         }
1312         if (initialMode == AnalyzeMode) {
1313           if (appData.noChessProgram) {
1314             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1315             return;
1316           }
1317           if (appData.icsActive) {
1318             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1319             return;
1320           }
1321           AnalyzeModeEvent();
1322         } else if (initialMode == AnalyzeFile) {
1323           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1324           ShowThinkingEvent();
1325           AnalyzeFileEvent();
1326           AnalysisPeriodicEvent(1);
1327         } else if (initialMode == MachinePlaysWhite) {
1328           if (appData.noChessProgram) {
1329             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1330                               0, 2);
1331             return;
1332           }
1333           if (appData.icsActive) {
1334             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1335                               0, 2);
1336             return;
1337           }
1338           MachineWhiteEvent();
1339         } else if (initialMode == MachinePlaysBlack) {
1340           if (appData.noChessProgram) {
1341             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1342                               0, 2);
1343             return;
1344           }
1345           if (appData.icsActive) {
1346             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1347                               0, 2);
1348             return;
1349           }
1350           MachineBlackEvent();
1351         } else if (initialMode == TwoMachinesPlay) {
1352           if (appData.noChessProgram) {
1353             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1354                               0, 2);
1355             return;
1356           }
1357           if (appData.icsActive) {
1358             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1359                               0, 2);
1360             return;
1361           }
1362           TwoMachinesEvent();
1363         } else if (initialMode == EditGame) {
1364           EditGameEvent();
1365         } else if (initialMode == EditPosition) {
1366           EditPositionEvent();
1367         } else if (initialMode == Training) {
1368           if (*appData.loadGameFile == NULLCHAR) {
1369             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1370             return;
1371           }
1372           TrainingEvent();
1373         }
1374     }
1375 }
1376
1377 /*
1378  * Establish will establish a contact to a remote host.port.
1379  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1380  *  used to talk to the host.
1381  * Returns 0 if okay, error code if not.
1382  */
1383 int
1384 establish()
1385 {
1386     char buf[MSG_SIZ];
1387
1388     if (*appData.icsCommPort != NULLCHAR) {
1389         /* Talk to the host through a serial comm port */
1390         return OpenCommPort(appData.icsCommPort, &icsPR);
1391
1392     } else if (*appData.gateway != NULLCHAR) {
1393         if (*appData.remoteShell == NULLCHAR) {
1394             /* Use the rcmd protocol to run telnet program on a gateway host */
1395             snprintf(buf, sizeof(buf), "%s %s %s",
1396                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1397             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1398
1399         } else {
1400             /* Use the rsh program to run telnet program on a gateway host */
1401             if (*appData.remoteUser == NULLCHAR) {
1402                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1403                         appData.gateway, appData.telnetProgram,
1404                         appData.icsHost, appData.icsPort);
1405             } else {
1406                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1407                         appData.remoteShell, appData.gateway,
1408                         appData.remoteUser, appData.telnetProgram,
1409                         appData.icsHost, appData.icsPort);
1410             }
1411             return StartChildProcess(buf, "", &icsPR);
1412
1413         }
1414     } else if (appData.useTelnet) {
1415         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1416
1417     } else {
1418         /* TCP socket interface differs somewhat between
1419            Unix and NT; handle details in the front end.
1420            */
1421         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1422     }
1423 }
1424
1425 void EscapeExpand(char *p, char *q)
1426 {       // [HGM] initstring: routine to shape up string arguments
1427         while(*p++ = *q++) if(p[-1] == '\\')
1428             switch(*q++) {
1429                 case 'n': p[-1] = '\n'; break;
1430                 case 'r': p[-1] = '\r'; break;
1431                 case 't': p[-1] = '\t'; break;
1432                 case '\\': p[-1] = '\\'; break;
1433                 case 0: *p = 0; return;
1434                 default: p[-1] = q[-1]; break;
1435             }
1436 }
1437
1438 void
1439 show_bytes(fp, buf, count)
1440      FILE *fp;
1441      char *buf;
1442      int count;
1443 {
1444     while (count--) {
1445         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1446             fprintf(fp, "\\%03o", *buf & 0xff);
1447         } else {
1448             putc(*buf, fp);
1449         }
1450         buf++;
1451     }
1452     fflush(fp);
1453 }
1454
1455 /* Returns an errno value */
1456 int
1457 OutputMaybeTelnet(pr, message, count, outError)
1458      ProcRef pr;
1459      char *message;
1460      int count;
1461      int *outError;
1462 {
1463     char buf[8192], *p, *q, *buflim;
1464     int left, newcount, outcount;
1465
1466     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1467         *appData.gateway != NULLCHAR) {
1468         if (appData.debugMode) {
1469             fprintf(debugFP, ">ICS: ");
1470             show_bytes(debugFP, message, count);
1471             fprintf(debugFP, "\n");
1472         }
1473         return OutputToProcess(pr, message, count, outError);
1474     }
1475
1476     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1477     p = message;
1478     q = buf;
1479     left = count;
1480     newcount = 0;
1481     while (left) {
1482         if (q >= buflim) {
1483             if (appData.debugMode) {
1484                 fprintf(debugFP, ">ICS: ");
1485                 show_bytes(debugFP, buf, newcount);
1486                 fprintf(debugFP, "\n");
1487             }
1488             outcount = OutputToProcess(pr, buf, newcount, outError);
1489             if (outcount < newcount) return -1; /* to be sure */
1490             q = buf;
1491             newcount = 0;
1492         }
1493         if (*p == '\n') {
1494             *q++ = '\r';
1495             newcount++;
1496         } else if (((unsigned char) *p) == TN_IAC) {
1497             *q++ = (char) TN_IAC;
1498             newcount ++;
1499         }
1500         *q++ = *p++;
1501         newcount++;
1502         left--;
1503     }
1504     if (appData.debugMode) {
1505         fprintf(debugFP, ">ICS: ");
1506         show_bytes(debugFP, buf, newcount);
1507         fprintf(debugFP, "\n");
1508     }
1509     outcount = OutputToProcess(pr, buf, newcount, outError);
1510     if (outcount < newcount) return -1; /* to be sure */
1511     return count;
1512 }
1513
1514 void
1515 read_from_player(isr, closure, message, count, error)
1516      InputSourceRef isr;
1517      VOIDSTAR closure;
1518      char *message;
1519      int count;
1520      int error;
1521 {
1522     int outError, outCount;
1523     static int gotEof = 0;
1524
1525     /* Pass data read from player on to ICS */
1526     if (count > 0) {
1527         gotEof = 0;
1528         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1529         if (outCount < count) {
1530             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1531         }
1532     } else if (count < 0) {
1533         RemoveInputSource(isr);
1534         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1535     } else if (gotEof++ > 0) {
1536         RemoveInputSource(isr);
1537         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1538     }
1539 }
1540
1541 void
1542 KeepAlive()
1543 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1544     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1545     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1546     SendToICS("date\n");
1547     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1548 }
1549
1550 /* added routine for printf style output to ics */
1551 void ics_printf(char *format, ...)
1552 {
1553     char buffer[MSG_SIZ];
1554     va_list args;
1555
1556     va_start(args, format);
1557     vsnprintf(buffer, sizeof(buffer), format, args);
1558     buffer[sizeof(buffer)-1] = '\0';
1559     SendToICS(buffer);
1560     va_end(args);
1561 }
1562
1563 void
1564 SendToICS(s)
1565      char *s;
1566 {
1567     int count, outCount, outError;
1568
1569     if (icsPR == NULL) return;
1570
1571     count = strlen(s);
1572     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1573     if (outCount < count) {
1574         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1575     }
1576 }
1577
1578 /* This is used for sending logon scripts to the ICS. Sending
1579    without a delay causes problems when using timestamp on ICC
1580    (at least on my machine). */
1581 void
1582 SendToICSDelayed(s,msdelay)
1583      char *s;
1584      long msdelay;
1585 {
1586     int count, outCount, outError;
1587
1588     if (icsPR == NULL) return;
1589
1590     count = strlen(s);
1591     if (appData.debugMode) {
1592         fprintf(debugFP, ">ICS: ");
1593         show_bytes(debugFP, s, count);
1594         fprintf(debugFP, "\n");
1595     }
1596     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1597                                       msdelay);
1598     if (outCount < count) {
1599         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1600     }
1601 }
1602
1603
1604 /* Remove all highlighting escape sequences in s
1605    Also deletes any suffix starting with '('
1606    */
1607 char *
1608 StripHighlightAndTitle(s)
1609      char *s;
1610 {
1611     static char retbuf[MSG_SIZ];
1612     char *p = retbuf;
1613
1614     while (*s != NULLCHAR) {
1615         while (*s == '\033') {
1616             while (*s != NULLCHAR && !isalpha(*s)) s++;
1617             if (*s != NULLCHAR) s++;
1618         }
1619         while (*s != NULLCHAR && *s != '\033') {
1620             if (*s == '(' || *s == '[') {
1621                 *p = NULLCHAR;
1622                 return retbuf;
1623             }
1624             *p++ = *s++;
1625         }
1626     }
1627     *p = NULLCHAR;
1628     return retbuf;
1629 }
1630
1631 /* Remove all highlighting escape sequences in s */
1632 char *
1633 StripHighlight(s)
1634      char *s;
1635 {
1636     static char retbuf[MSG_SIZ];
1637     char *p = retbuf;
1638
1639     while (*s != NULLCHAR) {
1640         while (*s == '\033') {
1641             while (*s != NULLCHAR && !isalpha(*s)) s++;
1642             if (*s != NULLCHAR) s++;
1643         }
1644         while (*s != NULLCHAR && *s != '\033') {
1645             *p++ = *s++;
1646         }
1647     }
1648     *p = NULLCHAR;
1649     return retbuf;
1650 }
1651
1652 char *variantNames[] = VARIANT_NAMES;
1653 char *
1654 VariantName(v)
1655      VariantClass v;
1656 {
1657     return variantNames[v];
1658 }
1659
1660
1661 /* Identify a variant from the strings the chess servers use or the
1662    PGN Variant tag names we use. */
1663 VariantClass
1664 StringToVariant(e)
1665      char *e;
1666 {
1667     char *p;
1668     int wnum = -1;
1669     VariantClass v = VariantNormal;
1670     int i, found = FALSE;
1671     char buf[MSG_SIZ];
1672     int len;
1673
1674     if (!e) return v;
1675
1676     /* [HGM] skip over optional board-size prefixes */
1677     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1678         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1679         while( *e++ != '_');
1680     }
1681
1682     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1683         v = VariantNormal;
1684         found = TRUE;
1685     } else
1686     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1687       if (StrCaseStr(e, variantNames[i])) {
1688         v = (VariantClass) i;
1689         found = TRUE;
1690         break;
1691       }
1692     }
1693
1694     if (!found) {
1695       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1696           || StrCaseStr(e, "wild/fr")
1697           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1698         v = VariantFischeRandom;
1699       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1700                  (i = 1, p = StrCaseStr(e, "w"))) {
1701         p += i;
1702         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1703         if (isdigit(*p)) {
1704           wnum = atoi(p);
1705         } else {
1706           wnum = -1;
1707         }
1708         switch (wnum) {
1709         case 0: /* FICS only, actually */
1710         case 1:
1711           /* Castling legal even if K starts on d-file */
1712           v = VariantWildCastle;
1713           break;
1714         case 2:
1715         case 3:
1716         case 4:
1717           /* Castling illegal even if K & R happen to start in
1718              normal positions. */
1719           v = VariantNoCastle;
1720           break;
1721         case 5:
1722         case 7:
1723         case 8:
1724         case 10:
1725         case 11:
1726         case 12:
1727         case 13:
1728         case 14:
1729         case 15:
1730         case 18:
1731         case 19:
1732           /* Castling legal iff K & R start in normal positions */
1733           v = VariantNormal;
1734           break;
1735         case 6:
1736         case 20:
1737         case 21:
1738           /* Special wilds for position setup; unclear what to do here */
1739           v = VariantLoadable;
1740           break;
1741         case 9:
1742           /* Bizarre ICC game */
1743           v = VariantTwoKings;
1744           break;
1745         case 16:
1746           v = VariantKriegspiel;
1747           break;
1748         case 17:
1749           v = VariantLosers;
1750           break;
1751         case 22:
1752           v = VariantFischeRandom;
1753           break;
1754         case 23:
1755           v = VariantCrazyhouse;
1756           break;
1757         case 24:
1758           v = VariantBughouse;
1759           break;
1760         case 25:
1761           v = Variant3Check;
1762           break;
1763         case 26:
1764           /* Not quite the same as FICS suicide! */
1765           v = VariantGiveaway;
1766           break;
1767         case 27:
1768           v = VariantAtomic;
1769           break;
1770         case 28:
1771           v = VariantShatranj;
1772           break;
1773
1774         /* Temporary names for future ICC types.  The name *will* change in
1775            the next xboard/WinBoard release after ICC defines it. */
1776         case 29:
1777           v = Variant29;
1778           break;
1779         case 30:
1780           v = Variant30;
1781           break;
1782         case 31:
1783           v = Variant31;
1784           break;
1785         case 32:
1786           v = Variant32;
1787           break;
1788         case 33:
1789           v = Variant33;
1790           break;
1791         case 34:
1792           v = Variant34;
1793           break;
1794         case 35:
1795           v = Variant35;
1796           break;
1797         case 36:
1798           v = Variant36;
1799           break;
1800         case 37:
1801           v = VariantShogi;
1802           break;
1803         case 38:
1804           v = VariantXiangqi;
1805           break;
1806         case 39:
1807           v = VariantCourier;
1808           break;
1809         case 40:
1810           v = VariantGothic;
1811           break;
1812         case 41:
1813           v = VariantCapablanca;
1814           break;
1815         case 42:
1816           v = VariantKnightmate;
1817           break;
1818         case 43:
1819           v = VariantFairy;
1820           break;
1821         case 44:
1822           v = VariantCylinder;
1823           break;
1824         case 45:
1825           v = VariantFalcon;
1826           break;
1827         case 46:
1828           v = VariantCapaRandom;
1829           break;
1830         case 47:
1831           v = VariantBerolina;
1832           break;
1833         case 48:
1834           v = VariantJanus;
1835           break;
1836         case 49:
1837           v = VariantSuper;
1838           break;
1839         case 50:
1840           v = VariantGreat;
1841           break;
1842         case -1:
1843           /* Found "wild" or "w" in the string but no number;
1844              must assume it's normal chess. */
1845           v = VariantNormal;
1846           break;
1847         default:
1848           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1849           if( (len > MSG_SIZ) && appData.debugMode )
1850             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1851
1852           DisplayError(buf, 0);
1853           v = VariantUnknown;
1854           break;
1855         }
1856       }
1857     }
1858     if (appData.debugMode) {
1859       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1860               e, wnum, VariantName(v));
1861     }
1862     return v;
1863 }
1864
1865 static int leftover_start = 0, leftover_len = 0;
1866 char star_match[STAR_MATCH_N][MSG_SIZ];
1867
1868 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1869    advance *index beyond it, and set leftover_start to the new value of
1870    *index; else return FALSE.  If pattern contains the character '*', it
1871    matches any sequence of characters not containing '\r', '\n', or the
1872    character following the '*' (if any), and the matched sequence(s) are
1873    copied into star_match.
1874    */
1875 int
1876 looking_at(buf, index, pattern)
1877      char *buf;
1878      int *index;
1879      char *pattern;
1880 {
1881     char *bufp = &buf[*index], *patternp = pattern;
1882     int star_count = 0;
1883     char *matchp = star_match[0];
1884
1885     for (;;) {
1886         if (*patternp == NULLCHAR) {
1887             *index = leftover_start = bufp - buf;
1888             *matchp = NULLCHAR;
1889             return TRUE;
1890         }
1891         if (*bufp == NULLCHAR) return FALSE;
1892         if (*patternp == '*') {
1893             if (*bufp == *(patternp + 1)) {
1894                 *matchp = NULLCHAR;
1895                 matchp = star_match[++star_count];
1896                 patternp += 2;
1897                 bufp++;
1898                 continue;
1899             } else if (*bufp == '\n' || *bufp == '\r') {
1900                 patternp++;
1901                 if (*patternp == NULLCHAR)
1902                   continue;
1903                 else
1904                   return FALSE;
1905             } else {
1906                 *matchp++ = *bufp++;
1907                 continue;
1908             }
1909         }
1910         if (*patternp != *bufp) return FALSE;
1911         patternp++;
1912         bufp++;
1913     }
1914 }
1915
1916 void
1917 SendToPlayer(data, length)
1918      char *data;
1919      int length;
1920 {
1921     int error, outCount;
1922     outCount = OutputToProcess(NoProc, data, length, &error);
1923     if (outCount < length) {
1924         DisplayFatalError(_("Error writing to display"), error, 1);
1925     }
1926 }
1927
1928 void
1929 PackHolding(packed, holding)
1930      char packed[];
1931      char *holding;
1932 {
1933     char *p = holding;
1934     char *q = packed;
1935     int runlength = 0;
1936     int curr = 9999;
1937     do {
1938         if (*p == curr) {
1939             runlength++;
1940         } else {
1941             switch (runlength) {
1942               case 0:
1943                 break;
1944               case 1:
1945                 *q++ = curr;
1946                 break;
1947               case 2:
1948                 *q++ = curr;
1949                 *q++ = curr;
1950                 break;
1951               default:
1952                 sprintf(q, "%d", runlength);
1953                 while (*q) q++;
1954                 *q++ = curr;
1955                 break;
1956             }
1957             runlength = 1;
1958             curr = *p;
1959         }
1960     } while (*p++);
1961     *q = NULLCHAR;
1962 }
1963
1964 /* Telnet protocol requests from the front end */
1965 void
1966 TelnetRequest(ddww, option)
1967      unsigned char ddww, option;
1968 {
1969     unsigned char msg[3];
1970     int outCount, outError;
1971
1972     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1973
1974     if (appData.debugMode) {
1975         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1976         switch (ddww) {
1977           case TN_DO:
1978             ddwwStr = "DO";
1979             break;
1980           case TN_DONT:
1981             ddwwStr = "DONT";
1982             break;
1983           case TN_WILL:
1984             ddwwStr = "WILL";
1985             break;
1986           case TN_WONT:
1987             ddwwStr = "WONT";
1988             break;
1989           default:
1990             ddwwStr = buf1;
1991             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
1992             break;
1993         }
1994         switch (option) {
1995           case TN_ECHO:
1996             optionStr = "ECHO";
1997             break;
1998           default:
1999             optionStr = buf2;
2000             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2001             break;
2002         }
2003         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2004     }
2005     msg[0] = TN_IAC;
2006     msg[1] = ddww;
2007     msg[2] = option;
2008     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2009     if (outCount < 3) {
2010         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2011     }
2012 }
2013
2014 void
2015 DoEcho()
2016 {
2017     if (!appData.icsActive) return;
2018     TelnetRequest(TN_DO, TN_ECHO);
2019 }
2020
2021 void
2022 DontEcho()
2023 {
2024     if (!appData.icsActive) return;
2025     TelnetRequest(TN_DONT, TN_ECHO);
2026 }
2027
2028 void
2029 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2030 {
2031     /* put the holdings sent to us by the server on the board holdings area */
2032     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2033     char p;
2034     ChessSquare piece;
2035
2036     if(gameInfo.holdingsWidth < 2)  return;
2037     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2038         return; // prevent overwriting by pre-board holdings
2039
2040     if( (int)lowestPiece >= BlackPawn ) {
2041         holdingsColumn = 0;
2042         countsColumn = 1;
2043         holdingsStartRow = BOARD_HEIGHT-1;
2044         direction = -1;
2045     } else {
2046         holdingsColumn = BOARD_WIDTH-1;
2047         countsColumn = BOARD_WIDTH-2;
2048         holdingsStartRow = 0;
2049         direction = 1;
2050     }
2051
2052     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2053         board[i][holdingsColumn] = EmptySquare;
2054         board[i][countsColumn]   = (ChessSquare) 0;
2055     }
2056     while( (p=*holdings++) != NULLCHAR ) {
2057         piece = CharToPiece( ToUpper(p) );
2058         if(piece == EmptySquare) continue;
2059         /*j = (int) piece - (int) WhitePawn;*/
2060         j = PieceToNumber(piece);
2061         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2062         if(j < 0) continue;               /* should not happen */
2063         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2064         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2065         board[holdingsStartRow+j*direction][countsColumn]++;
2066     }
2067 }
2068
2069
2070 void
2071 VariantSwitch(Board board, VariantClass newVariant)
2072 {
2073    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2074    static Board oldBoard;
2075
2076    startedFromPositionFile = FALSE;
2077    if(gameInfo.variant == newVariant) return;
2078
2079    /* [HGM] This routine is called each time an assignment is made to
2080     * gameInfo.variant during a game, to make sure the board sizes
2081     * are set to match the new variant. If that means adding or deleting
2082     * holdings, we shift the playing board accordingly
2083     * This kludge is needed because in ICS observe mode, we get boards
2084     * of an ongoing game without knowing the variant, and learn about the
2085     * latter only later. This can be because of the move list we requested,
2086     * in which case the game history is refilled from the beginning anyway,
2087     * but also when receiving holdings of a crazyhouse game. In the latter
2088     * case we want to add those holdings to the already received position.
2089     */
2090
2091
2092    if (appData.debugMode) {
2093      fprintf(debugFP, "Switch board from %s to %s\n",
2094              VariantName(gameInfo.variant), VariantName(newVariant));
2095      setbuf(debugFP, NULL);
2096    }
2097    shuffleOpenings = 0;       /* [HGM] shuffle */
2098    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2099    switch(newVariant)
2100      {
2101      case VariantShogi:
2102        newWidth = 9;  newHeight = 9;
2103        gameInfo.holdingsSize = 7;
2104      case VariantBughouse:
2105      case VariantCrazyhouse:
2106        newHoldingsWidth = 2; break;
2107      case VariantGreat:
2108        newWidth = 10;
2109      case VariantSuper:
2110        newHoldingsWidth = 2;
2111        gameInfo.holdingsSize = 8;
2112        break;
2113      case VariantGothic:
2114      case VariantCapablanca:
2115      case VariantCapaRandom:
2116        newWidth = 10;
2117      default:
2118        newHoldingsWidth = gameInfo.holdingsSize = 0;
2119      };
2120
2121    if(newWidth  != gameInfo.boardWidth  ||
2122       newHeight != gameInfo.boardHeight ||
2123       newHoldingsWidth != gameInfo.holdingsWidth ) {
2124
2125      /* shift position to new playing area, if needed */
2126      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2127        for(i=0; i<BOARD_HEIGHT; i++)
2128          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2129            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2130              board[i][j];
2131        for(i=0; i<newHeight; i++) {
2132          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2133          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2134        }
2135      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2136        for(i=0; i<BOARD_HEIGHT; i++)
2137          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2138            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2139              board[i][j];
2140      }
2141      gameInfo.boardWidth  = newWidth;
2142      gameInfo.boardHeight = newHeight;
2143      gameInfo.holdingsWidth = newHoldingsWidth;
2144      gameInfo.variant = newVariant;
2145      InitDrawingSizes(-2, 0);
2146    } else gameInfo.variant = newVariant;
2147    CopyBoard(oldBoard, board);   // remember correctly formatted board
2148      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2149    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2150 }
2151
2152 static int loggedOn = FALSE;
2153
2154 /*-- Game start info cache: --*/
2155 int gs_gamenum;
2156 char gs_kind[MSG_SIZ];
2157 static char player1Name[128] = "";
2158 static char player2Name[128] = "";
2159 static char cont_seq[] = "\n\\   ";
2160 static int player1Rating = -1;
2161 static int player2Rating = -1;
2162 /*----------------------------*/
2163
2164 ColorClass curColor = ColorNormal;
2165 int suppressKibitz = 0;
2166
2167 // [HGM] seekgraph
2168 Boolean soughtPending = FALSE;
2169 Boolean seekGraphUp;
2170 #define MAX_SEEK_ADS 200
2171 #define SQUARE 0x80
2172 char *seekAdList[MAX_SEEK_ADS];
2173 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2174 float tcList[MAX_SEEK_ADS];
2175 char colorList[MAX_SEEK_ADS];
2176 int nrOfSeekAds = 0;
2177 int minRating = 1010, maxRating = 2800;
2178 int hMargin = 10, vMargin = 20, h, w;
2179 extern int squareSize, lineGap;
2180
2181 void
2182 PlotSeekAd(int i)
2183 {
2184         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2185         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2186         if(r < minRating+100 && r >=0 ) r = minRating+100;
2187         if(r > maxRating) r = maxRating;
2188         if(tc < 1.) tc = 1.;
2189         if(tc > 95.) tc = 95.;
2190         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2191         y = ((double)r - minRating)/(maxRating - minRating)
2192             * (h-vMargin-squareSize/8-1) + vMargin;
2193         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2194         if(strstr(seekAdList[i], " u ")) color = 1;
2195         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2196            !strstr(seekAdList[i], "bullet") &&
2197            !strstr(seekAdList[i], "blitz") &&
2198            !strstr(seekAdList[i], "standard") ) color = 2;
2199         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2200         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2201 }
2202
2203 void
2204 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2205 {
2206         char buf[MSG_SIZ], *ext = "";
2207         VariantClass v = StringToVariant(type);
2208         if(strstr(type, "wild")) {
2209             ext = type + 4; // append wild number
2210             if(v == VariantFischeRandom) type = "chess960"; else
2211             if(v == VariantLoadable) type = "setup"; else
2212             type = VariantName(v);
2213         }
2214         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2215         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2216             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2217             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2218             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2219             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2220             seekNrList[nrOfSeekAds] = nr;
2221             zList[nrOfSeekAds] = 0;
2222             seekAdList[nrOfSeekAds++] = StrSave(buf);
2223             if(plot) PlotSeekAd(nrOfSeekAds-1);
2224         }
2225 }
2226
2227 void
2228 EraseSeekDot(int i)
2229 {
2230     int x = xList[i], y = yList[i], d=squareSize/4, k;
2231     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2232     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2233     // now replot every dot that overlapped
2234     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2235         int xx = xList[k], yy = yList[k];
2236         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2237             DrawSeekDot(xx, yy, colorList[k]);
2238     }
2239 }
2240
2241 void
2242 RemoveSeekAd(int nr)
2243 {
2244         int i;
2245         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2246             EraseSeekDot(i);
2247             if(seekAdList[i]) free(seekAdList[i]);
2248             seekAdList[i] = seekAdList[--nrOfSeekAds];
2249             seekNrList[i] = seekNrList[nrOfSeekAds];
2250             ratingList[i] = ratingList[nrOfSeekAds];
2251             colorList[i]  = colorList[nrOfSeekAds];
2252             tcList[i] = tcList[nrOfSeekAds];
2253             xList[i]  = xList[nrOfSeekAds];
2254             yList[i]  = yList[nrOfSeekAds];
2255             zList[i]  = zList[nrOfSeekAds];
2256             seekAdList[nrOfSeekAds] = NULL;
2257             break;
2258         }
2259 }
2260
2261 Boolean
2262 MatchSoughtLine(char *line)
2263 {
2264     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2265     int nr, base, inc, u=0; char dummy;
2266
2267     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2268        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2269        (u=1) &&
2270        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2271         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2272         // match: compact and save the line
2273         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2274         return TRUE;
2275     }
2276     return FALSE;
2277 }
2278
2279 int
2280 DrawSeekGraph()
2281 {
2282     int i;
2283     if(!seekGraphUp) return FALSE;
2284     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2285     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2286
2287     DrawSeekBackground(0, 0, w, h);
2288     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2289     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2290     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2291         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2292         yy = h-1-yy;
2293         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2294         if(i%500 == 0) {
2295             char buf[MSG_SIZ];
2296             snprintf(buf, MSG_SIZ, "%d", i);
2297             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2298         }
2299     }
2300     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2301     for(i=1; i<100; i+=(i<10?1:5)) {
2302         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2303         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2304         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2305             char buf[MSG_SIZ];
2306             snprintf(buf, MSG_SIZ, "%d", i);
2307             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2308         }
2309     }
2310     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2311     return TRUE;
2312 }
2313
2314 int SeekGraphClick(ClickType click, int x, int y, int moving)
2315 {
2316     static int lastDown = 0, displayed = 0, lastSecond;
2317     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2318         if(click == Release || moving) return FALSE;
2319         nrOfSeekAds = 0;
2320         soughtPending = TRUE;
2321         SendToICS(ics_prefix);
2322         SendToICS("sought\n"); // should this be "sought all"?
2323     } else { // issue challenge based on clicked ad
2324         int dist = 10000; int i, closest = 0, second = 0;
2325         for(i=0; i<nrOfSeekAds; i++) {
2326             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2327             if(d < dist) { dist = d; closest = i; }
2328             second += (d - zList[i] < 120); // count in-range ads
2329             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2330         }
2331         if(dist < 120) {
2332             char buf[MSG_SIZ];
2333             second = (second > 1);
2334             if(displayed != closest || second != lastSecond) {
2335                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2336                 lastSecond = second; displayed = closest;
2337             }
2338             if(click == Press) {
2339                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2340                 lastDown = closest;
2341                 return TRUE;
2342             } // on press 'hit', only show info
2343             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2344             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2345             SendToICS(ics_prefix);
2346             SendToICS(buf);
2347             return TRUE; // let incoming board of started game pop down the graph
2348         } else if(click == Release) { // release 'miss' is ignored
2349             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2350             if(moving == 2) { // right up-click
2351                 nrOfSeekAds = 0; // refresh graph
2352                 soughtPending = TRUE;
2353                 SendToICS(ics_prefix);
2354                 SendToICS("sought\n"); // should this be "sought all"?
2355             }
2356             return TRUE;
2357         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2358         // press miss or release hit 'pop down' seek graph
2359         seekGraphUp = FALSE;
2360         DrawPosition(TRUE, NULL);
2361     }
2362     return TRUE;
2363 }
2364
2365 void
2366 read_from_ics(isr, closure, data, count, error)
2367      InputSourceRef isr;
2368      VOIDSTAR closure;
2369      char *data;
2370      int count;
2371      int error;
2372 {
2373 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2374 #define STARTED_NONE 0
2375 #define STARTED_MOVES 1
2376 #define STARTED_BOARD 2
2377 #define STARTED_OBSERVE 3
2378 #define STARTED_HOLDINGS 4
2379 #define STARTED_CHATTER 5
2380 #define STARTED_COMMENT 6
2381 #define STARTED_MOVES_NOHIDE 7
2382
2383     static int started = STARTED_NONE;
2384     static char parse[20000];
2385     static int parse_pos = 0;
2386     static char buf[BUF_SIZE + 1];
2387     static int firstTime = TRUE, intfSet = FALSE;
2388     static ColorClass prevColor = ColorNormal;
2389     static int savingComment = FALSE;
2390     static int cmatch = 0; // continuation sequence match
2391     char *bp;
2392     char str[MSG_SIZ];
2393     int i, oldi;
2394     int buf_len;
2395     int next_out;
2396     int tkind;
2397     int backup;    /* [DM] For zippy color lines */
2398     char *p;
2399     char talker[MSG_SIZ]; // [HGM] chat
2400     int channel;
2401
2402     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2403
2404     if (appData.debugMode) {
2405       if (!error) {
2406         fprintf(debugFP, "<ICS: ");
2407         show_bytes(debugFP, data, count);
2408         fprintf(debugFP, "\n");
2409       }
2410     }
2411
2412     if (appData.debugMode) { int f = forwardMostMove;
2413         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2414                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2415                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2416     }
2417     if (count > 0) {
2418         /* If last read ended with a partial line that we couldn't parse,
2419            prepend it to the new read and try again. */
2420         if (leftover_len > 0) {
2421             for (i=0; i<leftover_len; i++)
2422               buf[i] = buf[leftover_start + i];
2423         }
2424
2425     /* copy new characters into the buffer */
2426     bp = buf + leftover_len;
2427     buf_len=leftover_len;
2428     for (i=0; i<count; i++)
2429     {
2430         // ignore these
2431         if (data[i] == '\r')
2432             continue;
2433
2434         // join lines split by ICS?
2435         if (!appData.noJoin)
2436         {
2437             /*
2438                 Joining just consists of finding matches against the
2439                 continuation sequence, and discarding that sequence
2440                 if found instead of copying it.  So, until a match
2441                 fails, there's nothing to do since it might be the
2442                 complete sequence, and thus, something we don't want
2443                 copied.
2444             */
2445             if (data[i] == cont_seq[cmatch])
2446             {
2447                 cmatch++;
2448                 if (cmatch == strlen(cont_seq))
2449                 {
2450                     cmatch = 0; // complete match.  just reset the counter
2451
2452                     /*
2453                         it's possible for the ICS to not include the space
2454                         at the end of the last word, making our [correct]
2455                         join operation fuse two separate words.  the server
2456                         does this when the space occurs at the width setting.
2457                     */
2458                     if (!buf_len || buf[buf_len-1] != ' ')
2459                     {
2460                         *bp++ = ' ';
2461                         buf_len++;
2462                     }
2463                 }
2464                 continue;
2465             }
2466             else if (cmatch)
2467             {
2468                 /*
2469                     match failed, so we have to copy what matched before
2470                     falling through and copying this character.  In reality,
2471                     this will only ever be just the newline character, but
2472                     it doesn't hurt to be precise.
2473                 */
2474                 strncpy(bp, cont_seq, cmatch);
2475                 bp += cmatch;
2476                 buf_len += cmatch;
2477                 cmatch = 0;
2478             }
2479         }
2480
2481         // copy this char
2482         *bp++ = data[i];
2483         buf_len++;
2484     }
2485
2486         buf[buf_len] = NULLCHAR;
2487 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2488         next_out = 0;
2489         leftover_start = 0;
2490
2491         i = 0;
2492         while (i < buf_len) {
2493             /* Deal with part of the TELNET option negotiation
2494                protocol.  We refuse to do anything beyond the
2495                defaults, except that we allow the WILL ECHO option,
2496                which ICS uses to turn off password echoing when we are
2497                directly connected to it.  We reject this option
2498                if localLineEditing mode is on (always on in xboard)
2499                and we are talking to port 23, which might be a real
2500                telnet server that will try to keep WILL ECHO on permanently.
2501              */
2502             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2503                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2504                 unsigned char option;
2505                 oldi = i;
2506                 switch ((unsigned char) buf[++i]) {
2507                   case TN_WILL:
2508                     if (appData.debugMode)
2509                       fprintf(debugFP, "\n<WILL ");
2510                     switch (option = (unsigned char) buf[++i]) {
2511                       case TN_ECHO:
2512                         if (appData.debugMode)
2513                           fprintf(debugFP, "ECHO ");
2514                         /* Reply only if this is a change, according
2515                            to the protocol rules. */
2516                         if (remoteEchoOption) break;
2517                         if (appData.localLineEditing &&
2518                             atoi(appData.icsPort) == TN_PORT) {
2519                             TelnetRequest(TN_DONT, TN_ECHO);
2520                         } else {
2521                             EchoOff();
2522                             TelnetRequest(TN_DO, TN_ECHO);
2523                             remoteEchoOption = TRUE;
2524                         }
2525                         break;
2526                       default:
2527                         if (appData.debugMode)
2528                           fprintf(debugFP, "%d ", option);
2529                         /* Whatever this is, we don't want it. */
2530                         TelnetRequest(TN_DONT, option);
2531                         break;
2532                     }
2533                     break;
2534                   case TN_WONT:
2535                     if (appData.debugMode)
2536                       fprintf(debugFP, "\n<WONT ");
2537                     switch (option = (unsigned char) buf[++i]) {
2538                       case TN_ECHO:
2539                         if (appData.debugMode)
2540                           fprintf(debugFP, "ECHO ");
2541                         /* Reply only if this is a change, according
2542                            to the protocol rules. */
2543                         if (!remoteEchoOption) break;
2544                         EchoOn();
2545                         TelnetRequest(TN_DONT, TN_ECHO);
2546                         remoteEchoOption = FALSE;
2547                         break;
2548                       default:
2549                         if (appData.debugMode)
2550                           fprintf(debugFP, "%d ", (unsigned char) option);
2551                         /* Whatever this is, it must already be turned
2552                            off, because we never agree to turn on
2553                            anything non-default, so according to the
2554                            protocol rules, we don't reply. */
2555                         break;
2556                     }
2557                     break;
2558                   case TN_DO:
2559                     if (appData.debugMode)
2560                       fprintf(debugFP, "\n<DO ");
2561                     switch (option = (unsigned char) buf[++i]) {
2562                       default:
2563                         /* Whatever this is, we refuse to do it. */
2564                         if (appData.debugMode)
2565                           fprintf(debugFP, "%d ", option);
2566                         TelnetRequest(TN_WONT, option);
2567                         break;
2568                     }
2569                     break;
2570                   case TN_DONT:
2571                     if (appData.debugMode)
2572                       fprintf(debugFP, "\n<DONT ");
2573                     switch (option = (unsigned char) buf[++i]) {
2574                       default:
2575                         if (appData.debugMode)
2576                           fprintf(debugFP, "%d ", option);
2577                         /* Whatever this is, we are already not doing
2578                            it, because we never agree to do anything
2579                            non-default, so according to the protocol
2580                            rules, we don't reply. */
2581                         break;
2582                     }
2583                     break;
2584                   case TN_IAC:
2585                     if (appData.debugMode)
2586                       fprintf(debugFP, "\n<IAC ");
2587                     /* Doubled IAC; pass it through */
2588                     i--;
2589                     break;
2590                   default:
2591                     if (appData.debugMode)
2592                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2593                     /* Drop all other telnet commands on the floor */
2594                     break;
2595                 }
2596                 if (oldi > next_out)
2597                   SendToPlayer(&buf[next_out], oldi - next_out);
2598                 if (++i > next_out)
2599                   next_out = i;
2600                 continue;
2601             }
2602
2603             /* OK, this at least will *usually* work */
2604             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2605                 loggedOn = TRUE;
2606             }
2607
2608             if (loggedOn && !intfSet) {
2609                 if (ics_type == ICS_ICC) {
2610                   snprintf(str, MSG_SIZ,
2611                           "/set-quietly interface %s\n/set-quietly style 12\n",
2612                           programVersion);
2613                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2614                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2615                 } else if (ics_type == ICS_CHESSNET) {
2616                   snprintf(str, MSG_SIZ, "/style 12\n");
2617                 } else {
2618                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2619                   strcat(str, programVersion);
2620                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2621                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2622                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2623 #ifdef WIN32
2624                   strcat(str, "$iset nohighlight 1\n");
2625 #endif
2626                   strcat(str, "$iset lock 1\n$style 12\n");
2627                 }
2628                 SendToICS(str);
2629                 NotifyFrontendLogin();
2630                 intfSet = TRUE;
2631             }
2632
2633             if (started == STARTED_COMMENT) {
2634                 /* Accumulate characters in comment */
2635                 parse[parse_pos++] = buf[i];
2636                 if (buf[i] == '\n') {
2637                     parse[parse_pos] = NULLCHAR;
2638                     if(chattingPartner>=0) {
2639                         char mess[MSG_SIZ];
2640                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2641                         OutputChatMessage(chattingPartner, mess);
2642                         chattingPartner = -1;
2643                         next_out = i+1; // [HGM] suppress printing in ICS window
2644                     } else
2645                     if(!suppressKibitz) // [HGM] kibitz
2646                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2647                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2648                         int nrDigit = 0, nrAlph = 0, j;
2649                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2650                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2651                         parse[parse_pos] = NULLCHAR;
2652                         // try to be smart: if it does not look like search info, it should go to
2653                         // ICS interaction window after all, not to engine-output window.
2654                         for(j=0; j<parse_pos; j++) { // count letters and digits
2655                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2656                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2657                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2658                         }
2659                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2660                             int depth=0; float score;
2661                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2662                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2663                                 pvInfoList[forwardMostMove-1].depth = depth;
2664                                 pvInfoList[forwardMostMove-1].score = 100*score;
2665                             }
2666                             OutputKibitz(suppressKibitz, parse);
2667                         } else {
2668                             char tmp[MSG_SIZ];
2669                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2670                             SendToPlayer(tmp, strlen(tmp));
2671                         }
2672                         next_out = i+1; // [HGM] suppress printing in ICS window
2673                     }
2674                     started = STARTED_NONE;
2675                 } else {
2676                     /* Don't match patterns against characters in comment */
2677                     i++;
2678                     continue;
2679                 }
2680             }
2681             if (started == STARTED_CHATTER) {
2682                 if (buf[i] != '\n') {
2683                     /* Don't match patterns against characters in chatter */
2684                     i++;
2685                     continue;
2686                 }
2687                 started = STARTED_NONE;
2688                 if(suppressKibitz) next_out = i+1;
2689             }
2690
2691             /* Kludge to deal with rcmd protocol */
2692             if (firstTime && looking_at(buf, &i, "\001*")) {
2693                 DisplayFatalError(&buf[1], 0, 1);
2694                 continue;
2695             } else {
2696                 firstTime = FALSE;
2697             }
2698
2699             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2700                 ics_type = ICS_ICC;
2701                 ics_prefix = "/";
2702                 if (appData.debugMode)
2703                   fprintf(debugFP, "ics_type %d\n", ics_type);
2704                 continue;
2705             }
2706             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2707                 ics_type = ICS_FICS;
2708                 ics_prefix = "$";
2709                 if (appData.debugMode)
2710                   fprintf(debugFP, "ics_type %d\n", ics_type);
2711                 continue;
2712             }
2713             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2714                 ics_type = ICS_CHESSNET;
2715                 ics_prefix = "/";
2716                 if (appData.debugMode)
2717                   fprintf(debugFP, "ics_type %d\n", ics_type);
2718                 continue;
2719             }
2720
2721             if (!loggedOn &&
2722                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2723                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2724                  looking_at(buf, &i, "will be \"*\""))) {
2725               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2726               continue;
2727             }
2728
2729             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2730               char buf[MSG_SIZ];
2731               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2732               DisplayIcsInteractionTitle(buf);
2733               have_set_title = TRUE;
2734             }
2735
2736             /* skip finger notes */
2737             if (started == STARTED_NONE &&
2738                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2739                  (buf[i] == '1' && buf[i+1] == '0')) &&
2740                 buf[i+2] == ':' && buf[i+3] == ' ') {
2741               started = STARTED_CHATTER;
2742               i += 3;
2743               continue;
2744             }
2745
2746             oldi = i;
2747             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2748             if(appData.seekGraph) {
2749                 if(soughtPending && MatchSoughtLine(buf+i)) {
2750                     i = strstr(buf+i, "rated") - buf;
2751                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2752                     next_out = leftover_start = i;
2753                     started = STARTED_CHATTER;
2754                     suppressKibitz = TRUE;
2755                     continue;
2756                 }
2757                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2758                         && looking_at(buf, &i, "* ads displayed")) {
2759                     soughtPending = FALSE;
2760                     seekGraphUp = TRUE;
2761                     DrawSeekGraph();
2762                     continue;
2763                 }
2764                 if(appData.autoRefresh) {
2765                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2766                         int s = (ics_type == ICS_ICC); // ICC format differs
2767                         if(seekGraphUp)
2768                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2769                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2770                         looking_at(buf, &i, "*% "); // eat prompt
2771                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2772                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2773                         next_out = i; // suppress
2774                         continue;
2775                     }
2776                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2777                         char *p = star_match[0];
2778                         while(*p) {
2779                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2780                             while(*p && *p++ != ' '); // next
2781                         }
2782                         looking_at(buf, &i, "*% "); // eat prompt
2783                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2784                         next_out = i;
2785                         continue;
2786                     }
2787                 }
2788             }
2789
2790             /* skip formula vars */
2791             if (started == STARTED_NONE &&
2792                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2793               started = STARTED_CHATTER;
2794               i += 3;
2795               continue;
2796             }
2797
2798             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2799             if (appData.autoKibitz && started == STARTED_NONE &&
2800                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2801                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2802                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2803                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2804                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2805                         suppressKibitz = TRUE;
2806                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2807                         next_out = i;
2808                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2809                                 && (gameMode == IcsPlayingWhite)) ||
2810                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2811                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2812                             started = STARTED_CHATTER; // own kibitz we simply discard
2813                         else {
2814                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2815                             parse_pos = 0; parse[0] = NULLCHAR;
2816                             savingComment = TRUE;
2817                             suppressKibitz = gameMode != IcsObserving ? 2 :
2818                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2819                         }
2820                         continue;
2821                 } else
2822                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2823                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2824                          && atoi(star_match[0])) {
2825                     // suppress the acknowledgements of our own autoKibitz
2826                     char *p;
2827                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2828                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2829                     SendToPlayer(star_match[0], strlen(star_match[0]));
2830                     if(looking_at(buf, &i, "*% ")) // eat prompt
2831                         suppressKibitz = FALSE;
2832                     next_out = i;
2833                     continue;
2834                 }
2835             } // [HGM] kibitz: end of patch
2836
2837             // [HGM] chat: intercept tells by users for which we have an open chat window
2838             channel = -1;
2839             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2840                                            looking_at(buf, &i, "* whispers:") ||
2841                                            looking_at(buf, &i, "* kibitzes:") ||
2842                                            looking_at(buf, &i, "* shouts:") ||
2843                                            looking_at(buf, &i, "* c-shouts:") ||
2844                                            looking_at(buf, &i, "--> * ") ||
2845                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2846                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2847                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2848                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2849                 int p;
2850                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2851                 chattingPartner = -1;
2852
2853                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2854                 for(p=0; p<MAX_CHAT; p++) {
2855                     if(channel == atoi(chatPartner[p])) {
2856                     talker[0] = '['; strcat(talker, "] ");
2857                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2858                     chattingPartner = p; break;
2859                     }
2860                 } else
2861                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2862                 for(p=0; p<MAX_CHAT; p++) {
2863                     if(!strcmp("kibitzes", chatPartner[p])) {
2864                         talker[0] = '['; strcat(talker, "] ");
2865                         chattingPartner = p; break;
2866                     }
2867                 } else
2868                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2869                 for(p=0; p<MAX_CHAT; p++) {
2870                     if(!strcmp("whispers", chatPartner[p])) {
2871                         talker[0] = '['; strcat(talker, "] ");
2872                         chattingPartner = p; break;
2873                     }
2874                 } else
2875                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2876                   if(buf[i-8] == '-' && buf[i-3] == 't')
2877                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2878                     if(!strcmp("c-shouts", chatPartner[p])) {
2879                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2880                         chattingPartner = p; break;
2881                     }
2882                   }
2883                   if(chattingPartner < 0)
2884                   for(p=0; p<MAX_CHAT; p++) {
2885                     if(!strcmp("shouts", chatPartner[p])) {
2886                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2887                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2888                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2889                         chattingPartner = p; break;
2890                     }
2891                   }
2892                 }
2893                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2894                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2895                     talker[0] = 0; Colorize(ColorTell, FALSE);
2896                     chattingPartner = p; break;
2897                 }
2898                 if(chattingPartner<0) i = oldi; else {
2899                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2900                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2901                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2902                     started = STARTED_COMMENT;
2903                     parse_pos = 0; parse[0] = NULLCHAR;
2904                     savingComment = 3 + chattingPartner; // counts as TRUE
2905                     suppressKibitz = TRUE;
2906                     continue;
2907                 }
2908             } // [HGM] chat: end of patch
2909
2910             if (appData.zippyTalk || appData.zippyPlay) {
2911                 /* [DM] Backup address for color zippy lines */
2912                 backup = i;
2913 #if ZIPPY
2914                if (loggedOn == TRUE)
2915                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2916                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2917 #endif
2918             } // [DM] 'else { ' deleted
2919                 if (
2920                     /* Regular tells and says */
2921                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2922                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2923                     looking_at(buf, &i, "* says: ") ||
2924                     /* Don't color "message" or "messages" output */
2925                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2926                     looking_at(buf, &i, "*. * at *:*: ") ||
2927                     looking_at(buf, &i, "--* (*:*): ") ||
2928                     /* Message notifications (same color as tells) */
2929                     looking_at(buf, &i, "* has left a message ") ||
2930                     looking_at(buf, &i, "* just sent you a message:\n") ||
2931                     /* Whispers and kibitzes */
2932                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2933                     looking_at(buf, &i, "* kibitzes: ") ||
2934                     /* Channel tells */
2935                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2936
2937                   if (tkind == 1 && strchr(star_match[0], ':')) {
2938                       /* Avoid "tells you:" spoofs in channels */
2939                      tkind = 3;
2940                   }
2941                   if (star_match[0][0] == NULLCHAR ||
2942                       strchr(star_match[0], ' ') ||
2943                       (tkind == 3 && strchr(star_match[1], ' '))) {
2944                     /* Reject bogus matches */
2945                     i = oldi;
2946                   } else {
2947                     if (appData.colorize) {
2948                       if (oldi > next_out) {
2949                         SendToPlayer(&buf[next_out], oldi - next_out);
2950                         next_out = oldi;
2951                       }
2952                       switch (tkind) {
2953                       case 1:
2954                         Colorize(ColorTell, FALSE);
2955                         curColor = ColorTell;
2956                         break;
2957                       case 2:
2958                         Colorize(ColorKibitz, FALSE);
2959                         curColor = ColorKibitz;
2960                         break;
2961                       case 3:
2962                         p = strrchr(star_match[1], '(');
2963                         if (p == NULL) {
2964                           p = star_match[1];
2965                         } else {
2966                           p++;
2967                         }
2968                         if (atoi(p) == 1) {
2969                           Colorize(ColorChannel1, FALSE);
2970                           curColor = ColorChannel1;
2971                         } else {
2972                           Colorize(ColorChannel, FALSE);
2973                           curColor = ColorChannel;
2974                         }
2975                         break;
2976                       case 5:
2977                         curColor = ColorNormal;
2978                         break;
2979                       }
2980                     }
2981                     if (started == STARTED_NONE && appData.autoComment &&
2982                         (gameMode == IcsObserving ||
2983                          gameMode == IcsPlayingWhite ||
2984                          gameMode == IcsPlayingBlack)) {
2985                       parse_pos = i - oldi;
2986                       memcpy(parse, &buf[oldi], parse_pos);
2987                       parse[parse_pos] = NULLCHAR;
2988                       started = STARTED_COMMENT;
2989                       savingComment = TRUE;
2990                     } else {
2991                       started = STARTED_CHATTER;
2992                       savingComment = FALSE;
2993                     }
2994                     loggedOn = TRUE;
2995                     continue;
2996                   }
2997                 }
2998
2999                 if (looking_at(buf, &i, "* s-shouts: ") ||
3000                     looking_at(buf, &i, "* c-shouts: ")) {
3001                     if (appData.colorize) {
3002                         if (oldi > next_out) {
3003                             SendToPlayer(&buf[next_out], oldi - next_out);
3004                             next_out = oldi;
3005                         }
3006                         Colorize(ColorSShout, FALSE);
3007                         curColor = ColorSShout;
3008                     }
3009                     loggedOn = TRUE;
3010                     started = STARTED_CHATTER;
3011                     continue;
3012                 }
3013
3014                 if (looking_at(buf, &i, "--->")) {
3015                     loggedOn = TRUE;
3016                     continue;
3017                 }
3018
3019                 if (looking_at(buf, &i, "* shouts: ") ||
3020                     looking_at(buf, &i, "--> ")) {
3021                     if (appData.colorize) {
3022                         if (oldi > next_out) {
3023                             SendToPlayer(&buf[next_out], oldi - next_out);
3024                             next_out = oldi;
3025                         }
3026                         Colorize(ColorShout, FALSE);
3027                         curColor = ColorShout;
3028                     }
3029                     loggedOn = TRUE;
3030                     started = STARTED_CHATTER;
3031                     continue;
3032                 }
3033
3034                 if (looking_at( buf, &i, "Challenge:")) {
3035                     if (appData.colorize) {
3036                         if (oldi > next_out) {
3037                             SendToPlayer(&buf[next_out], oldi - next_out);
3038                             next_out = oldi;
3039                         }
3040                         Colorize(ColorChallenge, FALSE);
3041                         curColor = ColorChallenge;
3042                     }
3043                     loggedOn = TRUE;
3044                     continue;
3045                 }
3046
3047                 if (looking_at(buf, &i, "* offers you") ||
3048                     looking_at(buf, &i, "* offers to be") ||
3049                     looking_at(buf, &i, "* would like to") ||
3050                     looking_at(buf, &i, "* requests to") ||
3051                     looking_at(buf, &i, "Your opponent offers") ||
3052                     looking_at(buf, &i, "Your opponent requests")) {
3053
3054                     if (appData.colorize) {
3055                         if (oldi > next_out) {
3056                             SendToPlayer(&buf[next_out], oldi - next_out);
3057                             next_out = oldi;
3058                         }
3059                         Colorize(ColorRequest, FALSE);
3060                         curColor = ColorRequest;
3061                     }
3062                     continue;
3063                 }
3064
3065                 if (looking_at(buf, &i, "* (*) seeking")) {
3066                     if (appData.colorize) {
3067                         if (oldi > next_out) {
3068                             SendToPlayer(&buf[next_out], oldi - next_out);
3069                             next_out = oldi;
3070                         }
3071                         Colorize(ColorSeek, FALSE);
3072                         curColor = ColorSeek;
3073                     }
3074                     continue;
3075             }
3076
3077             if (looking_at(buf, &i, "\\   ")) {
3078                 if (prevColor != ColorNormal) {
3079                     if (oldi > next_out) {
3080                         SendToPlayer(&buf[next_out], oldi - next_out);
3081                         next_out = oldi;
3082                     }
3083                     Colorize(prevColor, TRUE);
3084                     curColor = prevColor;
3085                 }
3086                 if (savingComment) {
3087                     parse_pos = i - oldi;
3088                     memcpy(parse, &buf[oldi], parse_pos);
3089                     parse[parse_pos] = NULLCHAR;
3090                     started = STARTED_COMMENT;
3091                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3092                         chattingPartner = savingComment - 3; // kludge to remember the box
3093                 } else {
3094                     started = STARTED_CHATTER;
3095                 }
3096                 continue;
3097             }
3098
3099             if (looking_at(buf, &i, "Black Strength :") ||
3100                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3101                 looking_at(buf, &i, "<10>") ||
3102                 looking_at(buf, &i, "#@#")) {
3103                 /* Wrong board style */
3104                 loggedOn = TRUE;
3105                 SendToICS(ics_prefix);
3106                 SendToICS("set style 12\n");
3107                 SendToICS(ics_prefix);
3108                 SendToICS("refresh\n");
3109                 continue;
3110             }
3111
3112             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3113                 ICSInitScript();
3114                 have_sent_ICS_logon = 1;
3115                 sending_ICS_password = 0; // in case we come back to login
3116                 sending_ICS_login = 1; 
3117                 continue;
3118             }
3119             /* need to shadow the password */
3120             if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
3121               sending_ICS_password = 1;
3122               continue;
3123             }
3124               
3125             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3126                 (looking_at(buf, &i, "\n<12> ") ||
3127                  looking_at(buf, &i, "<12> "))) {
3128                 loggedOn = TRUE;
3129                 if (oldi > next_out) {
3130                     SendToPlayer(&buf[next_out], oldi - next_out);
3131                 }
3132                 next_out = i;
3133                 started = STARTED_BOARD;
3134                 parse_pos = 0;
3135                 continue;
3136             }
3137
3138             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3139                 looking_at(buf, &i, "<b1> ")) {
3140                 if (oldi > next_out) {
3141                     SendToPlayer(&buf[next_out], oldi - next_out);
3142                 }
3143                 next_out = i;
3144                 started = STARTED_HOLDINGS;
3145                 parse_pos = 0;
3146                 continue;
3147             }
3148
3149             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3150                 loggedOn = TRUE;
3151                 /* Header for a move list -- first line */
3152
3153                 switch (ics_getting_history) {
3154                   case H_FALSE:
3155                     switch (gameMode) {
3156                       case IcsIdle:
3157                       case BeginningOfGame:
3158                         /* User typed "moves" or "oldmoves" while we
3159                            were idle.  Pretend we asked for these
3160                            moves and soak them up so user can step
3161                            through them and/or save them.
3162                            */
3163                         Reset(FALSE, TRUE);
3164                         gameMode = IcsObserving;
3165                         ModeHighlight();
3166                         ics_gamenum = -1;
3167                         ics_getting_history = H_GOT_UNREQ_HEADER;
3168                         break;
3169                       case EditGame: /*?*/
3170                       case EditPosition: /*?*/
3171                         /* Should above feature work in these modes too? */
3172                         /* For now it doesn't */
3173                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3174                         break;
3175                       default:
3176                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3177                         break;
3178                     }
3179                     break;
3180                   case H_REQUESTED:
3181                     /* Is this the right one? */
3182                     if (gameInfo.white && gameInfo.black &&
3183                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3184                         strcmp(gameInfo.black, star_match[2]) == 0) {
3185                         /* All is well */
3186                         ics_getting_history = H_GOT_REQ_HEADER;
3187                     }
3188                     break;
3189                   case H_GOT_REQ_HEADER:
3190                   case H_GOT_UNREQ_HEADER:
3191                   case H_GOT_UNWANTED_HEADER:
3192                   case H_GETTING_MOVES:
3193                     /* Should not happen */
3194                     DisplayError(_("Error gathering move list: two headers"), 0);
3195                     ics_getting_history = H_FALSE;
3196                     break;
3197                 }
3198
3199                 /* Save player ratings into gameInfo if needed */
3200                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3201                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3202                     (gameInfo.whiteRating == -1 ||
3203                      gameInfo.blackRating == -1)) {
3204
3205                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3206                     gameInfo.blackRating = string_to_rating(star_match[3]);
3207                     if (appData.debugMode)
3208                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3209                               gameInfo.whiteRating, gameInfo.blackRating);
3210                 }
3211                 continue;
3212             }
3213
3214             if (looking_at(buf, &i,
3215               "* * match, initial time: * minute*, increment: * second")) {
3216                 /* Header for a move list -- second line */
3217                 /* Initial board will follow if this is a wild game */
3218                 if (gameInfo.event != NULL) free(gameInfo.event);
3219                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3220                 gameInfo.event = StrSave(str);
3221                 /* [HGM] we switched variant. Translate boards if needed. */
3222                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3223                 continue;
3224             }
3225
3226             if (looking_at(buf, &i, "Move  ")) {
3227                 /* Beginning of a move list */
3228                 switch (ics_getting_history) {
3229                   case H_FALSE:
3230                     /* Normally should not happen */
3231                     /* Maybe user hit reset while we were parsing */
3232                     break;
3233                   case H_REQUESTED:
3234                     /* Happens if we are ignoring a move list that is not
3235                      * the one we just requested.  Common if the user
3236                      * tries to observe two games without turning off
3237                      * getMoveList */
3238                     break;
3239                   case H_GETTING_MOVES:
3240                     /* Should not happen */
3241                     DisplayError(_("Error gathering move list: nested"), 0);
3242                     ics_getting_history = H_FALSE;
3243                     break;
3244                   case H_GOT_REQ_HEADER:
3245                     ics_getting_history = H_GETTING_MOVES;
3246                     started = STARTED_MOVES;
3247                     parse_pos = 0;
3248                     if (oldi > next_out) {
3249                         SendToPlayer(&buf[next_out], oldi - next_out);
3250                     }
3251                     break;
3252                   case H_GOT_UNREQ_HEADER:
3253                     ics_getting_history = H_GETTING_MOVES;
3254                     started = STARTED_MOVES_NOHIDE;
3255                     parse_pos = 0;
3256                     break;
3257                   case H_GOT_UNWANTED_HEADER:
3258                     ics_getting_history = H_FALSE;
3259                     break;
3260                 }
3261                 continue;
3262             }
3263
3264             if (looking_at(buf, &i, "% ") ||
3265                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3266                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3267                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3268                     soughtPending = FALSE;
3269                     seekGraphUp = TRUE;
3270                     DrawSeekGraph();
3271                 }
3272                 if(suppressKibitz) next_out = i;
3273                 savingComment = FALSE;
3274                 suppressKibitz = 0;
3275                 switch (started) {
3276                   case STARTED_MOVES:
3277                   case STARTED_MOVES_NOHIDE:
3278                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3279                     parse[parse_pos + i - oldi] = NULLCHAR;
3280                     ParseGameHistory(parse);
3281 #if ZIPPY
3282                     if (appData.zippyPlay && first.initDone) {
3283                         FeedMovesToProgram(&first, forwardMostMove);
3284                         if (gameMode == IcsPlayingWhite) {
3285                             if (WhiteOnMove(forwardMostMove)) {
3286                                 if (first.sendTime) {
3287                                   if (first.useColors) {
3288                                     SendToProgram("black\n", &first);
3289                                   }
3290                                   SendTimeRemaining(&first, TRUE);
3291                                 }
3292                                 if (first.useColors) {
3293                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3294                                 }
3295                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3296                                 first.maybeThinking = TRUE;
3297                             } else {
3298                                 if (first.usePlayother) {
3299                                   if (first.sendTime) {
3300                                     SendTimeRemaining(&first, TRUE);
3301                                   }
3302                                   SendToProgram("playother\n", &first);
3303                                   firstMove = FALSE;
3304                                 } else {
3305                                   firstMove = TRUE;
3306                                 }
3307                             }
3308                         } else if (gameMode == IcsPlayingBlack) {
3309                             if (!WhiteOnMove(forwardMostMove)) {
3310                                 if (first.sendTime) {
3311                                   if (first.useColors) {
3312                                     SendToProgram("white\n", &first);
3313                                   }
3314                                   SendTimeRemaining(&first, FALSE);
3315                                 }
3316                                 if (first.useColors) {
3317                                   SendToProgram("black\n", &first);
3318                                 }
3319                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3320                                 first.maybeThinking = TRUE;
3321                             } else {
3322                                 if (first.usePlayother) {
3323                                   if (first.sendTime) {
3324                                     SendTimeRemaining(&first, FALSE);
3325                                   }
3326                                   SendToProgram("playother\n", &first);
3327                                   firstMove = FALSE;
3328                                 } else {
3329                                   firstMove = TRUE;
3330                                 }
3331                             }
3332                         }
3333                     }
3334 #endif
3335                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3336                         /* Moves came from oldmoves or moves command
3337                            while we weren't doing anything else.
3338                            */
3339                         currentMove = forwardMostMove;
3340                         ClearHighlights();/*!!could figure this out*/
3341                         flipView = appData.flipView;
3342                         DrawPosition(TRUE, boards[currentMove]);
3343                         DisplayBothClocks();
3344                         snprintf(str, MSG_SIZ, "%s vs. %s",
3345                                 gameInfo.white, gameInfo.black);
3346                         DisplayTitle(str);
3347                         gameMode = IcsIdle;
3348                     } else {
3349                         /* Moves were history of an active game */
3350                         if (gameInfo.resultDetails != NULL) {
3351                             free(gameInfo.resultDetails);
3352                             gameInfo.resultDetails = NULL;
3353                         }
3354                     }
3355                     HistorySet(parseList, backwardMostMove,
3356                                forwardMostMove, currentMove-1);
3357                     DisplayMove(currentMove - 1);
3358                     if (started == STARTED_MOVES) next_out = i;
3359                     started = STARTED_NONE;
3360                     ics_getting_history = H_FALSE;
3361                     break;
3362
3363                   case STARTED_OBSERVE:
3364                     started = STARTED_NONE;
3365                     SendToICS(ics_prefix);
3366                     SendToICS("refresh\n");
3367                     break;
3368
3369                   default:
3370                     break;
3371                 }
3372                 if(bookHit) { // [HGM] book: simulate book reply
3373                     static char bookMove[MSG_SIZ]; // a bit generous?
3374
3375                     programStats.nodes = programStats.depth = programStats.time =
3376                     programStats.score = programStats.got_only_move = 0;
3377                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3378
3379                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3380                     strcat(bookMove, bookHit);
3381                     HandleMachineMove(bookMove, &first);
3382                 }
3383                 continue;
3384             }
3385
3386             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3387                  started == STARTED_HOLDINGS ||
3388                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3389                 /* Accumulate characters in move list or board */
3390                 parse[parse_pos++] = buf[i];
3391             }
3392
3393             /* Start of game messages.  Mostly we detect start of game
3394                when the first board image arrives.  On some versions
3395                of the ICS, though, we need to do a "refresh" after starting
3396                to observe in order to get the current board right away. */
3397             if (looking_at(buf, &i, "Adding game * to observation list")) {
3398                 started = STARTED_OBSERVE;
3399                 continue;
3400             }
3401
3402             /* Handle auto-observe */
3403             if (appData.autoObserve &&
3404                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3405                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3406                 char *player;
3407                 /* Choose the player that was highlighted, if any. */
3408                 if (star_match[0][0] == '\033' ||
3409                     star_match[1][0] != '\033') {
3410                     player = star_match[0];
3411                 } else {
3412                     player = star_match[2];
3413                 }
3414                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3415                         ics_prefix, StripHighlightAndTitle(player));
3416                 SendToICS(str);
3417
3418                 /* Save ratings from notify string */
3419                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3420                 player1Rating = string_to_rating(star_match[1]);
3421                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3422                 player2Rating = string_to_rating(star_match[3]);
3423
3424                 if (appData.debugMode)
3425                   fprintf(debugFP,
3426                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3427                           player1Name, player1Rating,
3428                           player2Name, player2Rating);
3429
3430                 continue;
3431             }
3432
3433             /* Deal with automatic examine mode after a game,
3434                and with IcsObserving -> IcsExamining transition */
3435             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3436                 looking_at(buf, &i, "has made you an examiner of game *")) {
3437
3438                 int gamenum = atoi(star_match[0]);
3439                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3440                     gamenum == ics_gamenum) {
3441                     /* We were already playing or observing this game;
3442                        no need to refetch history */
3443                     gameMode = IcsExamining;
3444                     if (pausing) {
3445                         pauseExamForwardMostMove = forwardMostMove;
3446                     } else if (currentMove < forwardMostMove) {
3447                         ForwardInner(forwardMostMove);
3448                     }
3449                 } else {
3450                     /* I don't think this case really can happen */
3451                     SendToICS(ics_prefix);
3452                     SendToICS("refresh\n");
3453                 }
3454                 continue;
3455             }
3456
3457             /* Error messages */
3458 //          if (ics_user_moved) {
3459             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3460                 if (looking_at(buf, &i, "Illegal move") ||
3461                     looking_at(buf, &i, "Not a legal move") ||
3462                     looking_at(buf, &i, "Your king is in check") ||
3463                     looking_at(buf, &i, "It isn't your turn") ||
3464                     looking_at(buf, &i, "It is not your move")) {
3465                     /* Illegal move */
3466                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3467                         currentMove = forwardMostMove-1;
3468                         DisplayMove(currentMove - 1); /* before DMError */
3469                         DrawPosition(FALSE, boards[currentMove]);
3470                         SwitchClocks(forwardMostMove-1); // [HGM] race
3471                         DisplayBothClocks();
3472                     }
3473                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3474                     ics_user_moved = 0;
3475                     continue;
3476                 }
3477             }
3478
3479             if (looking_at(buf, &i, "still have time") ||
3480                 looking_at(buf, &i, "not out of time") ||
3481                 looking_at(buf, &i, "either player is out of time") ||
3482                 looking_at(buf, &i, "has timeseal; checking")) {
3483                 /* We must have called his flag a little too soon */
3484                 whiteFlag = blackFlag = FALSE;
3485                 continue;
3486             }
3487
3488             if (looking_at(buf, &i, "added * seconds to") ||
3489                 looking_at(buf, &i, "seconds were added to")) {
3490                 /* Update the clocks */
3491                 SendToICS(ics_prefix);
3492                 SendToICS("refresh\n");
3493                 continue;
3494             }
3495
3496             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3497                 ics_clock_paused = TRUE;
3498                 StopClocks();
3499                 continue;
3500             }
3501
3502             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3503                 ics_clock_paused = FALSE;
3504                 StartClocks();
3505                 continue;
3506             }
3507
3508             /* Grab player ratings from the Creating: message.
3509                Note we have to check for the special case when
3510                the ICS inserts things like [white] or [black]. */
3511             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3512                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3513                 /* star_matches:
3514                    0    player 1 name (not necessarily white)
3515                    1    player 1 rating
3516                    2    empty, white, or black (IGNORED)
3517                    3    player 2 name (not necessarily black)
3518                    4    player 2 rating
3519
3520                    The names/ratings are sorted out when the game
3521                    actually starts (below).
3522                 */
3523                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3524                 player1Rating = string_to_rating(star_match[1]);
3525                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3526                 player2Rating = string_to_rating(star_match[4]);
3527
3528                 if (appData.debugMode)
3529                   fprintf(debugFP,
3530                           "Ratings from 'Creating:' %s %d, %s %d\n",
3531                           player1Name, player1Rating,
3532                           player2Name, player2Rating);
3533
3534                 continue;
3535             }
3536
3537             /* Improved generic start/end-of-game messages */
3538             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3539                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3540                 /* If tkind == 0: */
3541                 /* star_match[0] is the game number */
3542                 /*           [1] is the white player's name */
3543                 /*           [2] is the black player's name */
3544                 /* For end-of-game: */
3545                 /*           [3] is the reason for the game end */
3546                 /*           [4] is a PGN end game-token, preceded by " " */
3547                 /* For start-of-game: */
3548                 /*           [3] begins with "Creating" or "Continuing" */
3549                 /*           [4] is " *" or empty (don't care). */
3550                 int gamenum = atoi(star_match[0]);
3551                 char *whitename, *blackname, *why, *endtoken;
3552                 ChessMove endtype = EndOfFile;
3553
3554                 if (tkind == 0) {
3555                   whitename = star_match[1];
3556                   blackname = star_match[2];
3557                   why = star_match[3];
3558                   endtoken = star_match[4];
3559                 } else {
3560                   whitename = star_match[1];
3561                   blackname = star_match[3];
3562                   why = star_match[5];
3563                   endtoken = star_match[6];
3564                 }
3565
3566                 /* Game start messages */
3567                 if (strncmp(why, "Creating ", 9) == 0 ||
3568                     strncmp(why, "Continuing ", 11) == 0) {
3569                     gs_gamenum = gamenum;
3570                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3571                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3572 #if ZIPPY
3573                     if (appData.zippyPlay) {
3574                         ZippyGameStart(whitename, blackname);
3575                     }
3576 #endif /*ZIPPY*/
3577                     partnerBoardValid = FALSE; // [HGM] bughouse
3578                     continue;
3579                 }
3580
3581                 /* Game end messages */
3582                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3583                     ics_gamenum != gamenum) {
3584                     continue;
3585                 }
3586                 while (endtoken[0] == ' ') endtoken++;
3587                 switch (endtoken[0]) {
3588                   case '*':
3589                   default:
3590                     endtype = GameUnfinished;
3591                     break;
3592                   case '0':
3593                     endtype = BlackWins;
3594                     break;
3595                   case '1':
3596                     if (endtoken[1] == '/')
3597                       endtype = GameIsDrawn;
3598                     else
3599                       endtype = WhiteWins;
3600                     break;
3601                 }
3602                 GameEnds(endtype, why, GE_ICS);
3603 #if ZIPPY
3604                 if (appData.zippyPlay && first.initDone) {
3605                     ZippyGameEnd(endtype, why);
3606                     if (first.pr == NULL) {
3607                       /* Start the next process early so that we'll
3608                          be ready for the next challenge */
3609                       StartChessProgram(&first);
3610                     }
3611                     /* Send "new" early, in case this command takes
3612                        a long time to finish, so that we'll be ready
3613                        for the next challenge. */
3614                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3615                     Reset(TRUE, TRUE);
3616                 }
3617 #endif /*ZIPPY*/
3618                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3619                 continue;
3620             }
3621
3622             if (looking_at(buf, &i, "Removing game * from observation") ||
3623                 looking_at(buf, &i, "no longer observing game *") ||
3624                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3625                 if (gameMode == IcsObserving &&
3626                     atoi(star_match[0]) == ics_gamenum)
3627                   {
3628                       /* icsEngineAnalyze */
3629                       if (appData.icsEngineAnalyze) {
3630                             ExitAnalyzeMode();
3631                             ModeHighlight();
3632                       }
3633                       StopClocks();
3634                       gameMode = IcsIdle;
3635                       ics_gamenum = -1;
3636                       ics_user_moved = FALSE;
3637                   }
3638                 continue;
3639             }
3640
3641             if (looking_at(buf, &i, "no longer examining game *")) {
3642                 if (gameMode == IcsExamining &&
3643                     atoi(star_match[0]) == ics_gamenum)
3644                   {
3645                       gameMode = IcsIdle;
3646                       ics_gamenum = -1;
3647                       ics_user_moved = FALSE;
3648                   }
3649                 continue;
3650             }
3651
3652             /* Advance leftover_start past any newlines we find,
3653                so only partial lines can get reparsed */
3654             if (looking_at(buf, &i, "\n")) {
3655                 prevColor = curColor;
3656                 if (curColor != ColorNormal) {
3657                     if (oldi > next_out) {
3658                         SendToPlayer(&buf[next_out], oldi - next_out);
3659                         next_out = oldi;
3660                     }
3661                     Colorize(ColorNormal, FALSE);
3662                     curColor = ColorNormal;
3663                 }
3664                 if (started == STARTED_BOARD) {
3665                     started = STARTED_NONE;
3666                     parse[parse_pos] = NULLCHAR;
3667                     ParseBoard12(parse);
3668                     ics_user_moved = 0;
3669
3670                     /* Send premove here */
3671                     if (appData.premove) {
3672                       char str[MSG_SIZ];
3673                       if (currentMove == 0 &&
3674                           gameMode == IcsPlayingWhite &&
3675                           appData.premoveWhite) {
3676                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3677                         if (appData.debugMode)
3678                           fprintf(debugFP, "Sending premove:\n");
3679                         SendToICS(str);
3680                       } else if (currentMove == 1 &&
3681                                  gameMode == IcsPlayingBlack &&
3682                                  appData.premoveBlack) {
3683                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3684                         if (appData.debugMode)
3685                           fprintf(debugFP, "Sending premove:\n");
3686                         SendToICS(str);
3687                       } else if (gotPremove) {
3688                         gotPremove = 0;
3689                         ClearPremoveHighlights();
3690                         if (appData.debugMode)
3691                           fprintf(debugFP, "Sending premove:\n");
3692                           UserMoveEvent(premoveFromX, premoveFromY,
3693                                         premoveToX, premoveToY,
3694                                         premovePromoChar);
3695                       }
3696                     }
3697
3698                     /* Usually suppress following prompt */
3699                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3700                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3701                         if (looking_at(buf, &i, "*% ")) {
3702                             savingComment = FALSE;
3703                             suppressKibitz = 0;
3704                         }
3705                     }
3706                     next_out = i;
3707                 } else if (started == STARTED_HOLDINGS) {
3708                     int gamenum;
3709                     char new_piece[MSG_SIZ];
3710                     started = STARTED_NONE;
3711                     parse[parse_pos] = NULLCHAR;
3712                     if (appData.debugMode)
3713                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3714                                                         parse, currentMove);
3715                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3716                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3717                         if (gameInfo.variant == VariantNormal) {
3718                           /* [HGM] We seem to switch variant during a game!
3719                            * Presumably no holdings were displayed, so we have
3720                            * to move the position two files to the right to
3721                            * create room for them!
3722                            */
3723                           VariantClass newVariant;
3724                           switch(gameInfo.boardWidth) { // base guess on board width
3725                                 case 9:  newVariant = VariantShogi; break;
3726                                 case 10: newVariant = VariantGreat; break;
3727                                 default: newVariant = VariantCrazyhouse; break;
3728                           }
3729                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3730                           /* Get a move list just to see the header, which
3731                              will tell us whether this is really bug or zh */
3732                           if (ics_getting_history == H_FALSE) {
3733                             ics_getting_history = H_REQUESTED;
3734                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3735                             SendToICS(str);
3736                           }
3737                         }
3738                         new_piece[0] = NULLCHAR;
3739                         sscanf(parse, "game %d white [%s black [%s <- %s",
3740                                &gamenum, white_holding, black_holding,
3741                                new_piece);
3742                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3743                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3744                         /* [HGM] copy holdings to board holdings area */
3745                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3746                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3747                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3748 #if ZIPPY
3749                         if (appData.zippyPlay && first.initDone) {
3750                             ZippyHoldings(white_holding, black_holding,
3751                                           new_piece);
3752                         }
3753 #endif /*ZIPPY*/
3754                         if (tinyLayout || smallLayout) {
3755                             char wh[16], bh[16];
3756                             PackHolding(wh, white_holding);
3757                             PackHolding(bh, black_holding);
3758                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3759                                     gameInfo.white, gameInfo.black);
3760                         } else {
3761                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3762                                     gameInfo.white, white_holding,
3763                                     gameInfo.black, black_holding);
3764                         }
3765                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3766                         DrawPosition(FALSE, boards[currentMove]);
3767                         DisplayTitle(str);
3768                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3769                         sscanf(parse, "game %d white [%s black [%s <- %s",
3770                                &gamenum, white_holding, black_holding,
3771                                new_piece);
3772                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3773                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3774                         /* [HGM] copy holdings to partner-board holdings area */
3775                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3776                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3777                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3778                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3779                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3780                       }
3781                     }
3782                     /* Suppress following prompt */
3783                     if (looking_at(buf, &i, "*% ")) {
3784                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3785                         savingComment = FALSE;
3786                         suppressKibitz = 0;
3787                     }
3788                     next_out = i;
3789                 }
3790                 continue;
3791             }
3792
3793             i++;                /* skip unparsed character and loop back */
3794         }
3795
3796         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3797 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3798 //          SendToPlayer(&buf[next_out], i - next_out);
3799             started != STARTED_HOLDINGS && leftover_start > next_out) {
3800             SendToPlayer(&buf[next_out], leftover_start - next_out);
3801             next_out = i;
3802         }
3803
3804         leftover_len = buf_len - leftover_start;
3805         /* if buffer ends with something we couldn't parse,
3806            reparse it after appending the next read */
3807
3808     } else if (count == 0) {
3809         RemoveInputSource(isr);
3810         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3811     } else {
3812         DisplayFatalError(_("Error reading from ICS"), error, 1);
3813     }
3814 }
3815
3816
3817 /* Board style 12 looks like this:
3818
3819    <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
3820
3821  * The "<12> " is stripped before it gets to this routine.  The two
3822  * trailing 0's (flip state and clock ticking) are later addition, and
3823  * some chess servers may not have them, or may have only the first.
3824  * Additional trailing fields may be added in the future.
3825  */
3826
3827 #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"
3828
3829 #define RELATION_OBSERVING_PLAYED    0
3830 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3831 #define RELATION_PLAYING_MYMOVE      1
3832 #define RELATION_PLAYING_NOTMYMOVE  -1
3833 #define RELATION_EXAMINING           2
3834 #define RELATION_ISOLATED_BOARD     -3
3835 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3836
3837 void
3838 ParseBoard12(string)
3839      char *string;
3840 {
3841     GameMode newGameMode;
3842     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3843     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3844     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3845     char to_play, board_chars[200];
3846     char move_str[500], str[500], elapsed_time[500];
3847     char black[32], white[32];
3848     Board board;
3849     int prevMove = currentMove;
3850     int ticking = 2;
3851     ChessMove moveType;
3852     int fromX, fromY, toX, toY;
3853     char promoChar;
3854     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3855     char *bookHit = NULL; // [HGM] book
3856     Boolean weird = FALSE, reqFlag = FALSE;
3857
3858     fromX = fromY = toX = toY = -1;
3859
3860     newGame = FALSE;
3861
3862     if (appData.debugMode)
3863       fprintf(debugFP, _("Parsing board: %s\n"), string);
3864
3865     move_str[0] = NULLCHAR;
3866     elapsed_time[0] = NULLCHAR;
3867     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3868         int  i = 0, j;
3869         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3870             if(string[i] == ' ') { ranks++; files = 0; }
3871             else files++;
3872             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3873             i++;
3874         }
3875         for(j = 0; j <i; j++) board_chars[j] = string[j];
3876         board_chars[i] = '\0';
3877         string += i + 1;
3878     }
3879     n = sscanf(string, PATTERN, &to_play, &double_push,
3880                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3881                &gamenum, white, black, &relation, &basetime, &increment,
3882                &white_stren, &black_stren, &white_time, &black_time,
3883                &moveNum, str, elapsed_time, move_str, &ics_flip,
3884                &ticking);
3885
3886     if (n < 21) {
3887         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3888         DisplayError(str, 0);
3889         return;
3890     }
3891
3892     /* Convert the move number to internal form */
3893     moveNum = (moveNum - 1) * 2;
3894     if (to_play == 'B') moveNum++;
3895     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3896       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3897                         0, 1);
3898       return;
3899     }
3900
3901     switch (relation) {
3902       case RELATION_OBSERVING_PLAYED:
3903       case RELATION_OBSERVING_STATIC:
3904         if (gamenum == -1) {
3905             /* Old ICC buglet */
3906             relation = RELATION_OBSERVING_STATIC;
3907         }
3908         newGameMode = IcsObserving;
3909         break;
3910       case RELATION_PLAYING_MYMOVE:
3911       case RELATION_PLAYING_NOTMYMOVE:
3912         newGameMode =
3913           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3914             IcsPlayingWhite : IcsPlayingBlack;
3915         break;
3916       case RELATION_EXAMINING:
3917         newGameMode = IcsExamining;
3918         break;
3919       case RELATION_ISOLATED_BOARD:
3920       default:
3921         /* Just display this board.  If user was doing something else,
3922            we will forget about it until the next board comes. */
3923         newGameMode = IcsIdle;
3924         break;
3925       case RELATION_STARTING_POSITION:
3926         newGameMode = gameMode;
3927         break;
3928     }
3929
3930     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3931          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3932       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3933       char *toSqr;
3934       for (k = 0; k < ranks; k++) {
3935         for (j = 0; j < files; j++)
3936           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3937         if(gameInfo.holdingsWidth > 1) {
3938              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3939              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3940         }
3941       }
3942       CopyBoard(partnerBoard, board);
3943       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3944         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3945         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3946       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3947       if(toSqr = strchr(str, '-')) {
3948         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3949         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3950       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3951       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3952       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3953       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3954       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3955       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3956                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3957       DisplayMessage(partnerStatus, "");
3958         partnerBoardValid = TRUE;
3959       return;
3960     }
3961
3962     /* Modify behavior for initial board display on move listing
3963        of wild games.
3964        */
3965     switch (ics_getting_history) {
3966       case H_FALSE:
3967       case H_REQUESTED:
3968         break;
3969       case H_GOT_REQ_HEADER:
3970       case H_GOT_UNREQ_HEADER:
3971         /* This is the initial position of the current game */
3972         gamenum = ics_gamenum;
3973         moveNum = 0;            /* old ICS bug workaround */
3974         if (to_play == 'B') {
3975           startedFromSetupPosition = TRUE;
3976           blackPlaysFirst = TRUE;
3977           moveNum = 1;
3978           if (forwardMostMove == 0) forwardMostMove = 1;
3979           if (backwardMostMove == 0) backwardMostMove = 1;
3980           if (currentMove == 0) currentMove = 1;
3981         }
3982         newGameMode = gameMode;
3983         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3984         break;
3985       case H_GOT_UNWANTED_HEADER:
3986         /* This is an initial board that we don't want */
3987         return;
3988       case H_GETTING_MOVES:
3989         /* Should not happen */
3990         DisplayError(_("Error gathering move list: extra board"), 0);
3991         ics_getting_history = H_FALSE;
3992         return;
3993     }
3994
3995    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3996                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3997      /* [HGM] We seem to have switched variant unexpectedly
3998       * Try to guess new variant from board size
3999       */
4000           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4001           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4002           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4003           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4004           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4005           if(!weird) newVariant = VariantNormal;
4006           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4007           /* Get a move list just to see the header, which
4008              will tell us whether this is really bug or zh */
4009           if (ics_getting_history == H_FALSE) {
4010             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4011             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4012             SendToICS(str);
4013           }
4014     }
4015
4016     /* Take action if this is the first board of a new game, or of a
4017        different game than is currently being displayed.  */
4018     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4019         relation == RELATION_ISOLATED_BOARD) {
4020
4021         /* Forget the old game and get the history (if any) of the new one */
4022         if (gameMode != BeginningOfGame) {
4023           Reset(TRUE, TRUE);
4024         }
4025         newGame = TRUE;
4026         if (appData.autoRaiseBoard) BoardToTop();
4027         prevMove = -3;
4028         if (gamenum == -1) {
4029             newGameMode = IcsIdle;
4030         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4031                    appData.getMoveList && !reqFlag) {
4032             /* Need to get game history */
4033             ics_getting_history = H_REQUESTED;
4034             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4035             SendToICS(str);
4036         }
4037
4038         /* Initially flip the board to have black on the bottom if playing
4039            black or if the ICS flip flag is set, but let the user change
4040            it with the Flip View button. */
4041         flipView = appData.autoFlipView ?
4042           (newGameMode == IcsPlayingBlack) || ics_flip :
4043           appData.flipView;
4044
4045         /* Done with values from previous mode; copy in new ones */
4046         gameMode = newGameMode;
4047         ModeHighlight();
4048         ics_gamenum = gamenum;
4049         if (gamenum == gs_gamenum) {
4050             int klen = strlen(gs_kind);
4051             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4052             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4053             gameInfo.event = StrSave(str);
4054         } else {
4055             gameInfo.event = StrSave("ICS game");
4056         }
4057         gameInfo.site = StrSave(appData.icsHost);
4058         gameInfo.date = PGNDate();
4059         gameInfo.round = StrSave("-");
4060         gameInfo.white = StrSave(white);
4061         gameInfo.black = StrSave(black);
4062         timeControl = basetime * 60 * 1000;
4063         timeControl_2 = 0;
4064         timeIncrement = increment * 1000;
4065         movesPerSession = 0;
4066         gameInfo.timeControl = TimeControlTagValue();
4067         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4068   if (appData.debugMode) {
4069     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4070     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4071     setbuf(debugFP, NULL);
4072   }
4073
4074         gameInfo.outOfBook = NULL;
4075
4076         /* Do we have the ratings? */
4077         if (strcmp(player1Name, white) == 0 &&
4078             strcmp(player2Name, black) == 0) {
4079             if (appData.debugMode)
4080               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4081                       player1Rating, player2Rating);
4082             gameInfo.whiteRating = player1Rating;
4083             gameInfo.blackRating = player2Rating;
4084         } else if (strcmp(player2Name, white) == 0 &&
4085                    strcmp(player1Name, black) == 0) {
4086             if (appData.debugMode)
4087               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4088                       player2Rating, player1Rating);
4089             gameInfo.whiteRating = player2Rating;
4090             gameInfo.blackRating = player1Rating;
4091         }
4092         player1Name[0] = player2Name[0] = NULLCHAR;
4093
4094         /* Silence shouts if requested */
4095         if (appData.quietPlay &&
4096             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4097             SendToICS(ics_prefix);
4098             SendToICS("set shout 0\n");
4099         }
4100     }
4101
4102     /* Deal with midgame name changes */
4103     if (!newGame) {
4104         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4105             if (gameInfo.white) free(gameInfo.white);
4106             gameInfo.white = StrSave(white);
4107         }
4108         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4109             if (gameInfo.black) free(gameInfo.black);
4110             gameInfo.black = StrSave(black);
4111         }
4112     }
4113
4114     /* Throw away game result if anything actually changes in examine mode */
4115     if (gameMode == IcsExamining && !newGame) {
4116         gameInfo.result = GameUnfinished;
4117         if (gameInfo.resultDetails != NULL) {
4118             free(gameInfo.resultDetails);
4119             gameInfo.resultDetails = NULL;
4120         }
4121     }
4122
4123     /* In pausing && IcsExamining mode, we ignore boards coming
4124        in if they are in a different variation than we are. */
4125     if (pauseExamInvalid) return;
4126     if (pausing && gameMode == IcsExamining) {
4127         if (moveNum <= pauseExamForwardMostMove) {
4128             pauseExamInvalid = TRUE;
4129             forwardMostMove = pauseExamForwardMostMove;
4130             return;
4131         }
4132     }
4133
4134   if (appData.debugMode) {
4135     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4136   }
4137     /* Parse the board */
4138     for (k = 0; k < ranks; k++) {
4139       for (j = 0; j < files; j++)
4140         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4141       if(gameInfo.holdingsWidth > 1) {
4142            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4143            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4144       }
4145     }
4146     CopyBoard(boards[moveNum], board);
4147     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4148     if (moveNum == 0) {
4149         startedFromSetupPosition =
4150           !CompareBoards(board, initialPosition);
4151         if(startedFromSetupPosition)
4152             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4153     }
4154
4155     /* [HGM] Set castling rights. Take the outermost Rooks,
4156        to make it also work for FRC opening positions. Note that board12
4157        is really defective for later FRC positions, as it has no way to
4158        indicate which Rook can castle if they are on the same side of King.
4159        For the initial position we grant rights to the outermost Rooks,
4160        and remember thos rights, and we then copy them on positions
4161        later in an FRC game. This means WB might not recognize castlings with
4162        Rooks that have moved back to their original position as illegal,
4163        but in ICS mode that is not its job anyway.
4164     */
4165     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4166     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4167
4168         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4169             if(board[0][i] == WhiteRook) j = i;
4170         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4171         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4172             if(board[0][i] == WhiteRook) j = i;
4173         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4174         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4175             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4176         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4177         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4178             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4179         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4180
4181         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4182         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4183             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4184         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4185             if(board[BOARD_HEIGHT-1][k] == bKing)
4186                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4187         if(gameInfo.variant == VariantTwoKings) {
4188             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4189             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4190             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4191         }
4192     } else { int r;
4193         r = boards[moveNum][CASTLING][0] = initialRights[0];
4194         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4195         r = boards[moveNum][CASTLING][1] = initialRights[1];
4196         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4197         r = boards[moveNum][CASTLING][3] = initialRights[3];
4198         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4199         r = boards[moveNum][CASTLING][4] = initialRights[4];
4200         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4201         /* wildcastle kludge: always assume King has rights */
4202         r = boards[moveNum][CASTLING][2] = initialRights[2];
4203         r = boards[moveNum][CASTLING][5] = initialRights[5];
4204     }
4205     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4206     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4207
4208
4209     if (ics_getting_history == H_GOT_REQ_HEADER ||
4210         ics_getting_history == H_GOT_UNREQ_HEADER) {
4211         /* This was an initial position from a move list, not
4212            the current position */
4213         return;
4214     }
4215
4216     /* Update currentMove and known move number limits */
4217     newMove = newGame || moveNum > forwardMostMove;
4218
4219     if (newGame) {
4220         forwardMostMove = backwardMostMove = currentMove = moveNum;
4221         if (gameMode == IcsExamining && moveNum == 0) {
4222           /* Workaround for ICS limitation: we are not told the wild
4223              type when starting to examine a game.  But if we ask for
4224              the move list, the move list header will tell us */
4225             ics_getting_history = H_REQUESTED;
4226             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4227             SendToICS(str);
4228         }
4229     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4230                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4231 #if ZIPPY
4232         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4233         /* [HGM] applied this also to an engine that is silently watching        */
4234         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4235             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4236             gameInfo.variant == currentlyInitializedVariant) {
4237           takeback = forwardMostMove - moveNum;
4238           for (i = 0; i < takeback; i++) {
4239             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4240             SendToProgram("undo\n", &first);
4241           }
4242         }
4243 #endif
4244
4245         forwardMostMove = moveNum;
4246         if (!pausing || currentMove > forwardMostMove)
4247           currentMove = forwardMostMove;
4248     } else {
4249         /* New part of history that is not contiguous with old part */
4250         if (pausing && gameMode == IcsExamining) {
4251             pauseExamInvalid = TRUE;
4252             forwardMostMove = pauseExamForwardMostMove;
4253             return;
4254         }
4255         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4256 #if ZIPPY
4257             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4258                 // [HGM] when we will receive the move list we now request, it will be
4259                 // fed to the engine from the first move on. So if the engine is not
4260                 // in the initial position now, bring it there.
4261                 InitChessProgram(&first, 0);
4262             }
4263 #endif
4264             ics_getting_history = H_REQUESTED;
4265             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4266             SendToICS(str);
4267         }
4268         forwardMostMove = backwardMostMove = currentMove = moveNum;
4269     }
4270
4271     /* Update the clocks */
4272     if (strchr(elapsed_time, '.')) {
4273       /* Time is in ms */
4274       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4275       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4276     } else {
4277       /* Time is in seconds */
4278       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4279       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4280     }
4281
4282
4283 #if ZIPPY
4284     if (appData.zippyPlay && newGame &&
4285         gameMode != IcsObserving && gameMode != IcsIdle &&
4286         gameMode != IcsExamining)
4287       ZippyFirstBoard(moveNum, basetime, increment);
4288 #endif
4289
4290     /* Put the move on the move list, first converting
4291        to canonical algebraic form. */
4292     if (moveNum > 0) {
4293   if (appData.debugMode) {
4294     if (appData.debugMode) { int f = forwardMostMove;
4295         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4296                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4297                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4298     }
4299     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4300     fprintf(debugFP, "moveNum = %d\n", moveNum);
4301     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4302     setbuf(debugFP, NULL);
4303   }
4304         if (moveNum <= backwardMostMove) {
4305             /* We don't know what the board looked like before
4306                this move.  Punt. */
4307           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4308             strcat(parseList[moveNum - 1], " ");
4309             strcat(parseList[moveNum - 1], elapsed_time);
4310             moveList[moveNum - 1][0] = NULLCHAR;
4311         } else if (strcmp(move_str, "none") == 0) {
4312             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4313             /* Again, we don't know what the board looked like;
4314                this is really the start of the game. */
4315             parseList[moveNum - 1][0] = NULLCHAR;
4316             moveList[moveNum - 1][0] = NULLCHAR;
4317             backwardMostMove = moveNum;
4318             startedFromSetupPosition = TRUE;
4319             fromX = fromY = toX = toY = -1;
4320         } else {
4321           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4322           //                 So we parse the long-algebraic move string in stead of the SAN move
4323           int valid; char buf[MSG_SIZ], *prom;
4324
4325           // str looks something like "Q/a1-a2"; kill the slash
4326           if(str[1] == '/')
4327             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4328           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4329           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4330                 strcat(buf, prom); // long move lacks promo specification!
4331           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4332                 if(appData.debugMode)
4333                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4334                 safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
4335           }
4336           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4337                                 &fromX, &fromY, &toX, &toY, &promoChar)
4338                || ParseOneMove(buf, moveNum - 1, &moveType,
4339                                 &fromX, &fromY, &toX, &toY, &promoChar);
4340           // end of long SAN patch
4341           if (valid) {
4342             (void) CoordsToAlgebraic(boards[moveNum - 1],
4343                                      PosFlags(moveNum - 1),
4344                                      fromY, fromX, toY, toX, promoChar,
4345                                      parseList[moveNum-1]);
4346             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4347               case MT_NONE:
4348               case MT_STALEMATE:
4349               default:
4350                 break;
4351               case MT_CHECK:
4352                 if(gameInfo.variant != VariantShogi)
4353                     strcat(parseList[moveNum - 1], "+");
4354                 break;
4355               case MT_CHECKMATE:
4356               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4357                 strcat(parseList[moveNum - 1], "#");
4358                 break;
4359             }
4360             strcat(parseList[moveNum - 1], " ");
4361             strcat(parseList[moveNum - 1], elapsed_time);
4362             /* currentMoveString is set as a side-effect of ParseOneMove */
4363             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4364             strcat(moveList[moveNum - 1], "\n");
4365           } else {
4366             /* Move from ICS was illegal!?  Punt. */
4367             if (appData.debugMode) {
4368               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4369               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4370             }
4371             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4372             strcat(parseList[moveNum - 1], " ");
4373             strcat(parseList[moveNum - 1], elapsed_time);
4374             moveList[moveNum - 1][0] = NULLCHAR;
4375             fromX = fromY = toX = toY = -1;
4376           }
4377         }
4378   if (appData.debugMode) {
4379     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4380     setbuf(debugFP, NULL);
4381   }
4382
4383 #if ZIPPY
4384         /* Send move to chess program (BEFORE animating it). */
4385         if (appData.zippyPlay && !newGame && newMove &&
4386            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4387
4388             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4389                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4390                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4391                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4392                             move_str);
4393                     DisplayError(str, 0);
4394                 } else {
4395                     if (first.sendTime) {
4396                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4397                     }
4398                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4399                     if (firstMove && !bookHit) {
4400                         firstMove = FALSE;
4401                         if (first.useColors) {
4402                           SendToProgram(gameMode == IcsPlayingWhite ?
4403                                         "white\ngo\n" :
4404                                         "black\ngo\n", &first);
4405                         } else {
4406                           SendToProgram("go\n", &first);
4407                         }
4408                         first.maybeThinking = TRUE;
4409                     }
4410                 }
4411             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4412               if (moveList[moveNum - 1][0] == NULLCHAR) {
4413                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4414                 DisplayError(str, 0);
4415               } else {
4416                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4417                 SendMoveToProgram(moveNum - 1, &first);
4418               }
4419             }
4420         }
4421 #endif
4422     }
4423
4424     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4425         /* If move comes from a remote source, animate it.  If it
4426            isn't remote, it will have already been animated. */
4427         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4428             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4429         }
4430         if (!pausing && appData.highlightLastMove) {
4431             SetHighlights(fromX, fromY, toX, toY);
4432         }
4433     }
4434
4435     /* Start the clocks */
4436     whiteFlag = blackFlag = FALSE;
4437     appData.clockMode = !(basetime == 0 && increment == 0);
4438     if (ticking == 0) {
4439       ics_clock_paused = TRUE;
4440       StopClocks();
4441     } else if (ticking == 1) {
4442       ics_clock_paused = FALSE;
4443     }
4444     if (gameMode == IcsIdle ||
4445         relation == RELATION_OBSERVING_STATIC ||
4446         relation == RELATION_EXAMINING ||
4447         ics_clock_paused)
4448       DisplayBothClocks();
4449     else
4450       StartClocks();
4451
4452     /* Display opponents and material strengths */
4453     if (gameInfo.variant != VariantBughouse &&
4454         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4455         if (tinyLayout || smallLayout) {
4456             if(gameInfo.variant == VariantNormal)
4457               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4458                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4459                     basetime, increment);
4460             else
4461               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4462                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4463                     basetime, increment, (int) gameInfo.variant);
4464         } else {
4465             if(gameInfo.variant == VariantNormal)
4466               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4467                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4468                     basetime, increment);
4469             else
4470               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4471                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4472                     basetime, increment, VariantName(gameInfo.variant));
4473         }
4474         DisplayTitle(str);
4475   if (appData.debugMode) {
4476     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4477   }
4478     }
4479
4480
4481     /* Display the board */
4482     if (!pausing && !appData.noGUI) {
4483
4484       if (appData.premove)
4485           if (!gotPremove ||
4486              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4487              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4488               ClearPremoveHighlights();
4489
4490       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4491         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4492       DrawPosition(j, boards[currentMove]);
4493
4494       DisplayMove(moveNum - 1);
4495       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4496             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4497               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4498         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4499       }
4500     }
4501
4502     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4503 #if ZIPPY
4504     if(bookHit) { // [HGM] book: simulate book reply
4505         static char bookMove[MSG_SIZ]; // a bit generous?
4506
4507         programStats.nodes = programStats.depth = programStats.time =
4508         programStats.score = programStats.got_only_move = 0;
4509         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4510
4511         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4512         strcat(bookMove, bookHit);
4513         HandleMachineMove(bookMove, &first);
4514     }
4515 #endif
4516 }
4517
4518 void
4519 GetMoveListEvent()
4520 {
4521     char buf[MSG_SIZ];
4522     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4523         ics_getting_history = H_REQUESTED;
4524         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4525         SendToICS(buf);
4526     }
4527 }
4528
4529 void
4530 AnalysisPeriodicEvent(force)
4531      int force;
4532 {
4533     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4534          && !force) || !appData.periodicUpdates)
4535       return;
4536
4537     /* Send . command to Crafty to collect stats */
4538     SendToProgram(".\n", &first);
4539
4540     /* Don't send another until we get a response (this makes
4541        us stop sending to old Crafty's which don't understand
4542        the "." command (sending illegal cmds resets node count & time,
4543        which looks bad)) */
4544     programStats.ok_to_send = 0;
4545 }
4546
4547 void ics_update_width(new_width)
4548         int new_width;
4549 {
4550         ics_printf("set width %d\n", new_width);
4551 }
4552
4553 void
4554 SendMoveToProgram(moveNum, cps)
4555      int moveNum;
4556      ChessProgramState *cps;
4557 {
4558     char buf[MSG_SIZ];
4559
4560     if (cps->useUsermove) {
4561       SendToProgram("usermove ", cps);
4562     }
4563     if (cps->useSAN) {
4564       char *space;
4565       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4566         int len = space - parseList[moveNum];
4567         memcpy(buf, parseList[moveNum], len);
4568         buf[len++] = '\n';
4569         buf[len] = NULLCHAR;
4570       } else {
4571         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4572       }
4573       SendToProgram(buf, cps);
4574     } else {
4575       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4576         AlphaRank(moveList[moveNum], 4);
4577         SendToProgram(moveList[moveNum], cps);
4578         AlphaRank(moveList[moveNum], 4); // and back
4579       } else
4580       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4581        * the engine. It would be nice to have a better way to identify castle
4582        * moves here. */
4583       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4584                                                                          && cps->useOOCastle) {
4585         int fromX = moveList[moveNum][0] - AAA;
4586         int fromY = moveList[moveNum][1] - ONE;
4587         int toX = moveList[moveNum][2] - AAA;
4588         int toY = moveList[moveNum][3] - ONE;
4589         if((boards[moveNum][fromY][fromX] == WhiteKing
4590             && boards[moveNum][toY][toX] == WhiteRook)
4591            || (boards[moveNum][fromY][fromX] == BlackKing
4592                && boards[moveNum][toY][toX] == BlackRook)) {
4593           if(toX > fromX) SendToProgram("O-O\n", cps);
4594           else SendToProgram("O-O-O\n", cps);
4595         }
4596         else SendToProgram(moveList[moveNum], cps);
4597       }
4598       else SendToProgram(moveList[moveNum], cps);
4599       /* End of additions by Tord */
4600     }
4601
4602     /* [HGM] setting up the opening has brought engine in force mode! */
4603     /*       Send 'go' if we are in a mode where machine should play. */
4604     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4605         (gameMode == TwoMachinesPlay   ||
4606 #if ZIPPY
4607          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4608 #endif
4609          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4610         SendToProgram("go\n", cps);
4611   if (appData.debugMode) {
4612     fprintf(debugFP, "(extra)\n");
4613   }
4614     }
4615     setboardSpoiledMachineBlack = 0;
4616 }
4617
4618 void
4619 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4620      ChessMove moveType;
4621      int fromX, fromY, toX, toY;
4622      char promoChar;
4623 {
4624     char user_move[MSG_SIZ];
4625
4626     switch (moveType) {
4627       default:
4628         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4629                 (int)moveType, fromX, fromY, toX, toY);
4630         DisplayError(user_move + strlen("say "), 0);
4631         break;
4632       case WhiteKingSideCastle:
4633       case BlackKingSideCastle:
4634       case WhiteQueenSideCastleWild:
4635       case BlackQueenSideCastleWild:
4636       /* PUSH Fabien */
4637       case WhiteHSideCastleFR:
4638       case BlackHSideCastleFR:
4639       /* POP Fabien */
4640         snprintf(user_move, MSG_SIZ, "o-o\n");
4641         break;
4642       case WhiteQueenSideCastle:
4643       case BlackQueenSideCastle:
4644       case WhiteKingSideCastleWild:
4645       case BlackKingSideCastleWild:
4646       /* PUSH Fabien */
4647       case WhiteASideCastleFR:
4648       case BlackASideCastleFR:
4649       /* POP Fabien */
4650         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4651         break;
4652       case WhiteNonPromotion:
4653       case BlackNonPromotion:
4654         sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4655         break;
4656       case WhitePromotion:
4657       case BlackPromotion:
4658         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4659           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4660                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4661                 PieceToChar(WhiteFerz));
4662         else if(gameInfo.variant == VariantGreat)
4663           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4664                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4665                 PieceToChar(WhiteMan));
4666         else
4667           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4668                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4669                 promoChar);
4670         break;
4671       case WhiteDrop:
4672       case BlackDrop:
4673         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4674                 ToUpper(PieceToChar((ChessSquare) fromX)),
4675                 AAA + toX, ONE + toY);
4676         break;
4677       case NormalMove:
4678       case WhiteCapturesEnPassant:
4679       case BlackCapturesEnPassant:
4680       case IllegalMove:  /* could be a variant we don't quite understand */
4681         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4682                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4683         break;
4684     }
4685     SendToICS(user_move);
4686     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4687         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4688 }
4689
4690 void
4691 UploadGameEvent()
4692 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4693     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4694     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4695     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4696         DisplayError("You cannot do this while you are playing or observing", 0);
4697         return;
4698     }
4699     if(gameMode != IcsExamining) { // is this ever not the case?
4700         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4701
4702         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4703           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4704         } else { // on FICS we must first go to general examine mode
4705           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4706         }
4707         if(gameInfo.variant != VariantNormal) {
4708             // try figure out wild number, as xboard names are not always valid on ICS
4709             for(i=1; i<=36; i++) {
4710               snprintf(buf, MSG_SIZ, "wild/%d", i);
4711                 if(StringToVariant(buf) == gameInfo.variant) break;
4712             }
4713             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4714             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4715             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4716         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4717         SendToICS(ics_prefix);
4718         SendToICS(buf);
4719         if(startedFromSetupPosition || backwardMostMove != 0) {
4720           fen = PositionToFEN(backwardMostMove, NULL);
4721           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4722             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4723             SendToICS(buf);
4724           } else { // FICS: everything has to set by separate bsetup commands
4725             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4726             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4727             SendToICS(buf);
4728             if(!WhiteOnMove(backwardMostMove)) {
4729                 SendToICS("bsetup tomove black\n");
4730             }
4731             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4732             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4733             SendToICS(buf);
4734             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4735             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4736             SendToICS(buf);
4737             i = boards[backwardMostMove][EP_STATUS];
4738             if(i >= 0) { // set e.p.
4739               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4740                 SendToICS(buf);
4741             }
4742             bsetup++;
4743           }
4744         }
4745       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4746             SendToICS("bsetup done\n"); // switch to normal examining.
4747     }
4748     for(i = backwardMostMove; i<last; i++) {
4749         char buf[20];
4750         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4751         SendToICS(buf);
4752     }
4753     SendToICS(ics_prefix);
4754     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4755 }
4756
4757 void
4758 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4759      int rf, ff, rt, ft;
4760      char promoChar;
4761      char move[7];
4762 {
4763     if (rf == DROP_RANK) {
4764       sprintf(move, "%c@%c%c\n",
4765                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4766     } else {
4767         if (promoChar == 'x' || promoChar == NULLCHAR) {
4768           sprintf(move, "%c%c%c%c\n",
4769                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4770         } else {
4771             sprintf(move, "%c%c%c%c%c\n",
4772                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4773         }
4774     }
4775 }
4776
4777 void
4778 ProcessICSInitScript(f)
4779      FILE *f;
4780 {
4781     char buf[MSG_SIZ];
4782
4783     while (fgets(buf, MSG_SIZ, f)) {
4784         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4785     }
4786
4787     fclose(f);
4788 }
4789
4790
4791 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4792 void
4793 AlphaRank(char *move, int n)
4794 {
4795 //    char *p = move, c; int x, y;
4796
4797     if (appData.debugMode) {
4798         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4799     }
4800
4801     if(move[1]=='*' &&
4802        move[2]>='0' && move[2]<='9' &&
4803        move[3]>='a' && move[3]<='x'    ) {
4804         move[1] = '@';
4805         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4806         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4807     } else
4808     if(move[0]>='0' && move[0]<='9' &&
4809        move[1]>='a' && move[1]<='x' &&
4810        move[2]>='0' && move[2]<='9' &&
4811        move[3]>='a' && move[3]<='x'    ) {
4812         /* input move, Shogi -> normal */
4813         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4814         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4815         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4816         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4817     } else
4818     if(move[1]=='@' &&
4819        move[3]>='0' && move[3]<='9' &&
4820        move[2]>='a' && move[2]<='x'    ) {
4821         move[1] = '*';
4822         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4823         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4824     } else
4825     if(
4826        move[0]>='a' && move[0]<='x' &&
4827        move[3]>='0' && move[3]<='9' &&
4828        move[2]>='a' && move[2]<='x'    ) {
4829          /* output move, normal -> Shogi */
4830         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4831         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4832         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4833         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4834         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4835     }
4836     if (appData.debugMode) {
4837         fprintf(debugFP, "   out = '%s'\n", move);
4838     }
4839 }
4840
4841 char yy_textstr[8000];
4842
4843 /* Parser for moves from gnuchess, ICS, or user typein box */
4844 Boolean
4845 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4846      char *move;
4847      int moveNum;
4848      ChessMove *moveType;
4849      int *fromX, *fromY, *toX, *toY;
4850      char *promoChar;
4851 {
4852     if (appData.debugMode) {
4853         fprintf(debugFP, "move to parse: %s\n", move);
4854     }
4855     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4856
4857     switch (*moveType) {
4858       case WhitePromotion:
4859       case BlackPromotion:
4860       case WhiteNonPromotion:
4861       case BlackNonPromotion:
4862       case NormalMove:
4863       case WhiteCapturesEnPassant:
4864       case BlackCapturesEnPassant:
4865       case WhiteKingSideCastle:
4866       case WhiteQueenSideCastle:
4867       case BlackKingSideCastle:
4868       case BlackQueenSideCastle:
4869       case WhiteKingSideCastleWild:
4870       case WhiteQueenSideCastleWild:
4871       case BlackKingSideCastleWild:
4872       case BlackQueenSideCastleWild:
4873       /* Code added by Tord: */
4874       case WhiteHSideCastleFR:
4875       case WhiteASideCastleFR:
4876       case BlackHSideCastleFR:
4877       case BlackASideCastleFR:
4878       /* End of code added by Tord */
4879       case IllegalMove:         /* bug or odd chess variant */
4880         *fromX = currentMoveString[0] - AAA;
4881         *fromY = currentMoveString[1] - ONE;
4882         *toX = currentMoveString[2] - AAA;
4883         *toY = currentMoveString[3] - ONE;
4884         *promoChar = currentMoveString[4];
4885         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4886             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4887     if (appData.debugMode) {
4888         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4889     }
4890             *fromX = *fromY = *toX = *toY = 0;
4891             return FALSE;
4892         }
4893         if (appData.testLegality) {
4894           return (*moveType != IllegalMove);
4895         } else {
4896           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4897                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4898         }
4899
4900       case WhiteDrop:
4901       case BlackDrop:
4902         *fromX = *moveType == WhiteDrop ?
4903           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4904           (int) CharToPiece(ToLower(currentMoveString[0]));
4905         *fromY = DROP_RANK;
4906         *toX = currentMoveString[2] - AAA;
4907         *toY = currentMoveString[3] - ONE;
4908         *promoChar = NULLCHAR;
4909         return TRUE;
4910
4911       case AmbiguousMove:
4912       case ImpossibleMove:
4913       case EndOfFile:
4914       case ElapsedTime:
4915       case Comment:
4916       case PGNTag:
4917       case NAG:
4918       case WhiteWins:
4919       case BlackWins:
4920       case GameIsDrawn:
4921       default:
4922     if (appData.debugMode) {
4923         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4924     }
4925         /* bug? */
4926         *fromX = *fromY = *toX = *toY = 0;
4927         *promoChar = NULLCHAR;
4928         return FALSE;
4929     }
4930 }
4931
4932
4933 void
4934 ParsePV(char *pv, Boolean storeComments)
4935 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4936   int fromX, fromY, toX, toY; char promoChar;
4937   ChessMove moveType;
4938   Boolean valid;
4939   int nr = 0;
4940
4941   endPV = forwardMostMove;
4942   do {
4943     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4944     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4945     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4946 if(appData.debugMode){
4947 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);
4948 }
4949     if(!valid && nr == 0 &&
4950        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4951         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4952         // Hande case where played move is different from leading PV move
4953         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4954         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4955         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4956         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4957           endPV += 2; // if position different, keep this
4958           moveList[endPV-1][0] = fromX + AAA;
4959           moveList[endPV-1][1] = fromY + ONE;
4960           moveList[endPV-1][2] = toX + AAA;
4961           moveList[endPV-1][3] = toY + ONE;
4962           parseList[endPV-1][0] = NULLCHAR;
4963           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4964         }
4965       }
4966     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4967     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4968     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4969     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4970         valid++; // allow comments in PV
4971         continue;
4972     }
4973     nr++;
4974     if(endPV+1 > framePtr) break; // no space, truncate
4975     if(!valid) break;
4976     endPV++;
4977     CopyBoard(boards[endPV], boards[endPV-1]);
4978     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4979     moveList[endPV-1][0] = fromX + AAA;
4980     moveList[endPV-1][1] = fromY + ONE;
4981     moveList[endPV-1][2] = toX + AAA;
4982     moveList[endPV-1][3] = toY + ONE;
4983     if(storeComments)
4984         CoordsToAlgebraic(boards[endPV - 1],
4985                              PosFlags(endPV - 1),
4986                              fromY, fromX, toY, toX, promoChar,
4987                              parseList[endPV - 1]);
4988     else
4989         parseList[endPV-1][0] = NULLCHAR;
4990   } while(valid);
4991   currentMove = endPV;
4992   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4993   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4994                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4995   DrawPosition(TRUE, boards[currentMove]);
4996 }
4997
4998 static int lastX, lastY;
4999
5000 Boolean
5001 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5002 {
5003         int startPV;
5004         char *p;
5005
5006         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5007         lastX = x; lastY = y;
5008         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5009         startPV = index;
5010         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5011         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5012         index = startPV;
5013         do{ while(buf[index] && buf[index] != '\n') index++;
5014         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5015         buf[index] = 0;
5016         ParsePV(buf+startPV, FALSE);
5017         *start = startPV; *end = index-1;
5018         return TRUE;
5019 }
5020
5021 Boolean
5022 LoadPV(int x, int y)
5023 { // called on right mouse click to load PV
5024   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5025   lastX = x; lastY = y;
5026   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5027   return TRUE;
5028 }
5029
5030 void
5031 UnLoadPV()
5032 {
5033   if(endPV < 0) return;
5034   endPV = -1;
5035   currentMove = forwardMostMove;
5036   ClearPremoveHighlights();
5037   DrawPosition(TRUE, boards[currentMove]);
5038 }
5039
5040 void
5041 MovePV(int x, int y, int h)
5042 { // step through PV based on mouse coordinates (called on mouse move)
5043   int margin = h>>3, step = 0;
5044
5045   if(endPV < 0) return;
5046   // we must somehow check if right button is still down (might be released off board!)
5047   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5048   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5049   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5050   if(!step) return;
5051   lastX = x; lastY = y;
5052   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5053   currentMove += step;
5054   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5055   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5056                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5057   DrawPosition(FALSE, boards[currentMove]);
5058 }
5059
5060
5061 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5062 // All positions will have equal probability, but the current method will not provide a unique
5063 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5064 #define DARK 1
5065 #define LITE 2
5066 #define ANY 3
5067
5068 int squaresLeft[4];
5069 int piecesLeft[(int)BlackPawn];
5070 int seed, nrOfShuffles;
5071
5072 void GetPositionNumber()
5073 {       // sets global variable seed
5074         int i;
5075
5076         seed = appData.defaultFrcPosition;
5077         if(seed < 0) { // randomize based on time for negative FRC position numbers
5078                 for(i=0; i<50; i++) seed += random();
5079                 seed = random() ^ random() >> 8 ^ random() << 8;
5080                 if(seed<0) seed = -seed;
5081         }
5082 }
5083
5084 int put(Board board, int pieceType, int rank, int n, int shade)
5085 // put the piece on the (n-1)-th empty squares of the given shade
5086 {
5087         int i;
5088
5089         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5090                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5091                         board[rank][i] = (ChessSquare) pieceType;
5092                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5093                         squaresLeft[ANY]--;
5094                         piecesLeft[pieceType]--;
5095                         return i;
5096                 }
5097         }
5098         return -1;
5099 }
5100
5101
5102 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5103 // calculate where the next piece goes, (any empty square), and put it there
5104 {
5105         int i;
5106
5107         i = seed % squaresLeft[shade];
5108         nrOfShuffles *= squaresLeft[shade];
5109         seed /= squaresLeft[shade];
5110         put(board, pieceType, rank, i, shade);
5111 }
5112
5113 void AddTwoPieces(Board board, int pieceType, int rank)
5114 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5115 {
5116         int i, n=squaresLeft[ANY], j=n-1, k;
5117
5118         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5119         i = seed % k;  // pick one
5120         nrOfShuffles *= k;
5121         seed /= k;
5122         while(i >= j) i -= j--;
5123         j = n - 1 - j; i += j;
5124         put(board, pieceType, rank, j, ANY);
5125         put(board, pieceType, rank, i, ANY);
5126 }
5127
5128 void SetUpShuffle(Board board, int number)
5129 {
5130         int i, p, first=1;
5131
5132         GetPositionNumber(); nrOfShuffles = 1;
5133
5134         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5135         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5136         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5137
5138         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5139
5140         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5141             p = (int) board[0][i];
5142             if(p < (int) BlackPawn) piecesLeft[p] ++;
5143             board[0][i] = EmptySquare;
5144         }
5145
5146         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5147             // shuffles restricted to allow normal castling put KRR first
5148             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5149                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5150             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5151                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5152             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5153                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5154             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5155                 put(board, WhiteRook, 0, 0, ANY);
5156             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5157         }
5158
5159         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5160             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5161             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5162                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5163                 while(piecesLeft[p] >= 2) {
5164                     AddOnePiece(board, p, 0, LITE);
5165                     AddOnePiece(board, p, 0, DARK);
5166                 }
5167                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5168             }
5169
5170         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5171             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5172             // but we leave King and Rooks for last, to possibly obey FRC restriction
5173             if(p == (int)WhiteRook) continue;
5174             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5175             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5176         }
5177
5178         // now everything is placed, except perhaps King (Unicorn) and Rooks
5179
5180         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5181             // Last King gets castling rights
5182             while(piecesLeft[(int)WhiteUnicorn]) {
5183                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5184                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5185             }
5186
5187             while(piecesLeft[(int)WhiteKing]) {
5188                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5189                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5190             }
5191
5192
5193         } else {
5194             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5195             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5196         }
5197
5198         // Only Rooks can be left; simply place them all
5199         while(piecesLeft[(int)WhiteRook]) {
5200                 i = put(board, WhiteRook, 0, 0, ANY);
5201                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5202                         if(first) {
5203                                 first=0;
5204                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5205                         }
5206                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5207                 }
5208         }
5209         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5210             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5211         }
5212
5213         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5214 }
5215
5216 int SetCharTable( char *table, const char * map )
5217 /* [HGM] moved here from winboard.c because of its general usefulness */
5218 /*       Basically a safe strcpy that uses the last character as King */
5219 {
5220     int result = FALSE; int NrPieces;
5221
5222     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5223                     && NrPieces >= 12 && !(NrPieces&1)) {
5224         int i; /* [HGM] Accept even length from 12 to 34 */
5225
5226         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5227         for( i=0; i<NrPieces/2-1; i++ ) {
5228             table[i] = map[i];
5229             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5230         }
5231         table[(int) WhiteKing]  = map[NrPieces/2-1];
5232         table[(int) BlackKing]  = map[NrPieces-1];
5233
5234         result = TRUE;
5235     }
5236
5237     return result;
5238 }
5239
5240 void Prelude(Board board)
5241 {       // [HGM] superchess: random selection of exo-pieces
5242         int i, j, k; ChessSquare p;
5243         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5244
5245         GetPositionNumber(); // use FRC position number
5246
5247         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5248             SetCharTable(pieceToChar, appData.pieceToCharTable);
5249             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5250                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5251         }
5252
5253         j = seed%4;                 seed /= 4;
5254         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5255         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5256         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5257         j = seed%3 + (seed%3 >= j); seed /= 3;
5258         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5259         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5260         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5261         j = seed%3;                 seed /= 3;
5262         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5263         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5264         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5265         j = seed%2 + (seed%2 >= j); seed /= 2;
5266         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5267         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5268         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5269         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5270         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5271         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5272         put(board, exoPieces[0],    0, 0, ANY);
5273         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5274 }
5275
5276 void
5277 InitPosition(redraw)
5278      int redraw;
5279 {
5280     ChessSquare (* pieces)[BOARD_FILES];
5281     int i, j, pawnRow, overrule,
5282     oldx = gameInfo.boardWidth,
5283     oldy = gameInfo.boardHeight,
5284     oldh = gameInfo.holdingsWidth,
5285     oldv = gameInfo.variant;
5286
5287     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5288
5289     /* [AS] Initialize pv info list [HGM] and game status */
5290     {
5291         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5292             pvInfoList[i].depth = 0;
5293             boards[i][EP_STATUS] = EP_NONE;
5294             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5295         }
5296
5297         initialRulePlies = 0; /* 50-move counter start */
5298
5299         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5300         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5301     }
5302
5303
5304     /* [HGM] logic here is completely changed. In stead of full positions */
5305     /* the initialized data only consist of the two backranks. The switch */
5306     /* selects which one we will use, which is than copied to the Board   */
5307     /* initialPosition, which for the rest is initialized by Pawns and    */
5308     /* empty squares. This initial position is then copied to boards[0],  */
5309     /* possibly after shuffling, so that it remains available.            */
5310
5311     gameInfo.holdingsWidth = 0; /* default board sizes */
5312     gameInfo.boardWidth    = 8;
5313     gameInfo.boardHeight   = 8;
5314     gameInfo.holdingsSize  = 0;
5315     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5316     for(i=0; i<BOARD_FILES-2; i++)
5317       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5318     initialPosition[EP_STATUS] = EP_NONE;
5319     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5320     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5321          SetCharTable(pieceNickName, appData.pieceNickNames);
5322     else SetCharTable(pieceNickName, "............");
5323
5324     switch (gameInfo.variant) {
5325     case VariantFischeRandom:
5326       shuffleOpenings = TRUE;
5327     default:
5328       pieces = FIDEArray;
5329       break;
5330     case VariantShatranj:
5331       pieces = ShatranjArray;
5332       nrCastlingRights = 0;
5333       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5334       break;
5335     case VariantMakruk:
5336       pieces = makrukArray;
5337       nrCastlingRights = 0;
5338       startedFromSetupPosition = TRUE;
5339       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5340       break;
5341     case VariantTwoKings:
5342       pieces = twoKingsArray;
5343       break;
5344     case VariantCapaRandom:
5345       shuffleOpenings = TRUE;
5346     case VariantCapablanca:
5347       pieces = CapablancaArray;
5348       gameInfo.boardWidth = 10;
5349       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5350       break;
5351     case VariantGothic:
5352       pieces = GothicArray;
5353       gameInfo.boardWidth = 10;
5354       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5355       break;
5356     case VariantJanus:
5357       pieces = JanusArray;
5358       gameInfo.boardWidth = 10;
5359       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5360       nrCastlingRights = 6;
5361         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5362         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5363         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5364         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5365         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5366         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5367       break;
5368     case VariantFalcon:
5369       pieces = FalconArray;
5370       gameInfo.boardWidth = 10;
5371       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5372       break;
5373     case VariantXiangqi:
5374       pieces = XiangqiArray;
5375       gameInfo.boardWidth  = 9;
5376       gameInfo.boardHeight = 10;
5377       nrCastlingRights = 0;
5378       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5379       break;
5380     case VariantShogi:
5381       pieces = ShogiArray;
5382       gameInfo.boardWidth  = 9;
5383       gameInfo.boardHeight = 9;
5384       gameInfo.holdingsSize = 7;
5385       nrCastlingRights = 0;
5386       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5387       break;
5388     case VariantCourier:
5389       pieces = CourierArray;
5390       gameInfo.boardWidth  = 12;
5391       nrCastlingRights = 0;
5392       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5393       break;
5394     case VariantKnightmate:
5395       pieces = KnightmateArray;
5396       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5397       break;
5398     case VariantFairy:
5399       pieces = fairyArray;
5400       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5401       break;
5402     case VariantGreat:
5403       pieces = GreatArray;
5404       gameInfo.boardWidth = 10;
5405       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5406       gameInfo.holdingsSize = 8;
5407       break;
5408     case VariantSuper:
5409       pieces = FIDEArray;
5410       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5411       gameInfo.holdingsSize = 8;
5412       startedFromSetupPosition = TRUE;
5413       break;
5414     case VariantCrazyhouse:
5415     case VariantBughouse:
5416       pieces = FIDEArray;
5417       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5418       gameInfo.holdingsSize = 5;
5419       break;
5420     case VariantWildCastle:
5421       pieces = FIDEArray;
5422       /* !!?shuffle with kings guaranteed to be on d or e file */
5423       shuffleOpenings = 1;
5424       break;
5425     case VariantNoCastle:
5426       pieces = FIDEArray;
5427       nrCastlingRights = 0;
5428       /* !!?unconstrained back-rank shuffle */
5429       shuffleOpenings = 1;
5430       break;
5431     }
5432
5433     overrule = 0;
5434     if(appData.NrFiles >= 0) {
5435         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5436         gameInfo.boardWidth = appData.NrFiles;
5437     }
5438     if(appData.NrRanks >= 0) {
5439         gameInfo.boardHeight = appData.NrRanks;
5440     }
5441     if(appData.holdingsSize >= 0) {
5442         i = appData.holdingsSize;
5443         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5444         gameInfo.holdingsSize = i;
5445     }
5446     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5447     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5448         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5449
5450     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5451     if(pawnRow < 1) pawnRow = 1;
5452     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5453
5454     /* User pieceToChar list overrules defaults */
5455     if(appData.pieceToCharTable != NULL)
5456         SetCharTable(pieceToChar, appData.pieceToCharTable);
5457
5458     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5459
5460         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5461             s = (ChessSquare) 0; /* account holding counts in guard band */
5462         for( i=0; i<BOARD_HEIGHT; i++ )
5463             initialPosition[i][j] = s;
5464
5465         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5466         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5467         initialPosition[pawnRow][j] = WhitePawn;
5468         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5469         if(gameInfo.variant == VariantXiangqi) {
5470             if(j&1) {
5471                 initialPosition[pawnRow][j] =
5472                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5473                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5474                    initialPosition[2][j] = WhiteCannon;
5475                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5476                 }
5477             }
5478         }
5479         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5480     }
5481     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5482
5483             j=BOARD_LEFT+1;
5484             initialPosition[1][j] = WhiteBishop;
5485             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5486             j=BOARD_RGHT-2;
5487             initialPosition[1][j] = WhiteRook;
5488             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5489     }
5490
5491     if( nrCastlingRights == -1) {
5492         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5493         /*       This sets default castling rights from none to normal corners   */
5494         /* Variants with other castling rights must set them themselves above    */
5495         nrCastlingRights = 6;
5496
5497         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5498         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5499         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5500         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5501         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5502         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5503      }
5504
5505      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5506      if(gameInfo.variant == VariantGreat) { // promotion commoners
5507         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5508         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5509         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5510         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5511      }
5512   if (appData.debugMode) {
5513     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5514   }
5515     if(shuffleOpenings) {
5516         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5517         startedFromSetupPosition = TRUE;
5518     }
5519     if(startedFromPositionFile) {
5520       /* [HGM] loadPos: use PositionFile for every new game */
5521       CopyBoard(initialPosition, filePosition);
5522       for(i=0; i<nrCastlingRights; i++)
5523           initialRights[i] = filePosition[CASTLING][i];
5524       startedFromSetupPosition = TRUE;
5525     }
5526
5527     CopyBoard(boards[0], initialPosition);
5528
5529     if(oldx != gameInfo.boardWidth ||
5530        oldy != gameInfo.boardHeight ||
5531        oldh != gameInfo.holdingsWidth
5532 #ifdef GOTHIC
5533        || oldv == VariantGothic ||        // For licensing popups
5534        gameInfo.variant == VariantGothic
5535 #endif
5536 #ifdef FALCON
5537        || oldv == VariantFalcon ||
5538        gameInfo.variant == VariantFalcon
5539 #endif
5540                                          )
5541             InitDrawingSizes(-2 ,0);
5542
5543     if (redraw)
5544       DrawPosition(TRUE, boards[currentMove]);
5545 }
5546
5547 void
5548 SendBoard(cps, moveNum)
5549      ChessProgramState *cps;
5550      int moveNum;
5551 {
5552     char message[MSG_SIZ];
5553
5554     if (cps->useSetboard) {
5555       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5556       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5557       SendToProgram(message, cps);
5558       free(fen);
5559
5560     } else {
5561       ChessSquare *bp;
5562       int i, j;
5563       /* Kludge to set black to move, avoiding the troublesome and now
5564        * deprecated "black" command.
5565        */
5566       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5567
5568       SendToProgram("edit\n", cps);
5569       SendToProgram("#\n", cps);
5570       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5571         bp = &boards[moveNum][i][BOARD_LEFT];
5572         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5573           if ((int) *bp < (int) BlackPawn) {
5574             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5575                     AAA + j, ONE + i);
5576             if(message[0] == '+' || message[0] == '~') {
5577               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5578                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5579                         AAA + j, ONE + i);
5580             }
5581             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5582                 message[1] = BOARD_RGHT   - 1 - j + '1';
5583                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5584             }
5585             SendToProgram(message, cps);
5586           }
5587         }
5588       }
5589
5590       SendToProgram("c\n", cps);
5591       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5592         bp = &boards[moveNum][i][BOARD_LEFT];
5593         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5594           if (((int) *bp != (int) EmptySquare)
5595               && ((int) *bp >= (int) BlackPawn)) {
5596             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5597                     AAA + j, ONE + i);
5598             if(message[0] == '+' || message[0] == '~') {
5599               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5600                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5601                         AAA + j, ONE + i);
5602             }
5603             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5604                 message[1] = BOARD_RGHT   - 1 - j + '1';
5605                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5606             }
5607             SendToProgram(message, cps);
5608           }
5609         }
5610       }
5611
5612       SendToProgram(".\n", cps);
5613     }
5614     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5615 }
5616
5617 static int autoQueen; // [HGM] oneclick
5618
5619 int
5620 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5621 {
5622     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5623     /* [HGM] add Shogi promotions */
5624     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5625     ChessSquare piece;
5626     ChessMove moveType;
5627     Boolean premove;
5628
5629     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5630     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5631
5632     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5633       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5634         return FALSE;
5635
5636     piece = boards[currentMove][fromY][fromX];
5637     if(gameInfo.variant == VariantShogi) {
5638         promotionZoneSize = BOARD_HEIGHT/3;
5639         highestPromotingPiece = (int)WhiteFerz;
5640     } else if(gameInfo.variant == VariantMakruk) {
5641         promotionZoneSize = 3;
5642     }
5643
5644     // next weed out all moves that do not touch the promotion zone at all
5645     if((int)piece >= BlackPawn) {
5646         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5647              return FALSE;
5648         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5649     } else {
5650         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5651            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5652     }
5653
5654     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5655
5656     // weed out mandatory Shogi promotions
5657     if(gameInfo.variant == VariantShogi) {
5658         if(piece >= BlackPawn) {
5659             if(toY == 0 && piece == BlackPawn ||
5660                toY == 0 && piece == BlackQueen ||
5661                toY <= 1 && piece == BlackKnight) {
5662                 *promoChoice = '+';
5663                 return FALSE;
5664             }
5665         } else {
5666             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5667                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5668                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5669                 *promoChoice = '+';
5670                 return FALSE;
5671             }
5672         }
5673     }
5674
5675     // weed out obviously illegal Pawn moves
5676     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5677         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5678         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5679         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5680         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5681         // note we are not allowed to test for valid (non-)capture, due to premove
5682     }
5683
5684     // we either have a choice what to promote to, or (in Shogi) whether to promote
5685     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5686         *promoChoice = PieceToChar(BlackFerz);  // no choice
5687         return FALSE;
5688     }
5689     if(autoQueen) { // predetermined
5690         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5691              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5692         else *promoChoice = PieceToChar(BlackQueen);
5693         return FALSE;
5694     }
5695
5696     // suppress promotion popup on illegal moves that are not premoves
5697     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5698               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5699     if(appData.testLegality && !premove) {
5700         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5701                         fromY, fromX, toY, toX, NULLCHAR);
5702         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5703             return FALSE;
5704     }
5705
5706     return TRUE;
5707 }
5708
5709 int
5710 InPalace(row, column)
5711      int row, column;
5712 {   /* [HGM] for Xiangqi */
5713     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5714          column < (BOARD_WIDTH + 4)/2 &&
5715          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5716     return FALSE;
5717 }
5718
5719 int
5720 PieceForSquare (x, y)
5721      int x;
5722      int y;
5723 {
5724   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5725      return -1;
5726   else
5727      return boards[currentMove][y][x];
5728 }
5729
5730 int
5731 OKToStartUserMove(x, y)
5732      int x, y;
5733 {
5734     ChessSquare from_piece;
5735     int white_piece;
5736
5737     if (matchMode) return FALSE;
5738     if (gameMode == EditPosition) return TRUE;
5739
5740     if (x >= 0 && y >= 0)
5741       from_piece = boards[currentMove][y][x];
5742     else
5743       from_piece = EmptySquare;
5744
5745     if (from_piece == EmptySquare) return FALSE;
5746
5747     white_piece = (int)from_piece >= (int)WhitePawn &&
5748       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5749
5750     switch (gameMode) {
5751       case PlayFromGameFile:
5752       case AnalyzeFile:
5753       case TwoMachinesPlay:
5754       case EndOfGame:
5755         return FALSE;
5756
5757       case IcsObserving:
5758       case IcsIdle:
5759         return FALSE;
5760
5761       case MachinePlaysWhite:
5762       case IcsPlayingBlack:
5763         if (appData.zippyPlay) return FALSE;
5764         if (white_piece) {
5765             DisplayMoveError(_("You are playing Black"));
5766             return FALSE;
5767         }
5768         break;
5769
5770       case MachinePlaysBlack:
5771       case IcsPlayingWhite:
5772         if (appData.zippyPlay) return FALSE;
5773         if (!white_piece) {
5774             DisplayMoveError(_("You are playing White"));
5775             return FALSE;
5776         }
5777         break;
5778
5779       case EditGame:
5780         if (!white_piece && WhiteOnMove(currentMove)) {
5781             DisplayMoveError(_("It is White's turn"));
5782             return FALSE;
5783         }
5784         if (white_piece && !WhiteOnMove(currentMove)) {
5785             DisplayMoveError(_("It is Black's turn"));
5786             return FALSE;
5787         }
5788         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5789             /* Editing correspondence game history */
5790             /* Could disallow this or prompt for confirmation */
5791             cmailOldMove = -1;
5792         }
5793         break;
5794
5795       case BeginningOfGame:
5796         if (appData.icsActive) return FALSE;
5797         if (!appData.noChessProgram) {
5798             if (!white_piece) {
5799                 DisplayMoveError(_("You are playing White"));
5800                 return FALSE;
5801             }
5802         }
5803         break;
5804
5805       case Training:
5806         if (!white_piece && WhiteOnMove(currentMove)) {
5807             DisplayMoveError(_("It is White's turn"));
5808             return FALSE;
5809         }
5810         if (white_piece && !WhiteOnMove(currentMove)) {
5811             DisplayMoveError(_("It is Black's turn"));
5812             return FALSE;
5813         }
5814         break;
5815
5816       default:
5817       case IcsExamining:
5818         break;
5819     }
5820     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5821         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5822         && gameMode != AnalyzeFile && gameMode != Training) {
5823         DisplayMoveError(_("Displayed position is not current"));
5824         return FALSE;
5825     }
5826     return TRUE;
5827 }
5828
5829 Boolean
5830 OnlyMove(int *x, int *y, Boolean captures) {
5831     DisambiguateClosure cl;
5832     if (appData.zippyPlay) return FALSE;
5833     switch(gameMode) {
5834       case MachinePlaysBlack:
5835       case IcsPlayingWhite:
5836       case BeginningOfGame:
5837         if(!WhiteOnMove(currentMove)) return FALSE;
5838         break;
5839       case MachinePlaysWhite:
5840       case IcsPlayingBlack:
5841         if(WhiteOnMove(currentMove)) return FALSE;
5842         break;
5843       default:
5844         return FALSE;
5845     }
5846     cl.pieceIn = EmptySquare;
5847     cl.rfIn = *y;
5848     cl.ffIn = *x;
5849     cl.rtIn = -1;
5850     cl.ftIn = -1;
5851     cl.promoCharIn = NULLCHAR;
5852     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5853     if( cl.kind == NormalMove ||
5854         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5855         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5856         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5857       fromX = cl.ff;
5858       fromY = cl.rf;
5859       *x = cl.ft;
5860       *y = cl.rt;
5861       return TRUE;
5862     }
5863     if(cl.kind != ImpossibleMove) return FALSE;
5864     cl.pieceIn = EmptySquare;
5865     cl.rfIn = -1;
5866     cl.ffIn = -1;
5867     cl.rtIn = *y;
5868     cl.ftIn = *x;
5869     cl.promoCharIn = NULLCHAR;
5870     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5871     if( cl.kind == NormalMove ||
5872         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5873         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5874         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5875       fromX = cl.ff;
5876       fromY = cl.rf;
5877       *x = cl.ft;
5878       *y = cl.rt;
5879       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5880       return TRUE;
5881     }
5882     return FALSE;
5883 }
5884
5885 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5886 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5887 int lastLoadGameUseList = FALSE;
5888 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5889 ChessMove lastLoadGameStart = EndOfFile;
5890
5891 void
5892 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5893      int fromX, fromY, toX, toY;
5894      int promoChar;
5895 {
5896     ChessMove moveType;
5897     ChessSquare pdown, pup;
5898
5899     /* Check if the user is playing in turn.  This is complicated because we
5900        let the user "pick up" a piece before it is his turn.  So the piece he
5901        tried to pick up may have been captured by the time he puts it down!
5902        Therefore we use the color the user is supposed to be playing in this
5903        test, not the color of the piece that is currently on the starting
5904        square---except in EditGame mode, where the user is playing both
5905        sides; fortunately there the capture race can't happen.  (It can
5906        now happen in IcsExamining mode, but that's just too bad.  The user
5907        will get a somewhat confusing message in that case.)
5908        */
5909
5910     switch (gameMode) {
5911       case PlayFromGameFile:
5912       case AnalyzeFile:
5913       case TwoMachinesPlay:
5914       case EndOfGame:
5915       case IcsObserving:
5916       case IcsIdle:
5917         /* We switched into a game mode where moves are not accepted,
5918            perhaps while the mouse button was down. */
5919         return;
5920
5921       case MachinePlaysWhite:
5922         /* User is moving for Black */
5923         if (WhiteOnMove(currentMove)) {
5924             DisplayMoveError(_("It is White's turn"));
5925             return;
5926         }
5927         break;
5928
5929       case MachinePlaysBlack:
5930         /* User is moving for White */
5931         if (!WhiteOnMove(currentMove)) {
5932             DisplayMoveError(_("It is Black's turn"));
5933             return;
5934         }
5935         break;
5936
5937       case EditGame:
5938       case IcsExamining:
5939       case BeginningOfGame:
5940       case AnalyzeMode:
5941       case Training:
5942         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5943             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5944             /* User is moving for Black */
5945             if (WhiteOnMove(currentMove)) {
5946                 DisplayMoveError(_("It is White's turn"));
5947                 return;
5948             }
5949         } else {
5950             /* User is moving for White */
5951             if (!WhiteOnMove(currentMove)) {
5952                 DisplayMoveError(_("It is Black's turn"));
5953                 return;
5954             }
5955         }
5956         break;
5957
5958       case IcsPlayingBlack:
5959         /* User is moving for Black */
5960         if (WhiteOnMove(currentMove)) {
5961             if (!appData.premove) {
5962                 DisplayMoveError(_("It is White's turn"));
5963             } else if (toX >= 0 && toY >= 0) {
5964                 premoveToX = toX;
5965                 premoveToY = toY;
5966                 premoveFromX = fromX;
5967                 premoveFromY = fromY;
5968                 premovePromoChar = promoChar;
5969                 gotPremove = 1;
5970                 if (appData.debugMode)
5971                     fprintf(debugFP, "Got premove: fromX %d,"
5972                             "fromY %d, toX %d, toY %d\n",
5973                             fromX, fromY, toX, toY);
5974             }
5975             return;
5976         }
5977         break;
5978
5979       case IcsPlayingWhite:
5980         /* User is moving for White */
5981         if (!WhiteOnMove(currentMove)) {
5982             if (!appData.premove) {
5983                 DisplayMoveError(_("It is Black's turn"));
5984             } else if (toX >= 0 && toY >= 0) {
5985                 premoveToX = toX;
5986                 premoveToY = toY;
5987                 premoveFromX = fromX;
5988                 premoveFromY = fromY;
5989                 premovePromoChar = promoChar;
5990                 gotPremove = 1;
5991                 if (appData.debugMode)
5992                     fprintf(debugFP, "Got premove: fromX %d,"
5993                             "fromY %d, toX %d, toY %d\n",
5994                             fromX, fromY, toX, toY);
5995             }
5996             return;
5997         }
5998         break;
5999
6000       default:
6001         break;
6002
6003       case EditPosition:
6004         /* EditPosition, empty square, or different color piece;
6005            click-click move is possible */
6006         if (toX == -2 || toY == -2) {
6007             boards[0][fromY][fromX] = EmptySquare;
6008             DrawPosition(FALSE, boards[currentMove]);
6009             return;
6010         } else if (toX >= 0 && toY >= 0) {
6011             boards[0][toY][toX] = boards[0][fromY][fromX];
6012             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6013                 if(boards[0][fromY][0] != EmptySquare) {
6014                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6015                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6016                 }
6017             } else
6018             if(fromX == BOARD_RGHT+1) {
6019                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6020                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6021                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6022                 }
6023             } else
6024             boards[0][fromY][fromX] = EmptySquare;
6025             DrawPosition(FALSE, boards[currentMove]);
6026             return;
6027         }
6028         return;
6029     }
6030
6031     if(toX < 0 || toY < 0) return;
6032     pdown = boards[currentMove][fromY][fromX];
6033     pup = boards[currentMove][toY][toX];
6034
6035     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6036     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
6037          if( pup != EmptySquare ) return;
6038          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6039            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6040                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6041            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6042            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6043            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6044            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6045          fromY = DROP_RANK;
6046     }
6047
6048     /* [HGM] always test for legality, to get promotion info */
6049     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6050                                          fromY, fromX, toY, toX, promoChar);
6051     /* [HGM] but possibly ignore an IllegalMove result */
6052     if (appData.testLegality) {
6053         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6054             DisplayMoveError(_("Illegal move"));
6055             return;
6056         }
6057     }
6058
6059     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6060 }
6061
6062 /* Common tail of UserMoveEvent and DropMenuEvent */
6063 int
6064 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6065      ChessMove moveType;
6066      int fromX, fromY, toX, toY;
6067      /*char*/int promoChar;
6068 {
6069     char *bookHit = 0;
6070
6071     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6072         // [HGM] superchess: suppress promotions to non-available piece
6073         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6074         if(WhiteOnMove(currentMove)) {
6075             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6076         } else {
6077             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6078         }
6079     }
6080
6081     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6082        move type in caller when we know the move is a legal promotion */
6083     if(moveType == NormalMove && promoChar)
6084         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6085
6086     /* [HGM] <popupFix> The following if has been moved here from
6087        UserMoveEvent(). Because it seemed to belong here (why not allow
6088        piece drops in training games?), and because it can only be
6089        performed after it is known to what we promote. */
6090     if (gameMode == Training) {
6091       /* compare the move played on the board to the next move in the
6092        * game. If they match, display the move and the opponent's response.
6093        * If they don't match, display an error message.
6094        */
6095       int saveAnimate;
6096       Board testBoard;
6097       CopyBoard(testBoard, boards[currentMove]);
6098       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6099
6100       if (CompareBoards(testBoard, boards[currentMove+1])) {
6101         ForwardInner(currentMove+1);
6102
6103         /* Autoplay the opponent's response.
6104          * if appData.animate was TRUE when Training mode was entered,
6105          * the response will be animated.
6106          */
6107         saveAnimate = appData.animate;
6108         appData.animate = animateTraining;
6109         ForwardInner(currentMove+1);
6110         appData.animate = saveAnimate;
6111
6112         /* check for the end of the game */
6113         if (currentMove >= forwardMostMove) {
6114           gameMode = PlayFromGameFile;
6115           ModeHighlight();
6116           SetTrainingModeOff();
6117           DisplayInformation(_("End of game"));
6118         }
6119       } else {
6120         DisplayError(_("Incorrect move"), 0);
6121       }
6122       return 1;
6123     }
6124
6125   /* Ok, now we know that the move is good, so we can kill
6126      the previous line in Analysis Mode */
6127   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6128                                 && currentMove < forwardMostMove) {
6129     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6130   }
6131
6132   /* If we need the chess program but it's dead, restart it */
6133   ResurrectChessProgram();
6134
6135   /* A user move restarts a paused game*/
6136   if (pausing)
6137     PauseEvent();
6138
6139   thinkOutput[0] = NULLCHAR;
6140
6141   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6142
6143   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6144
6145   if (gameMode == BeginningOfGame) {
6146     if (appData.noChessProgram) {
6147       gameMode = EditGame;
6148       SetGameInfo();
6149     } else {
6150       char buf[MSG_SIZ];
6151       gameMode = MachinePlaysBlack;
6152       StartClocks();
6153       SetGameInfo();
6154       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6155       DisplayTitle(buf);
6156       if (first.sendName) {
6157         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6158         SendToProgram(buf, &first);
6159       }
6160       StartClocks();
6161     }
6162     ModeHighlight();
6163   }
6164
6165   /* Relay move to ICS or chess engine */
6166   if (appData.icsActive) {
6167     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6168         gameMode == IcsExamining) {
6169       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6170         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6171         SendToICS("draw ");
6172         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6173       }
6174       // also send plain move, in case ICS does not understand atomic claims
6175       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6176       ics_user_moved = 1;
6177     }
6178   } else {
6179     if (first.sendTime && (gameMode == BeginningOfGame ||
6180                            gameMode == MachinePlaysWhite ||
6181                            gameMode == MachinePlaysBlack)) {
6182       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6183     }
6184     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6185          // [HGM] book: if program might be playing, let it use book
6186         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6187         first.maybeThinking = TRUE;
6188     } else SendMoveToProgram(forwardMostMove-1, &first);
6189     if (currentMove == cmailOldMove + 1) {
6190       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6191     }
6192   }
6193
6194   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6195
6196   switch (gameMode) {
6197   case EditGame:
6198     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6199     case MT_NONE:
6200     case MT_CHECK:
6201       break;
6202     case MT_CHECKMATE:
6203     case MT_STAINMATE:
6204       if (WhiteOnMove(currentMove)) {
6205         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6206       } else {
6207         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6208       }
6209       break;
6210     case MT_STALEMATE:
6211       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6212       break;
6213     }
6214     break;
6215
6216   case MachinePlaysBlack:
6217   case MachinePlaysWhite:
6218     /* disable certain menu options while machine is thinking */
6219     SetMachineThinkingEnables();
6220     break;
6221
6222   default:
6223     break;
6224   }
6225
6226   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6227
6228   if(bookHit) { // [HGM] book: simulate book reply
6229         static char bookMove[MSG_SIZ]; // a bit generous?
6230
6231         programStats.nodes = programStats.depth = programStats.time =
6232         programStats.score = programStats.got_only_move = 0;
6233         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6234
6235         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6236         strcat(bookMove, bookHit);
6237         HandleMachineMove(bookMove, &first);
6238   }
6239   return 1;
6240 }
6241
6242 void
6243 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6244      Board board;
6245      int flags;
6246      ChessMove kind;
6247      int rf, ff, rt, ft;
6248      VOIDSTAR closure;
6249 {
6250     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6251     Markers *m = (Markers *) closure;
6252     if(rf == fromY && ff == fromX)
6253         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6254                          || kind == WhiteCapturesEnPassant
6255                          || kind == BlackCapturesEnPassant);
6256     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6257 }
6258
6259 void
6260 MarkTargetSquares(int clear)
6261 {
6262   int x, y;
6263   if(!appData.markers || !appData.highlightDragging ||
6264      !appData.testLegality || gameMode == EditPosition) return;
6265   if(clear) {
6266     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6267   } else {
6268     int capt = 0;
6269     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6270     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6271       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6272       if(capt)
6273       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6274     }
6275   }
6276   DrawPosition(TRUE, NULL);
6277 }
6278
6279 void LeftClick(ClickType clickType, int xPix, int yPix)
6280 {
6281     int x, y;
6282     Boolean saveAnimate;
6283     static int second = 0, promotionChoice = 0, dragging = 0;
6284     char promoChoice = NULLCHAR;
6285
6286     if(appData.seekGraph && appData.icsActive && loggedOn &&
6287         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6288         SeekGraphClick(clickType, xPix, yPix, 0);
6289         return;
6290     }
6291
6292     if (clickType == Press) ErrorPopDown();
6293     MarkTargetSquares(1);
6294
6295     x = EventToSquare(xPix, BOARD_WIDTH);
6296     y = EventToSquare(yPix, BOARD_HEIGHT);
6297     if (!flipView && y >= 0) {
6298         y = BOARD_HEIGHT - 1 - y;
6299     }
6300     if (flipView && x >= 0) {
6301         x = BOARD_WIDTH - 1 - x;
6302     }
6303
6304     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6305         if(clickType == Release) return; // ignore upclick of click-click destination
6306         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6307         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6308         if(gameInfo.holdingsWidth &&
6309                 (WhiteOnMove(currentMove)
6310                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6311                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6312             // click in right holdings, for determining promotion piece
6313             ChessSquare p = boards[currentMove][y][x];
6314             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6315             if(p != EmptySquare) {
6316                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6317                 fromX = fromY = -1;
6318                 return;
6319             }
6320         }
6321         DrawPosition(FALSE, boards[currentMove]);
6322         return;
6323     }
6324
6325     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6326     if(clickType == Press
6327             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6328               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6329               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6330         return;
6331
6332     autoQueen = appData.alwaysPromoteToQueen;
6333
6334     if (fromX == -1) {
6335       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6336         if (clickType == Press) {
6337             /* First square */
6338             if (OKToStartUserMove(x, y)) {
6339                 fromX = x;
6340                 fromY = y;
6341                 second = 0;
6342                 MarkTargetSquares(0);
6343                 DragPieceBegin(xPix, yPix); dragging = 1;
6344                 if (appData.highlightDragging) {
6345                     SetHighlights(x, y, -1, -1);
6346                 }
6347             }
6348         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6349             DragPieceEnd(xPix, yPix); dragging = 0;
6350             DrawPosition(FALSE, NULL);
6351         }
6352         return;
6353       }
6354     }
6355
6356     /* fromX != -1 */
6357     if (clickType == Press && gameMode != EditPosition) {
6358         ChessSquare fromP;
6359         ChessSquare toP;
6360         int frc;
6361
6362         // ignore off-board to clicks
6363         if(y < 0 || x < 0) return;
6364
6365         /* Check if clicking again on the same color piece */
6366         fromP = boards[currentMove][fromY][fromX];
6367         toP = boards[currentMove][y][x];
6368         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6369         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6370              WhitePawn <= toP && toP <= WhiteKing &&
6371              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6372              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6373             (BlackPawn <= fromP && fromP <= BlackKing &&
6374              BlackPawn <= toP && toP <= BlackKing &&
6375              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6376              !(fromP == BlackKing && toP == BlackRook && frc))) {
6377             /* Clicked again on same color piece -- changed his mind */
6378             second = (x == fromX && y == fromY);
6379            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6380             if (appData.highlightDragging) {
6381                 SetHighlights(x, y, -1, -1);
6382             } else {
6383                 ClearHighlights();
6384             }
6385             if (OKToStartUserMove(x, y)) {
6386                 fromX = x;
6387                 fromY = y; dragging = 1;
6388                 MarkTargetSquares(0);
6389                 DragPieceBegin(xPix, yPix);
6390             }
6391             return;
6392            }
6393         }
6394         // ignore clicks on holdings
6395         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6396     }
6397
6398     if (clickType == Release && x == fromX && y == fromY) {
6399         DragPieceEnd(xPix, yPix); dragging = 0;
6400         if (appData.animateDragging) {
6401             /* Undo animation damage if any */
6402             DrawPosition(FALSE, NULL);
6403         }
6404         if (second) {
6405             /* Second up/down in same square; just abort move */
6406             second = 0;
6407             fromX = fromY = -1;
6408             ClearHighlights();
6409             gotPremove = 0;
6410             ClearPremoveHighlights();
6411         } else {
6412             /* First upclick in same square; start click-click mode */
6413             SetHighlights(x, y, -1, -1);
6414         }
6415         return;
6416     }
6417
6418     /* we now have a different from- and (possibly off-board) to-square */
6419     /* Completed move */
6420     toX = x;
6421     toY = y;
6422     saveAnimate = appData.animate;
6423     if (clickType == Press) {
6424         /* Finish clickclick move */
6425         if (appData.animate || appData.highlightLastMove) {
6426             SetHighlights(fromX, fromY, toX, toY);
6427         } else {
6428             ClearHighlights();
6429         }
6430     } else {
6431         /* Finish drag move */
6432         if (appData.highlightLastMove) {
6433             SetHighlights(fromX, fromY, toX, toY);
6434         } else {
6435             ClearHighlights();
6436         }
6437         DragPieceEnd(xPix, yPix); dragging = 0;
6438         /* Don't animate move and drag both */
6439         appData.animate = FALSE;
6440     }
6441
6442     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6443     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6444         ChessSquare piece = boards[currentMove][fromY][fromX];
6445         if(gameMode == EditPosition && piece != EmptySquare &&
6446            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6447             int n;
6448
6449             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6450                 n = PieceToNumber(piece - (int)BlackPawn);
6451                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6452                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6453                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6454             } else
6455             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6456                 n = PieceToNumber(piece);
6457                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6458                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6459                 boards[currentMove][n][BOARD_WIDTH-2]++;
6460             }
6461             boards[currentMove][fromY][fromX] = EmptySquare;
6462         }
6463         ClearHighlights();
6464         fromX = fromY = -1;
6465         DrawPosition(TRUE, boards[currentMove]);
6466         return;
6467     }
6468
6469     // off-board moves should not be highlighted
6470     if(x < 0 || x < 0) ClearHighlights();
6471
6472     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6473         SetHighlights(fromX, fromY, toX, toY);
6474         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6475             // [HGM] super: promotion to captured piece selected from holdings
6476             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6477             promotionChoice = TRUE;
6478             // kludge follows to temporarily execute move on display, without promoting yet
6479             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6480             boards[currentMove][toY][toX] = p;
6481             DrawPosition(FALSE, boards[currentMove]);
6482             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6483             boards[currentMove][toY][toX] = q;
6484             DisplayMessage("Click in holdings to choose piece", "");
6485             return;
6486         }
6487         PromotionPopUp();
6488     } else {
6489         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6490         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6491         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6492         fromX = fromY = -1;
6493     }
6494     appData.animate = saveAnimate;
6495     if (appData.animate || appData.animateDragging) {
6496         /* Undo animation damage if needed */
6497         DrawPosition(FALSE, NULL);
6498     }
6499 }
6500
6501 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6502 {   // front-end-free part taken out of PieceMenuPopup
6503     int whichMenu; int xSqr, ySqr;
6504
6505     if(seekGraphUp) { // [HGM] seekgraph
6506         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6507         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6508         return -2;
6509     }
6510
6511     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6512          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6513         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6514         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6515         if(action == Press)   {
6516             originalFlip = flipView;
6517             flipView = !flipView; // temporarily flip board to see game from partners perspective
6518             DrawPosition(TRUE, partnerBoard);
6519             DisplayMessage(partnerStatus, "");
6520             partnerUp = TRUE;
6521         } else if(action == Release) {
6522             flipView = originalFlip;
6523             DrawPosition(TRUE, boards[currentMove]);
6524             partnerUp = FALSE;
6525         }
6526         return -2;
6527     }
6528
6529     xSqr = EventToSquare(x, BOARD_WIDTH);
6530     ySqr = EventToSquare(y, BOARD_HEIGHT);
6531     if (action == Release) UnLoadPV(); // [HGM] pv
6532     if (action != Press) return -2; // return code to be ignored
6533     switch (gameMode) {
6534       case IcsExamining:
6535         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6536       case EditPosition:
6537         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6538         if (xSqr < 0 || ySqr < 0) return -1;\r
6539         whichMenu = 0; // edit-position menu
6540         break;
6541       case IcsObserving:
6542         if(!appData.icsEngineAnalyze) return -1;
6543       case IcsPlayingWhite:
6544       case IcsPlayingBlack:
6545         if(!appData.zippyPlay) goto noZip;
6546       case AnalyzeMode:
6547       case AnalyzeFile:
6548       case MachinePlaysWhite:
6549       case MachinePlaysBlack:
6550       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6551         if (!appData.dropMenu) {
6552           LoadPV(x, y);
6553           return 2; // flag front-end to grab mouse events
6554         }
6555         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6556            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6557       case EditGame:
6558       noZip:
6559         if (xSqr < 0 || ySqr < 0) return -1;
6560         if (!appData.dropMenu || appData.testLegality &&
6561             gameInfo.variant != VariantBughouse &&
6562             gameInfo.variant != VariantCrazyhouse) return -1;
6563         whichMenu = 1; // drop menu
6564         break;
6565       default:
6566         return -1;
6567     }
6568
6569     if (((*fromX = xSqr) < 0) ||
6570         ((*fromY = ySqr) < 0)) {
6571         *fromX = *fromY = -1;
6572         return -1;
6573     }
6574     if (flipView)
6575       *fromX = BOARD_WIDTH - 1 - *fromX;
6576     else
6577       *fromY = BOARD_HEIGHT - 1 - *fromY;
6578
6579     return whichMenu;
6580 }
6581
6582 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6583 {
6584 //    char * hint = lastHint;
6585     FrontEndProgramStats stats;
6586
6587     stats.which = cps == &first ? 0 : 1;
6588     stats.depth = cpstats->depth;
6589     stats.nodes = cpstats->nodes;
6590     stats.score = cpstats->score;
6591     stats.time = cpstats->time;
6592     stats.pv = cpstats->movelist;
6593     stats.hint = lastHint;
6594     stats.an_move_index = 0;
6595     stats.an_move_count = 0;
6596
6597     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6598         stats.hint = cpstats->move_name;
6599         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6600         stats.an_move_count = cpstats->nr_moves;
6601     }
6602
6603     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
6604
6605     SetProgramStats( &stats );
6606 }
6607
6608 void
6609 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6610 {       // count all piece types
6611         int p, f, r;
6612         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6613         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6614         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6615                 p = board[r][f];
6616                 pCnt[p]++;
6617                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6618                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6619                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6620                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6621                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6622                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6623         }
6624 }
6625
6626 int
6627 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6628 {
6629         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6630         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6631
6632         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6633         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6634         if(myPawns == 2 && nMine == 3) // KPP
6635             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6636         if(myPawns == 1 && nMine == 2) // KP
6637             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6638         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6639             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6640         if(myPawns) return FALSE;
6641         if(pCnt[WhiteRook+side])
6642             return pCnt[BlackRook-side] ||
6643                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6644                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6645                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6646         if(pCnt[WhiteCannon+side]) {
6647             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6648             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6649         }
6650         if(pCnt[WhiteKnight+side])
6651             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6652         return FALSE;
6653 }
6654
6655 int
6656 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6657 {
6658         VariantClass v = gameInfo.variant;
6659
6660         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6661         if(v == VariantShatranj) return TRUE; // always winnable through baring
6662         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6663         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6664
6665         if(v == VariantXiangqi) {
6666                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6667
6668                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6669                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6670                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6671                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6672                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6673                 if(stale) // we have at least one last-rank P plus perhaps C
6674                     return majors // KPKX
6675                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6676                 else // KCA*E*
6677                     return pCnt[WhiteFerz+side] // KCAK
6678                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6679                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6680                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6681
6682         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6683                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6684
6685                 if(nMine == 1) return FALSE; // bare King
6686                 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
6687                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6688                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6689                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6690                 if(pCnt[WhiteKnight+side])
6691                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6692                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6693                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6694                 if(nBishops)
6695                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6696                 if(pCnt[WhiteAlfil+side])
6697                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6698                 if(pCnt[WhiteWazir+side])
6699                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6700         }
6701
6702         return TRUE;
6703 }
6704
6705 int
6706 Adjudicate(ChessProgramState *cps)
6707 {       // [HGM] some adjudications useful with buggy engines
6708         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6709         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6710         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6711         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6712         int k, count = 0; static int bare = 1;
6713         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6714         Boolean canAdjudicate = !appData.icsActive;
6715
6716         // most tests only when we understand the game, i.e. legality-checking on
6717             if( appData.testLegality )
6718             {   /* [HGM] Some more adjudications for obstinate engines */
6719                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6720                 static int moveCount = 6;
6721                 ChessMove result;
6722                 char *reason = NULL;
6723
6724                 /* Count what is on board. */
6725                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6726
6727                 /* Some material-based adjudications that have to be made before stalemate test */
6728                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6729                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6730                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6731                      if(canAdjudicate && appData.checkMates) {
6732                          if(engineOpponent)
6733                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6734                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6735                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6736                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6737                          return 1;
6738                      }
6739                 }
6740
6741                 /* Bare King in Shatranj (loses) or Losers (wins) */
6742                 if( nrW == 1 || nrB == 1) {
6743                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6744                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6745                      if(canAdjudicate && appData.checkMates) {
6746                          if(engineOpponent)
6747                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6748                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6749                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6750                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6751                          return 1;
6752                      }
6753                   } else
6754                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6755                   {    /* bare King */
6756                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6757                         if(canAdjudicate && appData.checkMates) {
6758                             /* but only adjudicate if adjudication enabled */
6759                             if(engineOpponent)
6760                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6761                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6762                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6763                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6764                             return 1;
6765                         }
6766                   }
6767                 } else bare = 1;
6768
6769
6770             // don't wait for engine to announce game end if we can judge ourselves
6771             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6772               case MT_CHECK:
6773                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6774                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6775                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6776                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6777                             checkCnt++;
6778                         if(checkCnt >= 2) {
6779                             reason = "Xboard adjudication: 3rd check";
6780                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6781                             break;
6782                         }
6783                     }
6784                 }
6785               case MT_NONE:
6786               default:
6787                 break;
6788               case MT_STALEMATE:
6789               case MT_STAINMATE:
6790                 reason = "Xboard adjudication: Stalemate";
6791                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6792                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6793                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6794                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6795                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6796                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6797                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6798                                                                         EP_CHECKMATE : EP_WINS);
6799                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6800                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6801                 }
6802                 break;
6803               case MT_CHECKMATE:
6804                 reason = "Xboard adjudication: Checkmate";
6805                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6806                 break;
6807             }
6808
6809                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6810                     case EP_STALEMATE:
6811                         result = GameIsDrawn; break;
6812                     case EP_CHECKMATE:
6813                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6814                     case EP_WINS:
6815                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6816                     default:
6817                         result = EndOfFile;
6818                 }
6819                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6820                     if(engineOpponent)
6821                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6822                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6823                     GameEnds( result, reason, GE_XBOARD );
6824                     return 1;
6825                 }
6826
6827                 /* Next absolutely insufficient mating material. */
6828                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6829                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6830                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6831
6832                      /* always flag draws, for judging claims */
6833                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6834
6835                      if(canAdjudicate && appData.materialDraws) {
6836                          /* but only adjudicate them if adjudication enabled */
6837                          if(engineOpponent) {
6838                            SendToProgram("force\n", engineOpponent); // suppress reply
6839                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6840                          }
6841                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6842                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6843                          return 1;
6844                      }
6845                 }
6846
6847                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6848                 if(gameInfo.variant == VariantXiangqi ?
6849                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6850                  : nrW + nrB == 4 &&
6851                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6852                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6853                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6854                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6855                    ) ) {
6856                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6857                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6858                           if(engineOpponent) {
6859                             SendToProgram("force\n", engineOpponent); // suppress reply
6860                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6861                           }
6862                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6863                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6864                           return 1;
6865                      }
6866                 } else moveCount = 6;
6867             }
6868         if (appData.debugMode) { int i;
6869             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6870                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6871                     appData.drawRepeats);
6872             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6873               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6874
6875         }
6876
6877         // Repetition draws and 50-move rule can be applied independently of legality testing
6878
6879                 /* Check for rep-draws */
6880                 count = 0;
6881                 for(k = forwardMostMove-2;
6882                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6883                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6884                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6885                     k-=2)
6886                 {   int rights=0;
6887                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6888                         /* compare castling rights */
6889                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6890                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6891                                 rights++; /* King lost rights, while rook still had them */
6892                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6893                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6894                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6895                                    rights++; /* but at least one rook lost them */
6896                         }
6897                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6898                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6899                                 rights++;
6900                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6901                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6902                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6903                                    rights++;
6904                         }
6905                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6906                             && appData.drawRepeats > 1) {
6907                              /* adjudicate after user-specified nr of repeats */
6908                              int result = GameIsDrawn;
6909                              char *details = "XBoard adjudication: repetition draw";
6910                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6911                                 // [HGM] xiangqi: check for forbidden perpetuals
6912                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6913                                 for(m=forwardMostMove; m>k; m-=2) {
6914                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6915                                         ourPerpetual = 0; // the current mover did not always check
6916                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6917                                         hisPerpetual = 0; // the opponent did not always check
6918                                 }
6919                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6920                                                                         ourPerpetual, hisPerpetual);
6921                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6922                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6923                                     details = "Xboard adjudication: perpetual checking";
6924                                 } else
6925                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6926                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6927                                 } else
6928                                 // Now check for perpetual chases
6929                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6930                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6931                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6932                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6933                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6934                                         details = "Xboard adjudication: perpetual chasing";
6935                                     } else
6936                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6937                                         break; // Abort repetition-checking loop.
6938                                 }
6939                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6940                              }
6941                              if(engineOpponent) {
6942                                SendToProgram("force\n", engineOpponent); // suppress reply
6943                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6944                              }
6945                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6946                              GameEnds( result, details, GE_XBOARD );
6947                              return 1;
6948                         }
6949                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6950                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6951                     }
6952                 }
6953
6954                 /* Now we test for 50-move draws. Determine ply count */
6955                 count = forwardMostMove;
6956                 /* look for last irreversble move */
6957                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6958                     count--;
6959                 /* if we hit starting position, add initial plies */
6960                 if( count == backwardMostMove )
6961                     count -= initialRulePlies;
6962                 count = forwardMostMove - count;
6963                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6964                         // adjust reversible move counter for checks in Xiangqi
6965                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6966                         if(i < backwardMostMove) i = backwardMostMove;
6967                         while(i <= forwardMostMove) {
6968                                 lastCheck = inCheck; // check evasion does not count
6969                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6970                                 if(inCheck || lastCheck) count--; // check does not count
6971                                 i++;
6972                         }
6973                 }
6974                 if( count >= 100)
6975                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6976                          /* this is used to judge if draw claims are legal */
6977                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6978                          if(engineOpponent) {
6979                            SendToProgram("force\n", engineOpponent); // suppress reply
6980                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6981                          }
6982                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6983                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6984                          return 1;
6985                 }
6986
6987                 /* if draw offer is pending, treat it as a draw claim
6988                  * when draw condition present, to allow engines a way to
6989                  * claim draws before making their move to avoid a race
6990                  * condition occurring after their move
6991                  */
6992                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6993                          char *p = NULL;
6994                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6995                              p = "Draw claim: 50-move rule";
6996                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6997                              p = "Draw claim: 3-fold repetition";
6998                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6999                              p = "Draw claim: insufficient mating material";
7000                          if( p != NULL && canAdjudicate) {
7001                              if(engineOpponent) {
7002                                SendToProgram("force\n", engineOpponent); // suppress reply
7003                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7004                              }
7005                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7006                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7007                              return 1;
7008                          }
7009                 }
7010
7011                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7012                     if(engineOpponent) {
7013                       SendToProgram("force\n", engineOpponent); // suppress reply
7014                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7015                     }
7016                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7017                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7018                     return 1;
7019                 }
7020         return 0;
7021 }
7022
7023 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7024 {   // [HGM] book: this routine intercepts moves to simulate book replies
7025     char *bookHit = NULL;
7026
7027     //first determine if the incoming move brings opponent into his book
7028     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7029         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7030     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7031     if(bookHit != NULL && !cps->bookSuspend) {
7032         // make sure opponent is not going to reply after receiving move to book position
7033         SendToProgram("force\n", cps);
7034         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7035     }
7036     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7037     // now arrange restart after book miss
7038     if(bookHit) {
7039         // after a book hit we never send 'go', and the code after the call to this routine
7040         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7041         char buf[MSG_SIZ];
7042         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7043         SendToProgram(buf, cps);
7044         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7045     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7046         SendToProgram("go\n", cps);
7047         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7048     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7049         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7050             SendToProgram("go\n", cps);
7051         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7052     }
7053     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7054 }
7055
7056 char *savedMessage;
7057 ChessProgramState *savedState;
7058 void DeferredBookMove(void)
7059 {
7060         if(savedState->lastPing != savedState->lastPong)
7061                     ScheduleDelayedEvent(DeferredBookMove, 10);
7062         else
7063         HandleMachineMove(savedMessage, savedState);
7064 }
7065
7066 void
7067 HandleMachineMove(message, cps)
7068      char *message;
7069      ChessProgramState *cps;
7070 {
7071     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7072     char realname[MSG_SIZ];
7073     int fromX, fromY, toX, toY;
7074     ChessMove moveType;
7075     char promoChar;
7076     char *p;
7077     int machineWhite;
7078     char *bookHit;
7079
7080     cps->userError = 0;
7081
7082 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7083     /*
7084      * Kludge to ignore BEL characters
7085      */
7086     while (*message == '\007') message++;
7087
7088     /*
7089      * [HGM] engine debug message: ignore lines starting with '#' character
7090      */
7091     if(cps->debug && *message == '#') return;
7092
7093     /*
7094      * Look for book output
7095      */
7096     if (cps == &first && bookRequested) {
7097         if (message[0] == '\t' || message[0] == ' ') {
7098             /* Part of the book output is here; append it */
7099             strcat(bookOutput, message);
7100             strcat(bookOutput, "  \n");
7101             return;
7102         } else if (bookOutput[0] != NULLCHAR) {
7103             /* All of book output has arrived; display it */
7104             char *p = bookOutput;
7105             while (*p != NULLCHAR) {
7106                 if (*p == '\t') *p = ' ';
7107                 p++;
7108             }
7109             DisplayInformation(bookOutput);
7110             bookRequested = FALSE;
7111             /* Fall through to parse the current output */
7112         }
7113     }
7114
7115     /*
7116      * Look for machine move.
7117      */
7118     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7119         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7120     {
7121         /* This method is only useful on engines that support ping */
7122         if (cps->lastPing != cps->lastPong) {
7123           if (gameMode == BeginningOfGame) {
7124             /* Extra move from before last new; ignore */
7125             if (appData.debugMode) {
7126                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7127             }
7128           } else {
7129             if (appData.debugMode) {
7130                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7131                         cps->which, gameMode);
7132             }
7133
7134             SendToProgram("undo\n", cps);
7135           }
7136           return;
7137         }
7138
7139         switch (gameMode) {
7140           case BeginningOfGame:
7141             /* Extra move from before last reset; ignore */
7142             if (appData.debugMode) {
7143                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7144             }
7145             return;
7146
7147           case EndOfGame:
7148           case IcsIdle:
7149           default:
7150             /* Extra move after we tried to stop.  The mode test is
7151                not a reliable way of detecting this problem, but it's
7152                the best we can do on engines that don't support ping.
7153             */
7154             if (appData.debugMode) {
7155                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7156                         cps->which, gameMode);
7157             }
7158             SendToProgram("undo\n", cps);
7159             return;
7160
7161           case MachinePlaysWhite:
7162           case IcsPlayingWhite:
7163             machineWhite = TRUE;
7164             break;
7165
7166           case MachinePlaysBlack:
7167           case IcsPlayingBlack:
7168             machineWhite = FALSE;
7169             break;
7170
7171           case TwoMachinesPlay:
7172             machineWhite = (cps->twoMachinesColor[0] == 'w');
7173             break;
7174         }
7175         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7176             if (appData.debugMode) {
7177                 fprintf(debugFP,
7178                         "Ignoring move out of turn by %s, gameMode %d"
7179                         ", forwardMost %d\n",
7180                         cps->which, gameMode, forwardMostMove);
7181             }
7182             return;
7183         }
7184
7185     if (appData.debugMode) { int f = forwardMostMove;
7186         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7187                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7188                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7189     }
7190         if(cps->alphaRank) AlphaRank(machineMove, 4);
7191         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7192                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7193             /* Machine move could not be parsed; ignore it. */
7194           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7195                     machineMove, cps->which);
7196             DisplayError(buf1, 0);
7197             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7198                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7199             if (gameMode == TwoMachinesPlay) {
7200               GameEnds(machineWhite ? BlackWins : WhiteWins,
7201                        buf1, GE_XBOARD);
7202             }
7203             return;
7204         }
7205
7206         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7207         /* So we have to redo legality test with true e.p. status here,  */
7208         /* to make sure an illegal e.p. capture does not slip through,   */
7209         /* to cause a forfeit on a justified illegal-move complaint      */
7210         /* of the opponent.                                              */
7211         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7212            ChessMove moveType;
7213            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7214                              fromY, fromX, toY, toX, promoChar);
7215             if (appData.debugMode) {
7216                 int i;
7217                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7218                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7219                 fprintf(debugFP, "castling rights\n");
7220             }
7221             if(moveType == IllegalMove) {
7222               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7223                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7224                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7225                            buf1, GE_XBOARD);
7226                 return;
7227            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7228            /* [HGM] Kludge to handle engines that send FRC-style castling
7229               when they shouldn't (like TSCP-Gothic) */
7230            switch(moveType) {
7231              case WhiteASideCastleFR:
7232              case BlackASideCastleFR:
7233                toX+=2;
7234                currentMoveString[2]++;
7235                break;
7236              case WhiteHSideCastleFR:
7237              case BlackHSideCastleFR:
7238                toX--;
7239                currentMoveString[2]--;
7240                break;
7241              default: ; // nothing to do, but suppresses warning of pedantic compilers
7242            }
7243         }
7244         hintRequested = FALSE;
7245         lastHint[0] = NULLCHAR;
7246         bookRequested = FALSE;
7247         /* Program may be pondering now */
7248         cps->maybeThinking = TRUE;
7249         if (cps->sendTime == 2) cps->sendTime = 1;
7250         if (cps->offeredDraw) cps->offeredDraw--;
7251
7252         /* currentMoveString is set as a side-effect of ParseOneMove */
7253         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7254         strcat(machineMove, "\n");
7255         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7256
7257         /* [AS] Save move info*/
7258         pvInfoList[ forwardMostMove ].score = programStats.score;
7259         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7260         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7261
7262         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7263
7264         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7265         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7266             int count = 0;
7267
7268             while( count < adjudicateLossPlies ) {
7269                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7270
7271                 if( count & 1 ) {
7272                     score = -score; /* Flip score for winning side */
7273                 }
7274
7275                 if( score > adjudicateLossThreshold ) {
7276                     break;
7277                 }
7278
7279                 count++;
7280             }
7281
7282             if( count >= adjudicateLossPlies ) {
7283                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7284
7285                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7286                     "Xboard adjudication",
7287                     GE_XBOARD );
7288
7289                 return;
7290             }
7291         }
7292
7293         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7294
7295 #if ZIPPY
7296         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7297             first.initDone) {
7298           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7299                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7300                 SendToICS("draw ");
7301                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7302           }
7303           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7304           ics_user_moved = 1;
7305           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7306                 char buf[3*MSG_SIZ];
7307
7308                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7309                         programStats.score / 100.,
7310                         programStats.depth,
7311                         programStats.time / 100.,
7312                         (unsigned int)programStats.nodes,
7313                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7314                         programStats.movelist);
7315                 SendToICS(buf);
7316 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7317           }
7318         }
7319 #endif
7320
7321         /* [AS] Clear stats for next move */
7322         ClearProgramStats();
7323         thinkOutput[0] = NULLCHAR;
7324         hiddenThinkOutputState = 0;
7325
7326         bookHit = NULL;
7327         if (gameMode == TwoMachinesPlay) {
7328             /* [HGM] relaying draw offers moved to after reception of move */
7329             /* and interpreting offer as claim if it brings draw condition */
7330             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7331                 SendToProgram("draw\n", cps->other);
7332             }
7333             if (cps->other->sendTime) {
7334                 SendTimeRemaining(cps->other,
7335                                   cps->other->twoMachinesColor[0] == 'w');
7336             }
7337             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7338             if (firstMove && !bookHit) {
7339                 firstMove = FALSE;
7340                 if (cps->other->useColors) {
7341                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7342                 }
7343                 SendToProgram("go\n", cps->other);
7344             }
7345             cps->other->maybeThinking = TRUE;
7346         }
7347
7348         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7349
7350         if (!pausing && appData.ringBellAfterMoves) {
7351             RingBell();
7352         }
7353
7354         /*
7355          * Reenable menu items that were disabled while
7356          * machine was thinking
7357          */
7358         if (gameMode != TwoMachinesPlay)
7359             SetUserThinkingEnables();
7360
7361         // [HGM] book: after book hit opponent has received move and is now in force mode
7362         // force the book reply into it, and then fake that it outputted this move by jumping
7363         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7364         if(bookHit) {
7365                 static char bookMove[MSG_SIZ]; // a bit generous?
7366
7367                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7368                 strcat(bookMove, bookHit);
7369                 message = bookMove;
7370                 cps = cps->other;
7371                 programStats.nodes = programStats.depth = programStats.time =
7372                 programStats.score = programStats.got_only_move = 0;
7373                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7374
7375                 if(cps->lastPing != cps->lastPong) {
7376                     savedMessage = message; // args for deferred call
7377                     savedState = cps;
7378                     ScheduleDelayedEvent(DeferredBookMove, 10);
7379                     return;
7380                 }
7381                 goto FakeBookMove;
7382         }
7383
7384         return;
7385     }
7386
7387     /* Set special modes for chess engines.  Later something general
7388      *  could be added here; for now there is just one kludge feature,
7389      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7390      *  when "xboard" is given as an interactive command.
7391      */
7392     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7393         cps->useSigint = FALSE;
7394         cps->useSigterm = FALSE;
7395     }
7396     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7397       ParseFeatures(message+8, cps);
7398       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7399     }
7400
7401     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7402      * want this, I was asked to put it in, and obliged.
7403      */
7404     if (!strncmp(message, "setboard ", 9)) {
7405         Board initial_position;
7406
7407         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7408
7409         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7410             DisplayError(_("Bad FEN received from engine"), 0);
7411             return ;
7412         } else {
7413            Reset(TRUE, FALSE);
7414            CopyBoard(boards[0], initial_position);
7415            initialRulePlies = FENrulePlies;
7416            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7417            else gameMode = MachinePlaysBlack;
7418            DrawPosition(FALSE, boards[currentMove]);
7419         }
7420         return;
7421     }
7422
7423     /*
7424      * Look for communication commands
7425      */
7426     if (!strncmp(message, "telluser ", 9)) {
7427         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7428         DisplayNote(message + 9);
7429         return;
7430     }
7431     if (!strncmp(message, "tellusererror ", 14)) {
7432         cps->userError = 1;
7433         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7434         DisplayError(message + 14, 0);
7435         return;
7436     }
7437     if (!strncmp(message, "tellopponent ", 13)) {
7438       if (appData.icsActive) {
7439         if (loggedOn) {
7440           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7441           SendToICS(buf1);
7442         }
7443       } else {
7444         DisplayNote(message + 13);
7445       }
7446       return;
7447     }
7448     if (!strncmp(message, "tellothers ", 11)) {
7449       if (appData.icsActive) {
7450         if (loggedOn) {
7451           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7452           SendToICS(buf1);
7453         }
7454       }
7455       return;
7456     }
7457     if (!strncmp(message, "tellall ", 8)) {
7458       if (appData.icsActive) {
7459         if (loggedOn) {
7460           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7461           SendToICS(buf1);
7462         }
7463       } else {
7464         DisplayNote(message + 8);
7465       }
7466       return;
7467     }
7468     if (strncmp(message, "warning", 7) == 0) {
7469         /* Undocumented feature, use tellusererror in new code */
7470         DisplayError(message, 0);
7471         return;
7472     }
7473     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7474         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7475         strcat(realname, " query");
7476         AskQuestion(realname, buf2, buf1, cps->pr);
7477         return;
7478     }
7479     /* Commands from the engine directly to ICS.  We don't allow these to be
7480      *  sent until we are logged on. Crafty kibitzes have been known to
7481      *  interfere with the login process.
7482      */
7483     if (loggedOn) {
7484         if (!strncmp(message, "tellics ", 8)) {
7485             SendToICS(message + 8);
7486             SendToICS("\n");
7487             return;
7488         }
7489         if (!strncmp(message, "tellicsnoalias ", 15)) {
7490             SendToICS(ics_prefix);
7491             SendToICS(message + 15);
7492             SendToICS("\n");
7493             return;
7494         }
7495         /* The following are for backward compatibility only */
7496         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7497             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7498             SendToICS(ics_prefix);
7499             SendToICS(message);
7500             SendToICS("\n");
7501             return;
7502         }
7503     }
7504     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7505         return;
7506     }
7507     /*
7508      * If the move is illegal, cancel it and redraw the board.
7509      * Also deal with other error cases.  Matching is rather loose
7510      * here to accommodate engines written before the spec.
7511      */
7512     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7513         strncmp(message, "Error", 5) == 0) {
7514         if (StrStr(message, "name") ||
7515             StrStr(message, "rating") || StrStr(message, "?") ||
7516             StrStr(message, "result") || StrStr(message, "board") ||
7517             StrStr(message, "bk") || StrStr(message, "computer") ||
7518             StrStr(message, "variant") || StrStr(message, "hint") ||
7519             StrStr(message, "random") || StrStr(message, "depth") ||
7520             StrStr(message, "accepted")) {
7521             return;
7522         }
7523         if (StrStr(message, "protover")) {
7524           /* Program is responding to input, so it's apparently done
7525              initializing, and this error message indicates it is
7526              protocol version 1.  So we don't need to wait any longer
7527              for it to initialize and send feature commands. */
7528           FeatureDone(cps, 1);
7529           cps->protocolVersion = 1;
7530           return;
7531         }
7532         cps->maybeThinking = FALSE;
7533
7534         if (StrStr(message, "draw")) {
7535             /* Program doesn't have "draw" command */
7536             cps->sendDrawOffers = 0;
7537             return;
7538         }
7539         if (cps->sendTime != 1 &&
7540             (StrStr(message, "time") || StrStr(message, "otim"))) {
7541           /* Program apparently doesn't have "time" or "otim" command */
7542           cps->sendTime = 0;
7543           return;
7544         }
7545         if (StrStr(message, "analyze")) {
7546             cps->analysisSupport = FALSE;
7547             cps->analyzing = FALSE;
7548             Reset(FALSE, TRUE);
7549             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7550             DisplayError(buf2, 0);
7551             return;
7552         }
7553         if (StrStr(message, "(no matching move)st")) {
7554           /* Special kludge for GNU Chess 4 only */
7555           cps->stKludge = TRUE;
7556           SendTimeControl(cps, movesPerSession, timeControl,
7557                           timeIncrement, appData.searchDepth,
7558                           searchTime);
7559           return;
7560         }
7561         if (StrStr(message, "(no matching move)sd")) {
7562           /* Special kludge for GNU Chess 4 only */
7563           cps->sdKludge = TRUE;
7564           SendTimeControl(cps, movesPerSession, timeControl,
7565                           timeIncrement, appData.searchDepth,
7566                           searchTime);
7567           return;
7568         }
7569         if (!StrStr(message, "llegal")) {
7570             return;
7571         }
7572         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7573             gameMode == IcsIdle) return;
7574         if (forwardMostMove <= backwardMostMove) return;
7575         if (pausing) PauseEvent();
7576       if(appData.forceIllegal) {
7577             // [HGM] illegal: machine refused move; force position after move into it
7578           SendToProgram("force\n", cps);
7579           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7580                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7581                 // when black is to move, while there might be nothing on a2 or black
7582                 // might already have the move. So send the board as if white has the move.
7583                 // But first we must change the stm of the engine, as it refused the last move
7584                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7585                 if(WhiteOnMove(forwardMostMove)) {
7586                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7587                     SendBoard(cps, forwardMostMove); // kludgeless board
7588                 } else {
7589                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7590                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7591                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7592                 }
7593           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7594             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7595                  gameMode == TwoMachinesPlay)
7596               SendToProgram("go\n", cps);
7597             return;
7598       } else
7599         if (gameMode == PlayFromGameFile) {
7600             /* Stop reading this game file */
7601             gameMode = EditGame;
7602             ModeHighlight();
7603         }
7604         currentMove = forwardMostMove-1;
7605         DisplayMove(currentMove-1); /* before DisplayMoveError */
7606         SwitchClocks(forwardMostMove-1); // [HGM] race
7607         DisplayBothClocks();
7608         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7609                 parseList[currentMove], cps->which);
7610         DisplayMoveError(buf1);
7611         DrawPosition(FALSE, boards[currentMove]);
7612
7613         /* [HGM] illegal-move claim should forfeit game when Xboard */
7614         /* only passes fully legal moves                            */
7615         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7616             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7617                                 "False illegal-move claim", GE_XBOARD );
7618         }
7619         return;
7620     }
7621     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7622         /* Program has a broken "time" command that
7623            outputs a string not ending in newline.
7624            Don't use it. */
7625         cps->sendTime = 0;
7626     }
7627
7628     /*
7629      * If chess program startup fails, exit with an error message.
7630      * Attempts to recover here are futile.
7631      */
7632     if ((StrStr(message, "unknown host") != NULL)
7633         || (StrStr(message, "No remote directory") != NULL)
7634         || (StrStr(message, "not found") != NULL)
7635         || (StrStr(message, "No such file") != NULL)
7636         || (StrStr(message, "can't alloc") != NULL)
7637         || (StrStr(message, "Permission denied") != NULL)) {
7638
7639         cps->maybeThinking = FALSE;
7640         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7641                 cps->which, cps->program, cps->host, message);
7642         RemoveInputSource(cps->isr);
7643         DisplayFatalError(buf1, 0, 1);
7644         return;
7645     }
7646
7647     /*
7648      * Look for hint output
7649      */
7650     if (sscanf(message, "Hint: %s", buf1) == 1) {
7651         if (cps == &first && hintRequested) {
7652             hintRequested = FALSE;
7653             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7654                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7655                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7656                                     PosFlags(forwardMostMove),
7657                                     fromY, fromX, toY, toX, promoChar, buf1);
7658                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7659                 DisplayInformation(buf2);
7660             } else {
7661                 /* Hint move could not be parsed!? */
7662               snprintf(buf2, sizeof(buf2),
7663                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7664                         buf1, cps->which);
7665                 DisplayError(buf2, 0);
7666             }
7667         } else {
7668           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7669         }
7670         return;
7671     }
7672
7673     /*
7674      * Ignore other messages if game is not in progress
7675      */
7676     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7677         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7678
7679     /*
7680      * look for win, lose, draw, or draw offer
7681      */
7682     if (strncmp(message, "1-0", 3) == 0) {
7683         char *p, *q, *r = "";
7684         p = strchr(message, '{');
7685         if (p) {
7686             q = strchr(p, '}');
7687             if (q) {
7688                 *q = NULLCHAR;
7689                 r = p + 1;
7690             }
7691         }
7692         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7693         return;
7694     } else if (strncmp(message, "0-1", 3) == 0) {
7695         char *p, *q, *r = "";
7696         p = strchr(message, '{');
7697         if (p) {
7698             q = strchr(p, '}');
7699             if (q) {
7700                 *q = NULLCHAR;
7701                 r = p + 1;
7702             }
7703         }
7704         /* Kludge for Arasan 4.1 bug */
7705         if (strcmp(r, "Black resigns") == 0) {
7706             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7707             return;
7708         }
7709         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7710         return;
7711     } else if (strncmp(message, "1/2", 3) == 0) {
7712         char *p, *q, *r = "";
7713         p = strchr(message, '{');
7714         if (p) {
7715             q = strchr(p, '}');
7716             if (q) {
7717                 *q = NULLCHAR;
7718                 r = p + 1;
7719             }
7720         }
7721
7722         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7723         return;
7724
7725     } else if (strncmp(message, "White resign", 12) == 0) {
7726         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7727         return;
7728     } else if (strncmp(message, "Black resign", 12) == 0) {
7729         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7730         return;
7731     } else if (strncmp(message, "White matches", 13) == 0 ||
7732                strncmp(message, "Black matches", 13) == 0   ) {
7733         /* [HGM] ignore GNUShogi noises */
7734         return;
7735     } else if (strncmp(message, "White", 5) == 0 &&
7736                message[5] != '(' &&
7737                StrStr(message, "Black") == NULL) {
7738         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7739         return;
7740     } else if (strncmp(message, "Black", 5) == 0 &&
7741                message[5] != '(') {
7742         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7743         return;
7744     } else if (strcmp(message, "resign") == 0 ||
7745                strcmp(message, "computer resigns") == 0) {
7746         switch (gameMode) {
7747           case MachinePlaysBlack:
7748           case IcsPlayingBlack:
7749             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7750             break;
7751           case MachinePlaysWhite:
7752           case IcsPlayingWhite:
7753             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7754             break;
7755           case TwoMachinesPlay:
7756             if (cps->twoMachinesColor[0] == 'w')
7757               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7758             else
7759               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7760             break;
7761           default:
7762             /* can't happen */
7763             break;
7764         }
7765         return;
7766     } else if (strncmp(message, "opponent mates", 14) == 0) {
7767         switch (gameMode) {
7768           case MachinePlaysBlack:
7769           case IcsPlayingBlack:
7770             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7771             break;
7772           case MachinePlaysWhite:
7773           case IcsPlayingWhite:
7774             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7775             break;
7776           case TwoMachinesPlay:
7777             if (cps->twoMachinesColor[0] == 'w')
7778               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7779             else
7780               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7781             break;
7782           default:
7783             /* can't happen */
7784             break;
7785         }
7786         return;
7787     } else if (strncmp(message, "computer mates", 14) == 0) {
7788         switch (gameMode) {
7789           case MachinePlaysBlack:
7790           case IcsPlayingBlack:
7791             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7792             break;
7793           case MachinePlaysWhite:
7794           case IcsPlayingWhite:
7795             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7796             break;
7797           case TwoMachinesPlay:
7798             if (cps->twoMachinesColor[0] == 'w')
7799               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7800             else
7801               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7802             break;
7803           default:
7804             /* can't happen */
7805             break;
7806         }
7807         return;
7808     } else if (strncmp(message, "checkmate", 9) == 0) {
7809         if (WhiteOnMove(forwardMostMove)) {
7810             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7811         } else {
7812             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7813         }
7814         return;
7815     } else if (strstr(message, "Draw") != NULL ||
7816                strstr(message, "game is a draw") != NULL) {
7817         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7818         return;
7819     } else if (strstr(message, "offer") != NULL &&
7820                strstr(message, "draw") != NULL) {
7821 #if ZIPPY
7822         if (appData.zippyPlay && first.initDone) {
7823             /* Relay offer to ICS */
7824             SendToICS(ics_prefix);
7825             SendToICS("draw\n");
7826         }
7827 #endif
7828         cps->offeredDraw = 2; /* valid until this engine moves twice */
7829         if (gameMode == TwoMachinesPlay) {
7830             if (cps->other->offeredDraw) {
7831                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7832             /* [HGM] in two-machine mode we delay relaying draw offer      */
7833             /* until after we also have move, to see if it is really claim */
7834             }
7835         } else if (gameMode == MachinePlaysWhite ||
7836                    gameMode == MachinePlaysBlack) {
7837           if (userOfferedDraw) {
7838             DisplayInformation(_("Machine accepts your draw offer"));
7839             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7840           } else {
7841             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7842           }
7843         }
7844     }
7845
7846
7847     /*
7848      * Look for thinking output
7849      */
7850     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7851           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7852                                 ) {
7853         int plylev, mvleft, mvtot, curscore, time;
7854         char mvname[MOVE_LEN];
7855         u64 nodes; // [DM]
7856         char plyext;
7857         int ignore = FALSE;
7858         int prefixHint = FALSE;
7859         mvname[0] = NULLCHAR;
7860
7861         switch (gameMode) {
7862           case MachinePlaysBlack:
7863           case IcsPlayingBlack:
7864             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7865             break;
7866           case MachinePlaysWhite:
7867           case IcsPlayingWhite:
7868             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7869             break;
7870           case AnalyzeMode:
7871           case AnalyzeFile:
7872             break;
7873           case IcsObserving: /* [DM] icsEngineAnalyze */
7874             if (!appData.icsEngineAnalyze) ignore = TRUE;
7875             break;
7876           case TwoMachinesPlay:
7877             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7878                 ignore = TRUE;
7879             }
7880             break;
7881           default:
7882             ignore = TRUE;
7883             break;
7884         }
7885
7886         if (!ignore) {
7887             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7888             buf1[0] = NULLCHAR;
7889             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7890                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7891
7892                 if (plyext != ' ' && plyext != '\t') {
7893                     time *= 100;
7894                 }
7895
7896                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7897                 if( cps->scoreIsAbsolute &&
7898                     ( gameMode == MachinePlaysBlack ||
7899                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7900                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7901                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7902                      !WhiteOnMove(currentMove)
7903                     ) )
7904                 {
7905                     curscore = -curscore;
7906                 }
7907
7908
7909                 tempStats.depth = plylev;
7910                 tempStats.nodes = nodes;
7911                 tempStats.time = time;
7912                 tempStats.score = curscore;
7913                 tempStats.got_only_move = 0;
7914
7915                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7916                         int ticklen;
7917
7918                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7919                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7920                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7921                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7922                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7923                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7924                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7925                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7926                 }
7927
7928                 /* Buffer overflow protection */
7929                 if (buf1[0] != NULLCHAR) {
7930                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7931                         && appData.debugMode) {
7932                         fprintf(debugFP,
7933                                 "PV is too long; using the first %u bytes.\n",
7934                                 (unsigned) sizeof(tempStats.movelist) - 1);
7935                     }
7936
7937                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
7938                 } else {
7939                     sprintf(tempStats.movelist, " no PV\n");
7940                 }
7941
7942                 if (tempStats.seen_stat) {
7943                     tempStats.ok_to_send = 1;
7944                 }
7945
7946                 if (strchr(tempStats.movelist, '(') != NULL) {
7947                     tempStats.line_is_book = 1;
7948                     tempStats.nr_moves = 0;
7949                     tempStats.moves_left = 0;
7950                 } else {
7951                     tempStats.line_is_book = 0;
7952                 }
7953
7954                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7955                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7956
7957                 SendProgramStatsToFrontend( cps, &tempStats );
7958
7959                 /*
7960                     [AS] Protect the thinkOutput buffer from overflow... this
7961                     is only useful if buf1 hasn't overflowed first!
7962                 */
7963                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
7964                          plylev,
7965                          (gameMode == TwoMachinesPlay ?
7966                           ToUpper(cps->twoMachinesColor[0]) : ' '),
7967                          ((double) curscore) / 100.0,
7968                          prefixHint ? lastHint : "",
7969                          prefixHint ? " " : "" );
7970
7971                 if( buf1[0] != NULLCHAR ) {
7972                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7973
7974                     if( strlen(buf1) > max_len ) {
7975                         if( appData.debugMode) {
7976                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7977                         }
7978                         buf1[max_len+1] = '\0';
7979                     }
7980
7981                     strcat( thinkOutput, buf1 );
7982                 }
7983
7984                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7985                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7986                     DisplayMove(currentMove - 1);
7987                 }
7988                 return;
7989
7990             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7991                 /* crafty (9.25+) says "(only move) <move>"
7992                  * if there is only 1 legal move
7993                  */
7994                 sscanf(p, "(only move) %s", buf1);
7995                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
7996                 sprintf(programStats.movelist, "%s (only move)", buf1);
7997                 programStats.depth = 1;
7998                 programStats.nr_moves = 1;
7999                 programStats.moves_left = 1;
8000                 programStats.nodes = 1;
8001                 programStats.time = 1;
8002                 programStats.got_only_move = 1;
8003
8004                 /* Not really, but we also use this member to
8005                    mean "line isn't going to change" (Crafty
8006                    isn't searching, so stats won't change) */
8007                 programStats.line_is_book = 1;
8008
8009                 SendProgramStatsToFrontend( cps, &programStats );
8010
8011                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8012                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8013                     DisplayMove(currentMove - 1);
8014                 }
8015                 return;
8016             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8017                               &time, &nodes, &plylev, &mvleft,
8018                               &mvtot, mvname) >= 5) {
8019                 /* The stat01: line is from Crafty (9.29+) in response
8020                    to the "." command */
8021                 programStats.seen_stat = 1;
8022                 cps->maybeThinking = TRUE;
8023
8024                 if (programStats.got_only_move || !appData.periodicUpdates)
8025                   return;
8026
8027                 programStats.depth = plylev;
8028                 programStats.time = time;
8029                 programStats.nodes = nodes;
8030                 programStats.moves_left = mvleft;
8031                 programStats.nr_moves = mvtot;
8032                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8033                 programStats.ok_to_send = 1;
8034                 programStats.movelist[0] = '\0';
8035
8036                 SendProgramStatsToFrontend( cps, &programStats );
8037
8038                 return;
8039
8040             } else if (strncmp(message,"++",2) == 0) {
8041                 /* Crafty 9.29+ outputs this */
8042                 programStats.got_fail = 2;
8043                 return;
8044
8045             } else if (strncmp(message,"--",2) == 0) {
8046                 /* Crafty 9.29+ outputs this */
8047                 programStats.got_fail = 1;
8048                 return;
8049
8050             } else if (thinkOutput[0] != NULLCHAR &&
8051                        strncmp(message, "    ", 4) == 0) {
8052                 unsigned message_len;
8053
8054                 p = message;
8055                 while (*p && *p == ' ') p++;
8056
8057                 message_len = strlen( p );
8058
8059                 /* [AS] Avoid buffer overflow */
8060                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8061                     strcat(thinkOutput, " ");
8062                     strcat(thinkOutput, p);
8063                 }
8064
8065                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8066                     strcat(programStats.movelist, " ");
8067                     strcat(programStats.movelist, p);
8068                 }
8069
8070                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8071                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8072                     DisplayMove(currentMove - 1);
8073                 }
8074                 return;
8075             }
8076         }
8077         else {
8078             buf1[0] = NULLCHAR;
8079
8080             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8081                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8082             {
8083                 ChessProgramStats cpstats;
8084
8085                 if (plyext != ' ' && plyext != '\t') {
8086                     time *= 100;
8087                 }
8088
8089                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8090                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8091                     curscore = -curscore;
8092                 }
8093
8094                 cpstats.depth = plylev;
8095                 cpstats.nodes = nodes;
8096                 cpstats.time = time;
8097                 cpstats.score = curscore;
8098                 cpstats.got_only_move = 0;
8099                 cpstats.movelist[0] = '\0';
8100
8101                 if (buf1[0] != NULLCHAR) {
8102                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8103                 }
8104
8105                 cpstats.ok_to_send = 0;
8106                 cpstats.line_is_book = 0;
8107                 cpstats.nr_moves = 0;
8108                 cpstats.moves_left = 0;
8109
8110                 SendProgramStatsToFrontend( cps, &cpstats );
8111             }
8112         }
8113     }
8114 }
8115
8116
8117 /* Parse a game score from the character string "game", and
8118    record it as the history of the current game.  The game
8119    score is NOT assumed to start from the standard position.
8120    The display is not updated in any way.
8121    */
8122 void
8123 ParseGameHistory(game)
8124      char *game;
8125 {
8126     ChessMove moveType;
8127     int fromX, fromY, toX, toY, boardIndex;
8128     char promoChar;
8129     char *p, *q;
8130     char buf[MSG_SIZ];
8131
8132     if (appData.debugMode)
8133       fprintf(debugFP, "Parsing game history: %s\n", game);
8134
8135     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8136     gameInfo.site = StrSave(appData.icsHost);
8137     gameInfo.date = PGNDate();
8138     gameInfo.round = StrSave("-");
8139
8140     /* Parse out names of players */
8141     while (*game == ' ') game++;
8142     p = buf;
8143     while (*game != ' ') *p++ = *game++;
8144     *p = NULLCHAR;
8145     gameInfo.white = StrSave(buf);
8146     while (*game == ' ') game++;
8147     p = buf;
8148     while (*game != ' ' && *game != '\n') *p++ = *game++;
8149     *p = NULLCHAR;
8150     gameInfo.black = StrSave(buf);
8151
8152     /* Parse moves */
8153     boardIndex = blackPlaysFirst ? 1 : 0;
8154     yynewstr(game);
8155     for (;;) {
8156         yyboardindex = boardIndex;
8157         moveType = (ChessMove) yylex();
8158         switch (moveType) {
8159           case IllegalMove:             /* maybe suicide chess, etc. */
8160   if (appData.debugMode) {
8161     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8162     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8163     setbuf(debugFP, NULL);
8164   }
8165           case WhitePromotion:
8166           case BlackPromotion:
8167           case WhiteNonPromotion:
8168           case BlackNonPromotion:
8169           case NormalMove:
8170           case WhiteCapturesEnPassant:
8171           case BlackCapturesEnPassant:
8172           case WhiteKingSideCastle:
8173           case WhiteQueenSideCastle:
8174           case BlackKingSideCastle:
8175           case BlackQueenSideCastle:
8176           case WhiteKingSideCastleWild:
8177           case WhiteQueenSideCastleWild:
8178           case BlackKingSideCastleWild:
8179           case BlackQueenSideCastleWild:
8180           /* PUSH Fabien */
8181           case WhiteHSideCastleFR:
8182           case WhiteASideCastleFR:
8183           case BlackHSideCastleFR:
8184           case BlackASideCastleFR:
8185           /* POP Fabien */
8186             fromX = currentMoveString[0] - AAA;
8187             fromY = currentMoveString[1] - ONE;
8188             toX = currentMoveString[2] - AAA;
8189             toY = currentMoveString[3] - ONE;
8190             promoChar = currentMoveString[4];
8191             break;
8192           case WhiteDrop:
8193           case BlackDrop:
8194             fromX = moveType == WhiteDrop ?
8195               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8196             (int) CharToPiece(ToLower(currentMoveString[0]));
8197             fromY = DROP_RANK;
8198             toX = currentMoveString[2] - AAA;
8199             toY = currentMoveString[3] - ONE;
8200             promoChar = NULLCHAR;
8201             break;
8202           case AmbiguousMove:
8203             /* bug? */
8204             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8205   if (appData.debugMode) {
8206     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8207     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8208     setbuf(debugFP, NULL);
8209   }
8210             DisplayError(buf, 0);
8211             return;
8212           case ImpossibleMove:
8213             /* bug? */
8214             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8215   if (appData.debugMode) {
8216     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8217     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8218     setbuf(debugFP, NULL);
8219   }
8220             DisplayError(buf, 0);
8221             return;
8222           case EndOfFile:
8223             if (boardIndex < backwardMostMove) {
8224                 /* Oops, gap.  How did that happen? */
8225                 DisplayError(_("Gap in move list"), 0);
8226                 return;
8227             }
8228             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8229             if (boardIndex > forwardMostMove) {
8230                 forwardMostMove = boardIndex;
8231             }
8232             return;
8233           case ElapsedTime:
8234             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8235                 strcat(parseList[boardIndex-1], " ");
8236                 strcat(parseList[boardIndex-1], yy_text);
8237             }
8238             continue;
8239           case Comment:
8240           case PGNTag:
8241           case NAG:
8242           default:
8243             /* ignore */
8244             continue;
8245           case WhiteWins:
8246           case BlackWins:
8247           case GameIsDrawn:
8248           case GameUnfinished:
8249             if (gameMode == IcsExamining) {
8250                 if (boardIndex < backwardMostMove) {
8251                     /* Oops, gap.  How did that happen? */
8252                     return;
8253                 }
8254                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8255                 return;
8256             }
8257             gameInfo.result = moveType;
8258             p = strchr(yy_text, '{');
8259             if (p == NULL) p = strchr(yy_text, '(');
8260             if (p == NULL) {
8261                 p = yy_text;
8262                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8263             } else {
8264                 q = strchr(p, *p == '{' ? '}' : ')');
8265                 if (q != NULL) *q = NULLCHAR;
8266                 p++;
8267             }
8268             gameInfo.resultDetails = StrSave(p);
8269             continue;
8270         }
8271         if (boardIndex >= forwardMostMove &&
8272             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8273             backwardMostMove = blackPlaysFirst ? 1 : 0;
8274             return;
8275         }
8276         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8277                                  fromY, fromX, toY, toX, promoChar,
8278                                  parseList[boardIndex]);
8279         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8280         /* currentMoveString is set as a side-effect of yylex */
8281         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8282         strcat(moveList[boardIndex], "\n");
8283         boardIndex++;
8284         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8285         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8286           case MT_NONE:
8287           case MT_STALEMATE:
8288           default:
8289             break;
8290           case MT_CHECK:
8291             if(gameInfo.variant != VariantShogi)
8292                 strcat(parseList[boardIndex - 1], "+");
8293             break;
8294           case MT_CHECKMATE:
8295           case MT_STAINMATE:
8296             strcat(parseList[boardIndex - 1], "#");
8297             break;
8298         }
8299     }
8300 }
8301
8302
8303 /* Apply a move to the given board  */
8304 void
8305 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8306      int fromX, fromY, toX, toY;
8307      int promoChar;
8308      Board board;
8309 {
8310   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8311   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8312
8313     /* [HGM] compute & store e.p. status and castling rights for new position */
8314     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8315
8316       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8317       oldEP = (signed char)board[EP_STATUS];
8318       board[EP_STATUS] = EP_NONE;
8319
8320       if( board[toY][toX] != EmptySquare )
8321            board[EP_STATUS] = EP_CAPTURE;
8322
8323   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8324   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8325        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8326          
8327   if (fromY == DROP_RANK) {
8328         /* must be first */
8329         piece = board[toY][toX] = (ChessSquare) fromX;
8330   } else {
8331       int i;
8332
8333       if( board[fromY][fromX] == WhitePawn ) {
8334            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8335                board[EP_STATUS] = EP_PAWN_MOVE;
8336            if( toY-fromY==2) {
8337                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8338                         gameInfo.variant != VariantBerolina || toX < fromX)
8339                       board[EP_STATUS] = toX | berolina;
8340                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8341                         gameInfo.variant != VariantBerolina || toX > fromX)
8342                       board[EP_STATUS] = toX;
8343            }
8344       } else
8345       if( board[fromY][fromX] == BlackPawn ) {
8346            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8347                board[EP_STATUS] = EP_PAWN_MOVE;
8348            if( toY-fromY== -2) {
8349                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8350                         gameInfo.variant != VariantBerolina || toX < fromX)
8351                       board[EP_STATUS] = toX | berolina;
8352                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8353                         gameInfo.variant != VariantBerolina || toX > fromX)
8354                       board[EP_STATUS] = toX;
8355            }
8356        }
8357
8358        for(i=0; i<nrCastlingRights; i++) {
8359            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8360               board[CASTLING][i] == toX   && castlingRank[i] == toY
8361              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8362        }
8363
8364      if (fromX == toX && fromY == toY) return;
8365
8366      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8367      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8368      if(gameInfo.variant == VariantKnightmate)
8369          king += (int) WhiteUnicorn - (int) WhiteKing;
8370
8371     /* Code added by Tord: */
8372     /* FRC castling assumed when king captures friendly rook. */
8373     if (board[fromY][fromX] == WhiteKing &&
8374              board[toY][toX] == WhiteRook) {
8375       board[fromY][fromX] = EmptySquare;
8376       board[toY][toX] = EmptySquare;
8377       if(toX > fromX) {
8378         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8379       } else {
8380         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8381       }
8382     } else if (board[fromY][fromX] == BlackKing &&
8383                board[toY][toX] == BlackRook) {
8384       board[fromY][fromX] = EmptySquare;
8385       board[toY][toX] = EmptySquare;
8386       if(toX > fromX) {
8387         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8388       } else {
8389         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8390       }
8391     /* End of code added by Tord */
8392
8393     } else if (board[fromY][fromX] == king
8394         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8395         && toY == fromY && toX > fromX+1) {
8396         board[fromY][fromX] = EmptySquare;
8397         board[toY][toX] = king;
8398         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8399         board[fromY][BOARD_RGHT-1] = EmptySquare;
8400     } else if (board[fromY][fromX] == king
8401         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8402                && toY == fromY && toX < fromX-1) {
8403         board[fromY][fromX] = EmptySquare;
8404         board[toY][toX] = king;
8405         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8406         board[fromY][BOARD_LEFT] = EmptySquare;
8407     } else if (board[fromY][fromX] == WhitePawn
8408                && toY >= BOARD_HEIGHT-promoRank
8409                && gameInfo.variant != VariantXiangqi
8410                ) {
8411         /* white pawn promotion */
8412         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8413         if (board[toY][toX] == EmptySquare) {
8414             board[toY][toX] = WhiteQueen;
8415         }
8416         if(gameInfo.variant==VariantBughouse ||
8417            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8418             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8419         board[fromY][fromX] = EmptySquare;
8420     } else if ((fromY == BOARD_HEIGHT-4)
8421                && (toX != fromX)
8422                && gameInfo.variant != VariantXiangqi
8423                && gameInfo.variant != VariantBerolina
8424                && (board[fromY][fromX] == WhitePawn)
8425                && (board[toY][toX] == EmptySquare)) {
8426         board[fromY][fromX] = EmptySquare;
8427         board[toY][toX] = WhitePawn;
8428         captured = board[toY - 1][toX];
8429         board[toY - 1][toX] = EmptySquare;
8430     } else if ((fromY == BOARD_HEIGHT-4)
8431                && (toX == fromX)
8432                && gameInfo.variant == VariantBerolina
8433                && (board[fromY][fromX] == WhitePawn)
8434                && (board[toY][toX] == EmptySquare)) {
8435         board[fromY][fromX] = EmptySquare;
8436         board[toY][toX] = WhitePawn;
8437         if(oldEP & EP_BEROLIN_A) {
8438                 captured = board[fromY][fromX-1];
8439                 board[fromY][fromX-1] = EmptySquare;
8440         }else{  captured = board[fromY][fromX+1];
8441                 board[fromY][fromX+1] = EmptySquare;
8442         }
8443     } else if (board[fromY][fromX] == king
8444         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8445                && toY == fromY && toX > fromX+1) {
8446         board[fromY][fromX] = EmptySquare;
8447         board[toY][toX] = king;
8448         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8449         board[fromY][BOARD_RGHT-1] = EmptySquare;
8450     } else if (board[fromY][fromX] == king
8451         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8452                && toY == fromY && toX < fromX-1) {
8453         board[fromY][fromX] = EmptySquare;
8454         board[toY][toX] = king;
8455         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8456         board[fromY][BOARD_LEFT] = EmptySquare;
8457     } else if (fromY == 7 && fromX == 3
8458                && board[fromY][fromX] == BlackKing
8459                && toY == 7 && toX == 5) {
8460         board[fromY][fromX] = EmptySquare;
8461         board[toY][toX] = BlackKing;
8462         board[fromY][7] = EmptySquare;
8463         board[toY][4] = BlackRook;
8464     } else if (fromY == 7 && fromX == 3
8465                && board[fromY][fromX] == BlackKing
8466                && toY == 7 && toX == 1) {
8467         board[fromY][fromX] = EmptySquare;
8468         board[toY][toX] = BlackKing;
8469         board[fromY][0] = EmptySquare;
8470         board[toY][2] = BlackRook;
8471     } else if (board[fromY][fromX] == BlackPawn
8472                && toY < promoRank
8473                && gameInfo.variant != VariantXiangqi
8474                ) {
8475         /* black pawn promotion */
8476         board[toY][toX] = CharToPiece(ToLower(promoChar));
8477         if (board[toY][toX] == EmptySquare) {
8478             board[toY][toX] = BlackQueen;
8479         }
8480         if(gameInfo.variant==VariantBughouse ||
8481            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8482             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8483         board[fromY][fromX] = EmptySquare;
8484     } else if ((fromY == 3)
8485                && (toX != fromX)
8486                && gameInfo.variant != VariantXiangqi
8487                && gameInfo.variant != VariantBerolina
8488                && (board[fromY][fromX] == BlackPawn)
8489                && (board[toY][toX] == EmptySquare)) {
8490         board[fromY][fromX] = EmptySquare;
8491         board[toY][toX] = BlackPawn;
8492         captured = board[toY + 1][toX];
8493         board[toY + 1][toX] = EmptySquare;
8494     } else if ((fromY == 3)
8495                && (toX == fromX)
8496                && gameInfo.variant == VariantBerolina
8497                && (board[fromY][fromX] == BlackPawn)
8498                && (board[toY][toX] == EmptySquare)) {
8499         board[fromY][fromX] = EmptySquare;
8500         board[toY][toX] = BlackPawn;
8501         if(oldEP & EP_BEROLIN_A) {
8502                 captured = board[fromY][fromX-1];
8503                 board[fromY][fromX-1] = EmptySquare;
8504         }else{  captured = board[fromY][fromX+1];
8505                 board[fromY][fromX+1] = EmptySquare;
8506         }
8507     } else {
8508         board[toY][toX] = board[fromY][fromX];
8509         board[fromY][fromX] = EmptySquare;
8510     }
8511   }
8512
8513     if (gameInfo.holdingsWidth != 0) {
8514
8515       /* !!A lot more code needs to be written to support holdings  */
8516       /* [HGM] OK, so I have written it. Holdings are stored in the */
8517       /* penultimate board files, so they are automaticlly stored   */
8518       /* in the game history.                                       */
8519       if (fromY == DROP_RANK) {
8520         /* Delete from holdings, by decreasing count */
8521         /* and erasing image if necessary            */
8522         p = (int) fromX;
8523         if(p < (int) BlackPawn) { /* white drop */
8524              p -= (int)WhitePawn;
8525                  p = PieceToNumber((ChessSquare)p);
8526              if(p >= gameInfo.holdingsSize) p = 0;
8527              if(--board[p][BOARD_WIDTH-2] <= 0)
8528                   board[p][BOARD_WIDTH-1] = EmptySquare;
8529              if((int)board[p][BOARD_WIDTH-2] < 0)
8530                         board[p][BOARD_WIDTH-2] = 0;
8531         } else {                  /* black drop */
8532              p -= (int)BlackPawn;
8533                  p = PieceToNumber((ChessSquare)p);
8534              if(p >= gameInfo.holdingsSize) p = 0;
8535              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8536                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8537              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8538                         board[BOARD_HEIGHT-1-p][1] = 0;
8539         }
8540       }
8541       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8542           && gameInfo.variant != VariantBughouse        ) {
8543         /* [HGM] holdings: Add to holdings, if holdings exist */
8544         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8545                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8546                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8547         }
8548         p = (int) captured;
8549         if (p >= (int) BlackPawn) {
8550           p -= (int)BlackPawn;
8551           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8552                   /* in Shogi restore piece to its original  first */
8553                   captured = (ChessSquare) (DEMOTED captured);
8554                   p = DEMOTED p;
8555           }
8556           p = PieceToNumber((ChessSquare)p);
8557           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8558           board[p][BOARD_WIDTH-2]++;
8559           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8560         } else {
8561           p -= (int)WhitePawn;
8562           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8563                   captured = (ChessSquare) (DEMOTED captured);
8564                   p = DEMOTED p;
8565           }
8566           p = PieceToNumber((ChessSquare)p);
8567           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8568           board[BOARD_HEIGHT-1-p][1]++;
8569           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8570         }
8571       }
8572     } else if (gameInfo.variant == VariantAtomic) {
8573       if (captured != EmptySquare) {
8574         int y, x;
8575         for (y = toY-1; y <= toY+1; y++) {
8576           for (x = toX-1; x <= toX+1; x++) {
8577             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8578                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8579               board[y][x] = EmptySquare;
8580             }
8581           }
8582         }
8583         board[toY][toX] = EmptySquare;
8584       }
8585     }
8586     if(promoChar == '+') {
8587         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8588         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8589     } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
8590         board[toY][toX] = CharToPiece(promoChar);
8591     }
8592     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8593                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8594         // [HGM] superchess: take promotion piece out of holdings
8595         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8596         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8597             if(!--board[k][BOARD_WIDTH-2])
8598                 board[k][BOARD_WIDTH-1] = EmptySquare;
8599         } else {
8600             if(!--board[BOARD_HEIGHT-1-k][1])
8601                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8602         }
8603     }
8604
8605 }
8606
8607 /* Updates forwardMostMove */
8608 void
8609 MakeMove(fromX, fromY, toX, toY, promoChar)
8610      int fromX, fromY, toX, toY;
8611      int promoChar;
8612 {
8613 //    forwardMostMove++; // [HGM] bare: moved downstream
8614
8615     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8616         int timeLeft; static int lastLoadFlag=0; int king, piece;
8617         piece = boards[forwardMostMove][fromY][fromX];
8618         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8619         if(gameInfo.variant == VariantKnightmate)
8620             king += (int) WhiteUnicorn - (int) WhiteKing;
8621         if(forwardMostMove == 0) {
8622             if(blackPlaysFirst)
8623                 fprintf(serverMoves, "%s;", second.tidy);
8624             fprintf(serverMoves, "%s;", first.tidy);
8625             if(!blackPlaysFirst)
8626                 fprintf(serverMoves, "%s;", second.tidy);
8627         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8628         lastLoadFlag = loadFlag;
8629         // print base move
8630         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8631         // print castling suffix
8632         if( toY == fromY && piece == king ) {
8633             if(toX-fromX > 1)
8634                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8635             if(fromX-toX >1)
8636                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8637         }
8638         // e.p. suffix
8639         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8640              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8641              boards[forwardMostMove][toY][toX] == EmptySquare
8642              && fromX != toX && fromY != toY)
8643                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8644         // promotion suffix
8645         if(promoChar != NULLCHAR)
8646                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8647         if(!loadFlag) {
8648             fprintf(serverMoves, "/%d/%d",
8649                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8650             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8651             else                      timeLeft = blackTimeRemaining/1000;
8652             fprintf(serverMoves, "/%d", timeLeft);
8653         }
8654         fflush(serverMoves);
8655     }
8656
8657     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8658       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8659                         0, 1);
8660       return;
8661     }
8662     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8663     if (commentList[forwardMostMove+1] != NULL) {
8664         free(commentList[forwardMostMove+1]);
8665         commentList[forwardMostMove+1] = NULL;
8666     }
8667     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8668     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8669     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8670     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8671     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8672     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8673     gameInfo.result = GameUnfinished;
8674     if (gameInfo.resultDetails != NULL) {
8675         free(gameInfo.resultDetails);
8676         gameInfo.resultDetails = NULL;
8677     }
8678     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8679                               moveList[forwardMostMove - 1]);
8680     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8681                              PosFlags(forwardMostMove - 1),
8682                              fromY, fromX, toY, toX, promoChar,
8683                              parseList[forwardMostMove - 1]);
8684     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8685       case MT_NONE:
8686       case MT_STALEMATE:
8687       default:
8688         break;
8689       case MT_CHECK:
8690         if(gameInfo.variant != VariantShogi)
8691             strcat(parseList[forwardMostMove - 1], "+");
8692         break;
8693       case MT_CHECKMATE:
8694       case MT_STAINMATE:
8695         strcat(parseList[forwardMostMove - 1], "#");
8696         break;
8697     }
8698     if (appData.debugMode) {
8699         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8700     }
8701
8702 }
8703
8704 /* Updates currentMove if not pausing */
8705 void
8706 ShowMove(fromX, fromY, toX, toY)
8707 {
8708     int instant = (gameMode == PlayFromGameFile) ?
8709         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8710     if(appData.noGUI) return;
8711     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8712         if (!instant) {
8713             if (forwardMostMove == currentMove + 1) {
8714                 AnimateMove(boards[forwardMostMove - 1],
8715                             fromX, fromY, toX, toY);
8716             }
8717             if (appData.highlightLastMove) {
8718                 SetHighlights(fromX, fromY, toX, toY);
8719             }
8720         }
8721         currentMove = forwardMostMove;
8722     }
8723
8724     if (instant) return;
8725
8726     DisplayMove(currentMove - 1);
8727     DrawPosition(FALSE, boards[currentMove]);
8728     DisplayBothClocks();
8729     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8730 }
8731
8732 void SendEgtPath(ChessProgramState *cps)
8733 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8734         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8735
8736         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8737
8738         while(*p) {
8739             char c, *q = name+1, *r, *s;
8740
8741             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8742             while(*p && *p != ',') *q++ = *p++;
8743             *q++ = ':'; *q = 0;
8744             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8745                 strcmp(name, ",nalimov:") == 0 ) {
8746                 // take nalimov path from the menu-changeable option first, if it is defined
8747               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8748                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8749             } else
8750             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8751                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8752                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8753                 s = r = StrStr(s, ":") + 1; // beginning of path info
8754                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8755                 c = *r; *r = 0;             // temporarily null-terminate path info
8756                     *--q = 0;               // strip of trailig ':' from name
8757                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8758                 *r = c;
8759                 SendToProgram(buf,cps);     // send egtbpath command for this format
8760             }
8761             if(*p == ',') p++; // read away comma to position for next format name
8762         }
8763 }
8764
8765 void
8766 InitChessProgram(cps, setup)
8767      ChessProgramState *cps;
8768      int setup; /* [HGM] needed to setup FRC opening position */
8769 {
8770     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8771     if (appData.noChessProgram) return;
8772     hintRequested = FALSE;
8773     bookRequested = FALSE;
8774
8775     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8776     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8777     if(cps->memSize) { /* [HGM] memory */
8778       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8779         SendToProgram(buf, cps);
8780     }
8781     SendEgtPath(cps); /* [HGM] EGT */
8782     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8783       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8784         SendToProgram(buf, cps);
8785     }
8786
8787     SendToProgram(cps->initString, cps);
8788     if (gameInfo.variant != VariantNormal &&
8789         gameInfo.variant != VariantLoadable
8790         /* [HGM] also send variant if board size non-standard */
8791         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8792                                             ) {
8793       char *v = VariantName(gameInfo.variant);
8794       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8795         /* [HGM] in protocol 1 we have to assume all variants valid */
8796         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8797         DisplayFatalError(buf, 0, 1);
8798         return;
8799       }
8800
8801       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8802       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8803       if( gameInfo.variant == VariantXiangqi )
8804            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8805       if( gameInfo.variant == VariantShogi )
8806            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8807       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8808            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8809       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8810                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8811            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8812       if( gameInfo.variant == VariantCourier )
8813            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8814       if( gameInfo.variant == VariantSuper )
8815            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8816       if( gameInfo.variant == VariantGreat )
8817            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8818
8819       if(overruled) {
8820         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8821                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8822            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8823            if(StrStr(cps->variants, b) == NULL) {
8824                // specific sized variant not known, check if general sizing allowed
8825                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8826                    if(StrStr(cps->variants, "boardsize") == NULL) {
8827                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8828                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8829                        DisplayFatalError(buf, 0, 1);
8830                        return;
8831                    }
8832                    /* [HGM] here we really should compare with the maximum supported board size */
8833                }
8834            }
8835       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8836       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8837       SendToProgram(buf, cps);
8838     }
8839     currentlyInitializedVariant = gameInfo.variant;
8840
8841     /* [HGM] send opening position in FRC to first engine */
8842     if(setup) {
8843           SendToProgram("force\n", cps);
8844           SendBoard(cps, 0);
8845           /* engine is now in force mode! Set flag to wake it up after first move. */
8846           setboardSpoiledMachineBlack = 1;
8847     }
8848
8849     if (cps->sendICS) {
8850       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8851       SendToProgram(buf, cps);
8852     }
8853     cps->maybeThinking = FALSE;
8854     cps->offeredDraw = 0;
8855     if (!appData.icsActive) {
8856         SendTimeControl(cps, movesPerSession, timeControl,
8857                         timeIncrement, appData.searchDepth,
8858                         searchTime);
8859     }
8860     if (appData.showThinking
8861         // [HGM] thinking: four options require thinking output to be sent
8862         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8863                                 ) {
8864         SendToProgram("post\n", cps);
8865     }
8866     SendToProgram("hard\n", cps);
8867     if (!appData.ponderNextMove) {
8868         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8869            it without being sure what state we are in first.  "hard"
8870            is not a toggle, so that one is OK.
8871          */
8872         SendToProgram("easy\n", cps);
8873     }
8874     if (cps->usePing) {
8875       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8876       SendToProgram(buf, cps);
8877     }
8878     cps->initDone = TRUE;
8879 }
8880
8881
8882 void
8883 StartChessProgram(cps)
8884      ChessProgramState *cps;
8885 {
8886     char buf[MSG_SIZ];
8887     int err;
8888
8889     if (appData.noChessProgram) return;
8890     cps->initDone = FALSE;
8891
8892     if (strcmp(cps->host, "localhost") == 0) {
8893         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8894     } else if (*appData.remoteShell == NULLCHAR) {
8895         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8896     } else {
8897         if (*appData.remoteUser == NULLCHAR) {
8898           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8899                     cps->program);
8900         } else {
8901           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8902                     cps->host, appData.remoteUser, cps->program);
8903         }
8904         err = StartChildProcess(buf, "", &cps->pr);
8905     }
8906
8907     if (err != 0) {
8908       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8909         DisplayFatalError(buf, err, 1);
8910         cps->pr = NoProc;
8911         cps->isr = NULL;
8912         return;
8913     }
8914
8915     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8916     if (cps->protocolVersion > 1) {
8917       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8918       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8919       cps->comboCnt = 0;  //                and values of combo boxes
8920       SendToProgram(buf, cps);
8921     } else {
8922       SendToProgram("xboard\n", cps);
8923     }
8924 }
8925
8926
8927 void
8928 TwoMachinesEventIfReady P((void))
8929 {
8930   if (first.lastPing != first.lastPong) {
8931     DisplayMessage("", _("Waiting for first chess program"));
8932     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8933     return;
8934   }
8935   if (second.lastPing != second.lastPong) {
8936     DisplayMessage("", _("Waiting for second chess program"));
8937     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8938     return;
8939   }
8940   ThawUI();
8941   TwoMachinesEvent();
8942 }
8943
8944 void
8945 NextMatchGame P((void))
8946 {
8947     int index; /* [HGM] autoinc: step load index during match */
8948     Reset(FALSE, TRUE);
8949     if (*appData.loadGameFile != NULLCHAR) {
8950         index = appData.loadGameIndex;
8951         if(index < 0) { // [HGM] autoinc
8952             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8953             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8954         }
8955         LoadGameFromFile(appData.loadGameFile,
8956                          index,
8957                          appData.loadGameFile, FALSE);
8958     } else if (*appData.loadPositionFile != NULLCHAR) {
8959         index = appData.loadPositionIndex;
8960         if(index < 0) { // [HGM] autoinc
8961             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8962             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8963         }
8964         LoadPositionFromFile(appData.loadPositionFile,
8965                              index,
8966                              appData.loadPositionFile);
8967     }
8968     TwoMachinesEventIfReady();
8969 }
8970
8971 void UserAdjudicationEvent( int result )
8972 {
8973     ChessMove gameResult = GameIsDrawn;
8974
8975     if( result > 0 ) {
8976         gameResult = WhiteWins;
8977     }
8978     else if( result < 0 ) {
8979         gameResult = BlackWins;
8980     }
8981
8982     if( gameMode == TwoMachinesPlay ) {
8983         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8984     }
8985 }
8986
8987
8988 // [HGM] save: calculate checksum of game to make games easily identifiable
8989 int StringCheckSum(char *s)
8990 {
8991         int i = 0;
8992         if(s==NULL) return 0;
8993         while(*s) i = i*259 + *s++;
8994         return i;
8995 }
8996
8997 int GameCheckSum()
8998 {
8999         int i, sum=0;
9000         for(i=backwardMostMove; i<forwardMostMove; i++) {
9001                 sum += pvInfoList[i].depth;
9002                 sum += StringCheckSum(parseList[i]);
9003                 sum += StringCheckSum(commentList[i]);
9004                 sum *= 261;
9005         }
9006         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9007         return sum + StringCheckSum(commentList[i]);
9008 } // end of save patch
9009
9010 void
9011 GameEnds(result, resultDetails, whosays)
9012      ChessMove result;
9013      char *resultDetails;
9014      int whosays;
9015 {
9016     GameMode nextGameMode;
9017     int isIcsGame;
9018     char buf[MSG_SIZ], popupRequested = 0;
9019
9020     if(endingGame) return; /* [HGM] crash: forbid recursion */
9021     endingGame = 1;
9022     if(twoBoards) { // [HGM] dual: switch back to one board
9023         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9024         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9025     }
9026     if (appData.debugMode) {
9027       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9028               result, resultDetails ? resultDetails : "(null)", whosays);
9029     }
9030
9031     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9032
9033     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9034         /* If we are playing on ICS, the server decides when the
9035            game is over, but the engine can offer to draw, claim
9036            a draw, or resign.
9037          */
9038 #if ZIPPY
9039         if (appData.zippyPlay && first.initDone) {
9040             if (result == GameIsDrawn) {
9041                 /* In case draw still needs to be claimed */
9042                 SendToICS(ics_prefix);
9043                 SendToICS("draw\n");
9044             } else if (StrCaseStr(resultDetails, "resign")) {
9045                 SendToICS(ics_prefix);
9046                 SendToICS("resign\n");
9047             }
9048         }
9049 #endif
9050         endingGame = 0; /* [HGM] crash */
9051         return;
9052     }
9053
9054     /* If we're loading the game from a file, stop */
9055     if (whosays == GE_FILE) {
9056       (void) StopLoadGameTimer();
9057       gameFileFP = NULL;
9058     }
9059
9060     /* Cancel draw offers */
9061     first.offeredDraw = second.offeredDraw = 0;
9062
9063     /* If this is an ICS game, only ICS can really say it's done;
9064        if not, anyone can. */
9065     isIcsGame = (gameMode == IcsPlayingWhite ||
9066                  gameMode == IcsPlayingBlack ||
9067                  gameMode == IcsObserving    ||
9068                  gameMode == IcsExamining);
9069
9070     if (!isIcsGame || whosays == GE_ICS) {
9071         /* OK -- not an ICS game, or ICS said it was done */
9072         StopClocks();
9073         if (!isIcsGame && !appData.noChessProgram)
9074           SetUserThinkingEnables();
9075
9076         /* [HGM] if a machine claims the game end we verify this claim */
9077         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9078             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9079                 char claimer;
9080                 ChessMove trueResult = (ChessMove) -1;
9081
9082                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9083                                             first.twoMachinesColor[0] :
9084                                             second.twoMachinesColor[0] ;
9085
9086                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9087                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9088                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9089                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9090                 } else
9091                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9092                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9093                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9094                 } else
9095                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9096                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9097                 }
9098
9099                 // now verify win claims, but not in drop games, as we don't understand those yet
9100                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9101                                                  || gameInfo.variant == VariantGreat) &&
9102                     (result == WhiteWins && claimer == 'w' ||
9103                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9104                       if (appData.debugMode) {
9105                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9106                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9107                       }
9108                       if(result != trueResult) {
9109                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9110                               result = claimer == 'w' ? BlackWins : WhiteWins;
9111                               resultDetails = buf;
9112                       }
9113                 } else
9114                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9115                     && (forwardMostMove <= backwardMostMove ||
9116                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9117                         (claimer=='b')==(forwardMostMove&1))
9118                                                                                   ) {
9119                       /* [HGM] verify: draws that were not flagged are false claims */
9120                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9121                       result = claimer == 'w' ? BlackWins : WhiteWins;
9122                       resultDetails = buf;
9123                 }
9124                 /* (Claiming a loss is accepted no questions asked!) */
9125             }
9126             /* [HGM] bare: don't allow bare King to win */
9127             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9128                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9129                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9130                && result != GameIsDrawn)
9131             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9132                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9133                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9134                         if(p >= 0 && p <= (int)WhiteKing) k++;
9135                 }
9136                 if (appData.debugMode) {
9137                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9138                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9139                 }
9140                 if(k <= 1) {
9141                         result = GameIsDrawn;
9142                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9143                         resultDetails = buf;
9144                 }
9145             }
9146         }
9147
9148
9149         if(serverMoves != NULL && !loadFlag) { char c = '=';
9150             if(result==WhiteWins) c = '+';
9151             if(result==BlackWins) c = '-';
9152             if(resultDetails != NULL)
9153                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9154         }
9155         if (resultDetails != NULL) {
9156             gameInfo.result = result;
9157             gameInfo.resultDetails = StrSave(resultDetails);
9158
9159             /* display last move only if game was not loaded from file */
9160             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9161                 DisplayMove(currentMove - 1);
9162
9163             if (forwardMostMove != 0) {
9164                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9165                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9166                                                                 ) {
9167                     if (*appData.saveGameFile != NULLCHAR) {
9168                         SaveGameToFile(appData.saveGameFile, TRUE);
9169                     } else if (appData.autoSaveGames) {
9170                         AutoSaveGame();
9171                     }
9172                     if (*appData.savePositionFile != NULLCHAR) {
9173                         SavePositionToFile(appData.savePositionFile);
9174                     }
9175                 }
9176             }
9177
9178             /* Tell program how game ended in case it is learning */
9179             /* [HGM] Moved this to after saving the PGN, just in case */
9180             /* engine died and we got here through time loss. In that */
9181             /* case we will get a fatal error writing the pipe, which */
9182             /* would otherwise lose us the PGN.                       */
9183             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9184             /* output during GameEnds should never be fatal anymore   */
9185             if (gameMode == MachinePlaysWhite ||
9186                 gameMode == MachinePlaysBlack ||
9187                 gameMode == TwoMachinesPlay ||
9188                 gameMode == IcsPlayingWhite ||
9189                 gameMode == IcsPlayingBlack ||
9190                 gameMode == BeginningOfGame) {
9191                 char buf[MSG_SIZ];
9192                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9193                         resultDetails);
9194                 if (first.pr != NoProc) {
9195                     SendToProgram(buf, &first);
9196                 }
9197                 if (second.pr != NoProc &&
9198                     gameMode == TwoMachinesPlay) {
9199                     SendToProgram(buf, &second);
9200                 }
9201             }
9202         }
9203
9204         if (appData.icsActive) {
9205             if (appData.quietPlay &&
9206                 (gameMode == IcsPlayingWhite ||
9207                  gameMode == IcsPlayingBlack)) {
9208                 SendToICS(ics_prefix);
9209                 SendToICS("set shout 1\n");
9210             }
9211             nextGameMode = IcsIdle;
9212             ics_user_moved = FALSE;
9213             /* clean up premove.  It's ugly when the game has ended and the
9214              * premove highlights are still on the board.
9215              */
9216             if (gotPremove) {
9217               gotPremove = FALSE;
9218               ClearPremoveHighlights();
9219               DrawPosition(FALSE, boards[currentMove]);
9220             }
9221             if (whosays == GE_ICS) {
9222                 switch (result) {
9223                 case WhiteWins:
9224                     if (gameMode == IcsPlayingWhite)
9225                         PlayIcsWinSound();
9226                     else if(gameMode == IcsPlayingBlack)
9227                         PlayIcsLossSound();
9228                     break;
9229                 case BlackWins:
9230                     if (gameMode == IcsPlayingBlack)
9231                         PlayIcsWinSound();
9232                     else if(gameMode == IcsPlayingWhite)
9233                         PlayIcsLossSound();
9234                     break;
9235                 case GameIsDrawn:
9236                     PlayIcsDrawSound();
9237                     break;
9238                 default:
9239                     PlayIcsUnfinishedSound();
9240                 }
9241             }
9242         } else if (gameMode == EditGame ||
9243                    gameMode == PlayFromGameFile ||
9244                    gameMode == AnalyzeMode ||
9245                    gameMode == AnalyzeFile) {
9246             nextGameMode = gameMode;
9247         } else {
9248             nextGameMode = EndOfGame;
9249         }
9250         pausing = FALSE;
9251         ModeHighlight();
9252     } else {
9253         nextGameMode = gameMode;
9254     }
9255
9256     if (appData.noChessProgram) {
9257         gameMode = nextGameMode;
9258         ModeHighlight();
9259         endingGame = 0; /* [HGM] crash */
9260         return;
9261     }
9262
9263     if (first.reuse) {
9264         /* Put first chess program into idle state */
9265         if (first.pr != NoProc &&
9266             (gameMode == MachinePlaysWhite ||
9267              gameMode == MachinePlaysBlack ||
9268              gameMode == TwoMachinesPlay ||
9269              gameMode == IcsPlayingWhite ||
9270              gameMode == IcsPlayingBlack ||
9271              gameMode == BeginningOfGame)) {
9272             SendToProgram("force\n", &first);
9273             if (first.usePing) {
9274               char buf[MSG_SIZ];
9275               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9276               SendToProgram(buf, &first);
9277             }
9278         }
9279     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9280         /* Kill off first chess program */
9281         if (first.isr != NULL)
9282           RemoveInputSource(first.isr);
9283         first.isr = NULL;
9284
9285         if (first.pr != NoProc) {
9286             ExitAnalyzeMode();
9287             DoSleep( appData.delayBeforeQuit );
9288             SendToProgram("quit\n", &first);
9289             DoSleep( appData.delayAfterQuit );
9290             DestroyChildProcess(first.pr, first.useSigterm);
9291         }
9292         first.pr = NoProc;
9293     }
9294     if (second.reuse) {
9295         /* Put second chess program into idle state */
9296         if (second.pr != NoProc &&
9297             gameMode == TwoMachinesPlay) {
9298             SendToProgram("force\n", &second);
9299             if (second.usePing) {
9300               char buf[MSG_SIZ];
9301               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9302               SendToProgram(buf, &second);
9303             }
9304         }
9305     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9306         /* Kill off second chess program */
9307         if (second.isr != NULL)
9308           RemoveInputSource(second.isr);
9309         second.isr = NULL;
9310
9311         if (second.pr != NoProc) {
9312             DoSleep( appData.delayBeforeQuit );
9313             SendToProgram("quit\n", &second);
9314             DoSleep( appData.delayAfterQuit );
9315             DestroyChildProcess(second.pr, second.useSigterm);
9316         }
9317         second.pr = NoProc;
9318     }
9319
9320     if (matchMode && gameMode == TwoMachinesPlay) {
9321         switch (result) {
9322         case WhiteWins:
9323           if (first.twoMachinesColor[0] == 'w') {
9324             first.matchWins++;
9325           } else {
9326             second.matchWins++;
9327           }
9328           break;
9329         case BlackWins:
9330           if (first.twoMachinesColor[0] == 'b') {
9331             first.matchWins++;
9332           } else {
9333             second.matchWins++;
9334           }
9335           break;
9336         default:
9337           break;
9338         }
9339         if (matchGame < appData.matchGames) {
9340             char *tmp;
9341             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9342                 tmp = first.twoMachinesColor;
9343                 first.twoMachinesColor = second.twoMachinesColor;
9344                 second.twoMachinesColor = tmp;
9345             }
9346             gameMode = nextGameMode;
9347             matchGame++;
9348             if(appData.matchPause>10000 || appData.matchPause<10)
9349                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9350             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9351             endingGame = 0; /* [HGM] crash */
9352             return;
9353         } else {
9354             gameMode = nextGameMode;
9355             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9356                      first.tidy, second.tidy,
9357                      first.matchWins, second.matchWins,
9358                      appData.matchGames - (first.matchWins + second.matchWins));
9359             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9360         }
9361     }
9362     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9363         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9364       ExitAnalyzeMode();
9365     gameMode = nextGameMode;
9366     ModeHighlight();
9367     endingGame = 0;  /* [HGM] crash */
9368     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9369       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9370         matchMode = FALSE; appData.matchGames = matchGame = 0;
9371         DisplayNote(buf);
9372       }
9373     }
9374 }
9375
9376 /* Assumes program was just initialized (initString sent).
9377    Leaves program in force mode. */
9378 void
9379 FeedMovesToProgram(cps, upto)
9380      ChessProgramState *cps;
9381      int upto;
9382 {
9383     int i;
9384
9385     if (appData.debugMode)
9386       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9387               startedFromSetupPosition ? "position and " : "",
9388               backwardMostMove, upto, cps->which);
9389     if(currentlyInitializedVariant != gameInfo.variant) {
9390       char buf[MSG_SIZ];
9391         // [HGM] variantswitch: make engine aware of new variant
9392         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9393                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9394         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9395         SendToProgram(buf, cps);
9396         currentlyInitializedVariant = gameInfo.variant;
9397     }
9398     SendToProgram("force\n", cps);
9399     if (startedFromSetupPosition) {
9400         SendBoard(cps, backwardMostMove);
9401     if (appData.debugMode) {
9402         fprintf(debugFP, "feedMoves\n");
9403     }
9404     }
9405     for (i = backwardMostMove; i < upto; i++) {
9406         SendMoveToProgram(i, cps);
9407     }
9408 }
9409
9410
9411 void
9412 ResurrectChessProgram()
9413 {
9414      /* The chess program may have exited.
9415         If so, restart it and feed it all the moves made so far. */
9416
9417     if (appData.noChessProgram || first.pr != NoProc) return;
9418
9419     StartChessProgram(&first);
9420     InitChessProgram(&first, FALSE);
9421     FeedMovesToProgram(&first, currentMove);
9422
9423     if (!first.sendTime) {
9424         /* can't tell gnuchess what its clock should read,
9425            so we bow to its notion. */
9426         ResetClocks();
9427         timeRemaining[0][currentMove] = whiteTimeRemaining;
9428         timeRemaining[1][currentMove] = blackTimeRemaining;
9429     }
9430
9431     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9432                 appData.icsEngineAnalyze) && first.analysisSupport) {
9433       SendToProgram("analyze\n", &first);
9434       first.analyzing = TRUE;
9435     }
9436 }
9437
9438 /*
9439  * Button procedures
9440  */
9441 void
9442 Reset(redraw, init)
9443      int redraw, init;
9444 {
9445     int i;
9446
9447     if (appData.debugMode) {
9448         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9449                 redraw, init, gameMode);
9450     }
9451     CleanupTail(); // [HGM] vari: delete any stored variations
9452     pausing = pauseExamInvalid = FALSE;
9453     startedFromSetupPosition = blackPlaysFirst = FALSE;
9454     firstMove = TRUE;
9455     whiteFlag = blackFlag = FALSE;
9456     userOfferedDraw = FALSE;
9457     hintRequested = bookRequested = FALSE;
9458     first.maybeThinking = FALSE;
9459     second.maybeThinking = FALSE;
9460     first.bookSuspend = FALSE; // [HGM] book
9461     second.bookSuspend = FALSE;
9462     thinkOutput[0] = NULLCHAR;
9463     lastHint[0] = NULLCHAR;
9464     ClearGameInfo(&gameInfo);
9465     gameInfo.variant = StringToVariant(appData.variant);
9466     ics_user_moved = ics_clock_paused = FALSE;
9467     ics_getting_history = H_FALSE;
9468     ics_gamenum = -1;
9469     white_holding[0] = black_holding[0] = NULLCHAR;
9470     ClearProgramStats();
9471     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9472
9473     ResetFrontEnd();
9474     ClearHighlights();
9475     flipView = appData.flipView;
9476     ClearPremoveHighlights();
9477     gotPremove = FALSE;
9478     alarmSounded = FALSE;
9479
9480     GameEnds(EndOfFile, NULL, GE_PLAYER);
9481     if(appData.serverMovesName != NULL) {
9482         /* [HGM] prepare to make moves file for broadcasting */
9483         clock_t t = clock();
9484         if(serverMoves != NULL) fclose(serverMoves);
9485         serverMoves = fopen(appData.serverMovesName, "r");
9486         if(serverMoves != NULL) {
9487             fclose(serverMoves);
9488             /* delay 15 sec before overwriting, so all clients can see end */
9489             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9490         }
9491         serverMoves = fopen(appData.serverMovesName, "w");
9492     }
9493
9494     ExitAnalyzeMode();
9495     gameMode = BeginningOfGame;
9496     ModeHighlight();
9497     if(appData.icsActive) gameInfo.variant = VariantNormal;
9498     currentMove = forwardMostMove = backwardMostMove = 0;
9499     InitPosition(redraw);
9500     for (i = 0; i < MAX_MOVES; i++) {
9501         if (commentList[i] != NULL) {
9502             free(commentList[i]);
9503             commentList[i] = NULL;
9504         }
9505     }
9506     ResetClocks();
9507     timeRemaining[0][0] = whiteTimeRemaining;
9508     timeRemaining[1][0] = blackTimeRemaining;
9509     if (first.pr == NULL) {
9510         StartChessProgram(&first);
9511     }
9512     if (init) {
9513             InitChessProgram(&first, startedFromSetupPosition);
9514     }
9515     DisplayTitle("");
9516     DisplayMessage("", "");
9517     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9518     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9519 }
9520
9521 void
9522 AutoPlayGameLoop()
9523 {
9524     for (;;) {
9525         if (!AutoPlayOneMove())
9526           return;
9527         if (matchMode || appData.timeDelay == 0)
9528           continue;
9529         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9530           return;
9531         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9532         break;
9533     }
9534 }
9535
9536
9537 int
9538 AutoPlayOneMove()
9539 {
9540     int fromX, fromY, toX, toY;
9541
9542     if (appData.debugMode) {
9543       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9544     }
9545
9546     if (gameMode != PlayFromGameFile)
9547       return FALSE;
9548
9549     if (currentMove >= forwardMostMove) {
9550       gameMode = EditGame;
9551       ModeHighlight();
9552
9553       /* [AS] Clear current move marker at the end of a game */
9554       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9555
9556       return FALSE;
9557     }
9558
9559     toX = moveList[currentMove][2] - AAA;
9560     toY = moveList[currentMove][3] - ONE;
9561
9562     if (moveList[currentMove][1] == '@') {
9563         if (appData.highlightLastMove) {
9564             SetHighlights(-1, -1, toX, toY);
9565         }
9566     } else {
9567         fromX = moveList[currentMove][0] - AAA;
9568         fromY = moveList[currentMove][1] - ONE;
9569
9570         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9571
9572         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9573
9574         if (appData.highlightLastMove) {
9575             SetHighlights(fromX, fromY, toX, toY);
9576         }
9577     }
9578     DisplayMove(currentMove);
9579     SendMoveToProgram(currentMove++, &first);
9580     DisplayBothClocks();
9581     DrawPosition(FALSE, boards[currentMove]);
9582     // [HGM] PV info: always display, routine tests if empty
9583     DisplayComment(currentMove - 1, commentList[currentMove]);
9584     return TRUE;
9585 }
9586
9587
9588 int
9589 LoadGameOneMove(readAhead)
9590      ChessMove readAhead;
9591 {
9592     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9593     char promoChar = NULLCHAR;
9594     ChessMove moveType;
9595     char move[MSG_SIZ];
9596     char *p, *q;
9597
9598     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9599         gameMode != AnalyzeMode && gameMode != Training) {
9600         gameFileFP = NULL;
9601         return FALSE;
9602     }
9603
9604     yyboardindex = forwardMostMove;
9605     if (readAhead != EndOfFile) {
9606       moveType = readAhead;
9607     } else {
9608       if (gameFileFP == NULL)
9609           return FALSE;
9610       moveType = (ChessMove) yylex();
9611     }
9612
9613     done = FALSE;
9614     switch (moveType) {
9615       case Comment:
9616         if (appData.debugMode)
9617           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9618         p = yy_text;
9619
9620         /* append the comment but don't display it */
9621         AppendComment(currentMove, p, FALSE);
9622         return TRUE;
9623
9624       case WhiteCapturesEnPassant:
9625       case BlackCapturesEnPassant:
9626       case WhitePromotion:
9627       case BlackPromotion:
9628       case WhiteNonPromotion:
9629       case BlackNonPromotion:
9630       case NormalMove:
9631       case WhiteKingSideCastle:
9632       case WhiteQueenSideCastle:
9633       case BlackKingSideCastle:
9634       case BlackQueenSideCastle:
9635       case WhiteKingSideCastleWild:
9636       case WhiteQueenSideCastleWild:
9637       case BlackKingSideCastleWild:
9638       case BlackQueenSideCastleWild:
9639       /* PUSH Fabien */
9640       case WhiteHSideCastleFR:
9641       case WhiteASideCastleFR:
9642       case BlackHSideCastleFR:
9643       case BlackASideCastleFR:
9644       /* POP Fabien */
9645         if (appData.debugMode)
9646           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9647         fromX = currentMoveString[0] - AAA;
9648         fromY = currentMoveString[1] - ONE;
9649         toX = currentMoveString[2] - AAA;
9650         toY = currentMoveString[3] - ONE;
9651         promoChar = currentMoveString[4];
9652         break;
9653
9654       case WhiteDrop:
9655       case BlackDrop:
9656         if (appData.debugMode)
9657           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9658         fromX = moveType == WhiteDrop ?
9659           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9660         (int) CharToPiece(ToLower(currentMoveString[0]));
9661         fromY = DROP_RANK;
9662         toX = currentMoveString[2] - AAA;
9663         toY = currentMoveString[3] - ONE;
9664         break;
9665
9666       case WhiteWins:
9667       case BlackWins:
9668       case GameIsDrawn:
9669       case GameUnfinished:
9670         if (appData.debugMode)
9671           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9672         p = strchr(yy_text, '{');
9673         if (p == NULL) p = strchr(yy_text, '(');
9674         if (p == NULL) {
9675             p = yy_text;
9676             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9677         } else {
9678             q = strchr(p, *p == '{' ? '}' : ')');
9679             if (q != NULL) *q = NULLCHAR;
9680             p++;
9681         }
9682         GameEnds(moveType, p, GE_FILE);
9683         done = TRUE;
9684         if (cmailMsgLoaded) {
9685             ClearHighlights();
9686             flipView = WhiteOnMove(currentMove);
9687             if (moveType == GameUnfinished) flipView = !flipView;
9688             if (appData.debugMode)
9689               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9690         }
9691         break;
9692
9693       case EndOfFile:
9694         if (appData.debugMode)
9695           fprintf(debugFP, "Parser hit end of file\n");
9696         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9697           case MT_NONE:
9698           case MT_CHECK:
9699             break;
9700           case MT_CHECKMATE:
9701           case MT_STAINMATE:
9702             if (WhiteOnMove(currentMove)) {
9703                 GameEnds(BlackWins, "Black mates", GE_FILE);
9704             } else {
9705                 GameEnds(WhiteWins, "White mates", GE_FILE);
9706             }
9707             break;
9708           case MT_STALEMATE:
9709             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9710             break;
9711         }
9712         done = TRUE;
9713         break;
9714
9715       case MoveNumberOne:
9716         if (lastLoadGameStart == GNUChessGame) {
9717             /* GNUChessGames have numbers, but they aren't move numbers */
9718             if (appData.debugMode)
9719               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9720                       yy_text, (int) moveType);
9721             return LoadGameOneMove(EndOfFile); /* tail recursion */
9722         }
9723         /* else fall thru */
9724
9725       case XBoardGame:
9726       case GNUChessGame:
9727       case PGNTag:
9728         /* Reached start of next game in file */
9729         if (appData.debugMode)
9730           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9731         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9732           case MT_NONE:
9733           case MT_CHECK:
9734             break;
9735           case MT_CHECKMATE:
9736           case MT_STAINMATE:
9737             if (WhiteOnMove(currentMove)) {
9738                 GameEnds(BlackWins, "Black mates", GE_FILE);
9739             } else {
9740                 GameEnds(WhiteWins, "White mates", GE_FILE);
9741             }
9742             break;
9743           case MT_STALEMATE:
9744             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9745             break;
9746         }
9747         done = TRUE;
9748         break;
9749
9750       case PositionDiagram:     /* should not happen; ignore */
9751       case ElapsedTime:         /* ignore */
9752       case NAG:                 /* ignore */
9753         if (appData.debugMode)
9754           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9755                   yy_text, (int) moveType);
9756         return LoadGameOneMove(EndOfFile); /* tail recursion */
9757
9758       case IllegalMove:
9759         if (appData.testLegality) {
9760             if (appData.debugMode)
9761               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9762             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9763                     (forwardMostMove / 2) + 1,
9764                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9765             DisplayError(move, 0);
9766             done = TRUE;
9767         } else {
9768             if (appData.debugMode)
9769               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9770                       yy_text, currentMoveString);
9771             fromX = currentMoveString[0] - AAA;
9772             fromY = currentMoveString[1] - ONE;
9773             toX = currentMoveString[2] - AAA;
9774             toY = currentMoveString[3] - ONE;
9775             promoChar = currentMoveString[4];
9776         }
9777         break;
9778
9779       case AmbiguousMove:
9780         if (appData.debugMode)
9781           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9782         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9783                 (forwardMostMove / 2) + 1,
9784                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9785         DisplayError(move, 0);
9786         done = TRUE;
9787         break;
9788
9789       default:
9790       case ImpossibleMove:
9791         if (appData.debugMode)
9792           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9793         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9794                 (forwardMostMove / 2) + 1,
9795                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9796         DisplayError(move, 0);
9797         done = TRUE;
9798         break;
9799     }
9800
9801     if (done) {
9802         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9803             DrawPosition(FALSE, boards[currentMove]);
9804             DisplayBothClocks();
9805             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9806               DisplayComment(currentMove - 1, commentList[currentMove]);
9807         }
9808         (void) StopLoadGameTimer();
9809         gameFileFP = NULL;
9810         cmailOldMove = forwardMostMove;
9811         return FALSE;
9812     } else {
9813         /* currentMoveString is set as a side-effect of yylex */
9814         strcat(currentMoveString, "\n");
9815         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9816
9817         thinkOutput[0] = NULLCHAR;
9818         MakeMove(fromX, fromY, toX, toY, promoChar);
9819         currentMove = forwardMostMove;
9820         return TRUE;
9821     }
9822 }
9823
9824 /* Load the nth game from the given file */
9825 int
9826 LoadGameFromFile(filename, n, title, useList)
9827      char *filename;
9828      int n;
9829      char *title;
9830      /*Boolean*/ int useList;
9831 {
9832     FILE *f;
9833     char buf[MSG_SIZ];
9834
9835     if (strcmp(filename, "-") == 0) {
9836         f = stdin;
9837         title = "stdin";
9838     } else {
9839         f = fopen(filename, "rb");
9840         if (f == NULL) {
9841           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9842             DisplayError(buf, errno);
9843             return FALSE;
9844         }
9845     }
9846     if (fseek(f, 0, 0) == -1) {
9847         /* f is not seekable; probably a pipe */
9848         useList = FALSE;
9849     }
9850     if (useList && n == 0) {
9851         int error = GameListBuild(f);
9852         if (error) {
9853             DisplayError(_("Cannot build game list"), error);
9854         } else if (!ListEmpty(&gameList) &&
9855                    ((ListGame *) gameList.tailPred)->number > 1) {
9856             GameListPopUp(f, title);
9857             return TRUE;
9858         }
9859         GameListDestroy();
9860         n = 1;
9861     }
9862     if (n == 0) n = 1;
9863     return LoadGame(f, n, title, FALSE);
9864 }
9865
9866
9867 void
9868 MakeRegisteredMove()
9869 {
9870     int fromX, fromY, toX, toY;
9871     char promoChar;
9872     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9873         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9874           case CMAIL_MOVE:
9875           case CMAIL_DRAW:
9876             if (appData.debugMode)
9877               fprintf(debugFP, "Restoring %s for game %d\n",
9878                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9879
9880             thinkOutput[0] = NULLCHAR;
9881             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9882             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9883             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9884             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9885             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9886             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9887             MakeMove(fromX, fromY, toX, toY, promoChar);
9888             ShowMove(fromX, fromY, toX, toY);
9889
9890             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9891               case MT_NONE:
9892               case MT_CHECK:
9893                 break;
9894
9895               case MT_CHECKMATE:
9896               case MT_STAINMATE:
9897                 if (WhiteOnMove(currentMove)) {
9898                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9899                 } else {
9900                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9901                 }
9902                 break;
9903
9904               case MT_STALEMATE:
9905                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9906                 break;
9907             }
9908
9909             break;
9910
9911           case CMAIL_RESIGN:
9912             if (WhiteOnMove(currentMove)) {
9913                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9914             } else {
9915                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9916             }
9917             break;
9918
9919           case CMAIL_ACCEPT:
9920             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9921             break;
9922
9923           default:
9924             break;
9925         }
9926     }
9927
9928     return;
9929 }
9930
9931 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9932 int
9933 CmailLoadGame(f, gameNumber, title, useList)
9934      FILE *f;
9935      int gameNumber;
9936      char *title;
9937      int useList;
9938 {
9939     int retVal;
9940
9941     if (gameNumber > nCmailGames) {
9942         DisplayError(_("No more games in this message"), 0);
9943         return FALSE;
9944     }
9945     if (f == lastLoadGameFP) {
9946         int offset = gameNumber - lastLoadGameNumber;
9947         if (offset == 0) {
9948             cmailMsg[0] = NULLCHAR;
9949             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9950                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9951                 nCmailMovesRegistered--;
9952             }
9953             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9954             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9955                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9956             }
9957         } else {
9958             if (! RegisterMove()) return FALSE;
9959         }
9960     }
9961
9962     retVal = LoadGame(f, gameNumber, title, useList);
9963
9964     /* Make move registered during previous look at this game, if any */
9965     MakeRegisteredMove();
9966
9967     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9968         commentList[currentMove]
9969           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9970         DisplayComment(currentMove - 1, commentList[currentMove]);
9971     }
9972
9973     return retVal;
9974 }
9975
9976 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9977 int
9978 ReloadGame(offset)
9979      int offset;
9980 {
9981     int gameNumber = lastLoadGameNumber + offset;
9982     if (lastLoadGameFP == NULL) {
9983         DisplayError(_("No game has been loaded yet"), 0);
9984         return FALSE;
9985     }
9986     if (gameNumber <= 0) {
9987         DisplayError(_("Can't back up any further"), 0);
9988         return FALSE;
9989     }
9990     if (cmailMsgLoaded) {
9991         return CmailLoadGame(lastLoadGameFP, gameNumber,
9992                              lastLoadGameTitle, lastLoadGameUseList);
9993     } else {
9994         return LoadGame(lastLoadGameFP, gameNumber,
9995                         lastLoadGameTitle, lastLoadGameUseList);
9996     }
9997 }
9998
9999
10000
10001 /* Load the nth game from open file f */
10002 int
10003 LoadGame(f, gameNumber, title, useList)
10004      FILE *f;
10005      int gameNumber;
10006      char *title;
10007      int useList;
10008 {
10009     ChessMove cm;
10010     char buf[MSG_SIZ];
10011     int gn = gameNumber;
10012     ListGame *lg = NULL;
10013     int numPGNTags = 0;
10014     int err;
10015     GameMode oldGameMode;
10016     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10017
10018     if (appData.debugMode)
10019         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10020
10021     if (gameMode == Training )
10022         SetTrainingModeOff();
10023
10024     oldGameMode = gameMode;
10025     if (gameMode != BeginningOfGame) {
10026       Reset(FALSE, TRUE);
10027     }
10028
10029     gameFileFP = f;
10030     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10031         fclose(lastLoadGameFP);
10032     }
10033
10034     if (useList) {
10035         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10036
10037         if (lg) {
10038             fseek(f, lg->offset, 0);
10039             GameListHighlight(gameNumber);
10040             gn = 1;
10041         }
10042         else {
10043             DisplayError(_("Game number out of range"), 0);
10044             return FALSE;
10045         }
10046     } else {
10047         GameListDestroy();
10048         if (fseek(f, 0, 0) == -1) {
10049             if (f == lastLoadGameFP ?
10050                 gameNumber == lastLoadGameNumber + 1 :
10051                 gameNumber == 1) {
10052                 gn = 1;
10053             } else {
10054                 DisplayError(_("Can't seek on game file"), 0);
10055                 return FALSE;
10056             }
10057         }
10058     }
10059     lastLoadGameFP = f;
10060     lastLoadGameNumber = gameNumber;
10061     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10062     lastLoadGameUseList = useList;
10063
10064     yynewfile(f);
10065
10066     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10067       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10068                 lg->gameInfo.black);
10069             DisplayTitle(buf);
10070     } else if (*title != NULLCHAR) {
10071         if (gameNumber > 1) {
10072           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10073             DisplayTitle(buf);
10074         } else {
10075             DisplayTitle(title);
10076         }
10077     }
10078
10079     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10080         gameMode = PlayFromGameFile;
10081         ModeHighlight();
10082     }
10083
10084     currentMove = forwardMostMove = backwardMostMove = 0;
10085     CopyBoard(boards[0], initialPosition);
10086     StopClocks();
10087
10088     /*
10089      * Skip the first gn-1 games in the file.
10090      * Also skip over anything that precedes an identifiable
10091      * start of game marker, to avoid being confused by
10092      * garbage at the start of the file.  Currently
10093      * recognized start of game markers are the move number "1",
10094      * the pattern "gnuchess .* game", the pattern
10095      * "^[#;%] [^ ]* game file", and a PGN tag block.
10096      * A game that starts with one of the latter two patterns
10097      * will also have a move number 1, possibly
10098      * following a position diagram.
10099      * 5-4-02: Let's try being more lenient and allowing a game to
10100      * start with an unnumbered move.  Does that break anything?
10101      */
10102     cm = lastLoadGameStart = EndOfFile;
10103     while (gn > 0) {
10104         yyboardindex = forwardMostMove;
10105         cm = (ChessMove) yylex();
10106         switch (cm) {
10107           case EndOfFile:
10108             if (cmailMsgLoaded) {
10109                 nCmailGames = CMAIL_MAX_GAMES - gn;
10110             } else {
10111                 Reset(TRUE, TRUE);
10112                 DisplayError(_("Game not found in file"), 0);
10113             }
10114             return FALSE;
10115
10116           case GNUChessGame:
10117           case XBoardGame:
10118             gn--;
10119             lastLoadGameStart = cm;
10120             break;
10121
10122           case MoveNumberOne:
10123             switch (lastLoadGameStart) {
10124               case GNUChessGame:
10125               case XBoardGame:
10126               case PGNTag:
10127                 break;
10128               case MoveNumberOne:
10129               case EndOfFile:
10130                 gn--;           /* count this game */
10131                 lastLoadGameStart = cm;
10132                 break;
10133               default:
10134                 /* impossible */
10135                 break;
10136             }
10137             break;
10138
10139           case PGNTag:
10140             switch (lastLoadGameStart) {
10141               case GNUChessGame:
10142               case PGNTag:
10143               case MoveNumberOne:
10144               case EndOfFile:
10145                 gn--;           /* count this game */
10146                 lastLoadGameStart = cm;
10147                 break;
10148               case XBoardGame:
10149                 lastLoadGameStart = cm; /* game counted already */
10150                 break;
10151               default:
10152                 /* impossible */
10153                 break;
10154             }
10155             if (gn > 0) {
10156                 do {
10157                     yyboardindex = forwardMostMove;
10158                     cm = (ChessMove) yylex();
10159                 } while (cm == PGNTag || cm == Comment);
10160             }
10161             break;
10162
10163           case WhiteWins:
10164           case BlackWins:
10165           case GameIsDrawn:
10166             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10167                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10168                     != CMAIL_OLD_RESULT) {
10169                     nCmailResults ++ ;
10170                     cmailResult[  CMAIL_MAX_GAMES
10171                                 - gn - 1] = CMAIL_OLD_RESULT;
10172                 }
10173             }
10174             break;
10175
10176           case NormalMove:
10177             /* Only a NormalMove can be at the start of a game
10178              * without a position diagram. */
10179             if (lastLoadGameStart == EndOfFile ) {
10180               gn--;
10181               lastLoadGameStart = MoveNumberOne;
10182             }
10183             break;
10184
10185           default:
10186             break;
10187         }
10188     }
10189
10190     if (appData.debugMode)
10191       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10192
10193     if (cm == XBoardGame) {
10194         /* Skip any header junk before position diagram and/or move 1 */
10195         for (;;) {
10196             yyboardindex = forwardMostMove;
10197             cm = (ChessMove) yylex();
10198
10199             if (cm == EndOfFile ||
10200                 cm == GNUChessGame || cm == XBoardGame) {
10201                 /* Empty game; pretend end-of-file and handle later */
10202                 cm = EndOfFile;
10203                 break;
10204             }
10205
10206             if (cm == MoveNumberOne || cm == PositionDiagram ||
10207                 cm == PGNTag || cm == Comment)
10208               break;
10209         }
10210     } else if (cm == GNUChessGame) {
10211         if (gameInfo.event != NULL) {
10212             free(gameInfo.event);
10213         }
10214         gameInfo.event = StrSave(yy_text);
10215     }
10216
10217     startedFromSetupPosition = FALSE;
10218     while (cm == PGNTag) {
10219         if (appData.debugMode)
10220           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10221         err = ParsePGNTag(yy_text, &gameInfo);
10222         if (!err) numPGNTags++;
10223
10224         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10225         if(gameInfo.variant != oldVariant) {
10226             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10227             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10228             InitPosition(TRUE);
10229             oldVariant = gameInfo.variant;
10230             if (appData.debugMode)
10231               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10232         }
10233
10234
10235         if (gameInfo.fen != NULL) {
10236           Board initial_position;
10237           startedFromSetupPosition = TRUE;
10238           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10239             Reset(TRUE, TRUE);
10240             DisplayError(_("Bad FEN position in file"), 0);
10241             return FALSE;
10242           }
10243           CopyBoard(boards[0], initial_position);
10244           if (blackPlaysFirst) {
10245             currentMove = forwardMostMove = backwardMostMove = 1;
10246             CopyBoard(boards[1], initial_position);
10247             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10248             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10249             timeRemaining[0][1] = whiteTimeRemaining;
10250             timeRemaining[1][1] = blackTimeRemaining;
10251             if (commentList[0] != NULL) {
10252               commentList[1] = commentList[0];
10253               commentList[0] = NULL;
10254             }
10255           } else {
10256             currentMove = forwardMostMove = backwardMostMove = 0;
10257           }
10258           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10259           {   int i;
10260               initialRulePlies = FENrulePlies;
10261               for( i=0; i< nrCastlingRights; i++ )
10262                   initialRights[i] = initial_position[CASTLING][i];
10263           }
10264           yyboardindex = forwardMostMove;
10265           free(gameInfo.fen);
10266           gameInfo.fen = NULL;
10267         }
10268
10269         yyboardindex = forwardMostMove;
10270         cm = (ChessMove) yylex();
10271
10272         /* Handle comments interspersed among the tags */
10273         while (cm == Comment) {
10274             char *p;
10275             if (appData.debugMode)
10276               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10277             p = yy_text;
10278             AppendComment(currentMove, p, FALSE);
10279             yyboardindex = forwardMostMove;
10280             cm = (ChessMove) yylex();
10281         }
10282     }
10283
10284     /* don't rely on existence of Event tag since if game was
10285      * pasted from clipboard the Event tag may not exist
10286      */
10287     if (numPGNTags > 0){
10288         char *tags;
10289         if (gameInfo.variant == VariantNormal) {
10290           VariantClass v = StringToVariant(gameInfo.event);
10291           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10292           if(v < VariantShogi) gameInfo.variant = v;
10293         }
10294         if (!matchMode) {
10295           if( appData.autoDisplayTags ) {
10296             tags = PGNTags(&gameInfo);
10297             TagsPopUp(tags, CmailMsg());
10298             free(tags);
10299           }
10300         }
10301     } else {
10302         /* Make something up, but don't display it now */
10303         SetGameInfo();
10304         TagsPopDown();
10305     }
10306
10307     if (cm == PositionDiagram) {
10308         int i, j;
10309         char *p;
10310         Board initial_position;
10311
10312         if (appData.debugMode)
10313           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10314
10315         if (!startedFromSetupPosition) {
10316             p = yy_text;
10317             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10318               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10319                 switch (*p) {
10320                   case '[':
10321                   case '-':
10322                   case ' ':
10323                   case '\t':
10324                   case '\n':
10325                   case '\r':
10326                     break;
10327                   default:
10328                     initial_position[i][j++] = CharToPiece(*p);
10329                     break;
10330                 }
10331             while (*p == ' ' || *p == '\t' ||
10332                    *p == '\n' || *p == '\r') p++;
10333
10334             if (strncmp(p, "black", strlen("black"))==0)
10335               blackPlaysFirst = TRUE;
10336             else
10337               blackPlaysFirst = FALSE;
10338             startedFromSetupPosition = TRUE;
10339
10340             CopyBoard(boards[0], initial_position);
10341             if (blackPlaysFirst) {
10342                 currentMove = forwardMostMove = backwardMostMove = 1;
10343                 CopyBoard(boards[1], initial_position);
10344                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10345                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10346                 timeRemaining[0][1] = whiteTimeRemaining;
10347                 timeRemaining[1][1] = blackTimeRemaining;
10348                 if (commentList[0] != NULL) {
10349                     commentList[1] = commentList[0];
10350                     commentList[0] = NULL;
10351                 }
10352             } else {
10353                 currentMove = forwardMostMove = backwardMostMove = 0;
10354             }
10355         }
10356         yyboardindex = forwardMostMove;
10357         cm = (ChessMove) yylex();
10358     }
10359
10360     if (first.pr == NoProc) {
10361         StartChessProgram(&first);
10362     }
10363     InitChessProgram(&first, FALSE);
10364     SendToProgram("force\n", &first);
10365     if (startedFromSetupPosition) {
10366         SendBoard(&first, forwardMostMove);
10367     if (appData.debugMode) {
10368         fprintf(debugFP, "Load Game\n");
10369     }
10370         DisplayBothClocks();
10371     }
10372
10373     /* [HGM] server: flag to write setup moves in broadcast file as one */
10374     loadFlag = appData.suppressLoadMoves;
10375
10376     while (cm == Comment) {
10377         char *p;
10378         if (appData.debugMode)
10379           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10380         p = yy_text;
10381         AppendComment(currentMove, p, FALSE);
10382         yyboardindex = forwardMostMove;
10383         cm = (ChessMove) yylex();
10384     }
10385
10386     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10387         cm == WhiteWins || cm == BlackWins ||
10388         cm == GameIsDrawn || cm == GameUnfinished) {
10389         DisplayMessage("", _("No moves in game"));
10390         if (cmailMsgLoaded) {
10391             if (appData.debugMode)
10392               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10393             ClearHighlights();
10394             flipView = FALSE;
10395         }
10396         DrawPosition(FALSE, boards[currentMove]);
10397         DisplayBothClocks();
10398         gameMode = EditGame;
10399         ModeHighlight();
10400         gameFileFP = NULL;
10401         cmailOldMove = 0;
10402         return TRUE;
10403     }
10404
10405     // [HGM] PV info: routine tests if comment empty
10406     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10407         DisplayComment(currentMove - 1, commentList[currentMove]);
10408     }
10409     if (!matchMode && appData.timeDelay != 0)
10410       DrawPosition(FALSE, boards[currentMove]);
10411
10412     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10413       programStats.ok_to_send = 1;
10414     }
10415
10416     /* if the first token after the PGN tags is a move
10417      * and not move number 1, retrieve it from the parser
10418      */
10419     if (cm != MoveNumberOne)
10420         LoadGameOneMove(cm);
10421
10422     /* load the remaining moves from the file */
10423     while (LoadGameOneMove(EndOfFile)) {
10424       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10425       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10426     }
10427
10428     /* rewind to the start of the game */
10429     currentMove = backwardMostMove;
10430
10431     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10432
10433     if (oldGameMode == AnalyzeFile ||
10434         oldGameMode == AnalyzeMode) {
10435       AnalyzeFileEvent();
10436     }
10437
10438     if (matchMode || appData.timeDelay == 0) {
10439       ToEndEvent();
10440       gameMode = EditGame;
10441       ModeHighlight();
10442     } else if (appData.timeDelay > 0) {
10443       AutoPlayGameLoop();
10444     }
10445
10446     if (appData.debugMode)
10447         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10448
10449     loadFlag = 0; /* [HGM] true game starts */
10450     return TRUE;
10451 }
10452
10453 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10454 int
10455 ReloadPosition(offset)
10456      int offset;
10457 {
10458     int positionNumber = lastLoadPositionNumber + offset;
10459     if (lastLoadPositionFP == NULL) {
10460         DisplayError(_("No position has been loaded yet"), 0);
10461         return FALSE;
10462     }
10463     if (positionNumber <= 0) {
10464         DisplayError(_("Can't back up any further"), 0);
10465         return FALSE;
10466     }
10467     return LoadPosition(lastLoadPositionFP, positionNumber,
10468                         lastLoadPositionTitle);
10469 }
10470
10471 /* Load the nth position from the given file */
10472 int
10473 LoadPositionFromFile(filename, n, title)
10474      char *filename;
10475      int n;
10476      char *title;
10477 {
10478     FILE *f;
10479     char buf[MSG_SIZ];
10480
10481     if (strcmp(filename, "-") == 0) {
10482         return LoadPosition(stdin, n, "stdin");
10483     } else {
10484         f = fopen(filename, "rb");
10485         if (f == NULL) {
10486             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10487             DisplayError(buf, errno);
10488             return FALSE;
10489         } else {
10490             return LoadPosition(f, n, title);
10491         }
10492     }
10493 }
10494
10495 /* Load the nth position from the given open file, and close it */
10496 int
10497 LoadPosition(f, positionNumber, title)
10498      FILE *f;
10499      int positionNumber;
10500      char *title;
10501 {
10502     char *p, line[MSG_SIZ];
10503     Board initial_position;
10504     int i, j, fenMode, pn;
10505
10506     if (gameMode == Training )
10507         SetTrainingModeOff();
10508
10509     if (gameMode != BeginningOfGame) {
10510         Reset(FALSE, TRUE);
10511     }
10512     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10513         fclose(lastLoadPositionFP);
10514     }
10515     if (positionNumber == 0) positionNumber = 1;
10516     lastLoadPositionFP = f;
10517     lastLoadPositionNumber = positionNumber;
10518     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10519     if (first.pr == NoProc) {
10520       StartChessProgram(&first);
10521       InitChessProgram(&first, FALSE);
10522     }
10523     pn = positionNumber;
10524     if (positionNumber < 0) {
10525         /* Negative position number means to seek to that byte offset */
10526         if (fseek(f, -positionNumber, 0) == -1) {
10527             DisplayError(_("Can't seek on position file"), 0);
10528             return FALSE;
10529         };
10530         pn = 1;
10531     } else {
10532         if (fseek(f, 0, 0) == -1) {
10533             if (f == lastLoadPositionFP ?
10534                 positionNumber == lastLoadPositionNumber + 1 :
10535                 positionNumber == 1) {
10536                 pn = 1;
10537             } else {
10538                 DisplayError(_("Can't seek on position file"), 0);
10539                 return FALSE;
10540             }
10541         }
10542     }
10543     /* See if this file is FEN or old-style xboard */
10544     if (fgets(line, MSG_SIZ, f) == NULL) {
10545         DisplayError(_("Position not found in file"), 0);
10546         return FALSE;
10547     }
10548     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10549     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10550
10551     if (pn >= 2) {
10552         if (fenMode || line[0] == '#') pn--;
10553         while (pn > 0) {
10554             /* skip positions before number pn */
10555             if (fgets(line, MSG_SIZ, f) == NULL) {
10556                 Reset(TRUE, TRUE);
10557                 DisplayError(_("Position not found in file"), 0);
10558                 return FALSE;
10559             }
10560             if (fenMode || line[0] == '#') pn--;
10561         }
10562     }
10563
10564     if (fenMode) {
10565         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10566             DisplayError(_("Bad FEN position in file"), 0);
10567             return FALSE;
10568         }
10569     } else {
10570         (void) fgets(line, MSG_SIZ, f);
10571         (void) fgets(line, MSG_SIZ, f);
10572
10573         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10574             (void) fgets(line, MSG_SIZ, f);
10575             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10576                 if (*p == ' ')
10577                   continue;
10578                 initial_position[i][j++] = CharToPiece(*p);
10579             }
10580         }
10581
10582         blackPlaysFirst = FALSE;
10583         if (!feof(f)) {
10584             (void) fgets(line, MSG_SIZ, f);
10585             if (strncmp(line, "black", strlen("black"))==0)
10586               blackPlaysFirst = TRUE;
10587         }
10588     }
10589     startedFromSetupPosition = TRUE;
10590
10591     SendToProgram("force\n", &first);
10592     CopyBoard(boards[0], initial_position);
10593     if (blackPlaysFirst) {
10594         currentMove = forwardMostMove = backwardMostMove = 1;
10595         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10596         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10597         CopyBoard(boards[1], initial_position);
10598         DisplayMessage("", _("Black to play"));
10599     } else {
10600         currentMove = forwardMostMove = backwardMostMove = 0;
10601         DisplayMessage("", _("White to play"));
10602     }
10603     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10604     SendBoard(&first, forwardMostMove);
10605     if (appData.debugMode) {
10606 int i, j;
10607   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10608   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10609         fprintf(debugFP, "Load Position\n");
10610     }
10611
10612     if (positionNumber > 1) {
10613       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10614         DisplayTitle(line);
10615     } else {
10616         DisplayTitle(title);
10617     }
10618     gameMode = EditGame;
10619     ModeHighlight();
10620     ResetClocks();
10621     timeRemaining[0][1] = whiteTimeRemaining;
10622     timeRemaining[1][1] = blackTimeRemaining;
10623     DrawPosition(FALSE, boards[currentMove]);
10624
10625     return TRUE;
10626 }
10627
10628
10629 void
10630 CopyPlayerNameIntoFileName(dest, src)
10631      char **dest, *src;
10632 {
10633     while (*src != NULLCHAR && *src != ',') {
10634         if (*src == ' ') {
10635             *(*dest)++ = '_';
10636             src++;
10637         } else {
10638             *(*dest)++ = *src++;
10639         }
10640     }
10641 }
10642
10643 char *DefaultFileName(ext)
10644      char *ext;
10645 {
10646     static char def[MSG_SIZ];
10647     char *p;
10648
10649     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10650         p = def;
10651         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10652         *p++ = '-';
10653         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10654         *p++ = '.';
10655         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10656     } else {
10657         def[0] = NULLCHAR;
10658     }
10659     return def;
10660 }
10661
10662 /* Save the current game to the given file */
10663 int
10664 SaveGameToFile(filename, append)
10665      char *filename;
10666      int append;
10667 {
10668     FILE *f;
10669     char buf[MSG_SIZ];
10670
10671     if (strcmp(filename, "-") == 0) {
10672         return SaveGame(stdout, 0, NULL);
10673     } else {
10674         f = fopen(filename, append ? "a" : "w");
10675         if (f == NULL) {
10676             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10677             DisplayError(buf, errno);
10678             return FALSE;
10679         } else {
10680             return SaveGame(f, 0, NULL);
10681         }
10682     }
10683 }
10684
10685 char *
10686 SavePart(str)
10687      char *str;
10688 {
10689     static char buf[MSG_SIZ];
10690     char *p;
10691
10692     p = strchr(str, ' ');
10693     if (p == NULL) return str;
10694     strncpy(buf, str, p - str);
10695     buf[p - str] = NULLCHAR;
10696     return buf;
10697 }
10698
10699 #define PGN_MAX_LINE 75
10700
10701 #define PGN_SIDE_WHITE  0
10702 #define PGN_SIDE_BLACK  1
10703
10704 /* [AS] */
10705 static int FindFirstMoveOutOfBook( int side )
10706 {
10707     int result = -1;
10708
10709     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10710         int index = backwardMostMove;
10711         int has_book_hit = 0;
10712
10713         if( (index % 2) != side ) {
10714             index++;
10715         }
10716
10717         while( index < forwardMostMove ) {
10718             /* Check to see if engine is in book */
10719             int depth = pvInfoList[index].depth;
10720             int score = pvInfoList[index].score;
10721             int in_book = 0;
10722
10723             if( depth <= 2 ) {
10724                 in_book = 1;
10725             }
10726             else if( score == 0 && depth == 63 ) {
10727                 in_book = 1; /* Zappa */
10728             }
10729             else if( score == 2 && depth == 99 ) {
10730                 in_book = 1; /* Abrok */
10731             }
10732
10733             has_book_hit += in_book;
10734
10735             if( ! in_book ) {
10736                 result = index;
10737
10738                 break;
10739             }
10740
10741             index += 2;
10742         }
10743     }
10744
10745     return result;
10746 }
10747
10748 /* [AS] */
10749 void GetOutOfBookInfo( char * buf )
10750 {
10751     int oob[2];
10752     int i;
10753     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10754
10755     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10756     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10757
10758     *buf = '\0';
10759
10760     if( oob[0] >= 0 || oob[1] >= 0 ) {
10761         for( i=0; i<2; i++ ) {
10762             int idx = oob[i];
10763
10764             if( idx >= 0 ) {
10765                 if( i > 0 && oob[0] >= 0 ) {
10766                     strcat( buf, "   " );
10767                 }
10768
10769                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10770                 sprintf( buf+strlen(buf), "%s%.2f",
10771                     pvInfoList[idx].score >= 0 ? "+" : "",
10772                     pvInfoList[idx].score / 100.0 );
10773             }
10774         }
10775     }
10776 }
10777
10778 /* Save game in PGN style and close the file */
10779 int
10780 SaveGamePGN(f)
10781      FILE *f;
10782 {
10783     int i, offset, linelen, newblock;
10784     time_t tm;
10785 //    char *movetext;
10786     char numtext[32];
10787     int movelen, numlen, blank;
10788     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10789
10790     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10791
10792     tm = time((time_t *) NULL);
10793
10794     PrintPGNTags(f, &gameInfo);
10795
10796     if (backwardMostMove > 0 || startedFromSetupPosition) {
10797         char *fen = PositionToFEN(backwardMostMove, NULL);
10798         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10799         fprintf(f, "\n{--------------\n");
10800         PrintPosition(f, backwardMostMove);
10801         fprintf(f, "--------------}\n");
10802         free(fen);
10803     }
10804     else {
10805         /* [AS] Out of book annotation */
10806         if( appData.saveOutOfBookInfo ) {
10807             char buf[64];
10808
10809             GetOutOfBookInfo( buf );
10810
10811             if( buf[0] != '\0' ) {
10812                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10813             }
10814         }
10815
10816         fprintf(f, "\n");
10817     }
10818
10819     i = backwardMostMove;
10820     linelen = 0;
10821     newblock = TRUE;
10822
10823     while (i < forwardMostMove) {
10824         /* Print comments preceding this move */
10825         if (commentList[i] != NULL) {
10826             if (linelen > 0) fprintf(f, "\n");
10827             fprintf(f, "%s", commentList[i]);
10828             linelen = 0;
10829             newblock = TRUE;
10830         }
10831
10832         /* Format move number */
10833         if ((i % 2) == 0)
10834           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10835         else
10836           if (newblock)
10837             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10838           else
10839             numtext[0] = NULLCHAR;
10840
10841         numlen = strlen(numtext);
10842         newblock = FALSE;
10843
10844         /* Print move number */
10845         blank = linelen > 0 && numlen > 0;
10846         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10847             fprintf(f, "\n");
10848             linelen = 0;
10849             blank = 0;
10850         }
10851         if (blank) {
10852             fprintf(f, " ");
10853             linelen++;
10854         }
10855         fprintf(f, "%s", numtext);
10856         linelen += numlen;
10857
10858         /* Get move */
10859         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10860         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10861
10862         /* Print move */
10863         blank = linelen > 0 && movelen > 0;
10864         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10865             fprintf(f, "\n");
10866             linelen = 0;
10867             blank = 0;
10868         }
10869         if (blank) {
10870             fprintf(f, " ");
10871             linelen++;
10872         }
10873         fprintf(f, "%s", move_buffer);
10874         linelen += movelen;
10875
10876         /* [AS] Add PV info if present */
10877         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10878             /* [HGM] add time */
10879             char buf[MSG_SIZ]; int seconds;
10880
10881             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10882
10883             if( seconds <= 0)
10884               buf[0] = 0;
10885             else
10886               if( seconds < 30 )
10887                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10888               else
10889                 {
10890                   seconds = (seconds + 4)/10; // round to full seconds
10891                   if( seconds < 60 )
10892                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10893                   else
10894                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10895                 }
10896
10897             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10898                       pvInfoList[i].score >= 0 ? "+" : "",
10899                       pvInfoList[i].score / 100.0,
10900                       pvInfoList[i].depth,
10901                       buf );
10902
10903             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10904
10905             /* Print score/depth */
10906             blank = linelen > 0 && movelen > 0;
10907             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10908                 fprintf(f, "\n");
10909                 linelen = 0;
10910                 blank = 0;
10911             }
10912             if (blank) {
10913                 fprintf(f, " ");
10914                 linelen++;
10915             }
10916             fprintf(f, "%s", move_buffer);
10917             linelen += movelen;
10918         }
10919
10920         i++;
10921     }
10922
10923     /* Start a new line */
10924     if (linelen > 0) fprintf(f, "\n");
10925
10926     /* Print comments after last move */
10927     if (commentList[i] != NULL) {
10928         fprintf(f, "%s\n", commentList[i]);
10929     }
10930
10931     /* Print result */
10932     if (gameInfo.resultDetails != NULL &&
10933         gameInfo.resultDetails[0] != NULLCHAR) {
10934         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10935                 PGNResult(gameInfo.result));
10936     } else {
10937         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10938     }
10939
10940     fclose(f);
10941     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10942     return TRUE;
10943 }
10944
10945 /* Save game in old style and close the file */
10946 int
10947 SaveGameOldStyle(f)
10948      FILE *f;
10949 {
10950     int i, offset;
10951     time_t tm;
10952
10953     tm = time((time_t *) NULL);
10954
10955     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10956     PrintOpponents(f);
10957
10958     if (backwardMostMove > 0 || startedFromSetupPosition) {
10959         fprintf(f, "\n[--------------\n");
10960         PrintPosition(f, backwardMostMove);
10961         fprintf(f, "--------------]\n");
10962     } else {
10963         fprintf(f, "\n");
10964     }
10965
10966     i = backwardMostMove;
10967     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10968
10969     while (i < forwardMostMove) {
10970         if (commentList[i] != NULL) {
10971             fprintf(f, "[%s]\n", commentList[i]);
10972         }
10973
10974         if ((i % 2) == 1) {
10975             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10976             i++;
10977         } else {
10978             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10979             i++;
10980             if (commentList[i] != NULL) {
10981                 fprintf(f, "\n");
10982                 continue;
10983             }
10984             if (i >= forwardMostMove) {
10985                 fprintf(f, "\n");
10986                 break;
10987             }
10988             fprintf(f, "%s\n", parseList[i]);
10989             i++;
10990         }
10991     }
10992
10993     if (commentList[i] != NULL) {
10994         fprintf(f, "[%s]\n", commentList[i]);
10995     }
10996
10997     /* This isn't really the old style, but it's close enough */
10998     if (gameInfo.resultDetails != NULL &&
10999         gameInfo.resultDetails[0] != NULLCHAR) {
11000         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11001                 gameInfo.resultDetails);
11002     } else {
11003         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11004     }
11005
11006     fclose(f);
11007     return TRUE;
11008 }
11009
11010 /* Save the current game to open file f and close the file */
11011 int
11012 SaveGame(f, dummy, dummy2)
11013      FILE *f;
11014      int dummy;
11015      char *dummy2;
11016 {
11017     if (gameMode == EditPosition) EditPositionDone(TRUE);
11018     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11019     if (appData.oldSaveStyle)
11020       return SaveGameOldStyle(f);
11021     else
11022       return SaveGamePGN(f);
11023 }
11024
11025 /* Save the current position to the given file */
11026 int
11027 SavePositionToFile(filename)
11028      char *filename;
11029 {
11030     FILE *f;
11031     char buf[MSG_SIZ];
11032
11033     if (strcmp(filename, "-") == 0) {
11034         return SavePosition(stdout, 0, NULL);
11035     } else {
11036         f = fopen(filename, "a");
11037         if (f == NULL) {
11038             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11039             DisplayError(buf, errno);
11040             return FALSE;
11041         } else {
11042             SavePosition(f, 0, NULL);
11043             return TRUE;
11044         }
11045     }
11046 }
11047
11048 /* Save the current position to the given open file and close the file */
11049 int
11050 SavePosition(f, dummy, dummy2)
11051      FILE *f;
11052      int dummy;
11053      char *dummy2;
11054 {
11055     time_t tm;
11056     char *fen;
11057
11058     if (gameMode == EditPosition) EditPositionDone(TRUE);
11059     if (appData.oldSaveStyle) {
11060         tm = time((time_t *) NULL);
11061
11062         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11063         PrintOpponents(f);
11064         fprintf(f, "[--------------\n");
11065         PrintPosition(f, currentMove);
11066         fprintf(f, "--------------]\n");
11067     } else {
11068         fen = PositionToFEN(currentMove, NULL);
11069         fprintf(f, "%s\n", fen);
11070         free(fen);
11071     }
11072     fclose(f);
11073     return TRUE;
11074 }
11075
11076 void
11077 ReloadCmailMsgEvent(unregister)
11078      int unregister;
11079 {
11080 #if !WIN32
11081     static char *inFilename = NULL;
11082     static char *outFilename;
11083     int i;
11084     struct stat inbuf, outbuf;
11085     int status;
11086
11087     /* Any registered moves are unregistered if unregister is set, */
11088     /* i.e. invoked by the signal handler */
11089     if (unregister) {
11090         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11091             cmailMoveRegistered[i] = FALSE;
11092             if (cmailCommentList[i] != NULL) {
11093                 free(cmailCommentList[i]);
11094                 cmailCommentList[i] = NULL;
11095             }
11096         }
11097         nCmailMovesRegistered = 0;
11098     }
11099
11100     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11101         cmailResult[i] = CMAIL_NOT_RESULT;
11102     }
11103     nCmailResults = 0;
11104
11105     if (inFilename == NULL) {
11106         /* Because the filenames are static they only get malloced once  */
11107         /* and they never get freed                                      */
11108         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11109         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11110
11111         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11112         sprintf(outFilename, "%s.out", appData.cmailGameName);
11113     }
11114
11115     status = stat(outFilename, &outbuf);
11116     if (status < 0) {
11117         cmailMailedMove = FALSE;
11118     } else {
11119         status = stat(inFilename, &inbuf);
11120         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11121     }
11122
11123     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11124        counts the games, notes how each one terminated, etc.
11125
11126        It would be nice to remove this kludge and instead gather all
11127        the information while building the game list.  (And to keep it
11128        in the game list nodes instead of having a bunch of fixed-size
11129        parallel arrays.)  Note this will require getting each game's
11130        termination from the PGN tags, as the game list builder does
11131        not process the game moves.  --mann
11132        */
11133     cmailMsgLoaded = TRUE;
11134     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11135
11136     /* Load first game in the file or popup game menu */
11137     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11138
11139 #endif /* !WIN32 */
11140     return;
11141 }
11142
11143 int
11144 RegisterMove()
11145 {
11146     FILE *f;
11147     char string[MSG_SIZ];
11148
11149     if (   cmailMailedMove
11150         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11151         return TRUE;            /* Allow free viewing  */
11152     }
11153
11154     /* Unregister move to ensure that we don't leave RegisterMove        */
11155     /* with the move registered when the conditions for registering no   */
11156     /* longer hold                                                       */
11157     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11158         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11159         nCmailMovesRegistered --;
11160
11161         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11162           {
11163               free(cmailCommentList[lastLoadGameNumber - 1]);
11164               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11165           }
11166     }
11167
11168     if (cmailOldMove == -1) {
11169         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11170         return FALSE;
11171     }
11172
11173     if (currentMove > cmailOldMove + 1) {
11174         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11175         return FALSE;
11176     }
11177
11178     if (currentMove < cmailOldMove) {
11179         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11180         return FALSE;
11181     }
11182
11183     if (forwardMostMove > currentMove) {
11184         /* Silently truncate extra moves */
11185         TruncateGame();
11186     }
11187
11188     if (   (currentMove == cmailOldMove + 1)
11189         || (   (currentMove == cmailOldMove)
11190             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11191                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11192         if (gameInfo.result != GameUnfinished) {
11193             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11194         }
11195
11196         if (commentList[currentMove] != NULL) {
11197             cmailCommentList[lastLoadGameNumber - 1]
11198               = StrSave(commentList[currentMove]);
11199         }
11200         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11201
11202         if (appData.debugMode)
11203           fprintf(debugFP, "Saving %s for game %d\n",
11204                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11205
11206         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11207
11208         f = fopen(string, "w");
11209         if (appData.oldSaveStyle) {
11210             SaveGameOldStyle(f); /* also closes the file */
11211
11212             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11213             f = fopen(string, "w");
11214             SavePosition(f, 0, NULL); /* also closes the file */
11215         } else {
11216             fprintf(f, "{--------------\n");
11217             PrintPosition(f, currentMove);
11218             fprintf(f, "--------------}\n\n");
11219
11220             SaveGame(f, 0, NULL); /* also closes the file*/
11221         }
11222
11223         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11224         nCmailMovesRegistered ++;
11225     } else if (nCmailGames == 1) {
11226         DisplayError(_("You have not made a move yet"), 0);
11227         return FALSE;
11228     }
11229
11230     return TRUE;
11231 }
11232
11233 void
11234 MailMoveEvent()
11235 {
11236 #if !WIN32
11237     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11238     FILE *commandOutput;
11239     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11240     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11241     int nBuffers;
11242     int i;
11243     int archived;
11244     char *arcDir;
11245
11246     if (! cmailMsgLoaded) {
11247         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11248         return;
11249     }
11250
11251     if (nCmailGames == nCmailResults) {
11252         DisplayError(_("No unfinished games"), 0);
11253         return;
11254     }
11255
11256 #if CMAIL_PROHIBIT_REMAIL
11257     if (cmailMailedMove) {
11258       snprintf(msg, MSG_SIZ, _("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);
11259         DisplayError(msg, 0);
11260         return;
11261     }
11262 #endif
11263
11264     if (! (cmailMailedMove || RegisterMove())) return;
11265
11266     if (   cmailMailedMove
11267         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11268       snprintf(string, MSG_SIZ, partCommandString,
11269                appData.debugMode ? " -v" : "", appData.cmailGameName);
11270         commandOutput = popen(string, "r");
11271
11272         if (commandOutput == NULL) {
11273             DisplayError(_("Failed to invoke cmail"), 0);
11274         } else {
11275             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11276                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11277             }
11278             if (nBuffers > 1) {
11279                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11280                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11281                 nBytes = MSG_SIZ - 1;
11282             } else {
11283                 (void) memcpy(msg, buffer, nBytes);
11284             }
11285             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11286
11287             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11288                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11289
11290                 archived = TRUE;
11291                 for (i = 0; i < nCmailGames; i ++) {
11292                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11293                         archived = FALSE;
11294                     }
11295                 }
11296                 if (   archived
11297                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11298                         != NULL)) {
11299                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11300                            arcDir,
11301                            appData.cmailGameName,
11302                            gameInfo.date);
11303                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11304                     cmailMsgLoaded = FALSE;
11305                 }
11306             }
11307
11308             DisplayInformation(msg);
11309             pclose(commandOutput);
11310         }
11311     } else {
11312         if ((*cmailMsg) != '\0') {
11313             DisplayInformation(cmailMsg);
11314         }
11315     }
11316
11317     return;
11318 #endif /* !WIN32 */
11319 }
11320
11321 char *
11322 CmailMsg()
11323 {
11324 #if WIN32
11325     return NULL;
11326 #else
11327     int  prependComma = 0;
11328     char number[5];
11329     char string[MSG_SIZ];       /* Space for game-list */
11330     int  i;
11331
11332     if (!cmailMsgLoaded) return "";
11333
11334     if (cmailMailedMove) {
11335       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11336     } else {
11337         /* Create a list of games left */
11338       snprintf(string, MSG_SIZ, "[");
11339         for (i = 0; i < nCmailGames; i ++) {
11340             if (! (   cmailMoveRegistered[i]
11341                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11342                 if (prependComma) {
11343                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11344                 } else {
11345                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11346                     prependComma = 1;
11347                 }
11348
11349                 strcat(string, number);
11350             }
11351         }
11352         strcat(string, "]");
11353
11354         if (nCmailMovesRegistered + nCmailResults == 0) {
11355             switch (nCmailGames) {
11356               case 1:
11357                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11358                 break;
11359
11360               case 2:
11361                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11362                 break;
11363
11364               default:
11365                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11366                          nCmailGames);
11367                 break;
11368             }
11369         } else {
11370             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11371               case 1:
11372                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11373                          string);
11374                 break;
11375
11376               case 0:
11377                 if (nCmailResults == nCmailGames) {
11378                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11379                 } else {
11380                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11381                 }
11382                 break;
11383
11384               default:
11385                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11386                          string);
11387             }
11388         }
11389     }
11390     return cmailMsg;
11391 #endif /* WIN32 */
11392 }
11393
11394 void
11395 ResetGameEvent()
11396 {
11397     if (gameMode == Training)
11398       SetTrainingModeOff();
11399
11400     Reset(TRUE, TRUE);
11401     cmailMsgLoaded = FALSE;
11402     if (appData.icsActive) {
11403       SendToICS(ics_prefix);
11404       SendToICS("refresh\n");
11405     }
11406 }
11407
11408 void
11409 ExitEvent(status)
11410      int status;
11411 {
11412     exiting++;
11413     if (exiting > 2) {
11414       /* Give up on clean exit */
11415       exit(status);
11416     }
11417     if (exiting > 1) {
11418       /* Keep trying for clean exit */
11419       return;
11420     }
11421
11422     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11423
11424     if (telnetISR != NULL) {
11425       RemoveInputSource(telnetISR);
11426     }
11427     if (icsPR != NoProc) {
11428       DestroyChildProcess(icsPR, TRUE);
11429     }
11430
11431     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11432     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11433
11434     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11435     /* make sure this other one finishes before killing it!                  */
11436     if(endingGame) { int count = 0;
11437         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11438         while(endingGame && count++ < 10) DoSleep(1);
11439         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11440     }
11441
11442     /* Kill off chess programs */
11443     if (first.pr != NoProc) {
11444         ExitAnalyzeMode();
11445
11446         DoSleep( appData.delayBeforeQuit );
11447         SendToProgram("quit\n", &first);
11448         DoSleep( appData.delayAfterQuit );
11449         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11450     }
11451     if (second.pr != NoProc) {
11452         DoSleep( appData.delayBeforeQuit );
11453         SendToProgram("quit\n", &second);
11454         DoSleep( appData.delayAfterQuit );
11455         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11456     }
11457     if (first.isr != NULL) {
11458         RemoveInputSource(first.isr);
11459     }
11460     if (second.isr != NULL) {
11461         RemoveInputSource(second.isr);
11462     }
11463
11464     ShutDownFrontEnd();
11465     exit(status);
11466 }
11467
11468 void
11469 PauseEvent()
11470 {
11471     if (appData.debugMode)
11472         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11473     if (pausing) {
11474         pausing = FALSE;
11475         ModeHighlight();
11476         if (gameMode == MachinePlaysWhite ||
11477             gameMode == MachinePlaysBlack) {
11478             StartClocks();
11479         } else {
11480             DisplayBothClocks();
11481         }
11482         if (gameMode == PlayFromGameFile) {
11483             if (appData.timeDelay >= 0)
11484                 AutoPlayGameLoop();
11485         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11486             Reset(FALSE, TRUE);
11487             SendToICS(ics_prefix);
11488             SendToICS("refresh\n");
11489         } else if (currentMove < forwardMostMove) {
11490             ForwardInner(forwardMostMove);
11491         }
11492         pauseExamInvalid = FALSE;
11493     } else {
11494         switch (gameMode) {
11495           default:
11496             return;
11497           case IcsExamining:
11498             pauseExamForwardMostMove = forwardMostMove;
11499             pauseExamInvalid = FALSE;
11500             /* fall through */
11501           case IcsObserving:
11502           case IcsPlayingWhite:
11503           case IcsPlayingBlack:
11504             pausing = TRUE;
11505             ModeHighlight();
11506             return;
11507           case PlayFromGameFile:
11508             (void) StopLoadGameTimer();
11509             pausing = TRUE;
11510             ModeHighlight();
11511             break;
11512           case BeginningOfGame:
11513             if (appData.icsActive) return;
11514             /* else fall through */
11515           case MachinePlaysWhite:
11516           case MachinePlaysBlack:
11517           case TwoMachinesPlay:
11518             if (forwardMostMove == 0)
11519               return;           /* don't pause if no one has moved */
11520             if ((gameMode == MachinePlaysWhite &&
11521                  !WhiteOnMove(forwardMostMove)) ||
11522                 (gameMode == MachinePlaysBlack &&
11523                  WhiteOnMove(forwardMostMove))) {
11524                 StopClocks();
11525             }
11526             pausing = TRUE;
11527             ModeHighlight();
11528             break;
11529         }
11530     }
11531 }
11532
11533 void
11534 EditCommentEvent()
11535 {
11536     char title[MSG_SIZ];
11537
11538     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11539       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11540     } else {
11541       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11542                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11543                parseList[currentMove - 1]);
11544     }
11545
11546     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11547 }
11548
11549
11550 void
11551 EditTagsEvent()
11552 {
11553     char *tags = PGNTags(&gameInfo);
11554     EditTagsPopUp(tags);
11555     free(tags);
11556 }
11557
11558 void
11559 AnalyzeModeEvent()
11560 {
11561     if (appData.noChessProgram || gameMode == AnalyzeMode)
11562       return;
11563
11564     if (gameMode != AnalyzeFile) {
11565         if (!appData.icsEngineAnalyze) {
11566                EditGameEvent();
11567                if (gameMode != EditGame) return;
11568         }
11569         ResurrectChessProgram();
11570         SendToProgram("analyze\n", &first);
11571         first.analyzing = TRUE;
11572         /*first.maybeThinking = TRUE;*/
11573         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11574         EngineOutputPopUp();
11575     }
11576     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11577     pausing = FALSE;
11578     ModeHighlight();
11579     SetGameInfo();
11580
11581     StartAnalysisClock();
11582     GetTimeMark(&lastNodeCountTime);
11583     lastNodeCount = 0;
11584 }
11585
11586 void
11587 AnalyzeFileEvent()
11588 {
11589     if (appData.noChessProgram || gameMode == AnalyzeFile)
11590       return;
11591
11592     if (gameMode != AnalyzeMode) {
11593         EditGameEvent();
11594         if (gameMode != EditGame) return;
11595         ResurrectChessProgram();
11596         SendToProgram("analyze\n", &first);
11597         first.analyzing = TRUE;
11598         /*first.maybeThinking = TRUE;*/
11599         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11600         EngineOutputPopUp();
11601     }
11602     gameMode = AnalyzeFile;
11603     pausing = FALSE;
11604     ModeHighlight();
11605     SetGameInfo();
11606
11607     StartAnalysisClock();
11608     GetTimeMark(&lastNodeCountTime);
11609     lastNodeCount = 0;
11610 }
11611
11612 void
11613 MachineWhiteEvent()
11614 {
11615     char buf[MSG_SIZ];
11616     char *bookHit = NULL;
11617
11618     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11619       return;
11620
11621
11622     if (gameMode == PlayFromGameFile ||
11623         gameMode == TwoMachinesPlay  ||
11624         gameMode == Training         ||
11625         gameMode == AnalyzeMode      ||
11626         gameMode == EndOfGame)
11627         EditGameEvent();
11628
11629     if (gameMode == EditPosition)
11630         EditPositionDone(TRUE);
11631
11632     if (!WhiteOnMove(currentMove)) {
11633         DisplayError(_("It is not White's turn"), 0);
11634         return;
11635     }
11636
11637     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11638       ExitAnalyzeMode();
11639
11640     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11641         gameMode == AnalyzeFile)
11642         TruncateGame();
11643
11644     ResurrectChessProgram();    /* in case it isn't running */
11645     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11646         gameMode = MachinePlaysWhite;
11647         ResetClocks();
11648     } else
11649     gameMode = MachinePlaysWhite;
11650     pausing = FALSE;
11651     ModeHighlight();
11652     SetGameInfo();
11653     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11654     DisplayTitle(buf);
11655     if (first.sendName) {
11656       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11657       SendToProgram(buf, &first);
11658     }
11659     if (first.sendTime) {
11660       if (first.useColors) {
11661         SendToProgram("black\n", &first); /*gnu kludge*/
11662       }
11663       SendTimeRemaining(&first, TRUE);
11664     }
11665     if (first.useColors) {
11666       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11667     }
11668     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11669     SetMachineThinkingEnables();
11670     first.maybeThinking = TRUE;
11671     StartClocks();
11672     firstMove = FALSE;
11673
11674     if (appData.autoFlipView && !flipView) {
11675       flipView = !flipView;
11676       DrawPosition(FALSE, NULL);
11677       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11678     }
11679
11680     if(bookHit) { // [HGM] book: simulate book reply
11681         static char bookMove[MSG_SIZ]; // a bit generous?
11682
11683         programStats.nodes = programStats.depth = programStats.time =
11684         programStats.score = programStats.got_only_move = 0;
11685         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11686
11687         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11688         strcat(bookMove, bookHit);
11689         HandleMachineMove(bookMove, &first);
11690     }
11691 }
11692
11693 void
11694 MachineBlackEvent()
11695 {
11696   char buf[MSG_SIZ];
11697   char *bookHit = NULL;
11698
11699     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11700         return;
11701
11702
11703     if (gameMode == PlayFromGameFile ||
11704         gameMode == TwoMachinesPlay  ||
11705         gameMode == Training         ||
11706         gameMode == AnalyzeMode      ||
11707         gameMode == EndOfGame)
11708         EditGameEvent();
11709
11710     if (gameMode == EditPosition)
11711         EditPositionDone(TRUE);
11712
11713     if (WhiteOnMove(currentMove)) {
11714         DisplayError(_("It is not Black's turn"), 0);
11715         return;
11716     }
11717
11718     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11719       ExitAnalyzeMode();
11720
11721     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11722         gameMode == AnalyzeFile)
11723         TruncateGame();
11724
11725     ResurrectChessProgram();    /* in case it isn't running */
11726     gameMode = MachinePlaysBlack;
11727     pausing = FALSE;
11728     ModeHighlight();
11729     SetGameInfo();
11730     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11731     DisplayTitle(buf);
11732     if (first.sendName) {
11733       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11734       SendToProgram(buf, &first);
11735     }
11736     if (first.sendTime) {
11737       if (first.useColors) {
11738         SendToProgram("white\n", &first); /*gnu kludge*/
11739       }
11740       SendTimeRemaining(&first, FALSE);
11741     }
11742     if (first.useColors) {
11743       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11744     }
11745     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11746     SetMachineThinkingEnables();
11747     first.maybeThinking = TRUE;
11748     StartClocks();
11749
11750     if (appData.autoFlipView && flipView) {
11751       flipView = !flipView;
11752       DrawPosition(FALSE, NULL);
11753       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11754     }
11755     if(bookHit) { // [HGM] book: simulate book reply
11756         static char bookMove[MSG_SIZ]; // a bit generous?
11757
11758         programStats.nodes = programStats.depth = programStats.time =
11759         programStats.score = programStats.got_only_move = 0;
11760         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11761
11762         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11763         strcat(bookMove, bookHit);
11764         HandleMachineMove(bookMove, &first);
11765     }
11766 }
11767
11768
11769 void
11770 DisplayTwoMachinesTitle()
11771 {
11772     char buf[MSG_SIZ];
11773     if (appData.matchGames > 0) {
11774         if (first.twoMachinesColor[0] == 'w') {
11775           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11776                    gameInfo.white, gameInfo.black,
11777                    first.matchWins, second.matchWins,
11778                    matchGame - 1 - (first.matchWins + second.matchWins));
11779         } else {
11780           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11781                    gameInfo.white, gameInfo.black,
11782                    second.matchWins, first.matchWins,
11783                    matchGame - 1 - (first.matchWins + second.matchWins));
11784         }
11785     } else {
11786       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11787     }
11788     DisplayTitle(buf);
11789 }
11790
11791 void
11792 TwoMachinesEvent P((void))
11793 {
11794     int i;
11795     char buf[MSG_SIZ];
11796     ChessProgramState *onmove;
11797     char *bookHit = NULL;
11798
11799     if (appData.noChessProgram) return;
11800
11801     switch (gameMode) {
11802       case TwoMachinesPlay:
11803         return;
11804       case MachinePlaysWhite:
11805       case MachinePlaysBlack:
11806         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11807             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11808             return;
11809         }
11810         /* fall through */
11811       case BeginningOfGame:
11812       case PlayFromGameFile:
11813       case EndOfGame:
11814         EditGameEvent();
11815         if (gameMode != EditGame) return;
11816         break;
11817       case EditPosition:
11818         EditPositionDone(TRUE);
11819         break;
11820       case AnalyzeMode:
11821       case AnalyzeFile:
11822         ExitAnalyzeMode();
11823         break;
11824       case EditGame:
11825       default:
11826         break;
11827     }
11828
11829 //    forwardMostMove = currentMove;
11830     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11831     ResurrectChessProgram();    /* in case first program isn't running */
11832
11833     if (second.pr == NULL) {
11834         StartChessProgram(&second);
11835         if (second.protocolVersion == 1) {
11836           TwoMachinesEventIfReady();
11837         } else {
11838           /* kludge: allow timeout for initial "feature" command */
11839           FreezeUI();
11840           DisplayMessage("", _("Starting second chess program"));
11841           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11842         }
11843         return;
11844     }
11845     DisplayMessage("", "");
11846     InitChessProgram(&second, FALSE);
11847     SendToProgram("force\n", &second);
11848     if (startedFromSetupPosition) {
11849         SendBoard(&second, backwardMostMove);
11850     if (appData.debugMode) {
11851         fprintf(debugFP, "Two Machines\n");
11852     }
11853     }
11854     for (i = backwardMostMove; i < forwardMostMove; i++) {
11855         SendMoveToProgram(i, &second);
11856     }
11857
11858     gameMode = TwoMachinesPlay;
11859     pausing = FALSE;
11860     ModeHighlight();
11861     SetGameInfo();
11862     DisplayTwoMachinesTitle();
11863     firstMove = TRUE;
11864     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11865         onmove = &first;
11866     } else {
11867         onmove = &second;
11868     }
11869
11870     SendToProgram(first.computerString, &first);
11871     if (first.sendName) {
11872       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11873       SendToProgram(buf, &first);
11874     }
11875     SendToProgram(second.computerString, &second);
11876     if (second.sendName) {
11877       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11878       SendToProgram(buf, &second);
11879     }
11880
11881     ResetClocks();
11882     if (!first.sendTime || !second.sendTime) {
11883         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11884         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11885     }
11886     if (onmove->sendTime) {
11887       if (onmove->useColors) {
11888         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11889       }
11890       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11891     }
11892     if (onmove->useColors) {
11893       SendToProgram(onmove->twoMachinesColor, onmove);
11894     }
11895     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11896 //    SendToProgram("go\n", onmove);
11897     onmove->maybeThinking = TRUE;
11898     SetMachineThinkingEnables();
11899
11900     StartClocks();
11901
11902     if(bookHit) { // [HGM] book: simulate book reply
11903         static char bookMove[MSG_SIZ]; // a bit generous?
11904
11905         programStats.nodes = programStats.depth = programStats.time =
11906         programStats.score = programStats.got_only_move = 0;
11907         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11908
11909         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11910         strcat(bookMove, bookHit);
11911         savedMessage = bookMove; // args for deferred call
11912         savedState = onmove;
11913         ScheduleDelayedEvent(DeferredBookMove, 1);
11914     }
11915 }
11916
11917 void
11918 TrainingEvent()
11919 {
11920     if (gameMode == Training) {
11921       SetTrainingModeOff();
11922       gameMode = PlayFromGameFile;
11923       DisplayMessage("", _("Training mode off"));
11924     } else {
11925       gameMode = Training;
11926       animateTraining = appData.animate;
11927
11928       /* make sure we are not already at the end of the game */
11929       if (currentMove < forwardMostMove) {
11930         SetTrainingModeOn();
11931         DisplayMessage("", _("Training mode on"));
11932       } else {
11933         gameMode = PlayFromGameFile;
11934         DisplayError(_("Already at end of game"), 0);
11935       }
11936     }
11937     ModeHighlight();
11938 }
11939
11940 void
11941 IcsClientEvent()
11942 {
11943     if (!appData.icsActive) return;
11944     switch (gameMode) {
11945       case IcsPlayingWhite:
11946       case IcsPlayingBlack:
11947       case IcsObserving:
11948       case IcsIdle:
11949       case BeginningOfGame:
11950       case IcsExamining:
11951         return;
11952
11953       case EditGame:
11954         break;
11955
11956       case EditPosition:
11957         EditPositionDone(TRUE);
11958         break;
11959
11960       case AnalyzeMode:
11961       case AnalyzeFile:
11962         ExitAnalyzeMode();
11963         break;
11964
11965       default:
11966         EditGameEvent();
11967         break;
11968     }
11969
11970     gameMode = IcsIdle;
11971     ModeHighlight();
11972     return;
11973 }
11974
11975
11976 void
11977 EditGameEvent()
11978 {
11979     int i;
11980
11981     switch (gameMode) {
11982       case Training:
11983         SetTrainingModeOff();
11984         break;
11985       case MachinePlaysWhite:
11986       case MachinePlaysBlack:
11987       case BeginningOfGame:
11988         SendToProgram("force\n", &first);
11989         SetUserThinkingEnables();
11990         break;
11991       case PlayFromGameFile:
11992         (void) StopLoadGameTimer();
11993         if (gameFileFP != NULL) {
11994             gameFileFP = NULL;
11995         }
11996         break;
11997       case EditPosition:
11998         EditPositionDone(TRUE);
11999         break;
12000       case AnalyzeMode:
12001       case AnalyzeFile:
12002         ExitAnalyzeMode();
12003         SendToProgram("force\n", &first);
12004         break;
12005       case TwoMachinesPlay:
12006         GameEnds(EndOfFile, NULL, GE_PLAYER);
12007         ResurrectChessProgram();
12008         SetUserThinkingEnables();
12009         break;
12010       case EndOfGame:
12011         ResurrectChessProgram();
12012         break;
12013       case IcsPlayingBlack:
12014       case IcsPlayingWhite:
12015         DisplayError(_("Warning: You are still playing a game"), 0);
12016         break;
12017       case IcsObserving:
12018         DisplayError(_("Warning: You are still observing a game"), 0);
12019         break;
12020       case IcsExamining:
12021         DisplayError(_("Warning: You are still examining a game"), 0);
12022         break;
12023       case IcsIdle:
12024         break;
12025       case EditGame:
12026       default:
12027         return;
12028     }
12029
12030     pausing = FALSE;
12031     StopClocks();
12032     first.offeredDraw = second.offeredDraw = 0;
12033
12034     if (gameMode == PlayFromGameFile) {
12035         whiteTimeRemaining = timeRemaining[0][currentMove];
12036         blackTimeRemaining = timeRemaining[1][currentMove];
12037         DisplayTitle("");
12038     }
12039
12040     if (gameMode == MachinePlaysWhite ||
12041         gameMode == MachinePlaysBlack ||
12042         gameMode == TwoMachinesPlay ||
12043         gameMode == EndOfGame) {
12044         i = forwardMostMove;
12045         while (i > currentMove) {
12046             SendToProgram("undo\n", &first);
12047             i--;
12048         }
12049         whiteTimeRemaining = timeRemaining[0][currentMove];
12050         blackTimeRemaining = timeRemaining[1][currentMove];
12051         DisplayBothClocks();
12052         if (whiteFlag || blackFlag) {
12053             whiteFlag = blackFlag = 0;
12054         }
12055         DisplayTitle("");
12056     }
12057
12058     gameMode = EditGame;
12059     ModeHighlight();
12060     SetGameInfo();
12061 }
12062
12063
12064 void
12065 EditPositionEvent()
12066 {
12067     if (gameMode == EditPosition) {
12068         EditGameEvent();
12069         return;
12070     }
12071
12072     EditGameEvent();
12073     if (gameMode != EditGame) return;
12074
12075     gameMode = EditPosition;
12076     ModeHighlight();
12077     SetGameInfo();
12078     if (currentMove > 0)
12079       CopyBoard(boards[0], boards[currentMove]);
12080
12081     blackPlaysFirst = !WhiteOnMove(currentMove);
12082     ResetClocks();
12083     currentMove = forwardMostMove = backwardMostMove = 0;
12084     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12085     DisplayMove(-1);
12086 }
12087
12088 void
12089 ExitAnalyzeMode()
12090 {
12091     /* [DM] icsEngineAnalyze - possible call from other functions */
12092     if (appData.icsEngineAnalyze) {
12093         appData.icsEngineAnalyze = FALSE;
12094
12095         DisplayMessage("",_("Close ICS engine analyze..."));
12096     }
12097     if (first.analysisSupport && first.analyzing) {
12098       SendToProgram("exit\n", &first);
12099       first.analyzing = FALSE;
12100     }
12101     thinkOutput[0] = NULLCHAR;
12102 }
12103
12104 void
12105 EditPositionDone(Boolean fakeRights)
12106 {
12107     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12108
12109     startedFromSetupPosition = TRUE;
12110     InitChessProgram(&first, FALSE);
12111     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12112       boards[0][EP_STATUS] = EP_NONE;
12113       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12114     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12115         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12116         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12117       } else boards[0][CASTLING][2] = NoRights;
12118     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12119         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12120         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12121       } else boards[0][CASTLING][5] = NoRights;
12122     }
12123     SendToProgram("force\n", &first);
12124     if (blackPlaysFirst) {
12125         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12126         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12127         currentMove = forwardMostMove = backwardMostMove = 1;
12128         CopyBoard(boards[1], boards[0]);
12129     } else {
12130         currentMove = forwardMostMove = backwardMostMove = 0;
12131     }
12132     SendBoard(&first, forwardMostMove);
12133     if (appData.debugMode) {
12134         fprintf(debugFP, "EditPosDone\n");
12135     }
12136     DisplayTitle("");
12137     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12138     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12139     gameMode = EditGame;
12140     ModeHighlight();
12141     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12142     ClearHighlights(); /* [AS] */
12143 }
12144
12145 /* Pause for `ms' milliseconds */
12146 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12147 void
12148 TimeDelay(ms)
12149      long ms;
12150 {
12151     TimeMark m1, m2;
12152
12153     GetTimeMark(&m1);
12154     do {
12155         GetTimeMark(&m2);
12156     } while (SubtractTimeMarks(&m2, &m1) < ms);
12157 }
12158
12159 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12160 void
12161 SendMultiLineToICS(buf)
12162      char *buf;
12163 {
12164     char temp[MSG_SIZ+1], *p;
12165     int len;
12166
12167     len = strlen(buf);
12168     if (len > MSG_SIZ)
12169       len = MSG_SIZ;
12170
12171     strncpy(temp, buf, len);
12172     temp[len] = 0;
12173
12174     p = temp;
12175     while (*p) {
12176         if (*p == '\n' || *p == '\r')
12177           *p = ' ';
12178         ++p;
12179     }
12180
12181     strcat(temp, "\n");
12182     SendToICS(temp);
12183     SendToPlayer(temp, strlen(temp));
12184 }
12185
12186 void
12187 SetWhiteToPlayEvent()
12188 {
12189     if (gameMode == EditPosition) {
12190         blackPlaysFirst = FALSE;
12191         DisplayBothClocks();    /* works because currentMove is 0 */
12192     } else if (gameMode == IcsExamining) {
12193         SendToICS(ics_prefix);
12194         SendToICS("tomove white\n");
12195     }
12196 }
12197
12198 void
12199 SetBlackToPlayEvent()
12200 {
12201     if (gameMode == EditPosition) {
12202         blackPlaysFirst = TRUE;
12203         currentMove = 1;        /* kludge */
12204         DisplayBothClocks();
12205         currentMove = 0;
12206     } else if (gameMode == IcsExamining) {
12207         SendToICS(ics_prefix);
12208         SendToICS("tomove black\n");
12209     }
12210 }
12211
12212 void
12213 EditPositionMenuEvent(selection, x, y)
12214      ChessSquare selection;
12215      int x, y;
12216 {
12217     char buf[MSG_SIZ];
12218     ChessSquare piece = boards[0][y][x];
12219
12220     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12221
12222     switch (selection) {
12223       case ClearBoard:
12224         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12225             SendToICS(ics_prefix);
12226             SendToICS("bsetup clear\n");
12227         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12228             SendToICS(ics_prefix);
12229             SendToICS("clearboard\n");
12230         } else {
12231             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12232                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12233                 for (y = 0; y < BOARD_HEIGHT; y++) {
12234                     if (gameMode == IcsExamining) {
12235                         if (boards[currentMove][y][x] != EmptySquare) {
12236                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12237                                     AAA + x, ONE + y);
12238                             SendToICS(buf);
12239                         }
12240                     } else {
12241                         boards[0][y][x] = p;
12242                     }
12243                 }
12244             }
12245         }
12246         if (gameMode == EditPosition) {
12247             DrawPosition(FALSE, boards[0]);
12248         }
12249         break;
12250
12251       case WhitePlay:
12252         SetWhiteToPlayEvent();
12253         break;
12254
12255       case BlackPlay:
12256         SetBlackToPlayEvent();
12257         break;
12258
12259       case EmptySquare:
12260         if (gameMode == IcsExamining) {
12261             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12262             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12263             SendToICS(buf);
12264         } else {
12265             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12266                 if(x == BOARD_LEFT-2) {
12267                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12268                     boards[0][y][1] = 0;
12269                 } else
12270                 if(x == BOARD_RGHT+1) {
12271                     if(y >= gameInfo.holdingsSize) break;
12272                     boards[0][y][BOARD_WIDTH-2] = 0;
12273                 } else break;
12274             }
12275             boards[0][y][x] = EmptySquare;
12276             DrawPosition(FALSE, boards[0]);
12277         }
12278         break;
12279
12280       case PromotePiece:
12281         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12282            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12283             selection = (ChessSquare) (PROMOTED piece);
12284         } else if(piece == EmptySquare) selection = WhiteSilver;
12285         else selection = (ChessSquare)((int)piece - 1);
12286         goto defaultlabel;
12287
12288       case DemotePiece:
12289         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12290            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12291             selection = (ChessSquare) (DEMOTED piece);
12292         } else if(piece == EmptySquare) selection = BlackSilver;
12293         else selection = (ChessSquare)((int)piece + 1);
12294         goto defaultlabel;
12295
12296       case WhiteQueen:
12297       case BlackQueen:
12298         if(gameInfo.variant == VariantShatranj ||
12299            gameInfo.variant == VariantXiangqi  ||
12300            gameInfo.variant == VariantCourier  ||
12301            gameInfo.variant == VariantMakruk     )
12302             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12303         goto defaultlabel;
12304
12305       case WhiteKing:
12306       case BlackKing:
12307         if(gameInfo.variant == VariantXiangqi)
12308             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12309         if(gameInfo.variant == VariantKnightmate)
12310             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12311       default:
12312         defaultlabel:
12313         if (gameMode == IcsExamining) {
12314             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12315             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12316                      PieceToChar(selection), AAA + x, ONE + y);
12317             SendToICS(buf);
12318         } else {
12319             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12320                 int n;
12321                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12322                     n = PieceToNumber(selection - BlackPawn);
12323                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12324                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12325                     boards[0][BOARD_HEIGHT-1-n][1]++;
12326                 } else
12327                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12328                     n = PieceToNumber(selection);
12329                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12330                     boards[0][n][BOARD_WIDTH-1] = selection;
12331                     boards[0][n][BOARD_WIDTH-2]++;
12332                 }
12333             } else
12334             boards[0][y][x] = selection;
12335             DrawPosition(TRUE, boards[0]);
12336         }
12337         break;
12338     }
12339 }
12340
12341
12342 void
12343 DropMenuEvent(selection, x, y)
12344      ChessSquare selection;
12345      int x, y;
12346 {
12347     ChessMove moveType;
12348
12349     switch (gameMode) {
12350       case IcsPlayingWhite:
12351       case MachinePlaysBlack:
12352         if (!WhiteOnMove(currentMove)) {
12353             DisplayMoveError(_("It is Black's turn"));
12354             return;
12355         }
12356         moveType = WhiteDrop;
12357         break;
12358       case IcsPlayingBlack:
12359       case MachinePlaysWhite:
12360         if (WhiteOnMove(currentMove)) {
12361             DisplayMoveError(_("It is White's turn"));
12362             return;
12363         }
12364         moveType = BlackDrop;
12365         break;
12366       case EditGame:
12367         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12368         break;
12369       default:
12370         return;
12371     }
12372
12373     if (moveType == BlackDrop && selection < BlackPawn) {
12374       selection = (ChessSquare) ((int) selection
12375                                  + (int) BlackPawn - (int) WhitePawn);
12376     }
12377     if (boards[currentMove][y][x] != EmptySquare) {
12378         DisplayMoveError(_("That square is occupied"));
12379         return;
12380     }
12381
12382     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12383 }
12384
12385 void
12386 AcceptEvent()
12387 {
12388     /* Accept a pending offer of any kind from opponent */
12389
12390     if (appData.icsActive) {
12391         SendToICS(ics_prefix);
12392         SendToICS("accept\n");
12393     } else if (cmailMsgLoaded) {
12394         if (currentMove == cmailOldMove &&
12395             commentList[cmailOldMove] != NULL &&
12396             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12397                    "Black offers a draw" : "White offers a draw")) {
12398             TruncateGame();
12399             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12400             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12401         } else {
12402             DisplayError(_("There is no pending offer on this move"), 0);
12403             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12404         }
12405     } else {
12406         /* Not used for offers from chess program */
12407     }
12408 }
12409
12410 void
12411 DeclineEvent()
12412 {
12413     /* Decline a pending offer of any kind from opponent */
12414
12415     if (appData.icsActive) {
12416         SendToICS(ics_prefix);
12417         SendToICS("decline\n");
12418     } else if (cmailMsgLoaded) {
12419         if (currentMove == cmailOldMove &&
12420             commentList[cmailOldMove] != NULL &&
12421             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12422                    "Black offers a draw" : "White offers a draw")) {
12423 #ifdef NOTDEF
12424             AppendComment(cmailOldMove, "Draw declined", TRUE);
12425             DisplayComment(cmailOldMove - 1, "Draw declined");
12426 #endif /*NOTDEF*/
12427         } else {
12428             DisplayError(_("There is no pending offer on this move"), 0);
12429         }
12430     } else {
12431         /* Not used for offers from chess program */
12432     }
12433 }
12434
12435 void
12436 RematchEvent()
12437 {
12438     /* Issue ICS rematch command */
12439     if (appData.icsActive) {
12440         SendToICS(ics_prefix);
12441         SendToICS("rematch\n");
12442     }
12443 }
12444
12445 void
12446 CallFlagEvent()
12447 {
12448     /* Call your opponent's flag (claim a win on time) */
12449     if (appData.icsActive) {
12450         SendToICS(ics_prefix);
12451         SendToICS("flag\n");
12452     } else {
12453         switch (gameMode) {
12454           default:
12455             return;
12456           case MachinePlaysWhite:
12457             if (whiteFlag) {
12458                 if (blackFlag)
12459                   GameEnds(GameIsDrawn, "Both players ran out of time",
12460                            GE_PLAYER);
12461                 else
12462                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12463             } else {
12464                 DisplayError(_("Your opponent is not out of time"), 0);
12465             }
12466             break;
12467           case MachinePlaysBlack:
12468             if (blackFlag) {
12469                 if (whiteFlag)
12470                   GameEnds(GameIsDrawn, "Both players ran out of time",
12471                            GE_PLAYER);
12472                 else
12473                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12474             } else {
12475                 DisplayError(_("Your opponent is not out of time"), 0);
12476             }
12477             break;
12478         }
12479     }
12480 }
12481
12482 void
12483 DrawEvent()
12484 {
12485     /* Offer draw or accept pending draw offer from opponent */
12486
12487     if (appData.icsActive) {
12488         /* Note: tournament rules require draw offers to be
12489            made after you make your move but before you punch
12490            your clock.  Currently ICS doesn't let you do that;
12491            instead, you immediately punch your clock after making
12492            a move, but you can offer a draw at any time. */
12493
12494         SendToICS(ics_prefix);
12495         SendToICS("draw\n");
12496         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12497     } else if (cmailMsgLoaded) {
12498         if (currentMove == cmailOldMove &&
12499             commentList[cmailOldMove] != NULL &&
12500             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12501                    "Black offers a draw" : "White offers a draw")) {
12502             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12503             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12504         } else if (currentMove == cmailOldMove + 1) {
12505             char *offer = WhiteOnMove(cmailOldMove) ?
12506               "White offers a draw" : "Black offers a draw";
12507             AppendComment(currentMove, offer, TRUE);
12508             DisplayComment(currentMove - 1, offer);
12509             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12510         } else {
12511             DisplayError(_("You must make your move before offering a draw"), 0);
12512             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12513         }
12514     } else if (first.offeredDraw) {
12515         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12516     } else {
12517         if (first.sendDrawOffers) {
12518             SendToProgram("draw\n", &first);
12519             userOfferedDraw = TRUE;
12520         }
12521     }
12522 }
12523
12524 void
12525 AdjournEvent()
12526 {
12527     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12528
12529     if (appData.icsActive) {
12530         SendToICS(ics_prefix);
12531         SendToICS("adjourn\n");
12532     } else {
12533         /* Currently GNU Chess doesn't offer or accept Adjourns */
12534     }
12535 }
12536
12537
12538 void
12539 AbortEvent()
12540 {
12541     /* Offer Abort or accept pending Abort offer from opponent */
12542
12543     if (appData.icsActive) {
12544         SendToICS(ics_prefix);
12545         SendToICS("abort\n");
12546     } else {
12547         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12548     }
12549 }
12550
12551 void
12552 ResignEvent()
12553 {
12554     /* Resign.  You can do this even if it's not your turn. */
12555
12556     if (appData.icsActive) {
12557         SendToICS(ics_prefix);
12558         SendToICS("resign\n");
12559     } else {
12560         switch (gameMode) {
12561           case MachinePlaysWhite:
12562             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12563             break;
12564           case MachinePlaysBlack:
12565             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12566             break;
12567           case EditGame:
12568             if (cmailMsgLoaded) {
12569                 TruncateGame();
12570                 if (WhiteOnMove(cmailOldMove)) {
12571                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12572                 } else {
12573                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12574                 }
12575                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12576             }
12577             break;
12578           default:
12579             break;
12580         }
12581     }
12582 }
12583
12584
12585 void
12586 StopObservingEvent()
12587 {
12588     /* Stop observing current games */
12589     SendToICS(ics_prefix);
12590     SendToICS("unobserve\n");
12591 }
12592
12593 void
12594 StopExaminingEvent()
12595 {
12596     /* Stop observing current game */
12597     SendToICS(ics_prefix);
12598     SendToICS("unexamine\n");
12599 }
12600
12601 void
12602 ForwardInner(target)
12603      int target;
12604 {
12605     int limit;
12606
12607     if (appData.debugMode)
12608         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12609                 target, currentMove, forwardMostMove);
12610
12611     if (gameMode == EditPosition)
12612       return;
12613
12614     if (gameMode == PlayFromGameFile && !pausing)
12615       PauseEvent();
12616
12617     if (gameMode == IcsExamining && pausing)
12618       limit = pauseExamForwardMostMove;
12619     else
12620       limit = forwardMostMove;
12621
12622     if (target > limit) target = limit;
12623
12624     if (target > 0 && moveList[target - 1][0]) {
12625         int fromX, fromY, toX, toY;
12626         toX = moveList[target - 1][2] - AAA;
12627         toY = moveList[target - 1][3] - ONE;
12628         if (moveList[target - 1][1] == '@') {
12629             if (appData.highlightLastMove) {
12630                 SetHighlights(-1, -1, toX, toY);
12631             }
12632         } else {
12633             fromX = moveList[target - 1][0] - AAA;
12634             fromY = moveList[target - 1][1] - ONE;
12635             if (target == currentMove + 1) {
12636                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12637             }
12638             if (appData.highlightLastMove) {
12639                 SetHighlights(fromX, fromY, toX, toY);
12640             }
12641         }
12642     }
12643     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12644         gameMode == Training || gameMode == PlayFromGameFile ||
12645         gameMode == AnalyzeFile) {
12646         while (currentMove < target) {
12647             SendMoveToProgram(currentMove++, &first);
12648         }
12649     } else {
12650         currentMove = target;
12651     }
12652
12653     if (gameMode == EditGame || gameMode == EndOfGame) {
12654         whiteTimeRemaining = timeRemaining[0][currentMove];
12655         blackTimeRemaining = timeRemaining[1][currentMove];
12656     }
12657     DisplayBothClocks();
12658     DisplayMove(currentMove - 1);
12659     DrawPosition(FALSE, boards[currentMove]);
12660     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12661     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12662         DisplayComment(currentMove - 1, commentList[currentMove]);
12663     }
12664 }
12665
12666
12667 void
12668 ForwardEvent()
12669 {
12670     if (gameMode == IcsExamining && !pausing) {
12671         SendToICS(ics_prefix);
12672         SendToICS("forward\n");
12673     } else {
12674         ForwardInner(currentMove + 1);
12675     }
12676 }
12677
12678 void
12679 ToEndEvent()
12680 {
12681     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12682         /* to optimze, we temporarily turn off analysis mode while we feed
12683          * the remaining moves to the engine. Otherwise we get analysis output
12684          * after each move.
12685          */
12686         if (first.analysisSupport) {
12687           SendToProgram("exit\nforce\n", &first);
12688           first.analyzing = FALSE;
12689         }
12690     }
12691
12692     if (gameMode == IcsExamining && !pausing) {
12693         SendToICS(ics_prefix);
12694         SendToICS("forward 999999\n");
12695     } else {
12696         ForwardInner(forwardMostMove);
12697     }
12698
12699     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12700         /* we have fed all the moves, so reactivate analysis mode */
12701         SendToProgram("analyze\n", &first);
12702         first.analyzing = TRUE;
12703         /*first.maybeThinking = TRUE;*/
12704         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12705     }
12706 }
12707
12708 void
12709 BackwardInner(target)
12710      int target;
12711 {
12712     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12713
12714     if (appData.debugMode)
12715         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12716                 target, currentMove, forwardMostMove);
12717
12718     if (gameMode == EditPosition) return;
12719     if (currentMove <= backwardMostMove) {
12720         ClearHighlights();
12721         DrawPosition(full_redraw, boards[currentMove]);
12722         return;
12723     }
12724     if (gameMode == PlayFromGameFile && !pausing)
12725       PauseEvent();
12726
12727     if (moveList[target][0]) {
12728         int fromX, fromY, toX, toY;
12729         toX = moveList[target][2] - AAA;
12730         toY = moveList[target][3] - ONE;
12731         if (moveList[target][1] == '@') {
12732             if (appData.highlightLastMove) {
12733                 SetHighlights(-1, -1, toX, toY);
12734             }
12735         } else {
12736             fromX = moveList[target][0] - AAA;
12737             fromY = moveList[target][1] - ONE;
12738             if (target == currentMove - 1) {
12739                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12740             }
12741             if (appData.highlightLastMove) {
12742                 SetHighlights(fromX, fromY, toX, toY);
12743             }
12744         }
12745     }
12746     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12747         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12748         while (currentMove > target) {
12749             SendToProgram("undo\n", &first);
12750             currentMove--;
12751         }
12752     } else {
12753         currentMove = target;
12754     }
12755
12756     if (gameMode == EditGame || gameMode == EndOfGame) {
12757         whiteTimeRemaining = timeRemaining[0][currentMove];
12758         blackTimeRemaining = timeRemaining[1][currentMove];
12759     }
12760     DisplayBothClocks();
12761     DisplayMove(currentMove - 1);
12762     DrawPosition(full_redraw, boards[currentMove]);
12763     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12764     // [HGM] PV info: routine tests if comment empty
12765     DisplayComment(currentMove - 1, commentList[currentMove]);
12766 }
12767
12768 void
12769 BackwardEvent()
12770 {
12771     if (gameMode == IcsExamining && !pausing) {
12772         SendToICS(ics_prefix);
12773         SendToICS("backward\n");
12774     } else {
12775         BackwardInner(currentMove - 1);
12776     }
12777 }
12778
12779 void
12780 ToStartEvent()
12781 {
12782     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12783         /* to optimize, we temporarily turn off analysis mode while we undo
12784          * all the moves. Otherwise we get analysis output after each undo.
12785          */
12786         if (first.analysisSupport) {
12787           SendToProgram("exit\nforce\n", &first);
12788           first.analyzing = FALSE;
12789         }
12790     }
12791
12792     if (gameMode == IcsExamining && !pausing) {
12793         SendToICS(ics_prefix);
12794         SendToICS("backward 999999\n");
12795     } else {
12796         BackwardInner(backwardMostMove);
12797     }
12798
12799     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12800         /* we have fed all the moves, so reactivate analysis mode */
12801         SendToProgram("analyze\n", &first);
12802         first.analyzing = TRUE;
12803         /*first.maybeThinking = TRUE;*/
12804         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12805     }
12806 }
12807
12808 void
12809 ToNrEvent(int to)
12810 {
12811   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12812   if (to >= forwardMostMove) to = forwardMostMove;
12813   if (to <= backwardMostMove) to = backwardMostMove;
12814   if (to < currentMove) {
12815     BackwardInner(to);
12816   } else {
12817     ForwardInner(to);
12818   }
12819 }
12820
12821 void
12822 RevertEvent(Boolean annotate)
12823 {
12824     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12825         return;
12826     }
12827     if (gameMode != IcsExamining) {
12828         DisplayError(_("You are not examining a game"), 0);
12829         return;
12830     }
12831     if (pausing) {
12832         DisplayError(_("You can't revert while pausing"), 0);
12833         return;
12834     }
12835     SendToICS(ics_prefix);
12836     SendToICS("revert\n");
12837 }
12838
12839 void
12840 RetractMoveEvent()
12841 {
12842     switch (gameMode) {
12843       case MachinePlaysWhite:
12844       case MachinePlaysBlack:
12845         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12846             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12847             return;
12848         }
12849         if (forwardMostMove < 2) return;
12850         currentMove = forwardMostMove = forwardMostMove - 2;
12851         whiteTimeRemaining = timeRemaining[0][currentMove];
12852         blackTimeRemaining = timeRemaining[1][currentMove];
12853         DisplayBothClocks();
12854         DisplayMove(currentMove - 1);
12855         ClearHighlights();/*!! could figure this out*/
12856         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12857         SendToProgram("remove\n", &first);
12858         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12859         break;
12860
12861       case BeginningOfGame:
12862       default:
12863         break;
12864
12865       case IcsPlayingWhite:
12866       case IcsPlayingBlack:
12867         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12868             SendToICS(ics_prefix);
12869             SendToICS("takeback 2\n");
12870         } else {
12871             SendToICS(ics_prefix);
12872             SendToICS("takeback 1\n");
12873         }
12874         break;
12875     }
12876 }
12877
12878 void
12879 MoveNowEvent()
12880 {
12881     ChessProgramState *cps;
12882
12883     switch (gameMode) {
12884       case MachinePlaysWhite:
12885         if (!WhiteOnMove(forwardMostMove)) {
12886             DisplayError(_("It is your turn"), 0);
12887             return;
12888         }
12889         cps = &first;
12890         break;
12891       case MachinePlaysBlack:
12892         if (WhiteOnMove(forwardMostMove)) {
12893             DisplayError(_("It is your turn"), 0);
12894             return;
12895         }
12896         cps = &first;
12897         break;
12898       case TwoMachinesPlay:
12899         if (WhiteOnMove(forwardMostMove) ==
12900             (first.twoMachinesColor[0] == 'w')) {
12901             cps = &first;
12902         } else {
12903             cps = &second;
12904         }
12905         break;
12906       case BeginningOfGame:
12907       default:
12908         return;
12909     }
12910     SendToProgram("?\n", cps);
12911 }
12912
12913 void
12914 TruncateGameEvent()
12915 {
12916     EditGameEvent();
12917     if (gameMode != EditGame) return;
12918     TruncateGame();
12919 }
12920
12921 void
12922 TruncateGame()
12923 {
12924     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12925     if (forwardMostMove > currentMove) {
12926         if (gameInfo.resultDetails != NULL) {
12927             free(gameInfo.resultDetails);
12928             gameInfo.resultDetails = NULL;
12929             gameInfo.result = GameUnfinished;
12930         }
12931         forwardMostMove = currentMove;
12932         HistorySet(parseList, backwardMostMove, forwardMostMove,
12933                    currentMove-1);
12934     }
12935 }
12936
12937 void
12938 HintEvent()
12939 {
12940     if (appData.noChessProgram) return;
12941     switch (gameMode) {
12942       case MachinePlaysWhite:
12943         if (WhiteOnMove(forwardMostMove)) {
12944             DisplayError(_("Wait until your turn"), 0);
12945             return;
12946         }
12947         break;
12948       case BeginningOfGame:
12949       case MachinePlaysBlack:
12950         if (!WhiteOnMove(forwardMostMove)) {
12951             DisplayError(_("Wait until your turn"), 0);
12952             return;
12953         }
12954         break;
12955       default:
12956         DisplayError(_("No hint available"), 0);
12957         return;
12958     }
12959     SendToProgram("hint\n", &first);
12960     hintRequested = TRUE;
12961 }
12962
12963 void
12964 BookEvent()
12965 {
12966     if (appData.noChessProgram) return;
12967     switch (gameMode) {
12968       case MachinePlaysWhite:
12969         if (WhiteOnMove(forwardMostMove)) {
12970             DisplayError(_("Wait until your turn"), 0);
12971             return;
12972         }
12973         break;
12974       case BeginningOfGame:
12975       case MachinePlaysBlack:
12976         if (!WhiteOnMove(forwardMostMove)) {
12977             DisplayError(_("Wait until your turn"), 0);
12978             return;
12979         }
12980         break;
12981       case EditPosition:
12982         EditPositionDone(TRUE);
12983         break;
12984       case TwoMachinesPlay:
12985         return;
12986       default:
12987         break;
12988     }
12989     SendToProgram("bk\n", &first);
12990     bookOutput[0] = NULLCHAR;
12991     bookRequested = TRUE;
12992 }
12993
12994 void
12995 AboutGameEvent()
12996 {
12997     char *tags = PGNTags(&gameInfo);
12998     TagsPopUp(tags, CmailMsg());
12999     free(tags);
13000 }
13001
13002 /* end button procedures */
13003
13004 void
13005 PrintPosition(fp, move)
13006      FILE *fp;
13007      int move;
13008 {
13009     int i, j;
13010
13011     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13012         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13013             char c = PieceToChar(boards[move][i][j]);
13014             fputc(c == 'x' ? '.' : c, fp);
13015             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13016         }
13017     }
13018     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13019       fprintf(fp, "white to play\n");
13020     else
13021       fprintf(fp, "black to play\n");
13022 }
13023
13024 void
13025 PrintOpponents(fp)
13026      FILE *fp;
13027 {
13028     if (gameInfo.white != NULL) {
13029         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13030     } else {
13031         fprintf(fp, "\n");
13032     }
13033 }
13034
13035 /* Find last component of program's own name, using some heuristics */
13036 void
13037 TidyProgramName(prog, host, buf)
13038      char *prog, *host, buf[MSG_SIZ];
13039 {
13040     char *p, *q;
13041     int local = (strcmp(host, "localhost") == 0);
13042     while (!local && (p = strchr(prog, ';')) != NULL) {
13043         p++;
13044         while (*p == ' ') p++;
13045         prog = p;
13046     }
13047     if (*prog == '"' || *prog == '\'') {
13048         q = strchr(prog + 1, *prog);
13049     } else {
13050         q = strchr(prog, ' ');
13051     }
13052     if (q == NULL) q = prog + strlen(prog);
13053     p = q;
13054     while (p >= prog && *p != '/' && *p != '\\') p--;
13055     p++;
13056     if(p == prog && *p == '"') p++;
13057     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13058     memcpy(buf, p, q - p);
13059     buf[q - p] = NULLCHAR;
13060     if (!local) {
13061         strcat(buf, "@");
13062         strcat(buf, host);
13063     }
13064 }
13065
13066 char *
13067 TimeControlTagValue()
13068 {
13069     char buf[MSG_SIZ];
13070     if (!appData.clockMode) {
13071       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13072     } else if (movesPerSession > 0) {
13073       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13074     } else if (timeIncrement == 0) {
13075       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13076     } else {
13077       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13078     }
13079     return StrSave(buf);
13080 }
13081
13082 void
13083 SetGameInfo()
13084 {
13085     /* This routine is used only for certain modes */
13086     VariantClass v = gameInfo.variant;
13087     ChessMove r = GameUnfinished;
13088     char *p = NULL;
13089
13090     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13091         r = gameInfo.result;
13092         p = gameInfo.resultDetails;
13093         gameInfo.resultDetails = NULL;
13094     }
13095     ClearGameInfo(&gameInfo);
13096     gameInfo.variant = v;
13097
13098     switch (gameMode) {
13099       case MachinePlaysWhite:
13100         gameInfo.event = StrSave( appData.pgnEventHeader );
13101         gameInfo.site = StrSave(HostName());
13102         gameInfo.date = PGNDate();
13103         gameInfo.round = StrSave("-");
13104         gameInfo.white = StrSave(first.tidy);
13105         gameInfo.black = StrSave(UserName());
13106         gameInfo.timeControl = TimeControlTagValue();
13107         break;
13108
13109       case MachinePlaysBlack:
13110         gameInfo.event = StrSave( appData.pgnEventHeader );
13111         gameInfo.site = StrSave(HostName());
13112         gameInfo.date = PGNDate();
13113         gameInfo.round = StrSave("-");
13114         gameInfo.white = StrSave(UserName());
13115         gameInfo.black = StrSave(first.tidy);
13116         gameInfo.timeControl = TimeControlTagValue();
13117         break;
13118
13119       case TwoMachinesPlay:
13120         gameInfo.event = StrSave( appData.pgnEventHeader );
13121         gameInfo.site = StrSave(HostName());
13122         gameInfo.date = PGNDate();
13123         if (matchGame > 0) {
13124             char buf[MSG_SIZ];
13125             snprintf(buf, MSG_SIZ, "%d", matchGame);
13126             gameInfo.round = StrSave(buf);
13127         } else {
13128             gameInfo.round = StrSave("-");
13129         }
13130         if (first.twoMachinesColor[0] == 'w') {
13131             gameInfo.white = StrSave(first.tidy);
13132             gameInfo.black = StrSave(second.tidy);
13133         } else {
13134             gameInfo.white = StrSave(second.tidy);
13135             gameInfo.black = StrSave(first.tidy);
13136         }
13137         gameInfo.timeControl = TimeControlTagValue();
13138         break;
13139
13140       case EditGame:
13141         gameInfo.event = StrSave("Edited game");
13142         gameInfo.site = StrSave(HostName());
13143         gameInfo.date = PGNDate();
13144         gameInfo.round = StrSave("-");
13145         gameInfo.white = StrSave("-");
13146         gameInfo.black = StrSave("-");
13147         gameInfo.result = r;
13148         gameInfo.resultDetails = p;
13149         break;
13150
13151       case EditPosition:
13152         gameInfo.event = StrSave("Edited position");
13153         gameInfo.site = StrSave(HostName());
13154         gameInfo.date = PGNDate();
13155         gameInfo.round = StrSave("-");
13156         gameInfo.white = StrSave("-");
13157         gameInfo.black = StrSave("-");
13158         break;
13159
13160       case IcsPlayingWhite:
13161       case IcsPlayingBlack:
13162       case IcsObserving:
13163       case IcsExamining:
13164         break;
13165
13166       case PlayFromGameFile:
13167         gameInfo.event = StrSave("Game from non-PGN file");
13168         gameInfo.site = StrSave(HostName());
13169         gameInfo.date = PGNDate();
13170         gameInfo.round = StrSave("-");
13171         gameInfo.white = StrSave("?");
13172         gameInfo.black = StrSave("?");
13173         break;
13174
13175       default:
13176         break;
13177     }
13178 }
13179
13180 void
13181 ReplaceComment(index, text)
13182      int index;
13183      char *text;
13184 {
13185     int len;
13186
13187     while (*text == '\n') text++;
13188     len = strlen(text);
13189     while (len > 0 && text[len - 1] == '\n') len--;
13190
13191     if (commentList[index] != NULL)
13192       free(commentList[index]);
13193
13194     if (len == 0) {
13195         commentList[index] = NULL;
13196         return;
13197     }
13198   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13199       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13200       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13201     commentList[index] = (char *) malloc(len + 2);
13202     strncpy(commentList[index], text, len);
13203     commentList[index][len] = '\n';
13204     commentList[index][len + 1] = NULLCHAR;
13205   } else {
13206     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13207     char *p;
13208     commentList[index] = (char *) malloc(len + 7);
13209     safeStrCpy(commentList[index], "{\n", 3);
13210     safeStrCpy(commentList[index]+2, text, len+1);
13211     commentList[index][len+2] = NULLCHAR;
13212     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13213     strcat(commentList[index], "\n}\n");
13214   }
13215 }
13216
13217 void
13218 CrushCRs(text)
13219      char *text;
13220 {
13221   char *p = text;
13222   char *q = text;
13223   char ch;
13224
13225   do {
13226     ch = *p++;
13227     if (ch == '\r') continue;
13228     *q++ = ch;
13229   } while (ch != '\0');
13230 }
13231
13232 void
13233 AppendComment(index, text, addBraces)
13234      int index;
13235      char *text;
13236      Boolean addBraces; // [HGM] braces: tells if we should add {}
13237 {
13238     int oldlen, len;
13239     char *old;
13240
13241 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13242     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13243
13244     CrushCRs(text);
13245     while (*text == '\n') text++;
13246     len = strlen(text);
13247     while (len > 0 && text[len - 1] == '\n') len--;
13248
13249     if (len == 0) return;
13250
13251     if (commentList[index] != NULL) {
13252         old = commentList[index];
13253         oldlen = strlen(old);
13254         while(commentList[index][oldlen-1] ==  '\n')
13255           commentList[index][--oldlen] = NULLCHAR;
13256         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13257         safeStrCpy(commentList[index], old, oldlen);
13258         free(old);
13259         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13260         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13261           if(addBraces) addBraces = FALSE; else { text++; len--; }
13262           while (*text == '\n') { text++; len--; }
13263           commentList[index][--oldlen] = NULLCHAR;
13264       }
13265         if(addBraces) strcat(commentList[index], "\n{\n");
13266         else          strcat(commentList[index], "\n");
13267         strcat(commentList[index], text);
13268         if(addBraces) strcat(commentList[index], "\n}\n");
13269         else          strcat(commentList[index], "\n");
13270     } else {
13271         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13272         if(addBraces)
13273           safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0]));
13274         else commentList[index][0] = NULLCHAR;
13275         strcat(commentList[index], text);
13276         strcat(commentList[index], "\n");
13277         if(addBraces) strcat(commentList[index], "}\n");
13278     }
13279 }
13280
13281 static char * FindStr( char * text, char * sub_text )
13282 {
13283     char * result = strstr( text, sub_text );
13284
13285     if( result != NULL ) {
13286         result += strlen( sub_text );
13287     }
13288
13289     return result;
13290 }
13291
13292 /* [AS] Try to extract PV info from PGN comment */
13293 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13294 char *GetInfoFromComment( int index, char * text )
13295 {
13296     char * sep = text;
13297
13298     if( text != NULL && index > 0 ) {
13299         int score = 0;
13300         int depth = 0;
13301         int time = -1, sec = 0, deci;
13302         char * s_eval = FindStr( text, "[%eval " );
13303         char * s_emt = FindStr( text, "[%emt " );
13304
13305         if( s_eval != NULL || s_emt != NULL ) {
13306             /* New style */
13307             char delim;
13308
13309             if( s_eval != NULL ) {
13310                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13311                     return text;
13312                 }
13313
13314                 if( delim != ']' ) {
13315                     return text;
13316                 }
13317             }
13318
13319             if( s_emt != NULL ) {
13320             }
13321                 return text;
13322         }
13323         else {
13324             /* We expect something like: [+|-]nnn.nn/dd */
13325             int score_lo = 0;
13326
13327             if(*text != '{') return text; // [HGM] braces: must be normal comment
13328
13329             sep = strchr( text, '/' );
13330             if( sep == NULL || sep < (text+4) ) {
13331                 return text;
13332             }
13333
13334             time = -1; sec = -1; deci = -1;
13335             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13336                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13337                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13338                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13339                 return text;
13340             }
13341
13342             if( score_lo < 0 || score_lo >= 100 ) {
13343                 return text;
13344             }
13345
13346             if(sec >= 0) time = 600*time + 10*sec; else
13347             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13348
13349             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13350
13351             /* [HGM] PV time: now locate end of PV info */
13352             while( *++sep >= '0' && *sep <= '9'); // strip depth
13353             if(time >= 0)
13354             while( *++sep >= '0' && *sep <= '9'); // strip time
13355             if(sec >= 0)
13356             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13357             if(deci >= 0)
13358             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13359             while(*sep == ' ') sep++;
13360         }
13361
13362         if( depth <= 0 ) {
13363             return text;
13364         }
13365
13366         if( time < 0 ) {
13367             time = -1;
13368         }
13369
13370         pvInfoList[index-1].depth = depth;
13371         pvInfoList[index-1].score = score;
13372         pvInfoList[index-1].time  = 10*time; // centi-sec
13373         if(*sep == '}') *sep = 0; else *--sep = '{';
13374     }
13375     return sep;
13376 }
13377
13378 void
13379 SendToProgram(message, cps)
13380      char *message;
13381      ChessProgramState *cps;
13382 {
13383     int count, outCount, error;
13384     char buf[MSG_SIZ];
13385
13386     if (cps->pr == NULL) return;
13387     Attention(cps);
13388
13389     if (appData.debugMode) {
13390         TimeMark now;
13391         GetTimeMark(&now);
13392         fprintf(debugFP, "%ld >%-6s: %s",
13393                 SubtractTimeMarks(&now, &programStartTime),
13394                 cps->which, message);
13395     }
13396
13397     count = strlen(message);
13398     outCount = OutputToProcess(cps->pr, message, count, &error);
13399     if (outCount < count && !exiting
13400                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13401       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13402         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13403             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13404                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13405                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13406             } else {
13407                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13408             }
13409             gameInfo.resultDetails = StrSave(buf);
13410         }
13411         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13412     }
13413 }
13414
13415 void
13416 ReceiveFromProgram(isr, closure, message, count, error)
13417      InputSourceRef isr;
13418      VOIDSTAR closure;
13419      char *message;
13420      int count;
13421      int error;
13422 {
13423     char *end_str;
13424     char buf[MSG_SIZ];
13425     ChessProgramState *cps = (ChessProgramState *)closure;
13426
13427     if (isr != cps->isr) return; /* Killed intentionally */
13428     if (count <= 0) {
13429         if (count == 0) {
13430             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13431                     cps->which, cps->program);
13432         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13433                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13434                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13435                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13436                 } else {
13437                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13438                 }
13439                 gameInfo.resultDetails = StrSave(buf);
13440             }
13441             RemoveInputSource(cps->isr);
13442             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13443         } else {
13444             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13445                     cps->which, cps->program);
13446             RemoveInputSource(cps->isr);
13447
13448             /* [AS] Program is misbehaving badly... kill it */
13449             if( count == -2 ) {
13450                 DestroyChildProcess( cps->pr, 9 );
13451                 cps->pr = NoProc;
13452             }
13453
13454             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13455         }
13456         return;
13457     }
13458
13459     if ((end_str = strchr(message, '\r')) != NULL)
13460       *end_str = NULLCHAR;
13461     if ((end_str = strchr(message, '\n')) != NULL)
13462       *end_str = NULLCHAR;
13463
13464     if (appData.debugMode) {
13465         TimeMark now; int print = 1;
13466         char *quote = ""; char c; int i;
13467
13468         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13469                 char start = message[0];
13470                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13471                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13472                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13473                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13474                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13475                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13476                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13477                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13478                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13479                     print = (appData.engineComments >= 2);
13480                 }
13481                 message[0] = start; // restore original message
13482         }
13483         if(print) {
13484                 GetTimeMark(&now);
13485                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13486                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13487                         quote,
13488                         message);
13489         }
13490     }
13491
13492     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13493     if (appData.icsEngineAnalyze) {
13494         if (strstr(message, "whisper") != NULL ||
13495              strstr(message, "kibitz") != NULL ||
13496             strstr(message, "tellics") != NULL) return;
13497     }
13498
13499     HandleMachineMove(message, cps);
13500 }
13501
13502
13503 void
13504 SendTimeControl(cps, mps, tc, inc, sd, st)
13505      ChessProgramState *cps;
13506      int mps, inc, sd, st;
13507      long tc;
13508 {
13509     char buf[MSG_SIZ];
13510     int seconds;
13511
13512     if( timeControl_2 > 0 ) {
13513         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13514             tc = timeControl_2;
13515         }
13516     }
13517     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13518     inc /= cps->timeOdds;
13519     st  /= cps->timeOdds;
13520
13521     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13522
13523     if (st > 0) {
13524       /* Set exact time per move, normally using st command */
13525       if (cps->stKludge) {
13526         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13527         seconds = st % 60;
13528         if (seconds == 0) {
13529           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13530         } else {
13531           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13532         }
13533       } else {
13534         snprintf(buf, MSG_SIZ, "st %d\n", st);
13535       }
13536     } else {
13537       /* Set conventional or incremental time control, using level command */
13538       if (seconds == 0) {
13539         /* Note old gnuchess bug -- minutes:seconds used to not work.
13540            Fixed in later versions, but still avoid :seconds
13541            when seconds is 0. */
13542         snprintf(buf, MSG_SIZ, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13543       } else {
13544         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %d\n", mps, tc/60000,
13545                  seconds, inc/1000);
13546       }
13547     }
13548     SendToProgram(buf, cps);
13549
13550     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13551     /* Orthogonally, limit search to given depth */
13552     if (sd > 0) {
13553       if (cps->sdKludge) {
13554         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13555       } else {
13556         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13557       }
13558       SendToProgram(buf, cps);
13559     }
13560
13561     if(cps->nps > 0) { /* [HGM] nps */
13562         if(cps->supportsNPS == FALSE)
13563           cps->nps = -1; // don't use if engine explicitly says not supported!
13564         else {
13565           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13566           SendToProgram(buf, cps);
13567         }
13568     }
13569 }
13570
13571 ChessProgramState *WhitePlayer()
13572 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13573 {
13574     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13575        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13576         return &second;
13577     return &first;
13578 }
13579
13580 void
13581 SendTimeRemaining(cps, machineWhite)
13582      ChessProgramState *cps;
13583      int /*boolean*/ machineWhite;
13584 {
13585     char message[MSG_SIZ];
13586     long time, otime;
13587
13588     /* Note: this routine must be called when the clocks are stopped
13589        or when they have *just* been set or switched; otherwise
13590        it will be off by the time since the current tick started.
13591     */
13592     if (machineWhite) {
13593         time = whiteTimeRemaining / 10;
13594         otime = blackTimeRemaining / 10;
13595     } else {
13596         time = blackTimeRemaining / 10;
13597         otime = whiteTimeRemaining / 10;
13598     }
13599     /* [HGM] translate opponent's time by time-odds factor */
13600     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13601     if (appData.debugMode) {
13602         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13603     }
13604
13605     if (time <= 0) time = 1;
13606     if (otime <= 0) otime = 1;
13607
13608     snprintf(message, MSG_SIZ, "time %ld\n", time);
13609     SendToProgram(message, cps);
13610
13611     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13612     SendToProgram(message, cps);
13613 }
13614
13615 int
13616 BoolFeature(p, name, loc, cps)
13617      char **p;
13618      char *name;
13619      int *loc;
13620      ChessProgramState *cps;
13621 {
13622   char buf[MSG_SIZ];
13623   int len = strlen(name);
13624   int val;
13625
13626   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13627     (*p) += len + 1;
13628     sscanf(*p, "%d", &val);
13629     *loc = (val != 0);
13630     while (**p && **p != ' ')
13631       (*p)++;
13632     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13633     SendToProgram(buf, cps);
13634     return TRUE;
13635   }
13636   return FALSE;
13637 }
13638
13639 int
13640 IntFeature(p, name, loc, cps)
13641      char **p;
13642      char *name;
13643      int *loc;
13644      ChessProgramState *cps;
13645 {
13646   char buf[MSG_SIZ];
13647   int len = strlen(name);
13648   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13649     (*p) += len + 1;
13650     sscanf(*p, "%d", loc);
13651     while (**p && **p != ' ') (*p)++;
13652     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13653     SendToProgram(buf, cps);
13654     return TRUE;
13655   }
13656   return FALSE;
13657 }
13658
13659 int
13660 StringFeature(p, name, loc, cps)
13661      char **p;
13662      char *name;
13663      char loc[];
13664      ChessProgramState *cps;
13665 {
13666   char buf[MSG_SIZ];
13667   int len = strlen(name);
13668   if (strncmp((*p), name, len) == 0
13669       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13670     (*p) += len + 2;
13671     sscanf(*p, "%[^\"]", loc);
13672     while (**p && **p != '\"') (*p)++;
13673     if (**p == '\"') (*p)++;
13674     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13675     SendToProgram(buf, cps);
13676     return TRUE;
13677   }
13678   return FALSE;
13679 }
13680
13681 int
13682 ParseOption(Option *opt, ChessProgramState *cps)
13683 // [HGM] options: process the string that defines an engine option, and determine
13684 // name, type, default value, and allowed value range
13685 {
13686         char *p, *q, buf[MSG_SIZ];
13687         int n, min = (-1)<<31, max = 1<<31, def;
13688
13689         if(p = strstr(opt->name, " -spin ")) {
13690             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13691             if(max < min) max = min; // enforce consistency
13692             if(def < min) def = min;
13693             if(def > max) def = max;
13694             opt->value = def;
13695             opt->min = min;
13696             opt->max = max;
13697             opt->type = Spin;
13698         } else if((p = strstr(opt->name, " -slider "))) {
13699             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13700             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13701             if(max < min) max = min; // enforce consistency
13702             if(def < min) def = min;
13703             if(def > max) def = max;
13704             opt->value = def;
13705             opt->min = min;
13706             opt->max = max;
13707             opt->type = Spin; // Slider;
13708         } else if((p = strstr(opt->name, " -string "))) {
13709             opt->textValue = p+9;
13710             opt->type = TextBox;
13711         } else if((p = strstr(opt->name, " -file "))) {
13712             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13713             opt->textValue = p+7;
13714             opt->type = TextBox; // FileName;
13715         } else if((p = strstr(opt->name, " -path "))) {
13716             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13717             opt->textValue = p+7;
13718             opt->type = TextBox; // PathName;
13719         } else if(p = strstr(opt->name, " -check ")) {
13720             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13721             opt->value = (def != 0);
13722             opt->type = CheckBox;
13723         } else if(p = strstr(opt->name, " -combo ")) {
13724             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13725             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13726             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13727             opt->value = n = 0;
13728             while(q = StrStr(q, " /// ")) {
13729                 n++; *q = 0;    // count choices, and null-terminate each of them
13730                 q += 5;
13731                 if(*q == '*') { // remember default, which is marked with * prefix
13732                     q++;
13733                     opt->value = n;
13734                 }
13735                 cps->comboList[cps->comboCnt++] = q;
13736             }
13737             cps->comboList[cps->comboCnt++] = NULL;
13738             opt->max = n + 1;
13739             opt->type = ComboBox;
13740         } else if(p = strstr(opt->name, " -button")) {
13741             opt->type = Button;
13742         } else if(p = strstr(opt->name, " -save")) {
13743             opt->type = SaveButton;
13744         } else return FALSE;
13745         *p = 0; // terminate option name
13746         // now look if the command-line options define a setting for this engine option.
13747         if(cps->optionSettings && cps->optionSettings[0])
13748             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13749         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13750           snprintf(buf, MSG_SIZ, "option %s", p);
13751                 if(p = strstr(buf, ",")) *p = 0;
13752                 strcat(buf, "\n");
13753                 SendToProgram(buf, cps);
13754         }
13755         return TRUE;
13756 }
13757
13758 void
13759 FeatureDone(cps, val)
13760      ChessProgramState* cps;
13761      int val;
13762 {
13763   DelayedEventCallback cb = GetDelayedEvent();
13764   if ((cb == InitBackEnd3 && cps == &first) ||
13765       (cb == TwoMachinesEventIfReady && cps == &second)) {
13766     CancelDelayedEvent();
13767     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13768   }
13769   cps->initDone = val;
13770 }
13771
13772 /* Parse feature command from engine */
13773 void
13774 ParseFeatures(args, cps)
13775      char* args;
13776      ChessProgramState *cps;
13777 {
13778   char *p = args;
13779   char *q;
13780   int val;
13781   char buf[MSG_SIZ];
13782
13783   for (;;) {
13784     while (*p == ' ') p++;
13785     if (*p == NULLCHAR) return;
13786
13787     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13788     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13789     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13790     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13791     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13792     if (BoolFeature(&p, "reuse", &val, cps)) {
13793       /* Engine can disable reuse, but can't enable it if user said no */
13794       if (!val) cps->reuse = FALSE;
13795       continue;
13796     }
13797     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13798     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13799       if (gameMode == TwoMachinesPlay) {
13800         DisplayTwoMachinesTitle();
13801       } else {
13802         DisplayTitle("");
13803       }
13804       continue;
13805     }
13806     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13807     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13808     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13809     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13810     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13811     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13812     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13813     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13814     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13815     if (IntFeature(&p, "done", &val, cps)) {
13816       FeatureDone(cps, val);
13817       continue;
13818     }
13819     /* Added by Tord: */
13820     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13821     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13822     /* End of additions by Tord */
13823
13824     /* [HGM] added features: */
13825     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13826     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13827     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13828     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13829     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13830     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13831     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13832         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13833           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13834             SendToProgram(buf, cps);
13835             continue;
13836         }
13837         if(cps->nrOptions >= MAX_OPTIONS) {
13838             cps->nrOptions--;
13839             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13840             DisplayError(buf, 0);
13841         }
13842         continue;
13843     }
13844     /* End of additions by HGM */
13845
13846     /* unknown feature: complain and skip */
13847     q = p;
13848     while (*q && *q != '=') q++;
13849     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13850     SendToProgram(buf, cps);
13851     p = q;
13852     if (*p == '=') {
13853       p++;
13854       if (*p == '\"') {
13855         p++;
13856         while (*p && *p != '\"') p++;
13857         if (*p == '\"') p++;
13858       } else {
13859         while (*p && *p != ' ') p++;
13860       }
13861     }
13862   }
13863
13864 }
13865
13866 void
13867 PeriodicUpdatesEvent(newState)
13868      int newState;
13869 {
13870     if (newState == appData.periodicUpdates)
13871       return;
13872
13873     appData.periodicUpdates=newState;
13874
13875     /* Display type changes, so update it now */
13876 //    DisplayAnalysis();
13877
13878     /* Get the ball rolling again... */
13879     if (newState) {
13880         AnalysisPeriodicEvent(1);
13881         StartAnalysisClock();
13882     }
13883 }
13884
13885 void
13886 PonderNextMoveEvent(newState)
13887      int newState;
13888 {
13889     if (newState == appData.ponderNextMove) return;
13890     if (gameMode == EditPosition) EditPositionDone(TRUE);
13891     if (newState) {
13892         SendToProgram("hard\n", &first);
13893         if (gameMode == TwoMachinesPlay) {
13894             SendToProgram("hard\n", &second);
13895         }
13896     } else {
13897         SendToProgram("easy\n", &first);
13898         thinkOutput[0] = NULLCHAR;
13899         if (gameMode == TwoMachinesPlay) {
13900             SendToProgram("easy\n", &second);
13901         }
13902     }
13903     appData.ponderNextMove = newState;
13904 }
13905
13906 void
13907 NewSettingEvent(option, feature, command, value)
13908      char *command;
13909      int option, value, *feature;
13910 {
13911     char buf[MSG_SIZ];
13912
13913     if (gameMode == EditPosition) EditPositionDone(TRUE);
13914     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
13915     if(feature == NULL || *feature) SendToProgram(buf, &first);
13916     if (gameMode == TwoMachinesPlay) {
13917         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13918     }
13919 }
13920
13921 void
13922 ShowThinkingEvent()
13923 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13924 {
13925     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13926     int newState = appData.showThinking
13927         // [HGM] thinking: other features now need thinking output as well
13928         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13929
13930     if (oldState == newState) return;
13931     oldState = newState;
13932     if (gameMode == EditPosition) EditPositionDone(TRUE);
13933     if (oldState) {
13934         SendToProgram("post\n", &first);
13935         if (gameMode == TwoMachinesPlay) {
13936             SendToProgram("post\n", &second);
13937         }
13938     } else {
13939         SendToProgram("nopost\n", &first);
13940         thinkOutput[0] = NULLCHAR;
13941         if (gameMode == TwoMachinesPlay) {
13942             SendToProgram("nopost\n", &second);
13943         }
13944     }
13945 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13946 }
13947
13948 void
13949 AskQuestionEvent(title, question, replyPrefix, which)
13950      char *title; char *question; char *replyPrefix; char *which;
13951 {
13952   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13953   if (pr == NoProc) return;
13954   AskQuestion(title, question, replyPrefix, pr);
13955 }
13956
13957 void
13958 DisplayMove(moveNumber)
13959      int moveNumber;
13960 {
13961     char message[MSG_SIZ];
13962     char res[MSG_SIZ];
13963     char cpThinkOutput[MSG_SIZ];
13964
13965     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13966
13967     if (moveNumber == forwardMostMove - 1 ||
13968         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13969
13970         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
13971
13972         if (strchr(cpThinkOutput, '\n')) {
13973             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13974         }
13975     } else {
13976         *cpThinkOutput = NULLCHAR;
13977     }
13978
13979     /* [AS] Hide thinking from human user */
13980     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13981         *cpThinkOutput = NULLCHAR;
13982         if( thinkOutput[0] != NULLCHAR ) {
13983             int i;
13984
13985             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13986                 cpThinkOutput[i] = '.';
13987             }
13988             cpThinkOutput[i] = NULLCHAR;
13989             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13990         }
13991     }
13992
13993     if (moveNumber == forwardMostMove - 1 &&
13994         gameInfo.resultDetails != NULL) {
13995         if (gameInfo.resultDetails[0] == NULLCHAR) {
13996           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
13997         } else {
13998           snprintf(res, MSG_SIZ, " {%s} %s",
13999                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14000         }
14001     } else {
14002         res[0] = NULLCHAR;
14003     }
14004
14005     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14006         DisplayMessage(res, cpThinkOutput);
14007     } else {
14008       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14009                 WhiteOnMove(moveNumber) ? " " : ".. ",
14010                 parseList[moveNumber], res);
14011         DisplayMessage(message, cpThinkOutput);
14012     }
14013 }
14014
14015 void
14016 DisplayComment(moveNumber, text)
14017      int moveNumber;
14018      char *text;
14019 {
14020     char title[MSG_SIZ];
14021     char buf[8000]; // comment can be long!
14022     int score, depth;
14023
14024     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14025       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14026     } else {
14027       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14028               WhiteOnMove(moveNumber) ? " " : ".. ",
14029               parseList[moveNumber]);
14030     }
14031     // [HGM] PV info: display PV info together with (or as) comment
14032     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14033       if(text == NULL) text = "";
14034       score = pvInfoList[moveNumber].score;
14035       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14036               depth, (pvInfoList[moveNumber].time+50)/100, text);
14037       text = buf;
14038     }
14039     if (text != NULL && (appData.autoDisplayComment || commentUp))
14040         CommentPopUp(title, text);
14041 }
14042
14043 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14044  * might be busy thinking or pondering.  It can be omitted if your
14045  * gnuchess is configured to stop thinking immediately on any user
14046  * input.  However, that gnuchess feature depends on the FIONREAD
14047  * ioctl, which does not work properly on some flavors of Unix.
14048  */
14049 void
14050 Attention(cps)
14051      ChessProgramState *cps;
14052 {
14053 #if ATTENTION
14054     if (!cps->useSigint) return;
14055     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14056     switch (gameMode) {
14057       case MachinePlaysWhite:
14058       case MachinePlaysBlack:
14059       case TwoMachinesPlay:
14060       case IcsPlayingWhite:
14061       case IcsPlayingBlack:
14062       case AnalyzeMode:
14063       case AnalyzeFile:
14064         /* Skip if we know it isn't thinking */
14065         if (!cps->maybeThinking) return;
14066         if (appData.debugMode)
14067           fprintf(debugFP, "Interrupting %s\n", cps->which);
14068         InterruptChildProcess(cps->pr);
14069         cps->maybeThinking = FALSE;
14070         break;
14071       default:
14072         break;
14073     }
14074 #endif /*ATTENTION*/
14075 }
14076
14077 int
14078 CheckFlags()
14079 {
14080     if (whiteTimeRemaining <= 0) {
14081         if (!whiteFlag) {
14082             whiteFlag = TRUE;
14083             if (appData.icsActive) {
14084                 if (appData.autoCallFlag &&
14085                     gameMode == IcsPlayingBlack && !blackFlag) {
14086                   SendToICS(ics_prefix);
14087                   SendToICS("flag\n");
14088                 }
14089             } else {
14090                 if (blackFlag) {
14091                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14092                 } else {
14093                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14094                     if (appData.autoCallFlag) {
14095                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14096                         return TRUE;
14097                     }
14098                 }
14099             }
14100         }
14101     }
14102     if (blackTimeRemaining <= 0) {
14103         if (!blackFlag) {
14104             blackFlag = TRUE;
14105             if (appData.icsActive) {
14106                 if (appData.autoCallFlag &&
14107                     gameMode == IcsPlayingWhite && !whiteFlag) {
14108                   SendToICS(ics_prefix);
14109                   SendToICS("flag\n");
14110                 }
14111             } else {
14112                 if (whiteFlag) {
14113                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14114                 } else {
14115                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14116                     if (appData.autoCallFlag) {
14117                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14118                         return TRUE;
14119                     }
14120                 }
14121             }
14122         }
14123     }
14124     return FALSE;
14125 }
14126
14127 void
14128 CheckTimeControl()
14129 {
14130     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14131         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14132
14133     /*
14134      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14135      */
14136     if ( !WhiteOnMove(forwardMostMove) ) {
14137         /* White made time control */
14138         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14139         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14140         /* [HGM] time odds: correct new time quota for time odds! */
14141                                             / WhitePlayer()->timeOdds;
14142         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14143     } else {
14144         lastBlack -= blackTimeRemaining;
14145         /* Black made time control */
14146         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14147                                             / WhitePlayer()->other->timeOdds;
14148         lastWhite = whiteTimeRemaining;
14149     }
14150 }
14151
14152 void
14153 DisplayBothClocks()
14154 {
14155     int wom = gameMode == EditPosition ?
14156       !blackPlaysFirst : WhiteOnMove(currentMove);
14157     DisplayWhiteClock(whiteTimeRemaining, wom);
14158     DisplayBlackClock(blackTimeRemaining, !wom);
14159 }
14160
14161
14162 /* Timekeeping seems to be a portability nightmare.  I think everyone
14163    has ftime(), but I'm really not sure, so I'm including some ifdefs
14164    to use other calls if you don't.  Clocks will be less accurate if
14165    you have neither ftime nor gettimeofday.
14166 */
14167
14168 /* VS 2008 requires the #include outside of the function */
14169 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14170 #include <sys/timeb.h>
14171 #endif
14172
14173 /* Get the current time as a TimeMark */
14174 void
14175 GetTimeMark(tm)
14176      TimeMark *tm;
14177 {
14178 #if HAVE_GETTIMEOFDAY
14179
14180     struct timeval timeVal;
14181     struct timezone timeZone;
14182
14183     gettimeofday(&timeVal, &timeZone);
14184     tm->sec = (long) timeVal.tv_sec;
14185     tm->ms = (int) (timeVal.tv_usec / 1000L);
14186
14187 #else /*!HAVE_GETTIMEOFDAY*/
14188 #if HAVE_FTIME
14189
14190 // include <sys/timeb.h> / moved to just above start of function
14191     struct timeb timeB;
14192
14193     ftime(&timeB);
14194     tm->sec = (long) timeB.time;
14195     tm->ms = (int) timeB.millitm;
14196
14197 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14198     tm->sec = (long) time(NULL);
14199     tm->ms = 0;
14200 #endif
14201 #endif
14202 }
14203
14204 /* Return the difference in milliseconds between two
14205    time marks.  We assume the difference will fit in a long!
14206 */
14207 long
14208 SubtractTimeMarks(tm2, tm1)
14209      TimeMark *tm2, *tm1;
14210 {
14211     return 1000L*(tm2->sec - tm1->sec) +
14212            (long) (tm2->ms - tm1->ms);
14213 }
14214
14215
14216 /*
14217  * Code to manage the game clocks.
14218  *
14219  * In tournament play, black starts the clock and then white makes a move.
14220  * We give the human user a slight advantage if he is playing white---the
14221  * clocks don't run until he makes his first move, so it takes zero time.
14222  * Also, we don't account for network lag, so we could get out of sync
14223  * with GNU Chess's clock -- but then, referees are always right.
14224  */
14225
14226 static TimeMark tickStartTM;
14227 static long intendedTickLength;
14228
14229 long
14230 NextTickLength(timeRemaining)
14231      long timeRemaining;
14232 {
14233     long nominalTickLength, nextTickLength;
14234
14235     if (timeRemaining > 0L && timeRemaining <= 10000L)
14236       nominalTickLength = 100L;
14237     else
14238       nominalTickLength = 1000L;
14239     nextTickLength = timeRemaining % nominalTickLength;
14240     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14241
14242     return nextTickLength;
14243 }
14244
14245 /* Adjust clock one minute up or down */
14246 void
14247 AdjustClock(Boolean which, int dir)
14248 {
14249     if(which) blackTimeRemaining += 60000*dir;
14250     else      whiteTimeRemaining += 60000*dir;
14251     DisplayBothClocks();
14252 }
14253
14254 /* Stop clocks and reset to a fresh time control */
14255 void
14256 ResetClocks()
14257 {
14258     (void) StopClockTimer();
14259     if (appData.icsActive) {
14260         whiteTimeRemaining = blackTimeRemaining = 0;
14261     } else if (searchTime) {
14262         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14263         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14264     } else { /* [HGM] correct new time quote for time odds */
14265         whiteTC = blackTC = fullTimeControlString;
14266         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14267         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14268     }
14269     if (whiteFlag || blackFlag) {
14270         DisplayTitle("");
14271         whiteFlag = blackFlag = FALSE;
14272     }
14273     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14274     DisplayBothClocks();
14275 }
14276
14277 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14278
14279 /* Decrement running clock by amount of time that has passed */
14280 void
14281 DecrementClocks()
14282 {
14283     long timeRemaining;
14284     long lastTickLength, fudge;
14285     TimeMark now;
14286
14287     if (!appData.clockMode) return;
14288     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14289
14290     GetTimeMark(&now);
14291
14292     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14293
14294     /* Fudge if we woke up a little too soon */
14295     fudge = intendedTickLength - lastTickLength;
14296     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14297
14298     if (WhiteOnMove(forwardMostMove)) {
14299         if(whiteNPS >= 0) lastTickLength = 0;
14300         timeRemaining = whiteTimeRemaining -= lastTickLength;
14301         if(timeRemaining < 0) {
14302             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14303             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14304                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14305                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14306             }
14307         }
14308         DisplayWhiteClock(whiteTimeRemaining - fudge,
14309                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14310     } else {
14311         if(blackNPS >= 0) lastTickLength = 0;
14312         timeRemaining = blackTimeRemaining -= lastTickLength;
14313         if(timeRemaining < 0) { // [HGM] if we run out of a non-last incremental session, go to the next
14314             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14315             if(suddenDeath) {
14316                 blackStartMove = forwardMostMove;
14317                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14318             }
14319         }
14320         DisplayBlackClock(blackTimeRemaining - fudge,
14321                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14322     }
14323     if (CheckFlags()) return;
14324
14325     tickStartTM = now;
14326     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14327     StartClockTimer(intendedTickLength);
14328
14329     /* if the time remaining has fallen below the alarm threshold, sound the
14330      * alarm. if the alarm has sounded and (due to a takeback or time control
14331      * with increment) the time remaining has increased to a level above the
14332      * threshold, reset the alarm so it can sound again.
14333      */
14334
14335     if (appData.icsActive && appData.icsAlarm) {
14336
14337         /* make sure we are dealing with the user's clock */
14338         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14339                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14340            )) return;
14341
14342         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14343             alarmSounded = FALSE;
14344         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14345             PlayAlarmSound();
14346             alarmSounded = TRUE;
14347         }
14348     }
14349 }
14350
14351
14352 /* A player has just moved, so stop the previously running
14353    clock and (if in clock mode) start the other one.
14354    We redisplay both clocks in case we're in ICS mode, because
14355    ICS gives us an update to both clocks after every move.
14356    Note that this routine is called *after* forwardMostMove
14357    is updated, so the last fractional tick must be subtracted
14358    from the color that is *not* on move now.
14359 */
14360 void
14361 SwitchClocks(int newMoveNr)
14362 {
14363     long lastTickLength;
14364     TimeMark now;
14365     int flagged = FALSE;
14366
14367     GetTimeMark(&now);
14368
14369     if (StopClockTimer() && appData.clockMode) {
14370         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14371         if (!WhiteOnMove(forwardMostMove)) {
14372             if(blackNPS >= 0) lastTickLength = 0;
14373             blackTimeRemaining -= lastTickLength;
14374            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14375 //         if(pvInfoList[forwardMostMove-1].time == -1)
14376                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14377                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14378         } else {
14379            if(whiteNPS >= 0) lastTickLength = 0;
14380            whiteTimeRemaining -= lastTickLength;
14381            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14382 //         if(pvInfoList[forwardMostMove-1].time == -1)
14383                  pvInfoList[forwardMostMove-1].time =
14384                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14385         }
14386         flagged = CheckFlags();
14387     }
14388     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14389     CheckTimeControl();
14390
14391     if (flagged || !appData.clockMode) return;
14392
14393     switch (gameMode) {
14394       case MachinePlaysBlack:
14395       case MachinePlaysWhite:
14396       case BeginningOfGame:
14397         if (pausing) return;
14398         break;
14399
14400       case EditGame:
14401       case PlayFromGameFile:
14402       case IcsExamining:
14403         return;
14404
14405       default:
14406         break;
14407     }
14408
14409     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14410         if(WhiteOnMove(forwardMostMove))
14411              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14412         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14413     }
14414
14415     tickStartTM = now;
14416     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14417       whiteTimeRemaining : blackTimeRemaining);
14418     StartClockTimer(intendedTickLength);
14419 }
14420
14421
14422 /* Stop both clocks */
14423 void
14424 StopClocks()
14425 {
14426     long lastTickLength;
14427     TimeMark now;
14428
14429     if (!StopClockTimer()) return;
14430     if (!appData.clockMode) return;
14431
14432     GetTimeMark(&now);
14433
14434     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14435     if (WhiteOnMove(forwardMostMove)) {
14436         if(whiteNPS >= 0) lastTickLength = 0;
14437         whiteTimeRemaining -= lastTickLength;
14438         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14439     } else {
14440         if(blackNPS >= 0) lastTickLength = 0;
14441         blackTimeRemaining -= lastTickLength;
14442         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14443     }
14444     CheckFlags();
14445 }
14446
14447 /* Start clock of player on move.  Time may have been reset, so
14448    if clock is already running, stop and restart it. */
14449 void
14450 StartClocks()
14451 {
14452     (void) StopClockTimer(); /* in case it was running already */
14453     DisplayBothClocks();
14454     if (CheckFlags()) return;
14455
14456     if (!appData.clockMode) return;
14457     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14458
14459     GetTimeMark(&tickStartTM);
14460     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14461       whiteTimeRemaining : blackTimeRemaining);
14462
14463    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14464     whiteNPS = blackNPS = -1;
14465     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14466        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14467         whiteNPS = first.nps;
14468     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14469        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14470         blackNPS = first.nps;
14471     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14472         whiteNPS = second.nps;
14473     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14474         blackNPS = second.nps;
14475     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14476
14477     StartClockTimer(intendedTickLength);
14478 }
14479
14480 char *
14481 TimeString(ms)
14482      long ms;
14483 {
14484     long second, minute, hour, day;
14485     char *sign = "";
14486     static char buf[32];
14487
14488     if (ms > 0 && ms <= 9900) {
14489       /* convert milliseconds to tenths, rounding up */
14490       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14491
14492       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14493       return buf;
14494     }
14495
14496     /* convert milliseconds to seconds, rounding up */
14497     /* use floating point to avoid strangeness of integer division
14498        with negative dividends on many machines */
14499     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14500
14501     if (second < 0) {
14502         sign = "-";
14503         second = -second;
14504     }
14505
14506     day = second / (60 * 60 * 24);
14507     second = second % (60 * 60 * 24);
14508     hour = second / (60 * 60);
14509     second = second % (60 * 60);
14510     minute = second / 60;
14511     second = second % 60;
14512
14513     if (day > 0)
14514       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14515               sign, day, hour, minute, second);
14516     else if (hour > 0)
14517       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14518     else
14519       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14520
14521     return buf;
14522 }
14523
14524
14525 /*
14526  * This is necessary because some C libraries aren't ANSI C compliant yet.
14527  */
14528 char *
14529 StrStr(string, match)
14530      char *string, *match;
14531 {
14532     int i, length;
14533
14534     length = strlen(match);
14535
14536     for (i = strlen(string) - length; i >= 0; i--, string++)
14537       if (!strncmp(match, string, length))
14538         return string;
14539
14540     return NULL;
14541 }
14542
14543 char *
14544 StrCaseStr(string, match)
14545      char *string, *match;
14546 {
14547     int i, j, length;
14548
14549     length = strlen(match);
14550
14551     for (i = strlen(string) - length; i >= 0; i--, string++) {
14552         for (j = 0; j < length; j++) {
14553             if (ToLower(match[j]) != ToLower(string[j]))
14554               break;
14555         }
14556         if (j == length) return string;
14557     }
14558
14559     return NULL;
14560 }
14561
14562 #ifndef _amigados
14563 int
14564 StrCaseCmp(s1, s2)
14565      char *s1, *s2;
14566 {
14567     char c1, c2;
14568
14569     for (;;) {
14570         c1 = ToLower(*s1++);
14571         c2 = ToLower(*s2++);
14572         if (c1 > c2) return 1;
14573         if (c1 < c2) return -1;
14574         if (c1 == NULLCHAR) return 0;
14575     }
14576 }
14577
14578
14579 int
14580 ToLower(c)
14581      int c;
14582 {
14583     return isupper(c) ? tolower(c) : c;
14584 }
14585
14586
14587 int
14588 ToUpper(c)
14589      int c;
14590 {
14591     return islower(c) ? toupper(c) : c;
14592 }
14593 #endif /* !_amigados    */
14594
14595 char *
14596 StrSave(s)
14597      char *s;
14598 {
14599   char *ret;
14600
14601   if ((ret = (char *) malloc(strlen(s) + 1)))
14602     {
14603       safeStrCpy(ret, s, strlen(s)+1);
14604     }
14605   return ret;
14606 }
14607
14608 char *
14609 StrSavePtr(s, savePtr)
14610      char *s, **savePtr;
14611 {
14612     if (*savePtr) {
14613         free(*savePtr);
14614     }
14615     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14616       safeStrCpy(*savePtr, s, strlen(s)+1);
14617     }
14618     return(*savePtr);
14619 }
14620
14621 char *
14622 PGNDate()
14623 {
14624     time_t clock;
14625     struct tm *tm;
14626     char buf[MSG_SIZ];
14627
14628     clock = time((time_t *)NULL);
14629     tm = localtime(&clock);
14630     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14631             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14632     return StrSave(buf);
14633 }
14634
14635
14636 char *
14637 PositionToFEN(move, overrideCastling)
14638      int move;
14639      char *overrideCastling;
14640 {
14641     int i, j, fromX, fromY, toX, toY;
14642     int whiteToPlay;
14643     char buf[128];
14644     char *p, *q;
14645     int emptycount;
14646     ChessSquare piece;
14647
14648     whiteToPlay = (gameMode == EditPosition) ?
14649       !blackPlaysFirst : (move % 2 == 0);
14650     p = buf;
14651
14652     /* Piece placement data */
14653     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14654         emptycount = 0;
14655         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14656             if (boards[move][i][j] == EmptySquare) {
14657                 emptycount++;
14658             } else { ChessSquare piece = boards[move][i][j];
14659                 if (emptycount > 0) {
14660                     if(emptycount<10) /* [HGM] can be >= 10 */
14661                         *p++ = '0' + emptycount;
14662                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14663                     emptycount = 0;
14664                 }
14665                 if(PieceToChar(piece) == '+') {
14666                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14667                     *p++ = '+';
14668                     piece = (ChessSquare)(DEMOTED piece);
14669                 }
14670                 *p++ = PieceToChar(piece);
14671                 if(p[-1] == '~') {
14672                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14673                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14674                     *p++ = '~';
14675                 }
14676             }
14677         }
14678         if (emptycount > 0) {
14679             if(emptycount<10) /* [HGM] can be >= 10 */
14680                 *p++ = '0' + emptycount;
14681             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14682             emptycount = 0;
14683         }
14684         *p++ = '/';
14685     }
14686     *(p - 1) = ' ';
14687
14688     /* [HGM] print Crazyhouse or Shogi holdings */
14689     if( gameInfo.holdingsWidth ) {
14690         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14691         q = p;
14692         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14693             piece = boards[move][i][BOARD_WIDTH-1];
14694             if( piece != EmptySquare )
14695               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14696                   *p++ = PieceToChar(piece);
14697         }
14698         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14699             piece = boards[move][BOARD_HEIGHT-i-1][0];
14700             if( piece != EmptySquare )
14701               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14702                   *p++ = PieceToChar(piece);
14703         }
14704
14705         if( q == p ) *p++ = '-';
14706         *p++ = ']';
14707         *p++ = ' ';
14708     }
14709
14710     /* Active color */
14711     *p++ = whiteToPlay ? 'w' : 'b';
14712     *p++ = ' ';
14713
14714   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14715     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14716   } else {
14717   if(nrCastlingRights) {
14718      q = p;
14719      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14720        /* [HGM] write directly from rights */
14721            if(boards[move][CASTLING][2] != NoRights &&
14722               boards[move][CASTLING][0] != NoRights   )
14723                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14724            if(boards[move][CASTLING][2] != NoRights &&
14725               boards[move][CASTLING][1] != NoRights   )
14726                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14727            if(boards[move][CASTLING][5] != NoRights &&
14728               boards[move][CASTLING][3] != NoRights   )
14729                 *p++ = boards[move][CASTLING][3] + AAA;
14730            if(boards[move][CASTLING][5] != NoRights &&
14731               boards[move][CASTLING][4] != NoRights   )
14732                 *p++ = boards[move][CASTLING][4] + AAA;
14733      } else {
14734
14735         /* [HGM] write true castling rights */
14736         if( nrCastlingRights == 6 ) {
14737             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14738                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14739             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14740                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14741             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14742                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14743             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14744                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14745         }
14746      }
14747      if (q == p) *p++ = '-'; /* No castling rights */
14748      *p++ = ' ';
14749   }
14750
14751   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14752      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14753     /* En passant target square */
14754     if (move > backwardMostMove) {
14755         fromX = moveList[move - 1][0] - AAA;
14756         fromY = moveList[move - 1][1] - ONE;
14757         toX = moveList[move - 1][2] - AAA;
14758         toY = moveList[move - 1][3] - ONE;
14759         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14760             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14761             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14762             fromX == toX) {
14763             /* 2-square pawn move just happened */
14764             *p++ = toX + AAA;
14765             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14766         } else {
14767             *p++ = '-';
14768         }
14769     } else if(move == backwardMostMove) {
14770         // [HGM] perhaps we should always do it like this, and forget the above?
14771         if((signed char)boards[move][EP_STATUS] >= 0) {
14772             *p++ = boards[move][EP_STATUS] + AAA;
14773             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14774         } else {
14775             *p++ = '-';
14776         }
14777     } else {
14778         *p++ = '-';
14779     }
14780     *p++ = ' ';
14781   }
14782   }
14783
14784     /* [HGM] find reversible plies */
14785     {   int i = 0, j=move;
14786
14787         if (appData.debugMode) { int k;
14788             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14789             for(k=backwardMostMove; k<=forwardMostMove; k++)
14790                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14791
14792         }
14793
14794         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14795         if( j == backwardMostMove ) i += initialRulePlies;
14796         sprintf(p, "%d ", i);
14797         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14798     }
14799     /* Fullmove number */
14800     sprintf(p, "%d", (move / 2) + 1);
14801
14802     return StrSave(buf);
14803 }
14804
14805 Boolean
14806 ParseFEN(board, blackPlaysFirst, fen)
14807     Board board;
14808      int *blackPlaysFirst;
14809      char *fen;
14810 {
14811     int i, j;
14812     char *p, c;
14813     int emptycount;
14814     ChessSquare piece;
14815
14816     p = fen;
14817
14818     /* [HGM] by default clear Crazyhouse holdings, if present */
14819     if(gameInfo.holdingsWidth) {
14820        for(i=0; i<BOARD_HEIGHT; i++) {
14821            board[i][0]             = EmptySquare; /* black holdings */
14822            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14823            board[i][1]             = (ChessSquare) 0; /* black counts */
14824            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14825        }
14826     }
14827
14828     /* Piece placement data */
14829     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14830         j = 0;
14831         for (;;) {
14832             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14833                 if (*p == '/') p++;
14834                 emptycount = gameInfo.boardWidth - j;
14835                 while (emptycount--)
14836                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14837                 break;
14838 #if(BOARD_FILES >= 10)
14839             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14840                 p++; emptycount=10;
14841                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14842                 while (emptycount--)
14843                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14844 #endif
14845             } else if (isdigit(*p)) {
14846                 emptycount = *p++ - '0';
14847                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14848                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14849                 while (emptycount--)
14850                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14851             } else if (*p == '+' || isalpha(*p)) {
14852                 if (j >= gameInfo.boardWidth) return FALSE;
14853                 if(*p=='+') {
14854                     piece = CharToPiece(*++p);
14855                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14856                     piece = (ChessSquare) (PROMOTED piece ); p++;
14857                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14858                 } else piece = CharToPiece(*p++);
14859
14860                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14861                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14862                     piece = (ChessSquare) (PROMOTED piece);
14863                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14864                     p++;
14865                 }
14866                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14867             } else {
14868                 return FALSE;
14869             }
14870         }
14871     }
14872     while (*p == '/' || *p == ' ') p++;
14873
14874     /* [HGM] look for Crazyhouse holdings here */
14875     while(*p==' ') p++;
14876     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14877         if(*p == '[') p++;
14878         if(*p == '-' ) p++; /* empty holdings */ else {
14879             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14880             /* if we would allow FEN reading to set board size, we would   */
14881             /* have to add holdings and shift the board read so far here   */
14882             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14883                 p++;
14884                 if((int) piece >= (int) BlackPawn ) {
14885                     i = (int)piece - (int)BlackPawn;
14886                     i = PieceToNumber((ChessSquare)i);
14887                     if( i >= gameInfo.holdingsSize ) return FALSE;
14888                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14889                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14890                 } else {
14891                     i = (int)piece - (int)WhitePawn;
14892                     i = PieceToNumber((ChessSquare)i);
14893                     if( i >= gameInfo.holdingsSize ) return FALSE;
14894                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14895                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14896                 }
14897             }
14898         }
14899         if(*p == ']') p++;
14900     }
14901
14902     while(*p == ' ') p++;
14903
14904     /* Active color */
14905     c = *p++;
14906     if(appData.colorNickNames) {
14907       if( c == appData.colorNickNames[0] ) c = 'w'; else
14908       if( c == appData.colorNickNames[1] ) c = 'b';
14909     }
14910     switch (c) {
14911       case 'w':
14912         *blackPlaysFirst = FALSE;
14913         break;
14914       case 'b':
14915         *blackPlaysFirst = TRUE;
14916         break;
14917       default:
14918         return FALSE;
14919     }
14920
14921     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14922     /* return the extra info in global variiables             */
14923
14924     /* set defaults in case FEN is incomplete */
14925     board[EP_STATUS] = EP_UNKNOWN;
14926     for(i=0; i<nrCastlingRights; i++ ) {
14927         board[CASTLING][i] =
14928             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14929     }   /* assume possible unless obviously impossible */
14930     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14931     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14932     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14933                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14934     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14935     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14936     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14937                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14938     FENrulePlies = 0;
14939
14940     while(*p==' ') p++;
14941     if(nrCastlingRights) {
14942       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14943           /* castling indicator present, so default becomes no castlings */
14944           for(i=0; i<nrCastlingRights; i++ ) {
14945                  board[CASTLING][i] = NoRights;
14946           }
14947       }
14948       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14949              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14950              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14951              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14952         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14953
14954         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14955             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14956             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14957         }
14958         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14959             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14960         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14961                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14962         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14963                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14964         switch(c) {
14965           case'K':
14966               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14967               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14968               board[CASTLING][2] = whiteKingFile;
14969               break;
14970           case'Q':
14971               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14972               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14973               board[CASTLING][2] = whiteKingFile;
14974               break;
14975           case'k':
14976               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14977               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14978               board[CASTLING][5] = blackKingFile;
14979               break;
14980           case'q':
14981               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14982               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14983               board[CASTLING][5] = blackKingFile;
14984           case '-':
14985               break;
14986           default: /* FRC castlings */
14987               if(c >= 'a') { /* black rights */
14988                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14989                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14990                   if(i == BOARD_RGHT) break;
14991                   board[CASTLING][5] = i;
14992                   c -= AAA;
14993                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14994                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14995                   if(c > i)
14996                       board[CASTLING][3] = c;
14997                   else
14998                       board[CASTLING][4] = c;
14999               } else { /* white rights */
15000                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15001                     if(board[0][i] == WhiteKing) break;
15002                   if(i == BOARD_RGHT) break;
15003                   board[CASTLING][2] = i;
15004                   c -= AAA - 'a' + 'A';
15005                   if(board[0][c] >= WhiteKing) break;
15006                   if(c > i)
15007                       board[CASTLING][0] = c;
15008                   else
15009                       board[CASTLING][1] = c;
15010               }
15011         }
15012       }
15013       for(i=0; i<nrCastlingRights; i++)
15014         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15015     if (appData.debugMode) {
15016         fprintf(debugFP, "FEN castling rights:");
15017         for(i=0; i<nrCastlingRights; i++)
15018         fprintf(debugFP, " %d", board[CASTLING][i]);
15019         fprintf(debugFP, "\n");
15020     }
15021
15022       while(*p==' ') p++;
15023     }
15024
15025     /* read e.p. field in games that know e.p. capture */
15026     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15027        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15028       if(*p=='-') {
15029         p++; board[EP_STATUS] = EP_NONE;
15030       } else {
15031          char c = *p++ - AAA;
15032
15033          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15034          if(*p >= '0' && *p <='9') p++;
15035          board[EP_STATUS] = c;
15036       }
15037     }
15038
15039
15040     if(sscanf(p, "%d", &i) == 1) {
15041         FENrulePlies = i; /* 50-move ply counter */
15042         /* (The move number is still ignored)    */
15043     }
15044
15045     return TRUE;
15046 }
15047
15048 void
15049 EditPositionPasteFEN(char *fen)
15050 {
15051   if (fen != NULL) {
15052     Board initial_position;
15053
15054     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15055       DisplayError(_("Bad FEN position in clipboard"), 0);
15056       return ;
15057     } else {
15058       int savedBlackPlaysFirst = blackPlaysFirst;
15059       EditPositionEvent();
15060       blackPlaysFirst = savedBlackPlaysFirst;
15061       CopyBoard(boards[0], initial_position);
15062       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15063       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15064       DisplayBothClocks();
15065       DrawPosition(FALSE, boards[currentMove]);
15066     }
15067   }
15068 }
15069
15070 static char cseq[12] = "\\   ";
15071
15072 Boolean set_cont_sequence(char *new_seq)
15073 {
15074     int len;
15075     Boolean ret;
15076
15077     // handle bad attempts to set the sequence
15078         if (!new_seq)
15079                 return 0; // acceptable error - no debug
15080
15081     len = strlen(new_seq);
15082     ret = (len > 0) && (len < sizeof(cseq));
15083     if (ret)
15084       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15085     else if (appData.debugMode)
15086       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15087     return ret;
15088 }
15089
15090 /*
15091     reformat a source message so words don't cross the width boundary.  internal
15092     newlines are not removed.  returns the wrapped size (no null character unless
15093     included in source message).  If dest is NULL, only calculate the size required
15094     for the dest buffer.  lp argument indicats line position upon entry, and it's
15095     passed back upon exit.
15096 */
15097 int wrap(char *dest, char *src, int count, int width, int *lp)
15098 {
15099     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15100
15101     cseq_len = strlen(cseq);
15102     old_line = line = *lp;
15103     ansi = len = clen = 0;
15104
15105     for (i=0; i < count; i++)
15106     {
15107         if (src[i] == '\033')
15108             ansi = 1;
15109
15110         // if we hit the width, back up
15111         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15112         {
15113             // store i & len in case the word is too long
15114             old_i = i, old_len = len;
15115
15116             // find the end of the last word
15117             while (i && src[i] != ' ' && src[i] != '\n')
15118             {
15119                 i--;
15120                 len--;
15121             }
15122
15123             // word too long?  restore i & len before splitting it
15124             if ((old_i-i+clen) >= width)
15125             {
15126                 i = old_i;
15127                 len = old_len;
15128             }
15129
15130             // extra space?
15131             if (i && src[i-1] == ' ')
15132                 len--;
15133
15134             if (src[i] != ' ' && src[i] != '\n')
15135             {
15136                 i--;
15137                 if (len)
15138                     len--;
15139             }
15140
15141             // now append the newline and continuation sequence
15142             if (dest)
15143                 dest[len] = '\n';
15144             len++;
15145             if (dest)
15146                 strncpy(dest+len, cseq, cseq_len);
15147             len += cseq_len;
15148             line = cseq_len;
15149             clen = cseq_len;
15150             continue;
15151         }
15152
15153         if (dest)
15154             dest[len] = src[i];
15155         len++;
15156         if (!ansi)
15157             line++;
15158         if (src[i] == '\n')
15159             line = 0;
15160         if (src[i] == 'm')
15161             ansi = 0;
15162     }
15163     if (dest && appData.debugMode)
15164     {
15165         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15166             count, width, line, len, *lp);
15167         show_bytes(debugFP, src, count);
15168         fprintf(debugFP, "\ndest: ");
15169         show_bytes(debugFP, dest, len);
15170         fprintf(debugFP, "\n");
15171     }
15172     *lp = dest ? line : old_line;
15173
15174     return len;
15175 }
15176
15177 // [HGM] vari: routines for shelving variations
15178
15179 void
15180 PushTail(int firstMove, int lastMove)
15181 {
15182         int i, j, nrMoves = lastMove - firstMove;
15183
15184         if(appData.icsActive) { // only in local mode
15185                 forwardMostMove = currentMove; // mimic old ICS behavior
15186                 return;
15187         }
15188         if(storedGames >= MAX_VARIATIONS-1) return;
15189
15190         // push current tail of game on stack
15191         savedResult[storedGames] = gameInfo.result;
15192         savedDetails[storedGames] = gameInfo.resultDetails;
15193         gameInfo.resultDetails = NULL;
15194         savedFirst[storedGames] = firstMove;
15195         savedLast [storedGames] = lastMove;
15196         savedFramePtr[storedGames] = framePtr;
15197         framePtr -= nrMoves; // reserve space for the boards
15198         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15199             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15200             for(j=0; j<MOVE_LEN; j++)
15201                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15202             for(j=0; j<2*MOVE_LEN; j++)
15203                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15204             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15205             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15206             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15207             pvInfoList[firstMove+i-1].depth = 0;
15208             commentList[framePtr+i] = commentList[firstMove+i];
15209             commentList[firstMove+i] = NULL;
15210         }
15211
15212         storedGames++;
15213         forwardMostMove = firstMove; // truncate game so we can start variation
15214         if(storedGames == 1) GreyRevert(FALSE);
15215 }
15216
15217 Boolean
15218 PopTail(Boolean annotate)
15219 {
15220         int i, j, nrMoves;
15221         char buf[8000], moveBuf[20];
15222
15223         if(appData.icsActive) return FALSE; // only in local mode
15224         if(!storedGames) return FALSE; // sanity
15225         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15226
15227         storedGames--;
15228         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15229         nrMoves = savedLast[storedGames] - currentMove;
15230         if(annotate) {
15231                 int cnt = 10;
15232                 if(!WhiteOnMove(currentMove))
15233                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15234                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15235                 for(i=currentMove; i<forwardMostMove; i++) {
15236                         if(WhiteOnMove(i))
15237                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15238                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15239                         strcat(buf, moveBuf);
15240                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15241                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15242                 }
15243                 strcat(buf, ")");
15244         }
15245         for(i=1; i<=nrMoves; i++) { // copy last variation back
15246             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15247             for(j=0; j<MOVE_LEN; j++)
15248                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15249             for(j=0; j<2*MOVE_LEN; j++)
15250                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15251             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15252             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15253             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15254             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15255             commentList[currentMove+i] = commentList[framePtr+i];
15256             commentList[framePtr+i] = NULL;
15257         }
15258         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15259         framePtr = savedFramePtr[storedGames];
15260         gameInfo.result = savedResult[storedGames];
15261         if(gameInfo.resultDetails != NULL) {
15262             free(gameInfo.resultDetails);
15263       }
15264         gameInfo.resultDetails = savedDetails[storedGames];
15265         forwardMostMove = currentMove + nrMoves;
15266         if(storedGames == 0) GreyRevert(TRUE);
15267         return TRUE;
15268 }
15269
15270 void
15271 CleanupTail()
15272 {       // remove all shelved variations
15273         int i;
15274         for(i=0; i<storedGames; i++) {
15275             if(savedDetails[i])
15276                 free(savedDetails[i]);
15277             savedDetails[i] = NULL;
15278         }
15279         for(i=framePtr; i<MAX_MOVES; i++) {
15280                 if(commentList[i]) free(commentList[i]);
15281                 commentList[i] = NULL;
15282         }
15283         framePtr = MAX_MOVES-1;
15284         storedGames = 0;
15285 }
15286
15287 void
15288 LoadVariation(int index, char *text)
15289 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15290         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15291         int level = 0, move;
15292
15293         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15294         // first find outermost bracketing variation
15295         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15296             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15297                 if(*p == '{') wait = '}'; else
15298                 if(*p == '[') wait = ']'; else
15299                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15300                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15301             }
15302             if(*p == wait) wait = NULLCHAR; // closing ]} found
15303             p++;
15304         }
15305         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15306         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15307         end[1] = NULLCHAR; // clip off comment beyond variation
15308         ToNrEvent(currentMove-1);
15309         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15310         // kludge: use ParsePV() to append variation to game
15311         move = currentMove;
15312         ParsePV(start, TRUE);
15313         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15314         ClearPremoveHighlights();
15315         CommentPopDown();
15316         ToNrEvent(currentMove+1);
15317 }
15318