b041f9823eccd55f5d0ccdcc57880affee98f173
[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 int shiftKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int sending_ICS_login    = 0;
456 int sending_ICS_password = 0;
457
458 int movesPerSession;
459 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
460 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
461 long timeControl_2; /* [AS] Allow separate time controls */
462 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
463 long timeRemaining[2][MAX_MOVES];
464 int matchGame = 0;
465 TimeMark programStartTime;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 int shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
530         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
531 };
532
533 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
535         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
537         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
538 };
539
540 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
542         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackMan, BlackFerz,
544         BlackKing, BlackMan, BlackKnight, BlackRook }
545 };
546
547
548 #if (BOARD_FILES>=10)
549 ChessSquare ShogiArray[2][BOARD_FILES] = {
550     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
551         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
552     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
553         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
554 };
555
556 ChessSquare XiangqiArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
558         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
560         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
561 };
562
563 ChessSquare CapablancaArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
567         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
568 };
569
570 ChessSquare GreatArray[2][BOARD_FILES] = {
571     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
572         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
573     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
574         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
575 };
576
577 ChessSquare JanusArray[2][BOARD_FILES] = {
578     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
579         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
580     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
581         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
582 };
583
584 #ifdef GOTHIC
585 ChessSquare GothicArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
587         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
589         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
590 };
591 #else // !GOTHIC
592 #define GothicArray CapablancaArray
593 #endif // !GOTHIC
594
595 #ifdef FALCON
596 ChessSquare FalconArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
598         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
600         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
601 };
602 #else // !FALCON
603 #define FalconArray CapablancaArray
604 #endif // !FALCON
605
606 #else // !(BOARD_FILES>=10)
607 #define XiangqiPosition FIDEArray
608 #define CapablancaArray FIDEArray
609 #define GothicArray FIDEArray
610 #define GreatArray FIDEArray
611 #endif // !(BOARD_FILES>=10)
612
613 #if (BOARD_FILES>=12)
614 ChessSquare CourierArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
616         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
618         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
619 };
620 #else // !(BOARD_FILES>=12)
621 #define CourierArray CapablancaArray
622 #endif // !(BOARD_FILES>=12)
623
624
625 Board initialPosition;
626
627
628 /* Convert str to a rating. Checks for special cases of "----",
629
630    "++++", etc. Also strips ()'s */
631 int
632 string_to_rating(str)
633   char *str;
634 {
635   while(*str && !isdigit(*str)) ++str;
636   if (!*str)
637     return 0;   /* One of the special "no rating" cases */
638   else
639     return atoi(str);
640 }
641
642 void
643 ClearProgramStats()
644 {
645     /* Init programStats */
646     programStats.movelist[0] = 0;
647     programStats.depth = 0;
648     programStats.nr_moves = 0;
649     programStats.moves_left = 0;
650     programStats.nodes = 0;
651     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
652     programStats.score = 0;
653     programStats.got_only_move = 0;
654     programStats.got_fail = 0;
655     programStats.line_is_book = 0;
656 }
657
658 void
659 InitBackEnd1()
660 {
661     int matched, min, sec;
662
663     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
664     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
665
666     GetTimeMark(&programStartTime);
667     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
668
669     ClearProgramStats();
670     programStats.ok_to_send = 1;
671     programStats.seen_stat = 0;
672
673     /*
674      * Initialize game list
675      */
676     ListNew(&gameList);
677
678
679     /*
680      * Internet chess server status
681      */
682     if (appData.icsActive) {
683         appData.matchMode = FALSE;
684         appData.matchGames = 0;
685 #if ZIPPY
686         appData.noChessProgram = !appData.zippyPlay;
687 #else
688         appData.zippyPlay = FALSE;
689         appData.zippyTalk = FALSE;
690         appData.noChessProgram = TRUE;
691 #endif
692         if (*appData.icsHelper != NULLCHAR) {
693             appData.useTelnet = TRUE;
694             appData.telnetProgram = appData.icsHelper;
695         }
696     } else {
697         appData.zippyTalk = appData.zippyPlay = FALSE;
698     }
699
700     /* [AS] Initialize pv info list [HGM] and game state */
701     {
702         int i, j;
703
704         for( i=0; i<=framePtr; i++ ) {
705             pvInfoList[i].depth = -1;
706             boards[i][EP_STATUS] = EP_NONE;
707             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
708         }
709     }
710
711     /*
712      * Parse timeControl resource
713      */
714     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
715                           appData.movesPerSession)) {
716         char buf[MSG_SIZ];
717         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
718         DisplayFatalError(buf, 0, 2);
719     }
720
721     /*
722      * Parse searchTime resource
723      */
724     if (*appData.searchTime != NULLCHAR) {
725         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
726         if (matched == 1) {
727             searchTime = min * 60;
728         } else if (matched == 2) {
729             searchTime = min * 60 + sec;
730         } else {
731             char buf[MSG_SIZ];
732             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
733             DisplayFatalError(buf, 0, 2);
734         }
735     }
736
737     /* [AS] Adjudication threshold */
738     adjudicateLossThreshold = appData.adjudicateLossThreshold;
739
740     first.which = _("first");
741     second.which = _("second");
742     first.maybeThinking = second.maybeThinking = FALSE;
743     first.pr = second.pr = NoProc;
744     first.isr = second.isr = NULL;
745     first.sendTime = second.sendTime = 2;
746     first.sendDrawOffers = 1;
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754     first.program = appData.firstChessProgram;
755     second.program = appData.secondChessProgram;
756     first.host = appData.firstHost;
757     second.host = appData.secondHost;
758     first.dir = appData.firstDirectory;
759     second.dir = appData.secondDirectory;
760     first.other = &second;
761     second.other = &first;
762     first.initString = appData.initString;
763     second.initString = appData.secondInitString;
764     first.computerString = appData.firstComputerString;
765     second.computerString = appData.secondComputerString;
766     first.useSigint = second.useSigint = TRUE;
767     first.useSigterm = second.useSigterm = TRUE;
768     first.reuse = appData.reuseFirst;
769     second.reuse = appData.reuseSecond;
770     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
771     second.nps = appData.secondNPS;
772     first.useSetboard = second.useSetboard = FALSE;
773     first.useSAN = second.useSAN = FALSE;
774     first.usePing = second.usePing = FALSE;
775     first.lastPing = second.lastPing = 0;
776     first.lastPong = second.lastPong = 0;
777     first.usePlayother = second.usePlayother = FALSE;
778     first.useColors = second.useColors = TRUE;
779     first.useUsermove = second.useUsermove = FALSE;
780     first.sendICS = second.sendICS = FALSE;
781     first.sendName = second.sendName = appData.icsActive;
782     first.sdKludge = second.sdKludge = FALSE;
783     first.stKludge = second.stKludge = FALSE;
784     TidyProgramName(first.program, first.host, first.tidy);
785     TidyProgramName(second.program, second.host, second.tidy);
786     first.matchWins = second.matchWins = 0;
787     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
788     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
789     first.analysisSupport = second.analysisSupport = 2; /* detect */
790     first.analyzing = second.analyzing = FALSE;
791     first.initDone = second.initDone = FALSE;
792
793     /* New features added by Tord: */
794     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
795     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
796     /* End of new features added by Tord. */
797     first.fenOverride  = appData.fenOverride1;
798     second.fenOverride = appData.fenOverride2;
799
800     /* [HGM] time odds: set factor for each machine */
801     first.timeOdds  = appData.firstTimeOdds;
802     second.timeOdds = appData.secondTimeOdds;
803     { float norm = 1;
804         if(appData.timeOddsMode) {
805             norm = first.timeOdds;
806             if(norm > second.timeOdds) norm = second.timeOdds;
807         }
808         first.timeOdds /= norm;
809         second.timeOdds /= norm;
810     }
811
812     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
813     first.accumulateTC = appData.firstAccumulateTC;
814     second.accumulateTC = appData.secondAccumulateTC;
815     first.maxNrOfSessions = second.maxNrOfSessions = 1;
816
817     /* [HGM] debug */
818     first.debug = second.debug = FALSE;
819     first.supportsNPS = second.supportsNPS = UNKNOWN;
820
821     /* [HGM] options */
822     first.optionSettings  = appData.firstOptions;
823     second.optionSettings = appData.secondOptions;
824
825     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
826     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
827     first.isUCI = appData.firstIsUCI; /* [AS] */
828     second.isUCI = appData.secondIsUCI; /* [AS] */
829     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
830     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
831
832     if (appData.firstProtocolVersion > PROTOVER
833         || appData.firstProtocolVersion < 1)
834       {
835         char buf[MSG_SIZ];
836         int len;
837
838         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
839                        appData.firstProtocolVersion);
840         if( (len > MSG_SIZ) && appData.debugMode )
841           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842
843         DisplayFatalError(buf, 0, 2);
844       }
845     else
846       {
847         first.protocolVersion = appData.firstProtocolVersion;
848       }
849
850     if (appData.secondProtocolVersion > PROTOVER
851         || appData.secondProtocolVersion < 1)
852       {
853         char buf[MSG_SIZ];
854         int len;
855
856         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
857                        appData.secondProtocolVersion);
858         if( (len > MSG_SIZ) && appData.debugMode )
859           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
860
861         DisplayFatalError(buf, 0, 2);
862       }
863     else
864       {
865         second.protocolVersion = appData.secondProtocolVersion;
866       }
867
868     if (appData.icsActive) {
869         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
870 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
871     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
872         appData.clockMode = FALSE;
873         first.sendTime = second.sendTime = 0;
874     }
875
876 #if ZIPPY
877     /* Override some settings from environment variables, for backward
878        compatibility.  Unfortunately it's not feasible to have the env
879        vars just set defaults, at least in xboard.  Ugh.
880     */
881     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
882       ZippyInit();
883     }
884 #endif
885
886     if (appData.noChessProgram) {
887         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
888         sprintf(programVersion, "%s", PACKAGE_STRING);
889     } else {
890       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
891       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
892       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
893     }
894
895     if (!appData.icsActive) {
896       char buf[MSG_SIZ];
897       int len;
898
899       /* Check for variants that are supported only in ICS mode,
900          or not at all.  Some that are accepted here nevertheless
901          have bugs; see comments below.
902       */
903       VariantClass variant = StringToVariant(appData.variant);
904       switch (variant) {
905       case VariantBughouse:     /* need four players and two boards */
906       case VariantKriegspiel:   /* need to hide pieces and move details */
907         /* case VariantFischeRandom: (Fabien: moved below) */
908         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
909         if( (len > MSG_SIZ) && appData.debugMode )
910           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
911
912         DisplayFatalError(buf, 0, 2);
913         return;
914
915       case VariantUnknown:
916       case VariantLoadable:
917       case Variant29:
918       case Variant30:
919       case Variant31:
920       case Variant32:
921       case Variant33:
922       case Variant34:
923       case Variant35:
924       case Variant36:
925       default:
926         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
927         if( (len > MSG_SIZ) && appData.debugMode )
928           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
929
930         DisplayFatalError(buf, 0, 2);
931         return;
932
933       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
934       case VariantFairy:      /* [HGM] TestLegality definitely off! */
935       case VariantGothic:     /* [HGM] should work */
936       case VariantCapablanca: /* [HGM] should work */
937       case VariantCourier:    /* [HGM] initial forced moves not implemented */
938       case VariantShogi:      /* [HGM] could still mate with pawn drop */
939       case VariantKnightmate: /* [HGM] should work */
940       case VariantCylinder:   /* [HGM] untested */
941       case VariantFalcon:     /* [HGM] untested */
942       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
943                                  offboard interposition not understood */
944       case VariantNormal:     /* definitely works! */
945       case VariantWildCastle: /* pieces not automatically shuffled */
946       case VariantNoCastle:   /* pieces not automatically shuffled */
947       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
948       case VariantLosers:     /* should work except for win condition,
949                                  and doesn't know captures are mandatory */
950       case VariantSuicide:    /* should work except for win condition,
951                                  and doesn't know captures are mandatory */
952       case VariantGiveaway:   /* should work except for win condition,
953                                  and doesn't know captures are mandatory */
954       case VariantTwoKings:   /* should work */
955       case VariantAtomic:     /* should work except for win condition */
956       case Variant3Check:     /* should work except for win condition */
957       case VariantShatranj:   /* should work except for all win conditions */
958       case VariantMakruk:     /* should work except for daw countdown */
959       case VariantBerolina:   /* might work if TestLegality is off */
960       case VariantCapaRandom: /* should work */
961       case VariantJanus:      /* should work */
962       case VariantSuper:      /* experimental */
963       case VariantGreat:      /* experimental, requires legality testing to be off */
964         break;
965       }
966     }
967
968     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
969     InitEngineUCI( installDir, &second );
970 }
971
972 int NextIntegerFromString( char ** str, long * value )
973 {
974     int result = -1;
975     char * s = *str;
976
977     while( *s == ' ' || *s == '\t' ) {
978         s++;
979     }
980
981     *value = 0;
982
983     if( *s >= '0' && *s <= '9' ) {
984         while( *s >= '0' && *s <= '9' ) {
985             *value = *value * 10 + (*s - '0');
986             s++;
987         }
988
989         result = 0;
990     }
991
992     *str = s;
993
994     return result;
995 }
996
997 int NextTimeControlFromString( char ** str, long * value )
998 {
999     long temp;
1000     int result = NextIntegerFromString( str, &temp );
1001
1002     if( result == 0 ) {
1003         *value = temp * 60; /* Minutes */
1004         if( **str == ':' ) {
1005             (*str)++;
1006             result = NextIntegerFromString( str, &temp );
1007             *value += temp; /* Seconds */
1008         }
1009     }
1010
1011     return result;
1012 }
1013
1014 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1015 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1016     int result = -1, type = 0; long temp, temp2;
1017
1018     if(**str != ':') return -1; // old params remain in force!
1019     (*str)++;
1020     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1021     if( NextIntegerFromString( str, &temp ) ) return -1;
1022     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1023
1024     if(**str != '/') {
1025         /* time only: incremental or sudden-death time control */
1026         if(**str == '+') { /* increment follows; read it */
1027             (*str)++;
1028             if(**str == '!') type = *(*str)++; // Bronstein TC
1029             if(result = NextIntegerFromString( str, &temp2)) return -1;
1030             *inc = temp2 * 1000;
1031             if(**str == '.') { // read fraction of increment
1032                 char *start = ++(*str);
1033                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1034                 temp2 *= 1000;
1035                 while(start++ < *str) temp2 /= 10;
1036                 *inc += temp2;
1037             }
1038         } else *inc = 0;
1039         *moves = 0; *tc = temp * 1000; *incType = type;
1040         return 0;
1041     }
1042
1043     (*str)++; /* classical time control */
1044     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1045
1046     if(result == 0) {
1047         *moves = temp;
1048         *tc    = temp2 * 1000;
1049         *inc   = 0;
1050         *incType = type;
1051     }
1052     return result;
1053 }
1054
1055 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1056 {   /* [HGM] get time to add from the multi-session time-control string */
1057     int incType, moves=1; /* kludge to force reading of first session */
1058     long time, increment;
1059     char *s = tcString;
1060
1061     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1062     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1063     do {
1064         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1065         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1066         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1067         if(movenr == -1) return time;    /* last move before new session     */
1068         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1069         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1070         if(!moves) return increment;     /* current session is incremental   */
1071         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1072     } while(movenr >= -1);               /* try again for next session       */
1073
1074     return 0; // no new time quota on this move
1075 }
1076
1077 int
1078 ParseTimeControl(tc, ti, mps)
1079      char *tc;
1080      float ti;
1081      int mps;
1082 {
1083   long tc1;
1084   long tc2;
1085   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1086   int min, sec=0;
1087
1088   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1089   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1090       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1091   if(ti > 0) {
1092
1093     if(mps)
1094       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1095     else
1096       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1097   } else {
1098     if(mps)
1099       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1100     else
1101       snprintf(buf, MSG_SIZ, ":%s", mytc);
1102   }
1103   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1104
1105   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1106     return FALSE;
1107   }
1108
1109   if( *tc == '/' ) {
1110     /* Parse second time control */
1111     tc++;
1112
1113     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1114       return FALSE;
1115     }
1116
1117     if( tc2 == 0 ) {
1118       return FALSE;
1119     }
1120
1121     timeControl_2 = tc2 * 1000;
1122   }
1123   else {
1124     timeControl_2 = 0;
1125   }
1126
1127   if( tc1 == 0 ) {
1128     return FALSE;
1129   }
1130
1131   timeControl = tc1 * 1000;
1132
1133   if (ti >= 0) {
1134     timeIncrement = ti * 1000;  /* convert to ms */
1135     movesPerSession = 0;
1136   } else {
1137     timeIncrement = 0;
1138     movesPerSession = mps;
1139   }
1140   return TRUE;
1141 }
1142
1143 void
1144 InitBackEnd2()
1145 {
1146     if (appData.debugMode) {
1147         fprintf(debugFP, "%s\n", programVersion);
1148     }
1149
1150     set_cont_sequence(appData.wrapContSeq);
1151     if (appData.matchGames > 0) {
1152         appData.matchMode = TRUE;
1153     } else if (appData.matchMode) {
1154         appData.matchGames = 1;
1155     }
1156     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1157         appData.matchGames = appData.sameColorGames;
1158     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1159         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1160         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1161     }
1162     Reset(TRUE, FALSE);
1163     if (appData.noChessProgram || first.protocolVersion == 1) {
1164       InitBackEnd3();
1165     } else {
1166       /* kludge: allow timeout for initial "feature" commands */
1167       FreezeUI();
1168       DisplayMessage("", _("Starting chess program"));
1169       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1170     }
1171 }
1172
1173 void
1174 InitBackEnd3 P((void))
1175 {
1176     GameMode initialMode;
1177     char buf[MSG_SIZ];
1178     int err, len;
1179
1180     InitChessProgram(&first, startedFromSetupPosition);
1181
1182     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1183         free(programVersion);
1184         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1185         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1186     }
1187
1188     if (appData.icsActive) {
1189 #ifdef WIN32
1190         /* [DM] Make a console window if needed [HGM] merged ifs */
1191         ConsoleCreate();
1192 #endif
1193         err = establish();
1194         if (err != 0)
1195           {
1196             if (*appData.icsCommPort != NULLCHAR)
1197               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1198                              appData.icsCommPort);
1199             else
1200               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1201                         appData.icsHost, appData.icsPort);
1202
1203             if( (len > MSG_SIZ) && appData.debugMode )
1204               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1205
1206             DisplayFatalError(buf, err, 1);
1207             return;
1208         }
1209         SetICSMode();
1210         telnetISR =
1211           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1212         fromUserISR =
1213           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1214         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1215             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1216     } else if (appData.noChessProgram) {
1217         SetNCPMode();
1218     } else {
1219         SetGNUMode();
1220     }
1221
1222     if (*appData.cmailGameName != NULLCHAR) {
1223         SetCmailMode();
1224         OpenLoopback(&cmailPR);
1225         cmailISR =
1226           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1227     }
1228
1229     ThawUI();
1230     DisplayMessage("", "");
1231     if (StrCaseCmp(appData.initialMode, "") == 0) {
1232       initialMode = BeginningOfGame;
1233     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1234       initialMode = TwoMachinesPlay;
1235     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1236       initialMode = AnalyzeFile;
1237     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1238       initialMode = AnalyzeMode;
1239     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1240       initialMode = MachinePlaysWhite;
1241     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1242       initialMode = MachinePlaysBlack;
1243     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1244       initialMode = EditGame;
1245     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1246       initialMode = EditPosition;
1247     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1248       initialMode = Training;
1249     } else {
1250       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1251       if( (len > MSG_SIZ) && appData.debugMode )
1252         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1253
1254       DisplayFatalError(buf, 0, 2);
1255       return;
1256     }
1257
1258     if (appData.matchMode) {
1259         /* Set up machine vs. machine match */
1260         if (appData.noChessProgram) {
1261             DisplayFatalError(_("Can't have a match with no chess programs"),
1262                               0, 2);
1263             return;
1264         }
1265         matchMode = TRUE;
1266         matchGame = 1;
1267         if (*appData.loadGameFile != NULLCHAR) {
1268             int index = appData.loadGameIndex; // [HGM] autoinc
1269             if(index<0) lastIndex = index = 1;
1270             if (!LoadGameFromFile(appData.loadGameFile,
1271                                   index,
1272                                   appData.loadGameFile, FALSE)) {
1273                 DisplayFatalError(_("Bad game file"), 0, 1);
1274                 return;
1275             }
1276         } else if (*appData.loadPositionFile != NULLCHAR) {
1277             int index = appData.loadPositionIndex; // [HGM] autoinc
1278             if(index<0) lastIndex = index = 1;
1279             if (!LoadPositionFromFile(appData.loadPositionFile,
1280                                       index,
1281                                       appData.loadPositionFile)) {
1282                 DisplayFatalError(_("Bad position file"), 0, 1);
1283                 return;
1284             }
1285         }
1286         TwoMachinesEvent();
1287     } else if (*appData.cmailGameName != NULLCHAR) {
1288         /* Set up cmail mode */
1289         ReloadCmailMsgEvent(TRUE);
1290     } else {
1291         /* Set up other modes */
1292         if (initialMode == AnalyzeFile) {
1293           if (*appData.loadGameFile == NULLCHAR) {
1294             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1295             return;
1296           }
1297         }
1298         if (*appData.loadGameFile != NULLCHAR) {
1299             (void) LoadGameFromFile(appData.loadGameFile,
1300                                     appData.loadGameIndex,
1301                                     appData.loadGameFile, TRUE);
1302         } else if (*appData.loadPositionFile != NULLCHAR) {
1303             (void) LoadPositionFromFile(appData.loadPositionFile,
1304                                         appData.loadPositionIndex,
1305                                         appData.loadPositionFile);
1306             /* [HGM] try to make self-starting even after FEN load */
1307             /* to allow automatic setup of fairy variants with wtm */
1308             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1309                 gameMode = BeginningOfGame;
1310                 setboardSpoiledMachineBlack = 1;
1311             }
1312             /* [HGM] loadPos: make that every new game uses the setup */
1313             /* from file as long as we do not switch variant          */
1314             if(!blackPlaysFirst) {
1315                 startedFromPositionFile = TRUE;
1316                 CopyBoard(filePosition, boards[0]);
1317             }
1318         }
1319         if (initialMode == AnalyzeMode) {
1320           if (appData.noChessProgram) {
1321             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1322             return;
1323           }
1324           if (appData.icsActive) {
1325             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1326             return;
1327           }
1328           AnalyzeModeEvent();
1329         } else if (initialMode == AnalyzeFile) {
1330           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1331           ShowThinkingEvent();
1332           AnalyzeFileEvent();
1333           AnalysisPeriodicEvent(1);
1334         } else if (initialMode == MachinePlaysWhite) {
1335           if (appData.noChessProgram) {
1336             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1337                               0, 2);
1338             return;
1339           }
1340           if (appData.icsActive) {
1341             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1342                               0, 2);
1343             return;
1344           }
1345           MachineWhiteEvent();
1346         } else if (initialMode == MachinePlaysBlack) {
1347           if (appData.noChessProgram) {
1348             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1349                               0, 2);
1350             return;
1351           }
1352           if (appData.icsActive) {
1353             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1354                               0, 2);
1355             return;
1356           }
1357           MachineBlackEvent();
1358         } else if (initialMode == TwoMachinesPlay) {
1359           if (appData.noChessProgram) {
1360             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1361                               0, 2);
1362             return;
1363           }
1364           if (appData.icsActive) {
1365             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1366                               0, 2);
1367             return;
1368           }
1369           TwoMachinesEvent();
1370         } else if (initialMode == EditGame) {
1371           EditGameEvent();
1372         } else if (initialMode == EditPosition) {
1373           EditPositionEvent();
1374         } else if (initialMode == Training) {
1375           if (*appData.loadGameFile == NULLCHAR) {
1376             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1377             return;
1378           }
1379           TrainingEvent();
1380         }
1381     }
1382 }
1383
1384 /*
1385  * Establish will establish a contact to a remote host.port.
1386  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1387  *  used to talk to the host.
1388  * Returns 0 if okay, error code if not.
1389  */
1390 int
1391 establish()
1392 {
1393     char buf[MSG_SIZ];
1394
1395     if (*appData.icsCommPort != NULLCHAR) {
1396         /* Talk to the host through a serial comm port */
1397         return OpenCommPort(appData.icsCommPort, &icsPR);
1398
1399     } else if (*appData.gateway != NULLCHAR) {
1400         if (*appData.remoteShell == NULLCHAR) {
1401             /* Use the rcmd protocol to run telnet program on a gateway host */
1402             snprintf(buf, sizeof(buf), "%s %s %s",
1403                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1404             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1405
1406         } else {
1407             /* Use the rsh program to run telnet program on a gateway host */
1408             if (*appData.remoteUser == NULLCHAR) {
1409                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1410                         appData.gateway, appData.telnetProgram,
1411                         appData.icsHost, appData.icsPort);
1412             } else {
1413                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1414                         appData.remoteShell, appData.gateway,
1415                         appData.remoteUser, appData.telnetProgram,
1416                         appData.icsHost, appData.icsPort);
1417             }
1418             return StartChildProcess(buf, "", &icsPR);
1419
1420         }
1421     } else if (appData.useTelnet) {
1422         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1423
1424     } else {
1425         /* TCP socket interface differs somewhat between
1426            Unix and NT; handle details in the front end.
1427            */
1428         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1429     }
1430 }
1431
1432 void EscapeExpand(char *p, char *q)
1433 {       // [HGM] initstring: routine to shape up string arguments
1434         while(*p++ = *q++) if(p[-1] == '\\')
1435             switch(*q++) {
1436                 case 'n': p[-1] = '\n'; break;
1437                 case 'r': p[-1] = '\r'; break;
1438                 case 't': p[-1] = '\t'; break;
1439                 case '\\': p[-1] = '\\'; break;
1440                 case 0: *p = 0; return;
1441                 default: p[-1] = q[-1]; break;
1442             }
1443 }
1444
1445 void
1446 show_bytes(fp, buf, count)
1447      FILE *fp;
1448      char *buf;
1449      int count;
1450 {
1451     while (count--) {
1452         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1453             fprintf(fp, "\\%03o", *buf & 0xff);
1454         } else {
1455             putc(*buf, fp);
1456         }
1457         buf++;
1458     }
1459     fflush(fp);
1460 }
1461
1462 /* Returns an errno value */
1463 int
1464 OutputMaybeTelnet(pr, message, count, outError)
1465      ProcRef pr;
1466      char *message;
1467      int count;
1468      int *outError;
1469 {
1470     char buf[8192], *p, *q, *buflim;
1471     int left, newcount, outcount;
1472
1473     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1474         *appData.gateway != NULLCHAR) {
1475         if (appData.debugMode) {
1476             fprintf(debugFP, ">ICS: ");
1477             show_bytes(debugFP, message, count);
1478             fprintf(debugFP, "\n");
1479         }
1480         return OutputToProcess(pr, message, count, outError);
1481     }
1482
1483     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1484     p = message;
1485     q = buf;
1486     left = count;
1487     newcount = 0;
1488     while (left) {
1489         if (q >= buflim) {
1490             if (appData.debugMode) {
1491                 fprintf(debugFP, ">ICS: ");
1492                 show_bytes(debugFP, buf, newcount);
1493                 fprintf(debugFP, "\n");
1494             }
1495             outcount = OutputToProcess(pr, buf, newcount, outError);
1496             if (outcount < newcount) return -1; /* to be sure */
1497             q = buf;
1498             newcount = 0;
1499         }
1500         if (*p == '\n') {
1501             *q++ = '\r';
1502             newcount++;
1503         } else if (((unsigned char) *p) == TN_IAC) {
1504             *q++ = (char) TN_IAC;
1505             newcount ++;
1506         }
1507         *q++ = *p++;
1508         newcount++;
1509         left--;
1510     }
1511     if (appData.debugMode) {
1512         fprintf(debugFP, ">ICS: ");
1513         show_bytes(debugFP, buf, newcount);
1514         fprintf(debugFP, "\n");
1515     }
1516     outcount = OutputToProcess(pr, buf, newcount, outError);
1517     if (outcount < newcount) return -1; /* to be sure */
1518     return count;
1519 }
1520
1521 void
1522 read_from_player(isr, closure, message, count, error)
1523      InputSourceRef isr;
1524      VOIDSTAR closure;
1525      char *message;
1526      int count;
1527      int error;
1528 {
1529     int outError, outCount;
1530     static int gotEof = 0;
1531
1532     /* Pass data read from player on to ICS */
1533     if (count > 0) {
1534         gotEof = 0;
1535         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1536         if (outCount < count) {
1537             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1538         }
1539     } else if (count < 0) {
1540         RemoveInputSource(isr);
1541         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1542     } else if (gotEof++ > 0) {
1543         RemoveInputSource(isr);
1544         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1545     }
1546 }
1547
1548 void
1549 KeepAlive()
1550 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1551     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1552     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1553     SendToICS("date\n");
1554     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1555 }
1556
1557 /* added routine for printf style output to ics */
1558 void ics_printf(char *format, ...)
1559 {
1560     char buffer[MSG_SIZ];
1561     va_list args;
1562
1563     va_start(args, format);
1564     vsnprintf(buffer, sizeof(buffer), format, args);
1565     buffer[sizeof(buffer)-1] = '\0';
1566     SendToICS(buffer);
1567     va_end(args);
1568 }
1569
1570 void
1571 SendToICS(s)
1572      char *s;
1573 {
1574     int count, outCount, outError;
1575
1576     if (icsPR == NULL) return;
1577
1578     count = strlen(s);
1579     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1580     if (outCount < count) {
1581         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1582     }
1583 }
1584
1585 /* This is used for sending logon scripts to the ICS. Sending
1586    without a delay causes problems when using timestamp on ICC
1587    (at least on my machine). */
1588 void
1589 SendToICSDelayed(s,msdelay)
1590      char *s;
1591      long msdelay;
1592 {
1593     int count, outCount, outError;
1594
1595     if (icsPR == NULL) return;
1596
1597     count = strlen(s);
1598     if (appData.debugMode) {
1599         fprintf(debugFP, ">ICS: ");
1600         show_bytes(debugFP, s, count);
1601         fprintf(debugFP, "\n");
1602     }
1603     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1604                                       msdelay);
1605     if (outCount < count) {
1606         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1607     }
1608 }
1609
1610
1611 /* Remove all highlighting escape sequences in s
1612    Also deletes any suffix starting with '('
1613    */
1614 char *
1615 StripHighlightAndTitle(s)
1616      char *s;
1617 {
1618     static char retbuf[MSG_SIZ];
1619     char *p = retbuf;
1620
1621     while (*s != NULLCHAR) {
1622         while (*s == '\033') {
1623             while (*s != NULLCHAR && !isalpha(*s)) s++;
1624             if (*s != NULLCHAR) s++;
1625         }
1626         while (*s != NULLCHAR && *s != '\033') {
1627             if (*s == '(' || *s == '[') {
1628                 *p = NULLCHAR;
1629                 return retbuf;
1630             }
1631             *p++ = *s++;
1632         }
1633     }
1634     *p = NULLCHAR;
1635     return retbuf;
1636 }
1637
1638 /* Remove all highlighting escape sequences in s */
1639 char *
1640 StripHighlight(s)
1641      char *s;
1642 {
1643     static char retbuf[MSG_SIZ];
1644     char *p = retbuf;
1645
1646     while (*s != NULLCHAR) {
1647         while (*s == '\033') {
1648             while (*s != NULLCHAR && !isalpha(*s)) s++;
1649             if (*s != NULLCHAR) s++;
1650         }
1651         while (*s != NULLCHAR && *s != '\033') {
1652             *p++ = *s++;
1653         }
1654     }
1655     *p = NULLCHAR;
1656     return retbuf;
1657 }
1658
1659 char *variantNames[] = VARIANT_NAMES;
1660 char *
1661 VariantName(v)
1662      VariantClass v;
1663 {
1664     return variantNames[v];
1665 }
1666
1667
1668 /* Identify a variant from the strings the chess servers use or the
1669    PGN Variant tag names we use. */
1670 VariantClass
1671 StringToVariant(e)
1672      char *e;
1673 {
1674     char *p;
1675     int wnum = -1;
1676     VariantClass v = VariantNormal;
1677     int i, found = FALSE;
1678     char buf[MSG_SIZ];
1679     int len;
1680
1681     if (!e) return v;
1682
1683     /* [HGM] skip over optional board-size prefixes */
1684     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1685         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1686         while( *e++ != '_');
1687     }
1688
1689     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1690         v = VariantNormal;
1691         found = TRUE;
1692     } else
1693     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1694       if (StrCaseStr(e, variantNames[i])) {
1695         v = (VariantClass) i;
1696         found = TRUE;
1697         break;
1698       }
1699     }
1700
1701     if (!found) {
1702       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1703           || StrCaseStr(e, "wild/fr")
1704           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1705         v = VariantFischeRandom;
1706       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1707                  (i = 1, p = StrCaseStr(e, "w"))) {
1708         p += i;
1709         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1710         if (isdigit(*p)) {
1711           wnum = atoi(p);
1712         } else {
1713           wnum = -1;
1714         }
1715         switch (wnum) {
1716         case 0: /* FICS only, actually */
1717         case 1:
1718           /* Castling legal even if K starts on d-file */
1719           v = VariantWildCastle;
1720           break;
1721         case 2:
1722         case 3:
1723         case 4:
1724           /* Castling illegal even if K & R happen to start in
1725              normal positions. */
1726           v = VariantNoCastle;
1727           break;
1728         case 5:
1729         case 7:
1730         case 8:
1731         case 10:
1732         case 11:
1733         case 12:
1734         case 13:
1735         case 14:
1736         case 15:
1737         case 18:
1738         case 19:
1739           /* Castling legal iff K & R start in normal positions */
1740           v = VariantNormal;
1741           break;
1742         case 6:
1743         case 20:
1744         case 21:
1745           /* Special wilds for position setup; unclear what to do here */
1746           v = VariantLoadable;
1747           break;
1748         case 9:
1749           /* Bizarre ICC game */
1750           v = VariantTwoKings;
1751           break;
1752         case 16:
1753           v = VariantKriegspiel;
1754           break;
1755         case 17:
1756           v = VariantLosers;
1757           break;
1758         case 22:
1759           v = VariantFischeRandom;
1760           break;
1761         case 23:
1762           v = VariantCrazyhouse;
1763           break;
1764         case 24:
1765           v = VariantBughouse;
1766           break;
1767         case 25:
1768           v = Variant3Check;
1769           break;
1770         case 26:
1771           /* Not quite the same as FICS suicide! */
1772           v = VariantGiveaway;
1773           break;
1774         case 27:
1775           v = VariantAtomic;
1776           break;
1777         case 28:
1778           v = VariantShatranj;
1779           break;
1780
1781         /* Temporary names for future ICC types.  The name *will* change in
1782            the next xboard/WinBoard release after ICC defines it. */
1783         case 29:
1784           v = Variant29;
1785           break;
1786         case 30:
1787           v = Variant30;
1788           break;
1789         case 31:
1790           v = Variant31;
1791           break;
1792         case 32:
1793           v = Variant32;
1794           break;
1795         case 33:
1796           v = Variant33;
1797           break;
1798         case 34:
1799           v = Variant34;
1800           break;
1801         case 35:
1802           v = Variant35;
1803           break;
1804         case 36:
1805           v = Variant36;
1806           break;
1807         case 37:
1808           v = VariantShogi;
1809           break;
1810         case 38:
1811           v = VariantXiangqi;
1812           break;
1813         case 39:
1814           v = VariantCourier;
1815           break;
1816         case 40:
1817           v = VariantGothic;
1818           break;
1819         case 41:
1820           v = VariantCapablanca;
1821           break;
1822         case 42:
1823           v = VariantKnightmate;
1824           break;
1825         case 43:
1826           v = VariantFairy;
1827           break;
1828         case 44:
1829           v = VariantCylinder;
1830           break;
1831         case 45:
1832           v = VariantFalcon;
1833           break;
1834         case 46:
1835           v = VariantCapaRandom;
1836           break;
1837         case 47:
1838           v = VariantBerolina;
1839           break;
1840         case 48:
1841           v = VariantJanus;
1842           break;
1843         case 49:
1844           v = VariantSuper;
1845           break;
1846         case 50:
1847           v = VariantGreat;
1848           break;
1849         case -1:
1850           /* Found "wild" or "w" in the string but no number;
1851              must assume it's normal chess. */
1852           v = VariantNormal;
1853           break;
1854         default:
1855           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1856           if( (len > MSG_SIZ) && appData.debugMode )
1857             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1858
1859           DisplayError(buf, 0);
1860           v = VariantUnknown;
1861           break;
1862         }
1863       }
1864     }
1865     if (appData.debugMode) {
1866       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1867               e, wnum, VariantName(v));
1868     }
1869     return v;
1870 }
1871
1872 static int leftover_start = 0, leftover_len = 0;
1873 char star_match[STAR_MATCH_N][MSG_SIZ];
1874
1875 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1876    advance *index beyond it, and set leftover_start to the new value of
1877    *index; else return FALSE.  If pattern contains the character '*', it
1878    matches any sequence of characters not containing '\r', '\n', or the
1879    character following the '*' (if any), and the matched sequence(s) are
1880    copied into star_match.
1881    */
1882 int
1883 looking_at(buf, index, pattern)
1884      char *buf;
1885      int *index;
1886      char *pattern;
1887 {
1888     char *bufp = &buf[*index], *patternp = pattern;
1889     int star_count = 0;
1890     char *matchp = star_match[0];
1891
1892     for (;;) {
1893         if (*patternp == NULLCHAR) {
1894             *index = leftover_start = bufp - buf;
1895             *matchp = NULLCHAR;
1896             return TRUE;
1897         }
1898         if (*bufp == NULLCHAR) return FALSE;
1899         if (*patternp == '*') {
1900             if (*bufp == *(patternp + 1)) {
1901                 *matchp = NULLCHAR;
1902                 matchp = star_match[++star_count];
1903                 patternp += 2;
1904                 bufp++;
1905                 continue;
1906             } else if (*bufp == '\n' || *bufp == '\r') {
1907                 patternp++;
1908                 if (*patternp == NULLCHAR)
1909                   continue;
1910                 else
1911                   return FALSE;
1912             } else {
1913                 *matchp++ = *bufp++;
1914                 continue;
1915             }
1916         }
1917         if (*patternp != *bufp) return FALSE;
1918         patternp++;
1919         bufp++;
1920     }
1921 }
1922
1923 void
1924 SendToPlayer(data, length)
1925      char *data;
1926      int length;
1927 {
1928     int error, outCount;
1929     outCount = OutputToProcess(NoProc, data, length, &error);
1930     if (outCount < length) {
1931         DisplayFatalError(_("Error writing to display"), error, 1);
1932     }
1933 }
1934
1935 void
1936 PackHolding(packed, holding)
1937      char packed[];
1938      char *holding;
1939 {
1940     char *p = holding;
1941     char *q = packed;
1942     int runlength = 0;
1943     int curr = 9999;
1944     do {
1945         if (*p == curr) {
1946             runlength++;
1947         } else {
1948             switch (runlength) {
1949               case 0:
1950                 break;
1951               case 1:
1952                 *q++ = curr;
1953                 break;
1954               case 2:
1955                 *q++ = curr;
1956                 *q++ = curr;
1957                 break;
1958               default:
1959                 sprintf(q, "%d", runlength);
1960                 while (*q) q++;
1961                 *q++ = curr;
1962                 break;
1963             }
1964             runlength = 1;
1965             curr = *p;
1966         }
1967     } while (*p++);
1968     *q = NULLCHAR;
1969 }
1970
1971 /* Telnet protocol requests from the front end */
1972 void
1973 TelnetRequest(ddww, option)
1974      unsigned char ddww, option;
1975 {
1976     unsigned char msg[3];
1977     int outCount, outError;
1978
1979     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1980
1981     if (appData.debugMode) {
1982         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1983         switch (ddww) {
1984           case TN_DO:
1985             ddwwStr = "DO";
1986             break;
1987           case TN_DONT:
1988             ddwwStr = "DONT";
1989             break;
1990           case TN_WILL:
1991             ddwwStr = "WILL";
1992             break;
1993           case TN_WONT:
1994             ddwwStr = "WONT";
1995             break;
1996           default:
1997             ddwwStr = buf1;
1998             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
1999             break;
2000         }
2001         switch (option) {
2002           case TN_ECHO:
2003             optionStr = "ECHO";
2004             break;
2005           default:
2006             optionStr = buf2;
2007             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2008             break;
2009         }
2010         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2011     }
2012     msg[0] = TN_IAC;
2013     msg[1] = ddww;
2014     msg[2] = option;
2015     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2016     if (outCount < 3) {
2017         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2018     }
2019 }
2020
2021 void
2022 DoEcho()
2023 {
2024     if (!appData.icsActive) return;
2025     TelnetRequest(TN_DO, TN_ECHO);
2026 }
2027
2028 void
2029 DontEcho()
2030 {
2031     if (!appData.icsActive) return;
2032     TelnetRequest(TN_DONT, TN_ECHO);
2033 }
2034
2035 void
2036 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2037 {
2038     /* put the holdings sent to us by the server on the board holdings area */
2039     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2040     char p;
2041     ChessSquare piece;
2042
2043     if(gameInfo.holdingsWidth < 2)  return;
2044     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2045         return; // prevent overwriting by pre-board holdings
2046
2047     if( (int)lowestPiece >= BlackPawn ) {
2048         holdingsColumn = 0;
2049         countsColumn = 1;
2050         holdingsStartRow = BOARD_HEIGHT-1;
2051         direction = -1;
2052     } else {
2053         holdingsColumn = BOARD_WIDTH-1;
2054         countsColumn = BOARD_WIDTH-2;
2055         holdingsStartRow = 0;
2056         direction = 1;
2057     }
2058
2059     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2060         board[i][holdingsColumn] = EmptySquare;
2061         board[i][countsColumn]   = (ChessSquare) 0;
2062     }
2063     while( (p=*holdings++) != NULLCHAR ) {
2064         piece = CharToPiece( ToUpper(p) );
2065         if(piece == EmptySquare) continue;
2066         /*j = (int) piece - (int) WhitePawn;*/
2067         j = PieceToNumber(piece);
2068         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2069         if(j < 0) continue;               /* should not happen */
2070         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2071         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2072         board[holdingsStartRow+j*direction][countsColumn]++;
2073     }
2074 }
2075
2076
2077 void
2078 VariantSwitch(Board board, VariantClass newVariant)
2079 {
2080    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2081    static Board oldBoard;
2082
2083    startedFromPositionFile = FALSE;
2084    if(gameInfo.variant == newVariant) return;
2085
2086    /* [HGM] This routine is called each time an assignment is made to
2087     * gameInfo.variant during a game, to make sure the board sizes
2088     * are set to match the new variant. If that means adding or deleting
2089     * holdings, we shift the playing board accordingly
2090     * This kludge is needed because in ICS observe mode, we get boards
2091     * of an ongoing game without knowing the variant, and learn about the
2092     * latter only later. This can be because of the move list we requested,
2093     * in which case the game history is refilled from the beginning anyway,
2094     * but also when receiving holdings of a crazyhouse game. In the latter
2095     * case we want to add those holdings to the already received position.
2096     */
2097
2098
2099    if (appData.debugMode) {
2100      fprintf(debugFP, "Switch board from %s to %s\n",
2101              VariantName(gameInfo.variant), VariantName(newVariant));
2102      setbuf(debugFP, NULL);
2103    }
2104    shuffleOpenings = 0;       /* [HGM] shuffle */
2105    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2106    switch(newVariant)
2107      {
2108      case VariantShogi:
2109        newWidth = 9;  newHeight = 9;
2110        gameInfo.holdingsSize = 7;
2111      case VariantBughouse:
2112      case VariantCrazyhouse:
2113        newHoldingsWidth = 2; break;
2114      case VariantGreat:
2115        newWidth = 10;
2116      case VariantSuper:
2117        newHoldingsWidth = 2;
2118        gameInfo.holdingsSize = 8;
2119        break;
2120      case VariantGothic:
2121      case VariantCapablanca:
2122      case VariantCapaRandom:
2123        newWidth = 10;
2124      default:
2125        newHoldingsWidth = gameInfo.holdingsSize = 0;
2126      };
2127
2128    if(newWidth  != gameInfo.boardWidth  ||
2129       newHeight != gameInfo.boardHeight ||
2130       newHoldingsWidth != gameInfo.holdingsWidth ) {
2131
2132      /* shift position to new playing area, if needed */
2133      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2134        for(i=0; i<BOARD_HEIGHT; i++)
2135          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2136            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2137              board[i][j];
2138        for(i=0; i<newHeight; i++) {
2139          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2140          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2141        }
2142      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2143        for(i=0; i<BOARD_HEIGHT; i++)
2144          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2145            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2146              board[i][j];
2147      }
2148      gameInfo.boardWidth  = newWidth;
2149      gameInfo.boardHeight = newHeight;
2150      gameInfo.holdingsWidth = newHoldingsWidth;
2151      gameInfo.variant = newVariant;
2152      InitDrawingSizes(-2, 0);
2153    } else gameInfo.variant = newVariant;
2154    CopyBoard(oldBoard, board);   // remember correctly formatted board
2155      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2156    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2157 }
2158
2159 static int loggedOn = FALSE;
2160
2161 /*-- Game start info cache: --*/
2162 int gs_gamenum;
2163 char gs_kind[MSG_SIZ];
2164 static char player1Name[128] = "";
2165 static char player2Name[128] = "";
2166 static char cont_seq[] = "\n\\   ";
2167 static int player1Rating = -1;
2168 static int player2Rating = -1;
2169 /*----------------------------*/
2170
2171 ColorClass curColor = ColorNormal;
2172 int suppressKibitz = 0;
2173
2174 // [HGM] seekgraph
2175 Boolean soughtPending = FALSE;
2176 Boolean seekGraphUp;
2177 #define MAX_SEEK_ADS 200
2178 #define SQUARE 0x80
2179 char *seekAdList[MAX_SEEK_ADS];
2180 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2181 float tcList[MAX_SEEK_ADS];
2182 char colorList[MAX_SEEK_ADS];
2183 int nrOfSeekAds = 0;
2184 int minRating = 1010, maxRating = 2800;
2185 int hMargin = 10, vMargin = 20, h, w;
2186 extern int squareSize, lineGap;
2187
2188 void
2189 PlotSeekAd(int i)
2190 {
2191         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2192         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2193         if(r < minRating+100 && r >=0 ) r = minRating+100;
2194         if(r > maxRating) r = maxRating;
2195         if(tc < 1.) tc = 1.;
2196         if(tc > 95.) tc = 95.;
2197         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2198         y = ((double)r - minRating)/(maxRating - minRating)
2199             * (h-vMargin-squareSize/8-1) + vMargin;
2200         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2201         if(strstr(seekAdList[i], " u ")) color = 1;
2202         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2203            !strstr(seekAdList[i], "bullet") &&
2204            !strstr(seekAdList[i], "blitz") &&
2205            !strstr(seekAdList[i], "standard") ) color = 2;
2206         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2207         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2208 }
2209
2210 void
2211 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2212 {
2213         char buf[MSG_SIZ], *ext = "";
2214         VariantClass v = StringToVariant(type);
2215         if(strstr(type, "wild")) {
2216             ext = type + 4; // append wild number
2217             if(v == VariantFischeRandom) type = "chess960"; else
2218             if(v == VariantLoadable) type = "setup"; else
2219             type = VariantName(v);
2220         }
2221         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2222         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2223             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2224             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2225             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2226             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2227             seekNrList[nrOfSeekAds] = nr;
2228             zList[nrOfSeekAds] = 0;
2229             seekAdList[nrOfSeekAds++] = StrSave(buf);
2230             if(plot) PlotSeekAd(nrOfSeekAds-1);
2231         }
2232 }
2233
2234 void
2235 EraseSeekDot(int i)
2236 {
2237     int x = xList[i], y = yList[i], d=squareSize/4, k;
2238     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2239     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2240     // now replot every dot that overlapped
2241     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2242         int xx = xList[k], yy = yList[k];
2243         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2244             DrawSeekDot(xx, yy, colorList[k]);
2245     }
2246 }
2247
2248 void
2249 RemoveSeekAd(int nr)
2250 {
2251         int i;
2252         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2253             EraseSeekDot(i);
2254             if(seekAdList[i]) free(seekAdList[i]);
2255             seekAdList[i] = seekAdList[--nrOfSeekAds];
2256             seekNrList[i] = seekNrList[nrOfSeekAds];
2257             ratingList[i] = ratingList[nrOfSeekAds];
2258             colorList[i]  = colorList[nrOfSeekAds];
2259             tcList[i] = tcList[nrOfSeekAds];
2260             xList[i]  = xList[nrOfSeekAds];
2261             yList[i]  = yList[nrOfSeekAds];
2262             zList[i]  = zList[nrOfSeekAds];
2263             seekAdList[nrOfSeekAds] = NULL;
2264             break;
2265         }
2266 }
2267
2268 Boolean
2269 MatchSoughtLine(char *line)
2270 {
2271     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2272     int nr, base, inc, u=0; char dummy;
2273
2274     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2275        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2276        (u=1) &&
2277        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2278         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2279         // match: compact and save the line
2280         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2281         return TRUE;
2282     }
2283     return FALSE;
2284 }
2285
2286 int
2287 DrawSeekGraph()
2288 {
2289     int i;
2290     if(!seekGraphUp) return FALSE;
2291     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2292     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2293
2294     DrawSeekBackground(0, 0, w, h);
2295     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2296     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2297     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2298         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2299         yy = h-1-yy;
2300         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2301         if(i%500 == 0) {
2302             char buf[MSG_SIZ];
2303             snprintf(buf, MSG_SIZ, "%d", i);
2304             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2305         }
2306     }
2307     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2308     for(i=1; i<100; i+=(i<10?1:5)) {
2309         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2310         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2311         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2312             char buf[MSG_SIZ];
2313             snprintf(buf, MSG_SIZ, "%d", i);
2314             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2315         }
2316     }
2317     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2318     return TRUE;
2319 }
2320
2321 int SeekGraphClick(ClickType click, int x, int y, int moving)
2322 {
2323     static int lastDown = 0, displayed = 0, lastSecond;
2324     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2325         if(click == Release || moving) return FALSE;
2326         nrOfSeekAds = 0;
2327         soughtPending = TRUE;
2328         SendToICS(ics_prefix);
2329         SendToICS("sought\n"); // should this be "sought all"?
2330     } else { // issue challenge based on clicked ad
2331         int dist = 10000; int i, closest = 0, second = 0;
2332         for(i=0; i<nrOfSeekAds; i++) {
2333             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2334             if(d < dist) { dist = d; closest = i; }
2335             second += (d - zList[i] < 120); // count in-range ads
2336             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2337         }
2338         if(dist < 120) {
2339             char buf[MSG_SIZ];
2340             second = (second > 1);
2341             if(displayed != closest || second != lastSecond) {
2342                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2343                 lastSecond = second; displayed = closest;
2344             }
2345             if(click == Press) {
2346                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2347                 lastDown = closest;
2348                 return TRUE;
2349             } // on press 'hit', only show info
2350             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2351             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2352             SendToICS(ics_prefix);
2353             SendToICS(buf);
2354             return TRUE; // let incoming board of started game pop down the graph
2355         } else if(click == Release) { // release 'miss' is ignored
2356             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2357             if(moving == 2) { // right up-click
2358                 nrOfSeekAds = 0; // refresh graph
2359                 soughtPending = TRUE;
2360                 SendToICS(ics_prefix);
2361                 SendToICS("sought\n"); // should this be "sought all"?
2362             }
2363             return TRUE;
2364         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2365         // press miss or release hit 'pop down' seek graph
2366         seekGraphUp = FALSE;
2367         DrawPosition(TRUE, NULL);
2368     }
2369     return TRUE;
2370 }
2371
2372 void
2373 read_from_ics(isr, closure, data, count, error)
2374      InputSourceRef isr;
2375      VOIDSTAR closure;
2376      char *data;
2377      int count;
2378      int error;
2379 {
2380 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2381 #define STARTED_NONE 0
2382 #define STARTED_MOVES 1
2383 #define STARTED_BOARD 2
2384 #define STARTED_OBSERVE 3
2385 #define STARTED_HOLDINGS 4
2386 #define STARTED_CHATTER 5
2387 #define STARTED_COMMENT 6
2388 #define STARTED_MOVES_NOHIDE 7
2389
2390     static int started = STARTED_NONE;
2391     static char parse[20000];
2392     static int parse_pos = 0;
2393     static char buf[BUF_SIZE + 1];
2394     static int firstTime = TRUE, intfSet = FALSE;
2395     static ColorClass prevColor = ColorNormal;
2396     static int savingComment = FALSE;
2397     static int cmatch = 0; // continuation sequence match
2398     char *bp;
2399     char str[MSG_SIZ];
2400     int i, oldi;
2401     int buf_len;
2402     int next_out;
2403     int tkind;
2404     int backup;    /* [DM] For zippy color lines */
2405     char *p;
2406     char talker[MSG_SIZ]; // [HGM] chat
2407     int channel;
2408
2409     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2410
2411     if (appData.debugMode) {
2412       if (!error) {
2413         fprintf(debugFP, "<ICS: ");
2414         show_bytes(debugFP, data, count);
2415         fprintf(debugFP, "\n");
2416       }
2417     }
2418
2419     if (appData.debugMode) { int f = forwardMostMove;
2420         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2421                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2422                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2423     }
2424     if (count > 0) {
2425         /* If last read ended with a partial line that we couldn't parse,
2426            prepend it to the new read and try again. */
2427         if (leftover_len > 0) {
2428             for (i=0; i<leftover_len; i++)
2429               buf[i] = buf[leftover_start + i];
2430         }
2431
2432     /* copy new characters into the buffer */
2433     bp = buf + leftover_len;
2434     buf_len=leftover_len;
2435     for (i=0; i<count; i++)
2436     {
2437         // ignore these
2438         if (data[i] == '\r')
2439             continue;
2440
2441         // join lines split by ICS?
2442         if (!appData.noJoin)
2443         {
2444             /*
2445                 Joining just consists of finding matches against the
2446                 continuation sequence, and discarding that sequence
2447                 if found instead of copying it.  So, until a match
2448                 fails, there's nothing to do since it might be the
2449                 complete sequence, and thus, something we don't want
2450                 copied.
2451             */
2452             if (data[i] == cont_seq[cmatch])
2453             {
2454                 cmatch++;
2455                 if (cmatch == strlen(cont_seq))
2456                 {
2457                     cmatch = 0; // complete match.  just reset the counter
2458
2459                     /*
2460                         it's possible for the ICS to not include the space
2461                         at the end of the last word, making our [correct]
2462                         join operation fuse two separate words.  the server
2463                         does this when the space occurs at the width setting.
2464                     */
2465                     if (!buf_len || buf[buf_len-1] != ' ')
2466                     {
2467                         *bp++ = ' ';
2468                         buf_len++;
2469                     }
2470                 }
2471                 continue;
2472             }
2473             else if (cmatch)
2474             {
2475                 /*
2476                     match failed, so we have to copy what matched before
2477                     falling through and copying this character.  In reality,
2478                     this will only ever be just the newline character, but
2479                     it doesn't hurt to be precise.
2480                 */
2481                 strncpy(bp, cont_seq, cmatch);
2482                 bp += cmatch;
2483                 buf_len += cmatch;
2484                 cmatch = 0;
2485             }
2486         }
2487
2488         // copy this char
2489         *bp++ = data[i];
2490         buf_len++;
2491     }
2492
2493         buf[buf_len] = NULLCHAR;
2494 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2495         next_out = 0;
2496         leftover_start = 0;
2497
2498         i = 0;
2499         while (i < buf_len) {
2500             /* Deal with part of the TELNET option negotiation
2501                protocol.  We refuse to do anything beyond the
2502                defaults, except that we allow the WILL ECHO option,
2503                which ICS uses to turn off password echoing when we are
2504                directly connected to it.  We reject this option
2505                if localLineEditing mode is on (always on in xboard)
2506                and we are talking to port 23, which might be a real
2507                telnet server that will try to keep WILL ECHO on permanently.
2508              */
2509             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2510                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2511                 unsigned char option;
2512                 oldi = i;
2513                 switch ((unsigned char) buf[++i]) {
2514                   case TN_WILL:
2515                     if (appData.debugMode)
2516                       fprintf(debugFP, "\n<WILL ");
2517                     switch (option = (unsigned char) buf[++i]) {
2518                       case TN_ECHO:
2519                         if (appData.debugMode)
2520                           fprintf(debugFP, "ECHO ");
2521                         /* Reply only if this is a change, according
2522                            to the protocol rules. */
2523                         if (remoteEchoOption) break;
2524                         if (appData.localLineEditing &&
2525                             atoi(appData.icsPort) == TN_PORT) {
2526                             TelnetRequest(TN_DONT, TN_ECHO);
2527                         } else {
2528                             EchoOff();
2529                             TelnetRequest(TN_DO, TN_ECHO);
2530                             remoteEchoOption = TRUE;
2531                         }
2532                         break;
2533                       default:
2534                         if (appData.debugMode)
2535                           fprintf(debugFP, "%d ", option);
2536                         /* Whatever this is, we don't want it. */
2537                         TelnetRequest(TN_DONT, option);
2538                         break;
2539                     }
2540                     break;
2541                   case TN_WONT:
2542                     if (appData.debugMode)
2543                       fprintf(debugFP, "\n<WONT ");
2544                     switch (option = (unsigned char) buf[++i]) {
2545                       case TN_ECHO:
2546                         if (appData.debugMode)
2547                           fprintf(debugFP, "ECHO ");
2548                         /* Reply only if this is a change, according
2549                            to the protocol rules. */
2550                         if (!remoteEchoOption) break;
2551                         EchoOn();
2552                         TelnetRequest(TN_DONT, TN_ECHO);
2553                         remoteEchoOption = FALSE;
2554                         break;
2555                       default:
2556                         if (appData.debugMode)
2557                           fprintf(debugFP, "%d ", (unsigned char) option);
2558                         /* Whatever this is, it must already be turned
2559                            off, because we never agree to turn on
2560                            anything non-default, so according to the
2561                            protocol rules, we don't reply. */
2562                         break;
2563                     }
2564                     break;
2565                   case TN_DO:
2566                     if (appData.debugMode)
2567                       fprintf(debugFP, "\n<DO ");
2568                     switch (option = (unsigned char) buf[++i]) {
2569                       default:
2570                         /* Whatever this is, we refuse to do it. */
2571                         if (appData.debugMode)
2572                           fprintf(debugFP, "%d ", option);
2573                         TelnetRequest(TN_WONT, option);
2574                         break;
2575                     }
2576                     break;
2577                   case TN_DONT:
2578                     if (appData.debugMode)
2579                       fprintf(debugFP, "\n<DONT ");
2580                     switch (option = (unsigned char) buf[++i]) {
2581                       default:
2582                         if (appData.debugMode)
2583                           fprintf(debugFP, "%d ", option);
2584                         /* Whatever this is, we are already not doing
2585                            it, because we never agree to do anything
2586                            non-default, so according to the protocol
2587                            rules, we don't reply. */
2588                         break;
2589                     }
2590                     break;
2591                   case TN_IAC:
2592                     if (appData.debugMode)
2593                       fprintf(debugFP, "\n<IAC ");
2594                     /* Doubled IAC; pass it through */
2595                     i--;
2596                     break;
2597                   default:
2598                     if (appData.debugMode)
2599                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2600                     /* Drop all other telnet commands on the floor */
2601                     break;
2602                 }
2603                 if (oldi > next_out)
2604                   SendToPlayer(&buf[next_out], oldi - next_out);
2605                 if (++i > next_out)
2606                   next_out = i;
2607                 continue;
2608             }
2609
2610             /* OK, this at least will *usually* work */
2611             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2612                 loggedOn = TRUE;
2613             }
2614
2615             if (loggedOn && !intfSet) {
2616                 if (ics_type == ICS_ICC) {
2617                   snprintf(str, MSG_SIZ,
2618                           "/set-quietly interface %s\n/set-quietly style 12\n",
2619                           programVersion);
2620                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2621                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2622                 } else if (ics_type == ICS_CHESSNET) {
2623                   snprintf(str, MSG_SIZ, "/style 12\n");
2624                 } else {
2625                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2626                   strcat(str, programVersion);
2627                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2628                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2629                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2630 #ifdef WIN32
2631                   strcat(str, "$iset nohighlight 1\n");
2632 #endif
2633                   strcat(str, "$iset lock 1\n$style 12\n");
2634                 }
2635                 SendToICS(str);
2636                 NotifyFrontendLogin();
2637                 intfSet = TRUE;
2638             }
2639
2640             if (started == STARTED_COMMENT) {
2641                 /* Accumulate characters in comment */
2642                 parse[parse_pos++] = buf[i];
2643                 if (buf[i] == '\n') {
2644                     parse[parse_pos] = NULLCHAR;
2645                     if(chattingPartner>=0) {
2646                         char mess[MSG_SIZ];
2647                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2648                         OutputChatMessage(chattingPartner, mess);
2649                         chattingPartner = -1;
2650                         next_out = i+1; // [HGM] suppress printing in ICS window
2651                     } else
2652                     if(!suppressKibitz) // [HGM] kibitz
2653                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2654                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2655                         int nrDigit = 0, nrAlph = 0, j;
2656                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2657                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2658                         parse[parse_pos] = NULLCHAR;
2659                         // try to be smart: if it does not look like search info, it should go to
2660                         // ICS interaction window after all, not to engine-output window.
2661                         for(j=0; j<parse_pos; j++) { // count letters and digits
2662                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2663                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2664                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2665                         }
2666                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2667                             int depth=0; float score;
2668                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2669                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2670                                 pvInfoList[forwardMostMove-1].depth = depth;
2671                                 pvInfoList[forwardMostMove-1].score = 100*score;
2672                             }
2673                             OutputKibitz(suppressKibitz, parse);
2674                         } else {
2675                             char tmp[MSG_SIZ];
2676                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2677                             SendToPlayer(tmp, strlen(tmp));
2678                         }
2679                         next_out = i+1; // [HGM] suppress printing in ICS window
2680                     }
2681                     started = STARTED_NONE;
2682                 } else {
2683                     /* Don't match patterns against characters in comment */
2684                     i++;
2685                     continue;
2686                 }
2687             }
2688             if (started == STARTED_CHATTER) {
2689                 if (buf[i] != '\n') {
2690                     /* Don't match patterns against characters in chatter */
2691                     i++;
2692                     continue;
2693                 }
2694                 started = STARTED_NONE;
2695                 if(suppressKibitz) next_out = i+1;
2696             }
2697
2698             /* Kludge to deal with rcmd protocol */
2699             if (firstTime && looking_at(buf, &i, "\001*")) {
2700                 DisplayFatalError(&buf[1], 0, 1);
2701                 continue;
2702             } else {
2703                 firstTime = FALSE;
2704             }
2705
2706             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2707                 ics_type = ICS_ICC;
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, "freechess.org")) {
2714                 ics_type = ICS_FICS;
2715                 ics_prefix = "$";
2716                 if (appData.debugMode)
2717                   fprintf(debugFP, "ics_type %d\n", ics_type);
2718                 continue;
2719             }
2720             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2721                 ics_type = ICS_CHESSNET;
2722                 ics_prefix = "/";
2723                 if (appData.debugMode)
2724                   fprintf(debugFP, "ics_type %d\n", ics_type);
2725                 continue;
2726             }
2727
2728             if (!loggedOn &&
2729                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2730                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2731                  looking_at(buf, &i, "will be \"*\""))) {
2732               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2733               continue;
2734             }
2735
2736             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2737               char buf[MSG_SIZ];
2738               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2739               DisplayIcsInteractionTitle(buf);
2740               have_set_title = TRUE;
2741             }
2742
2743             /* skip finger notes */
2744             if (started == STARTED_NONE &&
2745                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2746                  (buf[i] == '1' && buf[i+1] == '0')) &&
2747                 buf[i+2] == ':' && buf[i+3] == ' ') {
2748               started = STARTED_CHATTER;
2749               i += 3;
2750               continue;
2751             }
2752
2753             oldi = i;
2754             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2755             if(appData.seekGraph) {
2756                 if(soughtPending && MatchSoughtLine(buf+i)) {
2757                     i = strstr(buf+i, "rated") - buf;
2758                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2759                     next_out = leftover_start = i;
2760                     started = STARTED_CHATTER;
2761                     suppressKibitz = TRUE;
2762                     continue;
2763                 }
2764                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2765                         && looking_at(buf, &i, "* ads displayed")) {
2766                     soughtPending = FALSE;
2767                     seekGraphUp = TRUE;
2768                     DrawSeekGraph();
2769                     continue;
2770                 }
2771                 if(appData.autoRefresh) {
2772                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2773                         int s = (ics_type == ICS_ICC); // ICC format differs
2774                         if(seekGraphUp)
2775                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2776                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2777                         looking_at(buf, &i, "*% "); // eat prompt
2778                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2779                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2780                         next_out = i; // suppress
2781                         continue;
2782                     }
2783                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2784                         char *p = star_match[0];
2785                         while(*p) {
2786                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2787                             while(*p && *p++ != ' '); // next
2788                         }
2789                         looking_at(buf, &i, "*% "); // eat prompt
2790                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2791                         next_out = i;
2792                         continue;
2793                     }
2794                 }
2795             }
2796
2797             /* skip formula vars */
2798             if (started == STARTED_NONE &&
2799                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2800               started = STARTED_CHATTER;
2801               i += 3;
2802               continue;
2803             }
2804
2805             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2806             if (appData.autoKibitz && started == STARTED_NONE &&
2807                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2808                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2809                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2810                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2811                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2812                         suppressKibitz = TRUE;
2813                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2814                         next_out = i;
2815                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2816                                 && (gameMode == IcsPlayingWhite)) ||
2817                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2818                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2819                             started = STARTED_CHATTER; // own kibitz we simply discard
2820                         else {
2821                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2822                             parse_pos = 0; parse[0] = NULLCHAR;
2823                             savingComment = TRUE;
2824                             suppressKibitz = gameMode != IcsObserving ? 2 :
2825                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2826                         }
2827                         continue;
2828                 } else
2829                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2830                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2831                          && atoi(star_match[0])) {
2832                     // suppress the acknowledgements of our own autoKibitz
2833                     char *p;
2834                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2835                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2836                     SendToPlayer(star_match[0], strlen(star_match[0]));
2837                     if(looking_at(buf, &i, "*% ")) // eat prompt
2838                         suppressKibitz = FALSE;
2839                     next_out = i;
2840                     continue;
2841                 }
2842             } // [HGM] kibitz: end of patch
2843
2844             // [HGM] chat: intercept tells by users for which we have an open chat window
2845             channel = -1;
2846             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2847                                            looking_at(buf, &i, "* whispers:") ||
2848                                            looking_at(buf, &i, "* kibitzes:") ||
2849                                            looking_at(buf, &i, "* shouts:") ||
2850                                            looking_at(buf, &i, "* c-shouts:") ||
2851                                            looking_at(buf, &i, "--> * ") ||
2852                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2853                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2854                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2855                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2856                 int p;
2857                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2858                 chattingPartner = -1;
2859
2860                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2861                 for(p=0; p<MAX_CHAT; p++) {
2862                     if(channel == atoi(chatPartner[p])) {
2863                     talker[0] = '['; strcat(talker, "] ");
2864                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2865                     chattingPartner = p; break;
2866                     }
2867                 } else
2868                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2869                 for(p=0; p<MAX_CHAT; p++) {
2870                     if(!strcmp("kibitzes", chatPartner[p])) {
2871                         talker[0] = '['; strcat(talker, "] ");
2872                         chattingPartner = p; break;
2873                     }
2874                 } else
2875                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2876                 for(p=0; p<MAX_CHAT; p++) {
2877                     if(!strcmp("whispers", chatPartner[p])) {
2878                         talker[0] = '['; strcat(talker, "] ");
2879                         chattingPartner = p; break;
2880                     }
2881                 } else
2882                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2883                   if(buf[i-8] == '-' && buf[i-3] == 't')
2884                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2885                     if(!strcmp("c-shouts", chatPartner[p])) {
2886                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2887                         chattingPartner = p; break;
2888                     }
2889                   }
2890                   if(chattingPartner < 0)
2891                   for(p=0; p<MAX_CHAT; p++) {
2892                     if(!strcmp("shouts", chatPartner[p])) {
2893                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2894                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2895                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2896                         chattingPartner = p; break;
2897                     }
2898                   }
2899                 }
2900                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2901                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2902                     talker[0] = 0; Colorize(ColorTell, FALSE);
2903                     chattingPartner = p; break;
2904                 }
2905                 if(chattingPartner<0) i = oldi; else {
2906                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2907                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2908                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2909                     started = STARTED_COMMENT;
2910                     parse_pos = 0; parse[0] = NULLCHAR;
2911                     savingComment = 3 + chattingPartner; // counts as TRUE
2912                     suppressKibitz = TRUE;
2913                     continue;
2914                 }
2915             } // [HGM] chat: end of patch
2916
2917             if (appData.zippyTalk || appData.zippyPlay) {
2918                 /* [DM] Backup address for color zippy lines */
2919                 backup = i;
2920 #if ZIPPY
2921                if (loggedOn == TRUE)
2922                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2923                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2924 #endif
2925             } // [DM] 'else { ' deleted
2926                 if (
2927                     /* Regular tells and says */
2928                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2929                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2930                     looking_at(buf, &i, "* says: ") ||
2931                     /* Don't color "message" or "messages" output */
2932                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2933                     looking_at(buf, &i, "*. * at *:*: ") ||
2934                     looking_at(buf, &i, "--* (*:*): ") ||
2935                     /* Message notifications (same color as tells) */
2936                     looking_at(buf, &i, "* has left a message ") ||
2937                     looking_at(buf, &i, "* just sent you a message:\n") ||
2938                     /* Whispers and kibitzes */
2939                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2940                     looking_at(buf, &i, "* kibitzes: ") ||
2941                     /* Channel tells */
2942                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2943
2944                   if (tkind == 1 && strchr(star_match[0], ':')) {
2945                       /* Avoid "tells you:" spoofs in channels */
2946                      tkind = 3;
2947                   }
2948                   if (star_match[0][0] == NULLCHAR ||
2949                       strchr(star_match[0], ' ') ||
2950                       (tkind == 3 && strchr(star_match[1], ' '))) {
2951                     /* Reject bogus matches */
2952                     i = oldi;
2953                   } else {
2954                     if (appData.colorize) {
2955                       if (oldi > next_out) {
2956                         SendToPlayer(&buf[next_out], oldi - next_out);
2957                         next_out = oldi;
2958                       }
2959                       switch (tkind) {
2960                       case 1:
2961                         Colorize(ColorTell, FALSE);
2962                         curColor = ColorTell;
2963                         break;
2964                       case 2:
2965                         Colorize(ColorKibitz, FALSE);
2966                         curColor = ColorKibitz;
2967                         break;
2968                       case 3:
2969                         p = strrchr(star_match[1], '(');
2970                         if (p == NULL) {
2971                           p = star_match[1];
2972                         } else {
2973                           p++;
2974                         }
2975                         if (atoi(p) == 1) {
2976                           Colorize(ColorChannel1, FALSE);
2977                           curColor = ColorChannel1;
2978                         } else {
2979                           Colorize(ColorChannel, FALSE);
2980                           curColor = ColorChannel;
2981                         }
2982                         break;
2983                       case 5:
2984                         curColor = ColorNormal;
2985                         break;
2986                       }
2987                     }
2988                     if (started == STARTED_NONE && appData.autoComment &&
2989                         (gameMode == IcsObserving ||
2990                          gameMode == IcsPlayingWhite ||
2991                          gameMode == IcsPlayingBlack)) {
2992                       parse_pos = i - oldi;
2993                       memcpy(parse, &buf[oldi], parse_pos);
2994                       parse[parse_pos] = NULLCHAR;
2995                       started = STARTED_COMMENT;
2996                       savingComment = TRUE;
2997                     } else {
2998                       started = STARTED_CHATTER;
2999                       savingComment = FALSE;
3000                     }
3001                     loggedOn = TRUE;
3002                     continue;
3003                   }
3004                 }
3005
3006                 if (looking_at(buf, &i, "* s-shouts: ") ||
3007                     looking_at(buf, &i, "* c-shouts: ")) {
3008                     if (appData.colorize) {
3009                         if (oldi > next_out) {
3010                             SendToPlayer(&buf[next_out], oldi - next_out);
3011                             next_out = oldi;
3012                         }
3013                         Colorize(ColorSShout, FALSE);
3014                         curColor = ColorSShout;
3015                     }
3016                     loggedOn = TRUE;
3017                     started = STARTED_CHATTER;
3018                     continue;
3019                 }
3020
3021                 if (looking_at(buf, &i, "--->")) {
3022                     loggedOn = TRUE;
3023                     continue;
3024                 }
3025
3026                 if (looking_at(buf, &i, "* shouts: ") ||
3027                     looking_at(buf, &i, "--> ")) {
3028                     if (appData.colorize) {
3029                         if (oldi > next_out) {
3030                             SendToPlayer(&buf[next_out], oldi - next_out);
3031                             next_out = oldi;
3032                         }
3033                         Colorize(ColorShout, FALSE);
3034                         curColor = ColorShout;
3035                     }
3036                     loggedOn = TRUE;
3037                     started = STARTED_CHATTER;
3038                     continue;
3039                 }
3040
3041                 if (looking_at( buf, &i, "Challenge:")) {
3042                     if (appData.colorize) {
3043                         if (oldi > next_out) {
3044                             SendToPlayer(&buf[next_out], oldi - next_out);
3045                             next_out = oldi;
3046                         }
3047                         Colorize(ColorChallenge, FALSE);
3048                         curColor = ColorChallenge;
3049                     }
3050                     loggedOn = TRUE;
3051                     continue;
3052                 }
3053
3054                 if (looking_at(buf, &i, "* offers you") ||
3055                     looking_at(buf, &i, "* offers to be") ||
3056                     looking_at(buf, &i, "* would like to") ||
3057                     looking_at(buf, &i, "* requests to") ||
3058                     looking_at(buf, &i, "Your opponent offers") ||
3059                     looking_at(buf, &i, "Your opponent requests")) {
3060
3061                     if (appData.colorize) {
3062                         if (oldi > next_out) {
3063                             SendToPlayer(&buf[next_out], oldi - next_out);
3064                             next_out = oldi;
3065                         }
3066                         Colorize(ColorRequest, FALSE);
3067                         curColor = ColorRequest;
3068                     }
3069                     continue;
3070                 }
3071
3072                 if (looking_at(buf, &i, "* (*) seeking")) {
3073                     if (appData.colorize) {
3074                         if (oldi > next_out) {
3075                             SendToPlayer(&buf[next_out], oldi - next_out);
3076                             next_out = oldi;
3077                         }
3078                         Colorize(ColorSeek, FALSE);
3079                         curColor = ColorSeek;
3080                     }
3081                     continue;
3082             }
3083
3084             if (looking_at(buf, &i, "\\   ")) {
3085                 if (prevColor != ColorNormal) {
3086                     if (oldi > next_out) {
3087                         SendToPlayer(&buf[next_out], oldi - next_out);
3088                         next_out = oldi;
3089                     }
3090                     Colorize(prevColor, TRUE);
3091                     curColor = prevColor;
3092                 }
3093                 if (savingComment) {
3094                     parse_pos = i - oldi;
3095                     memcpy(parse, &buf[oldi], parse_pos);
3096                     parse[parse_pos] = NULLCHAR;
3097                     started = STARTED_COMMENT;
3098                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3099                         chattingPartner = savingComment - 3; // kludge to remember the box
3100                 } else {
3101                     started = STARTED_CHATTER;
3102                 }
3103                 continue;
3104             }
3105
3106             if (looking_at(buf, &i, "Black Strength :") ||
3107                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3108                 looking_at(buf, &i, "<10>") ||
3109                 looking_at(buf, &i, "#@#")) {
3110                 /* Wrong board style */
3111                 loggedOn = TRUE;
3112                 SendToICS(ics_prefix);
3113                 SendToICS("set style 12\n");
3114                 SendToICS(ics_prefix);
3115                 SendToICS("refresh\n");
3116                 continue;
3117             }
3118
3119             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3120                 ICSInitScript();
3121                 have_sent_ICS_logon = 1;
3122                 /* if we don't send the login/password via icsLogon, use special readline
3123                    code for it */
3124                 if (strlen(appData.icsLogon)==0)
3125                   {
3126                     sending_ICS_password = 0; // in case we come back to login
3127                     sending_ICS_login = 1;
3128                   };
3129                 continue;
3130             }
3131             /* need to shadow the password */
3132             if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
3133               /* if we don't send the login/password via icsLogon, use special readline
3134                  code for it */
3135               if (strlen(appData.icsLogon)==0)
3136                 sending_ICS_password = 1;
3137               continue;
3138             }
3139
3140             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3141                 (looking_at(buf, &i, "\n<12> ") ||
3142                  looking_at(buf, &i, "<12> "))) {
3143                 loggedOn = TRUE;
3144                 if (oldi > next_out) {
3145                     SendToPlayer(&buf[next_out], oldi - next_out);
3146                 }
3147                 next_out = i;
3148                 started = STARTED_BOARD;
3149                 parse_pos = 0;
3150                 continue;
3151             }
3152
3153             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3154                 looking_at(buf, &i, "<b1> ")) {
3155                 if (oldi > next_out) {
3156                     SendToPlayer(&buf[next_out], oldi - next_out);
3157                 }
3158                 next_out = i;
3159                 started = STARTED_HOLDINGS;
3160                 parse_pos = 0;
3161                 continue;
3162             }
3163
3164             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3165                 loggedOn = TRUE;
3166                 /* Header for a move list -- first line */
3167
3168                 switch (ics_getting_history) {
3169                   case H_FALSE:
3170                     switch (gameMode) {
3171                       case IcsIdle:
3172                       case BeginningOfGame:
3173                         /* User typed "moves" or "oldmoves" while we
3174                            were idle.  Pretend we asked for these
3175                            moves and soak them up so user can step
3176                            through them and/or save them.
3177                            */
3178                         Reset(FALSE, TRUE);
3179                         gameMode = IcsObserving;
3180                         ModeHighlight();
3181                         ics_gamenum = -1;
3182                         ics_getting_history = H_GOT_UNREQ_HEADER;
3183                         break;
3184                       case EditGame: /*?*/
3185                       case EditPosition: /*?*/
3186                         /* Should above feature work in these modes too? */
3187                         /* For now it doesn't */
3188                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3189                         break;
3190                       default:
3191                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3192                         break;
3193                     }
3194                     break;
3195                   case H_REQUESTED:
3196                     /* Is this the right one? */
3197                     if (gameInfo.white && gameInfo.black &&
3198                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3199                         strcmp(gameInfo.black, star_match[2]) == 0) {
3200                         /* All is well */
3201                         ics_getting_history = H_GOT_REQ_HEADER;
3202                     }
3203                     break;
3204                   case H_GOT_REQ_HEADER:
3205                   case H_GOT_UNREQ_HEADER:
3206                   case H_GOT_UNWANTED_HEADER:
3207                   case H_GETTING_MOVES:
3208                     /* Should not happen */
3209                     DisplayError(_("Error gathering move list: two headers"), 0);
3210                     ics_getting_history = H_FALSE;
3211                     break;
3212                 }
3213
3214                 /* Save player ratings into gameInfo if needed */
3215                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3216                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3217                     (gameInfo.whiteRating == -1 ||
3218                      gameInfo.blackRating == -1)) {
3219
3220                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3221                     gameInfo.blackRating = string_to_rating(star_match[3]);
3222                     if (appData.debugMode)
3223                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3224                               gameInfo.whiteRating, gameInfo.blackRating);
3225                 }
3226                 continue;
3227             }
3228
3229             if (looking_at(buf, &i,
3230               "* * match, initial time: * minute*, increment: * second")) {
3231                 /* Header for a move list -- second line */
3232                 /* Initial board will follow if this is a wild game */
3233                 if (gameInfo.event != NULL) free(gameInfo.event);
3234                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3235                 gameInfo.event = StrSave(str);
3236                 /* [HGM] we switched variant. Translate boards if needed. */
3237                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3238                 continue;
3239             }
3240
3241             if (looking_at(buf, &i, "Move  ")) {
3242                 /* Beginning of a move list */
3243                 switch (ics_getting_history) {
3244                   case H_FALSE:
3245                     /* Normally should not happen */
3246                     /* Maybe user hit reset while we were parsing */
3247                     break;
3248                   case H_REQUESTED:
3249                     /* Happens if we are ignoring a move list that is not
3250                      * the one we just requested.  Common if the user
3251                      * tries to observe two games without turning off
3252                      * getMoveList */
3253                     break;
3254                   case H_GETTING_MOVES:
3255                     /* Should not happen */
3256                     DisplayError(_("Error gathering move list: nested"), 0);
3257                     ics_getting_history = H_FALSE;
3258                     break;
3259                   case H_GOT_REQ_HEADER:
3260                     ics_getting_history = H_GETTING_MOVES;
3261                     started = STARTED_MOVES;
3262                     parse_pos = 0;
3263                     if (oldi > next_out) {
3264                         SendToPlayer(&buf[next_out], oldi - next_out);
3265                     }
3266                     break;
3267                   case H_GOT_UNREQ_HEADER:
3268                     ics_getting_history = H_GETTING_MOVES;
3269                     started = STARTED_MOVES_NOHIDE;
3270                     parse_pos = 0;
3271                     break;
3272                   case H_GOT_UNWANTED_HEADER:
3273                     ics_getting_history = H_FALSE;
3274                     break;
3275                 }
3276                 continue;
3277             }
3278
3279             if (looking_at(buf, &i, "% ") ||
3280                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3281                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3282                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3283                     soughtPending = FALSE;
3284                     seekGraphUp = TRUE;
3285                     DrawSeekGraph();
3286                 }
3287                 if(suppressKibitz) next_out = i;
3288                 savingComment = FALSE;
3289                 suppressKibitz = 0;
3290                 switch (started) {
3291                   case STARTED_MOVES:
3292                   case STARTED_MOVES_NOHIDE:
3293                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3294                     parse[parse_pos + i - oldi] = NULLCHAR;
3295                     ParseGameHistory(parse);
3296 #if ZIPPY
3297                     if (appData.zippyPlay && first.initDone) {
3298                         FeedMovesToProgram(&first, forwardMostMove);
3299                         if (gameMode == IcsPlayingWhite) {
3300                             if (WhiteOnMove(forwardMostMove)) {
3301                                 if (first.sendTime) {
3302                                   if (first.useColors) {
3303                                     SendToProgram("black\n", &first);
3304                                   }
3305                                   SendTimeRemaining(&first, TRUE);
3306                                 }
3307                                 if (first.useColors) {
3308                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3309                                 }
3310                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3311                                 first.maybeThinking = TRUE;
3312                             } else {
3313                                 if (first.usePlayother) {
3314                                   if (first.sendTime) {
3315                                     SendTimeRemaining(&first, TRUE);
3316                                   }
3317                                   SendToProgram("playother\n", &first);
3318                                   firstMove = FALSE;
3319                                 } else {
3320                                   firstMove = TRUE;
3321                                 }
3322                             }
3323                         } else if (gameMode == IcsPlayingBlack) {
3324                             if (!WhiteOnMove(forwardMostMove)) {
3325                                 if (first.sendTime) {
3326                                   if (first.useColors) {
3327                                     SendToProgram("white\n", &first);
3328                                   }
3329                                   SendTimeRemaining(&first, FALSE);
3330                                 }
3331                                 if (first.useColors) {
3332                                   SendToProgram("black\n", &first);
3333                                 }
3334                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3335                                 first.maybeThinking = TRUE;
3336                             } else {
3337                                 if (first.usePlayother) {
3338                                   if (first.sendTime) {
3339                                     SendTimeRemaining(&first, FALSE);
3340                                   }
3341                                   SendToProgram("playother\n", &first);
3342                                   firstMove = FALSE;
3343                                 } else {
3344                                   firstMove = TRUE;
3345                                 }
3346                             }
3347                         }
3348                     }
3349 #endif
3350                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3351                         /* Moves came from oldmoves or moves command
3352                            while we weren't doing anything else.
3353                            */
3354                         currentMove = forwardMostMove;
3355                         ClearHighlights();/*!!could figure this out*/
3356                         flipView = appData.flipView;
3357                         DrawPosition(TRUE, boards[currentMove]);
3358                         DisplayBothClocks();
3359                         snprintf(str, MSG_SIZ, "%s vs. %s",
3360                                 gameInfo.white, gameInfo.black);
3361                         DisplayTitle(str);
3362                         gameMode = IcsIdle;
3363                     } else {
3364                         /* Moves were history of an active game */
3365                         if (gameInfo.resultDetails != NULL) {
3366                             free(gameInfo.resultDetails);
3367                             gameInfo.resultDetails = NULL;
3368                         }
3369                     }
3370                     HistorySet(parseList, backwardMostMove,
3371                                forwardMostMove, currentMove-1);
3372                     DisplayMove(currentMove - 1);
3373                     if (started == STARTED_MOVES) next_out = i;
3374                     started = STARTED_NONE;
3375                     ics_getting_history = H_FALSE;
3376                     break;
3377
3378                   case STARTED_OBSERVE:
3379                     started = STARTED_NONE;
3380                     SendToICS(ics_prefix);
3381                     SendToICS("refresh\n");
3382                     break;
3383
3384                   default:
3385                     break;
3386                 }
3387                 if(bookHit) { // [HGM] book: simulate book reply
3388                     static char bookMove[MSG_SIZ]; // a bit generous?
3389
3390                     programStats.nodes = programStats.depth = programStats.time =
3391                     programStats.score = programStats.got_only_move = 0;
3392                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3393
3394                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3395                     strcat(bookMove, bookHit);
3396                     HandleMachineMove(bookMove, &first);
3397                 }
3398                 continue;
3399             }
3400
3401             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3402                  started == STARTED_HOLDINGS ||
3403                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3404                 /* Accumulate characters in move list or board */
3405                 parse[parse_pos++] = buf[i];
3406             }
3407
3408             /* Start of game messages.  Mostly we detect start of game
3409                when the first board image arrives.  On some versions
3410                of the ICS, though, we need to do a "refresh" after starting
3411                to observe in order to get the current board right away. */
3412             if (looking_at(buf, &i, "Adding game * to observation list")) {
3413                 started = STARTED_OBSERVE;
3414                 continue;
3415             }
3416
3417             /* Handle auto-observe */
3418             if (appData.autoObserve &&
3419                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3420                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3421                 char *player;
3422                 /* Choose the player that was highlighted, if any. */
3423                 if (star_match[0][0] == '\033' ||
3424                     star_match[1][0] != '\033') {
3425                     player = star_match[0];
3426                 } else {
3427                     player = star_match[2];
3428                 }
3429                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3430                         ics_prefix, StripHighlightAndTitle(player));
3431                 SendToICS(str);
3432
3433                 /* Save ratings from notify string */
3434                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3435                 player1Rating = string_to_rating(star_match[1]);
3436                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3437                 player2Rating = string_to_rating(star_match[3]);
3438
3439                 if (appData.debugMode)
3440                   fprintf(debugFP,
3441                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3442                           player1Name, player1Rating,
3443                           player2Name, player2Rating);
3444
3445                 continue;
3446             }
3447
3448             /* Deal with automatic examine mode after a game,
3449                and with IcsObserving -> IcsExamining transition */
3450             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3451                 looking_at(buf, &i, "has made you an examiner of game *")) {
3452
3453                 int gamenum = atoi(star_match[0]);
3454                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3455                     gamenum == ics_gamenum) {
3456                     /* We were already playing or observing this game;
3457                        no need to refetch history */
3458                     gameMode = IcsExamining;
3459                     if (pausing) {
3460                         pauseExamForwardMostMove = forwardMostMove;
3461                     } else if (currentMove < forwardMostMove) {
3462                         ForwardInner(forwardMostMove);
3463                     }
3464                 } else {
3465                     /* I don't think this case really can happen */
3466                     SendToICS(ics_prefix);
3467                     SendToICS("refresh\n");
3468                 }
3469                 continue;
3470             }
3471
3472             /* Error messages */
3473 //          if (ics_user_moved) {
3474             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3475                 if (looking_at(buf, &i, "Illegal move") ||
3476                     looking_at(buf, &i, "Not a legal move") ||
3477                     looking_at(buf, &i, "Your king is in check") ||
3478                     looking_at(buf, &i, "It isn't your turn") ||
3479                     looking_at(buf, &i, "It is not your move")) {
3480                     /* Illegal move */
3481                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3482                         currentMove = forwardMostMove-1;
3483                         DisplayMove(currentMove - 1); /* before DMError */
3484                         DrawPosition(FALSE, boards[currentMove]);
3485                         SwitchClocks(forwardMostMove-1); // [HGM] race
3486                         DisplayBothClocks();
3487                     }
3488                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3489                     ics_user_moved = 0;
3490                     continue;
3491                 }
3492             }
3493
3494             if (looking_at(buf, &i, "still have time") ||
3495                 looking_at(buf, &i, "not out of time") ||
3496                 looking_at(buf, &i, "either player is out of time") ||
3497                 looking_at(buf, &i, "has timeseal; checking")) {
3498                 /* We must have called his flag a little too soon */
3499                 whiteFlag = blackFlag = FALSE;
3500                 continue;
3501             }
3502
3503             if (looking_at(buf, &i, "added * seconds to") ||
3504                 looking_at(buf, &i, "seconds were added to")) {
3505                 /* Update the clocks */
3506                 SendToICS(ics_prefix);
3507                 SendToICS("refresh\n");
3508                 continue;
3509             }
3510
3511             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3512                 ics_clock_paused = TRUE;
3513                 StopClocks();
3514                 continue;
3515             }
3516
3517             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3518                 ics_clock_paused = FALSE;
3519                 StartClocks();
3520                 continue;
3521             }
3522
3523             /* Grab player ratings from the Creating: message.
3524                Note we have to check for the special case when
3525                the ICS inserts things like [white] or [black]. */
3526             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3527                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3528                 /* star_matches:
3529                    0    player 1 name (not necessarily white)
3530                    1    player 1 rating
3531                    2    empty, white, or black (IGNORED)
3532                    3    player 2 name (not necessarily black)
3533                    4    player 2 rating
3534
3535                    The names/ratings are sorted out when the game
3536                    actually starts (below).
3537                 */
3538                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3539                 player1Rating = string_to_rating(star_match[1]);
3540                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3541                 player2Rating = string_to_rating(star_match[4]);
3542
3543                 if (appData.debugMode)
3544                   fprintf(debugFP,
3545                           "Ratings from 'Creating:' %s %d, %s %d\n",
3546                           player1Name, player1Rating,
3547                           player2Name, player2Rating);
3548
3549                 continue;
3550             }
3551
3552             /* Improved generic start/end-of-game messages */
3553             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3554                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3555                 /* If tkind == 0: */
3556                 /* star_match[0] is the game number */
3557                 /*           [1] is the white player's name */
3558                 /*           [2] is the black player's name */
3559                 /* For end-of-game: */
3560                 /*           [3] is the reason for the game end */
3561                 /*           [4] is a PGN end game-token, preceded by " " */
3562                 /* For start-of-game: */
3563                 /*           [3] begins with "Creating" or "Continuing" */
3564                 /*           [4] is " *" or empty (don't care). */
3565                 int gamenum = atoi(star_match[0]);
3566                 char *whitename, *blackname, *why, *endtoken;
3567                 ChessMove endtype = EndOfFile;
3568
3569                 if (tkind == 0) {
3570                   whitename = star_match[1];
3571                   blackname = star_match[2];
3572                   why = star_match[3];
3573                   endtoken = star_match[4];
3574                 } else {
3575                   whitename = star_match[1];
3576                   blackname = star_match[3];
3577                   why = star_match[5];
3578                   endtoken = star_match[6];
3579                 }
3580
3581                 /* Game start messages */
3582                 if (strncmp(why, "Creating ", 9) == 0 ||
3583                     strncmp(why, "Continuing ", 11) == 0) {
3584                     gs_gamenum = gamenum;
3585                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3586                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3587 #if ZIPPY
3588                     if (appData.zippyPlay) {
3589                         ZippyGameStart(whitename, blackname);
3590                     }
3591 #endif /*ZIPPY*/
3592                     partnerBoardValid = FALSE; // [HGM] bughouse
3593                     continue;
3594                 }
3595
3596                 /* Game end messages */
3597                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3598                     ics_gamenum != gamenum) {
3599                     continue;
3600                 }
3601                 while (endtoken[0] == ' ') endtoken++;
3602                 switch (endtoken[0]) {
3603                   case '*':
3604                   default:
3605                     endtype = GameUnfinished;
3606                     break;
3607                   case '0':
3608                     endtype = BlackWins;
3609                     break;
3610                   case '1':
3611                     if (endtoken[1] == '/')
3612                       endtype = GameIsDrawn;
3613                     else
3614                       endtype = WhiteWins;
3615                     break;
3616                 }
3617                 GameEnds(endtype, why, GE_ICS);
3618 #if ZIPPY
3619                 if (appData.zippyPlay && first.initDone) {
3620                     ZippyGameEnd(endtype, why);
3621                     if (first.pr == NULL) {
3622                       /* Start the next process early so that we'll
3623                          be ready for the next challenge */
3624                       StartChessProgram(&first);
3625                     }
3626                     /* Send "new" early, in case this command takes
3627                        a long time to finish, so that we'll be ready
3628                        for the next challenge. */
3629                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3630                     Reset(TRUE, TRUE);
3631                 }
3632 #endif /*ZIPPY*/
3633                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3634                 continue;
3635             }
3636
3637             if (looking_at(buf, &i, "Removing game * from observation") ||
3638                 looking_at(buf, &i, "no longer observing game *") ||
3639                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3640                 if (gameMode == IcsObserving &&
3641                     atoi(star_match[0]) == ics_gamenum)
3642                   {
3643                       /* icsEngineAnalyze */
3644                       if (appData.icsEngineAnalyze) {
3645                             ExitAnalyzeMode();
3646                             ModeHighlight();
3647                       }
3648                       StopClocks();
3649                       gameMode = IcsIdle;
3650                       ics_gamenum = -1;
3651                       ics_user_moved = FALSE;
3652                   }
3653                 continue;
3654             }
3655
3656             if (looking_at(buf, &i, "no longer examining game *")) {
3657                 if (gameMode == IcsExamining &&
3658                     atoi(star_match[0]) == ics_gamenum)
3659                   {
3660                       gameMode = IcsIdle;
3661                       ics_gamenum = -1;
3662                       ics_user_moved = FALSE;
3663                   }
3664                 continue;
3665             }
3666
3667             /* Advance leftover_start past any newlines we find,
3668                so only partial lines can get reparsed */
3669             if (looking_at(buf, &i, "\n")) {
3670                 prevColor = curColor;
3671                 if (curColor != ColorNormal) {
3672                     if (oldi > next_out) {
3673                         SendToPlayer(&buf[next_out], oldi - next_out);
3674                         next_out = oldi;
3675                     }
3676                     Colorize(ColorNormal, FALSE);
3677                     curColor = ColorNormal;
3678                 }
3679                 if (started == STARTED_BOARD) {
3680                     started = STARTED_NONE;
3681                     parse[parse_pos] = NULLCHAR;
3682                     ParseBoard12(parse);
3683                     ics_user_moved = 0;
3684
3685                     /* Send premove here */
3686                     if (appData.premove) {
3687                       char str[MSG_SIZ];
3688                       if (currentMove == 0 &&
3689                           gameMode == IcsPlayingWhite &&
3690                           appData.premoveWhite) {
3691                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3692                         if (appData.debugMode)
3693                           fprintf(debugFP, "Sending premove:\n");
3694                         SendToICS(str);
3695                       } else if (currentMove == 1 &&
3696                                  gameMode == IcsPlayingBlack &&
3697                                  appData.premoveBlack) {
3698                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3699                         if (appData.debugMode)
3700                           fprintf(debugFP, "Sending premove:\n");
3701                         SendToICS(str);
3702                       } else if (gotPremove) {
3703                         gotPremove = 0;
3704                         ClearPremoveHighlights();
3705                         if (appData.debugMode)
3706                           fprintf(debugFP, "Sending premove:\n");
3707                           UserMoveEvent(premoveFromX, premoveFromY,
3708                                         premoveToX, premoveToY,
3709                                         premovePromoChar);
3710                       }
3711                     }
3712
3713                     /* Usually suppress following prompt */
3714                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3715                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3716                         if (looking_at(buf, &i, "*% ")) {
3717                             savingComment = FALSE;
3718                             suppressKibitz = 0;
3719                         }
3720                     }
3721                     next_out = i;
3722                 } else if (started == STARTED_HOLDINGS) {
3723                     int gamenum;
3724                     char new_piece[MSG_SIZ];
3725                     started = STARTED_NONE;
3726                     parse[parse_pos] = NULLCHAR;
3727                     if (appData.debugMode)
3728                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3729                                                         parse, currentMove);
3730                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3731                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3732                         if (gameInfo.variant == VariantNormal) {
3733                           /* [HGM] We seem to switch variant during a game!
3734                            * Presumably no holdings were displayed, so we have
3735                            * to move the position two files to the right to
3736                            * create room for them!
3737                            */
3738                           VariantClass newVariant;
3739                           switch(gameInfo.boardWidth) { // base guess on board width
3740                                 case 9:  newVariant = VariantShogi; break;
3741                                 case 10: newVariant = VariantGreat; break;
3742                                 default: newVariant = VariantCrazyhouse; break;
3743                           }
3744                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3745                           /* Get a move list just to see the header, which
3746                              will tell us whether this is really bug or zh */
3747                           if (ics_getting_history == H_FALSE) {
3748                             ics_getting_history = H_REQUESTED;
3749                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3750                             SendToICS(str);
3751                           }
3752                         }
3753                         new_piece[0] = NULLCHAR;
3754                         sscanf(parse, "game %d white [%s black [%s <- %s",
3755                                &gamenum, white_holding, black_holding,
3756                                new_piece);
3757                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3758                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3759                         /* [HGM] copy holdings to board holdings area */
3760                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3761                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3762                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3763 #if ZIPPY
3764                         if (appData.zippyPlay && first.initDone) {
3765                             ZippyHoldings(white_holding, black_holding,
3766                                           new_piece);
3767                         }
3768 #endif /*ZIPPY*/
3769                         if (tinyLayout || smallLayout) {
3770                             char wh[16], bh[16];
3771                             PackHolding(wh, white_holding);
3772                             PackHolding(bh, black_holding);
3773                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3774                                     gameInfo.white, gameInfo.black);
3775                         } else {
3776                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3777                                     gameInfo.white, white_holding,
3778                                     gameInfo.black, black_holding);
3779                         }
3780                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3781                         DrawPosition(FALSE, boards[currentMove]);
3782                         DisplayTitle(str);
3783                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3784                         sscanf(parse, "game %d white [%s black [%s <- %s",
3785                                &gamenum, white_holding, black_holding,
3786                                new_piece);
3787                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3788                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3789                         /* [HGM] copy holdings to partner-board holdings area */
3790                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3791                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3792                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3793                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3794                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3795                       }
3796                     }
3797                     /* Suppress following prompt */
3798                     if (looking_at(buf, &i, "*% ")) {
3799                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3800                         savingComment = FALSE;
3801                         suppressKibitz = 0;
3802                     }
3803                     next_out = i;
3804                 }
3805                 continue;
3806             }
3807
3808             i++;                /* skip unparsed character and loop back */
3809         }
3810
3811         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3812 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3813 //          SendToPlayer(&buf[next_out], i - next_out);
3814             started != STARTED_HOLDINGS && leftover_start > next_out) {
3815             SendToPlayer(&buf[next_out], leftover_start - next_out);
3816             next_out = i;
3817         }
3818
3819         leftover_len = buf_len - leftover_start;
3820         /* if buffer ends with something we couldn't parse,
3821            reparse it after appending the next read */
3822
3823     } else if (count == 0) {
3824         RemoveInputSource(isr);
3825         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3826     } else {
3827         DisplayFatalError(_("Error reading from ICS"), error, 1);
3828     }
3829 }
3830
3831
3832 /* Board style 12 looks like this:
3833
3834    <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
3835
3836  * The "<12> " is stripped before it gets to this routine.  The two
3837  * trailing 0's (flip state and clock ticking) are later addition, and
3838  * some chess servers may not have them, or may have only the first.
3839  * Additional trailing fields may be added in the future.
3840  */
3841
3842 #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"
3843
3844 #define RELATION_OBSERVING_PLAYED    0
3845 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3846 #define RELATION_PLAYING_MYMOVE      1
3847 #define RELATION_PLAYING_NOTMYMOVE  -1
3848 #define RELATION_EXAMINING           2
3849 #define RELATION_ISOLATED_BOARD     -3
3850 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3851
3852 void
3853 ParseBoard12(string)
3854      char *string;
3855 {
3856     GameMode newGameMode;
3857     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3858     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3859     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3860     char to_play, board_chars[200];
3861     char move_str[500], str[500], elapsed_time[500];
3862     char black[32], white[32];
3863     Board board;
3864     int prevMove = currentMove;
3865     int ticking = 2;
3866     ChessMove moveType;
3867     int fromX, fromY, toX, toY;
3868     char promoChar;
3869     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3870     char *bookHit = NULL; // [HGM] book
3871     Boolean weird = FALSE, reqFlag = FALSE;
3872
3873     fromX = fromY = toX = toY = -1;
3874
3875     newGame = FALSE;
3876
3877     if (appData.debugMode)
3878       fprintf(debugFP, _("Parsing board: %s\n"), string);
3879
3880     move_str[0] = NULLCHAR;
3881     elapsed_time[0] = NULLCHAR;
3882     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3883         int  i = 0, j;
3884         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3885             if(string[i] == ' ') { ranks++; files = 0; }
3886             else files++;
3887             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3888             i++;
3889         }
3890         for(j = 0; j <i; j++) board_chars[j] = string[j];
3891         board_chars[i] = '\0';
3892         string += i + 1;
3893     }
3894     n = sscanf(string, PATTERN, &to_play, &double_push,
3895                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3896                &gamenum, white, black, &relation, &basetime, &increment,
3897                &white_stren, &black_stren, &white_time, &black_time,
3898                &moveNum, str, elapsed_time, move_str, &ics_flip,
3899                &ticking);
3900
3901     if (n < 21) {
3902         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3903         DisplayError(str, 0);
3904         return;
3905     }
3906
3907     /* Convert the move number to internal form */
3908     moveNum = (moveNum - 1) * 2;
3909     if (to_play == 'B') moveNum++;
3910     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3911       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3912                         0, 1);
3913       return;
3914     }
3915
3916     switch (relation) {
3917       case RELATION_OBSERVING_PLAYED:
3918       case RELATION_OBSERVING_STATIC:
3919         if (gamenum == -1) {
3920             /* Old ICC buglet */
3921             relation = RELATION_OBSERVING_STATIC;
3922         }
3923         newGameMode = IcsObserving;
3924         break;
3925       case RELATION_PLAYING_MYMOVE:
3926       case RELATION_PLAYING_NOTMYMOVE:
3927         newGameMode =
3928           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3929             IcsPlayingWhite : IcsPlayingBlack;
3930         break;
3931       case RELATION_EXAMINING:
3932         newGameMode = IcsExamining;
3933         break;
3934       case RELATION_ISOLATED_BOARD:
3935       default:
3936         /* Just display this board.  If user was doing something else,
3937            we will forget about it until the next board comes. */
3938         newGameMode = IcsIdle;
3939         break;
3940       case RELATION_STARTING_POSITION:
3941         newGameMode = gameMode;
3942         break;
3943     }
3944
3945     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3946          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3947       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3948       char *toSqr;
3949       for (k = 0; k < ranks; k++) {
3950         for (j = 0; j < files; j++)
3951           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3952         if(gameInfo.holdingsWidth > 1) {
3953              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3954              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3955         }
3956       }
3957       CopyBoard(partnerBoard, board);
3958       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3959         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3960         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3961       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3962       if(toSqr = strchr(str, '-')) {
3963         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3964         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3965       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3966       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3967       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3968       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3969       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3970       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3971                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3972       DisplayMessage(partnerStatus, "");
3973         partnerBoardValid = TRUE;
3974       return;
3975     }
3976
3977     /* Modify behavior for initial board display on move listing
3978        of wild games.
3979        */
3980     switch (ics_getting_history) {
3981       case H_FALSE:
3982       case H_REQUESTED:
3983         break;
3984       case H_GOT_REQ_HEADER:
3985       case H_GOT_UNREQ_HEADER:
3986         /* This is the initial position of the current game */
3987         gamenum = ics_gamenum;
3988         moveNum = 0;            /* old ICS bug workaround */
3989         if (to_play == 'B') {
3990           startedFromSetupPosition = TRUE;
3991           blackPlaysFirst = TRUE;
3992           moveNum = 1;
3993           if (forwardMostMove == 0) forwardMostMove = 1;
3994           if (backwardMostMove == 0) backwardMostMove = 1;
3995           if (currentMove == 0) currentMove = 1;
3996         }
3997         newGameMode = gameMode;
3998         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3999         break;
4000       case H_GOT_UNWANTED_HEADER:
4001         /* This is an initial board that we don't want */
4002         return;
4003       case H_GETTING_MOVES:
4004         /* Should not happen */
4005         DisplayError(_("Error gathering move list: extra board"), 0);
4006         ics_getting_history = H_FALSE;
4007         return;
4008     }
4009
4010    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4011                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4012      /* [HGM] We seem to have switched variant unexpectedly
4013       * Try to guess new variant from board size
4014       */
4015           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4016           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4017           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4018           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4019           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4020           if(!weird) newVariant = VariantNormal;
4021           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4022           /* Get a move list just to see the header, which
4023              will tell us whether this is really bug or zh */
4024           if (ics_getting_history == H_FALSE) {
4025             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4026             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4027             SendToICS(str);
4028           }
4029     }
4030
4031     /* Take action if this is the first board of a new game, or of a
4032        different game than is currently being displayed.  */
4033     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4034         relation == RELATION_ISOLATED_BOARD) {
4035
4036         /* Forget the old game and get the history (if any) of the new one */
4037         if (gameMode != BeginningOfGame) {
4038           Reset(TRUE, TRUE);
4039         }
4040         newGame = TRUE;
4041         if (appData.autoRaiseBoard) BoardToTop();
4042         prevMove = -3;
4043         if (gamenum == -1) {
4044             newGameMode = IcsIdle;
4045         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4046                    appData.getMoveList && !reqFlag) {
4047             /* Need to get game history */
4048             ics_getting_history = H_REQUESTED;
4049             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4050             SendToICS(str);
4051         }
4052
4053         /* Initially flip the board to have black on the bottom if playing
4054            black or if the ICS flip flag is set, but let the user change
4055            it with the Flip View button. */
4056         flipView = appData.autoFlipView ?
4057           (newGameMode == IcsPlayingBlack) || ics_flip :
4058           appData.flipView;
4059
4060         /* Done with values from previous mode; copy in new ones */
4061         gameMode = newGameMode;
4062         ModeHighlight();
4063         ics_gamenum = gamenum;
4064         if (gamenum == gs_gamenum) {
4065             int klen = strlen(gs_kind);
4066             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4067             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4068             gameInfo.event = StrSave(str);
4069         } else {
4070             gameInfo.event = StrSave("ICS game");
4071         }
4072         gameInfo.site = StrSave(appData.icsHost);
4073         gameInfo.date = PGNDate();
4074         gameInfo.round = StrSave("-");
4075         gameInfo.white = StrSave(white);
4076         gameInfo.black = StrSave(black);
4077         timeControl = basetime * 60 * 1000;
4078         timeControl_2 = 0;
4079         timeIncrement = increment * 1000;
4080         movesPerSession = 0;
4081         gameInfo.timeControl = TimeControlTagValue();
4082         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4083   if (appData.debugMode) {
4084     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4085     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4086     setbuf(debugFP, NULL);
4087   }
4088
4089         gameInfo.outOfBook = NULL;
4090
4091         /* Do we have the ratings? */
4092         if (strcmp(player1Name, white) == 0 &&
4093             strcmp(player2Name, black) == 0) {
4094             if (appData.debugMode)
4095               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4096                       player1Rating, player2Rating);
4097             gameInfo.whiteRating = player1Rating;
4098             gameInfo.blackRating = player2Rating;
4099         } else if (strcmp(player2Name, white) == 0 &&
4100                    strcmp(player1Name, black) == 0) {
4101             if (appData.debugMode)
4102               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4103                       player2Rating, player1Rating);
4104             gameInfo.whiteRating = player2Rating;
4105             gameInfo.blackRating = player1Rating;
4106         }
4107         player1Name[0] = player2Name[0] = NULLCHAR;
4108
4109         /* Silence shouts if requested */
4110         if (appData.quietPlay &&
4111             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4112             SendToICS(ics_prefix);
4113             SendToICS("set shout 0\n");
4114         }
4115     }
4116
4117     /* Deal with midgame name changes */
4118     if (!newGame) {
4119         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4120             if (gameInfo.white) free(gameInfo.white);
4121             gameInfo.white = StrSave(white);
4122         }
4123         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4124             if (gameInfo.black) free(gameInfo.black);
4125             gameInfo.black = StrSave(black);
4126         }
4127     }
4128
4129     /* Throw away game result if anything actually changes in examine mode */
4130     if (gameMode == IcsExamining && !newGame) {
4131         gameInfo.result = GameUnfinished;
4132         if (gameInfo.resultDetails != NULL) {
4133             free(gameInfo.resultDetails);
4134             gameInfo.resultDetails = NULL;
4135         }
4136     }
4137
4138     /* In pausing && IcsExamining mode, we ignore boards coming
4139        in if they are in a different variation than we are. */
4140     if (pauseExamInvalid) return;
4141     if (pausing && gameMode == IcsExamining) {
4142         if (moveNum <= pauseExamForwardMostMove) {
4143             pauseExamInvalid = TRUE;
4144             forwardMostMove = pauseExamForwardMostMove;
4145             return;
4146         }
4147     }
4148
4149   if (appData.debugMode) {
4150     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4151   }
4152     /* Parse the board */
4153     for (k = 0; k < ranks; k++) {
4154       for (j = 0; j < files; j++)
4155         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4156       if(gameInfo.holdingsWidth > 1) {
4157            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4158            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4159       }
4160     }
4161     CopyBoard(boards[moveNum], board);
4162     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4163     if (moveNum == 0) {
4164         startedFromSetupPosition =
4165           !CompareBoards(board, initialPosition);
4166         if(startedFromSetupPosition)
4167             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4168     }
4169
4170     /* [HGM] Set castling rights. Take the outermost Rooks,
4171        to make it also work for FRC opening positions. Note that board12
4172        is really defective for later FRC positions, as it has no way to
4173        indicate which Rook can castle if they are on the same side of King.
4174        For the initial position we grant rights to the outermost Rooks,
4175        and remember thos rights, and we then copy them on positions
4176        later in an FRC game. This means WB might not recognize castlings with
4177        Rooks that have moved back to their original position as illegal,
4178        but in ICS mode that is not its job anyway.
4179     */
4180     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4181     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4182
4183         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4184             if(board[0][i] == WhiteRook) j = i;
4185         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4186         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4187             if(board[0][i] == WhiteRook) j = i;
4188         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4189         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4190             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4191         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4192         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4193             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4194         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4195
4196         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4197         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4198             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4199         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4200             if(board[BOARD_HEIGHT-1][k] == bKing)
4201                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4202         if(gameInfo.variant == VariantTwoKings) {
4203             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4204             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4205             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4206         }
4207     } else { int r;
4208         r = boards[moveNum][CASTLING][0] = initialRights[0];
4209         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4210         r = boards[moveNum][CASTLING][1] = initialRights[1];
4211         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4212         r = boards[moveNum][CASTLING][3] = initialRights[3];
4213         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4214         r = boards[moveNum][CASTLING][4] = initialRights[4];
4215         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4216         /* wildcastle kludge: always assume King has rights */
4217         r = boards[moveNum][CASTLING][2] = initialRights[2];
4218         r = boards[moveNum][CASTLING][5] = initialRights[5];
4219     }
4220     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4221     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4222
4223
4224     if (ics_getting_history == H_GOT_REQ_HEADER ||
4225         ics_getting_history == H_GOT_UNREQ_HEADER) {
4226         /* This was an initial position from a move list, not
4227            the current position */
4228         return;
4229     }
4230
4231     /* Update currentMove and known move number limits */
4232     newMove = newGame || moveNum > forwardMostMove;
4233
4234     if (newGame) {
4235         forwardMostMove = backwardMostMove = currentMove = moveNum;
4236         if (gameMode == IcsExamining && moveNum == 0) {
4237           /* Workaround for ICS limitation: we are not told the wild
4238              type when starting to examine a game.  But if we ask for
4239              the move list, the move list header will tell us */
4240             ics_getting_history = H_REQUESTED;
4241             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4242             SendToICS(str);
4243         }
4244     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4245                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4246 #if ZIPPY
4247         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4248         /* [HGM] applied this also to an engine that is silently watching        */
4249         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4250             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4251             gameInfo.variant == currentlyInitializedVariant) {
4252           takeback = forwardMostMove - moveNum;
4253           for (i = 0; i < takeback; i++) {
4254             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4255             SendToProgram("undo\n", &first);
4256           }
4257         }
4258 #endif
4259
4260         forwardMostMove = moveNum;
4261         if (!pausing || currentMove > forwardMostMove)
4262           currentMove = forwardMostMove;
4263     } else {
4264         /* New part of history that is not contiguous with old part */
4265         if (pausing && gameMode == IcsExamining) {
4266             pauseExamInvalid = TRUE;
4267             forwardMostMove = pauseExamForwardMostMove;
4268             return;
4269         }
4270         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4271 #if ZIPPY
4272             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4273                 // [HGM] when we will receive the move list we now request, it will be
4274                 // fed to the engine from the first move on. So if the engine is not
4275                 // in the initial position now, bring it there.
4276                 InitChessProgram(&first, 0);
4277             }
4278 #endif
4279             ics_getting_history = H_REQUESTED;
4280             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4281             SendToICS(str);
4282         }
4283         forwardMostMove = backwardMostMove = currentMove = moveNum;
4284     }
4285
4286     /* Update the clocks */
4287     if (strchr(elapsed_time, '.')) {
4288       /* Time is in ms */
4289       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4290       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4291     } else {
4292       /* Time is in seconds */
4293       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4294       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4295     }
4296
4297
4298 #if ZIPPY
4299     if (appData.zippyPlay && newGame &&
4300         gameMode != IcsObserving && gameMode != IcsIdle &&
4301         gameMode != IcsExamining)
4302       ZippyFirstBoard(moveNum, basetime, increment);
4303 #endif
4304
4305     /* Put the move on the move list, first converting
4306        to canonical algebraic form. */
4307     if (moveNum > 0) {
4308   if (appData.debugMode) {
4309     if (appData.debugMode) { int f = forwardMostMove;
4310         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4311                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4312                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4313     }
4314     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4315     fprintf(debugFP, "moveNum = %d\n", moveNum);
4316     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4317     setbuf(debugFP, NULL);
4318   }
4319         if (moveNum <= backwardMostMove) {
4320             /* We don't know what the board looked like before
4321                this move.  Punt. */
4322           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4323             strcat(parseList[moveNum - 1], " ");
4324             strcat(parseList[moveNum - 1], elapsed_time);
4325             moveList[moveNum - 1][0] = NULLCHAR;
4326         } else if (strcmp(move_str, "none") == 0) {
4327             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4328             /* Again, we don't know what the board looked like;
4329                this is really the start of the game. */
4330             parseList[moveNum - 1][0] = NULLCHAR;
4331             moveList[moveNum - 1][0] = NULLCHAR;
4332             backwardMostMove = moveNum;
4333             startedFromSetupPosition = TRUE;
4334             fromX = fromY = toX = toY = -1;
4335         } else {
4336           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4337           //                 So we parse the long-algebraic move string in stead of the SAN move
4338           int valid; char buf[MSG_SIZ], *prom;
4339
4340           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4341                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4342           // str looks something like "Q/a1-a2"; kill the slash
4343           if(str[1] == '/')
4344             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4345           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4346           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4347                 strcat(buf, prom); // long move lacks promo specification!
4348           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4349                 if(appData.debugMode)
4350                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4351                 safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
4352           }
4353           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4354                                 &fromX, &fromY, &toX, &toY, &promoChar)
4355                || ParseOneMove(buf, moveNum - 1, &moveType,
4356                                 &fromX, &fromY, &toX, &toY, &promoChar);
4357           // end of long SAN patch
4358           if (valid) {
4359             (void) CoordsToAlgebraic(boards[moveNum - 1],
4360                                      PosFlags(moveNum - 1),
4361                                      fromY, fromX, toY, toX, promoChar,
4362                                      parseList[moveNum-1]);
4363             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4364               case MT_NONE:
4365               case MT_STALEMATE:
4366               default:
4367                 break;
4368               case MT_CHECK:
4369                 if(gameInfo.variant != VariantShogi)
4370                     strcat(parseList[moveNum - 1], "+");
4371                 break;
4372               case MT_CHECKMATE:
4373               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4374                 strcat(parseList[moveNum - 1], "#");
4375                 break;
4376             }
4377             strcat(parseList[moveNum - 1], " ");
4378             strcat(parseList[moveNum - 1], elapsed_time);
4379             /* currentMoveString is set as a side-effect of ParseOneMove */
4380             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4381             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4382             strcat(moveList[moveNum - 1], "\n");
4383
4384             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4385               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4386                 ChessSquare old, new = boards[moveNum][k][j];
4387                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4388                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4389                   if(old == new) continue;
4390                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4391                   else if(new == WhiteWazir || new == BlackWazir) {
4392                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4393                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4394                       else boards[moveNum][k][j] = old; // preserve type of Gold
4395                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4396                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4397               }
4398           } else {
4399             /* Move from ICS was illegal!?  Punt. */
4400             if (appData.debugMode) {
4401               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4402               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4403             }
4404             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4405             strcat(parseList[moveNum - 1], " ");
4406             strcat(parseList[moveNum - 1], elapsed_time);
4407             moveList[moveNum - 1][0] = NULLCHAR;
4408             fromX = fromY = toX = toY = -1;
4409           }
4410         }
4411   if (appData.debugMode) {
4412     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4413     setbuf(debugFP, NULL);
4414   }
4415
4416 #if ZIPPY
4417         /* Send move to chess program (BEFORE animating it). */
4418         if (appData.zippyPlay && !newGame && newMove &&
4419            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4420
4421             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4422                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4423                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4424                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4425                             move_str);
4426                     DisplayError(str, 0);
4427                 } else {
4428                     if (first.sendTime) {
4429                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4430                     }
4431                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4432                     if (firstMove && !bookHit) {
4433                         firstMove = FALSE;
4434                         if (first.useColors) {
4435                           SendToProgram(gameMode == IcsPlayingWhite ?
4436                                         "white\ngo\n" :
4437                                         "black\ngo\n", &first);
4438                         } else {
4439                           SendToProgram("go\n", &first);
4440                         }
4441                         first.maybeThinking = TRUE;
4442                     }
4443                 }
4444             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4445               if (moveList[moveNum - 1][0] == NULLCHAR) {
4446                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4447                 DisplayError(str, 0);
4448               } else {
4449                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4450                 SendMoveToProgram(moveNum - 1, &first);
4451               }
4452             }
4453         }
4454 #endif
4455     }
4456
4457     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4458         /* If move comes from a remote source, animate it.  If it
4459            isn't remote, it will have already been animated. */
4460         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4461             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4462         }
4463         if (!pausing && appData.highlightLastMove) {
4464             SetHighlights(fromX, fromY, toX, toY);
4465         }
4466     }
4467
4468     /* Start the clocks */
4469     whiteFlag = blackFlag = FALSE;
4470     appData.clockMode = !(basetime == 0 && increment == 0);
4471     if (ticking == 0) {
4472       ics_clock_paused = TRUE;
4473       StopClocks();
4474     } else if (ticking == 1) {
4475       ics_clock_paused = FALSE;
4476     }
4477     if (gameMode == IcsIdle ||
4478         relation == RELATION_OBSERVING_STATIC ||
4479         relation == RELATION_EXAMINING ||
4480         ics_clock_paused)
4481       DisplayBothClocks();
4482     else
4483       StartClocks();
4484
4485     /* Display opponents and material strengths */
4486     if (gameInfo.variant != VariantBughouse &&
4487         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4488         if (tinyLayout || smallLayout) {
4489             if(gameInfo.variant == VariantNormal)
4490               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment);
4493             else
4494               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4495                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4496                     basetime, increment, (int) gameInfo.variant);
4497         } else {
4498             if(gameInfo.variant == VariantNormal)
4499               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4500                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4501                     basetime, increment);
4502             else
4503               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4504                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4505                     basetime, increment, VariantName(gameInfo.variant));
4506         }
4507         DisplayTitle(str);
4508   if (appData.debugMode) {
4509     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4510   }
4511     }
4512
4513
4514     /* Display the board */
4515     if (!pausing && !appData.noGUI) {
4516
4517       if (appData.premove)
4518           if (!gotPremove ||
4519              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4520              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4521               ClearPremoveHighlights();
4522
4523       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4524         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4525       DrawPosition(j, boards[currentMove]);
4526
4527       DisplayMove(moveNum - 1);
4528       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4529             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4530               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4531         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4532       }
4533     }
4534
4535     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4536 #if ZIPPY
4537     if(bookHit) { // [HGM] book: simulate book reply
4538         static char bookMove[MSG_SIZ]; // a bit generous?
4539
4540         programStats.nodes = programStats.depth = programStats.time =
4541         programStats.score = programStats.got_only_move = 0;
4542         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4543
4544         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4545         strcat(bookMove, bookHit);
4546         HandleMachineMove(bookMove, &first);
4547     }
4548 #endif
4549 }
4550
4551 void
4552 GetMoveListEvent()
4553 {
4554     char buf[MSG_SIZ];
4555     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4556         ics_getting_history = H_REQUESTED;
4557         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4558         SendToICS(buf);
4559     }
4560 }
4561
4562 void
4563 AnalysisPeriodicEvent(force)
4564      int force;
4565 {
4566     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4567          && !force) || !appData.periodicUpdates)
4568       return;
4569
4570     /* Send . command to Crafty to collect stats */
4571     SendToProgram(".\n", &first);
4572
4573     /* Don't send another until we get a response (this makes
4574        us stop sending to old Crafty's which don't understand
4575        the "." command (sending illegal cmds resets node count & time,
4576        which looks bad)) */
4577     programStats.ok_to_send = 0;
4578 }
4579
4580 void ics_update_width(new_width)
4581         int new_width;
4582 {
4583         ics_printf("set width %d\n", new_width);
4584 }
4585
4586 void
4587 SendMoveToProgram(moveNum, cps)
4588      int moveNum;
4589      ChessProgramState *cps;
4590 {
4591     char buf[MSG_SIZ];
4592
4593     if (cps->useUsermove) {
4594       SendToProgram("usermove ", cps);
4595     }
4596     if (cps->useSAN) {
4597       char *space;
4598       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4599         int len = space - parseList[moveNum];
4600         memcpy(buf, parseList[moveNum], len);
4601         buf[len++] = '\n';
4602         buf[len] = NULLCHAR;
4603       } else {
4604         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4605       }
4606       SendToProgram(buf, cps);
4607     } else {
4608       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4609         AlphaRank(moveList[moveNum], 4);
4610         SendToProgram(moveList[moveNum], cps);
4611         AlphaRank(moveList[moveNum], 4); // and back
4612       } else
4613       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4614        * the engine. It would be nice to have a better way to identify castle
4615        * moves here. */
4616       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4617                                                                          && cps->useOOCastle) {
4618         int fromX = moveList[moveNum][0] - AAA;
4619         int fromY = moveList[moveNum][1] - ONE;
4620         int toX = moveList[moveNum][2] - AAA;
4621         int toY = moveList[moveNum][3] - ONE;
4622         if((boards[moveNum][fromY][fromX] == WhiteKing
4623             && boards[moveNum][toY][toX] == WhiteRook)
4624            || (boards[moveNum][fromY][fromX] == BlackKing
4625                && boards[moveNum][toY][toX] == BlackRook)) {
4626           if(toX > fromX) SendToProgram("O-O\n", cps);
4627           else SendToProgram("O-O-O\n", cps);
4628         }
4629         else SendToProgram(moveList[moveNum], cps);
4630       }
4631       else SendToProgram(moveList[moveNum], cps);
4632       /* End of additions by Tord */
4633     }
4634
4635     /* [HGM] setting up the opening has brought engine in force mode! */
4636     /*       Send 'go' if we are in a mode where machine should play. */
4637     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4638         (gameMode == TwoMachinesPlay   ||
4639 #if ZIPPY
4640          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4641 #endif
4642          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4643         SendToProgram("go\n", cps);
4644   if (appData.debugMode) {
4645     fprintf(debugFP, "(extra)\n");
4646   }
4647     }
4648     setboardSpoiledMachineBlack = 0;
4649 }
4650
4651 void
4652 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4653      ChessMove moveType;
4654      int fromX, fromY, toX, toY;
4655      char promoChar;
4656 {
4657     char user_move[MSG_SIZ];
4658
4659     switch (moveType) {
4660       default:
4661         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4662                 (int)moveType, fromX, fromY, toX, toY);
4663         DisplayError(user_move + strlen("say "), 0);
4664         break;
4665       case WhiteKingSideCastle:
4666       case BlackKingSideCastle:
4667       case WhiteQueenSideCastleWild:
4668       case BlackQueenSideCastleWild:
4669       /* PUSH Fabien */
4670       case WhiteHSideCastleFR:
4671       case BlackHSideCastleFR:
4672       /* POP Fabien */
4673         snprintf(user_move, MSG_SIZ, "o-o\n");
4674         break;
4675       case WhiteQueenSideCastle:
4676       case BlackQueenSideCastle:
4677       case WhiteKingSideCastleWild:
4678       case BlackKingSideCastleWild:
4679       /* PUSH Fabien */
4680       case WhiteASideCastleFR:
4681       case BlackASideCastleFR:
4682       /* POP Fabien */
4683         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4684         break;
4685       case WhiteNonPromotion:
4686       case BlackNonPromotion:
4687         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4688         break;
4689       case WhitePromotion:
4690       case BlackPromotion:
4691         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4692           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4693                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4694                 PieceToChar(WhiteFerz));
4695         else if(gameInfo.variant == VariantGreat)
4696           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4697                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4698                 PieceToChar(WhiteMan));
4699         else
4700           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4701                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4702                 promoChar);
4703         break;
4704       case WhiteDrop:
4705       case BlackDrop:
4706       drop:
4707         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4708                  ToUpper(PieceToChar((ChessSquare) fromX)),
4709                  AAA + toX, ONE + toY);
4710         break;
4711       case IllegalMove:  /* could be a variant we don't quite understand */
4712         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4713       case NormalMove:
4714       case WhiteCapturesEnPassant:
4715       case BlackCapturesEnPassant:
4716         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4717                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4718         break;
4719     }
4720     SendToICS(user_move);
4721     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4722         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4723 }
4724
4725 void
4726 UploadGameEvent()
4727 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4728     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4729     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4730     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4731         DisplayError("You cannot do this while you are playing or observing", 0);
4732         return;
4733     }
4734     if(gameMode != IcsExamining) { // is this ever not the case?
4735         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4736
4737         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4738           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4739         } else { // on FICS we must first go to general examine mode
4740           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4741         }
4742         if(gameInfo.variant != VariantNormal) {
4743             // try figure out wild number, as xboard names are not always valid on ICS
4744             for(i=1; i<=36; i++) {
4745               snprintf(buf, MSG_SIZ, "wild/%d", i);
4746                 if(StringToVariant(buf) == gameInfo.variant) break;
4747             }
4748             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4749             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4750             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4751         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4752         SendToICS(ics_prefix);
4753         SendToICS(buf);
4754         if(startedFromSetupPosition || backwardMostMove != 0) {
4755           fen = PositionToFEN(backwardMostMove, NULL);
4756           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4757             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4758             SendToICS(buf);
4759           } else { // FICS: everything has to set by separate bsetup commands
4760             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4761             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4762             SendToICS(buf);
4763             if(!WhiteOnMove(backwardMostMove)) {
4764                 SendToICS("bsetup tomove black\n");
4765             }
4766             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4767             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4768             SendToICS(buf);
4769             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4770             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4771             SendToICS(buf);
4772             i = boards[backwardMostMove][EP_STATUS];
4773             if(i >= 0) { // set e.p.
4774               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4775                 SendToICS(buf);
4776             }
4777             bsetup++;
4778           }
4779         }
4780       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4781             SendToICS("bsetup done\n"); // switch to normal examining.
4782     }
4783     for(i = backwardMostMove; i<last; i++) {
4784         char buf[20];
4785         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4786         SendToICS(buf);
4787     }
4788     SendToICS(ics_prefix);
4789     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4790 }
4791
4792 void
4793 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4794      int rf, ff, rt, ft;
4795      char promoChar;
4796      char move[7];
4797 {
4798     if (rf == DROP_RANK) {
4799       sprintf(move, "%c@%c%c\n",
4800                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4801     } else {
4802         if (promoChar == 'x' || promoChar == NULLCHAR) {
4803           sprintf(move, "%c%c%c%c\n",
4804                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4805         } else {
4806             sprintf(move, "%c%c%c%c%c\n",
4807                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4808         }
4809     }
4810 }
4811
4812 void
4813 ProcessICSInitScript(f)
4814      FILE *f;
4815 {
4816     char buf[MSG_SIZ];
4817
4818     while (fgets(buf, MSG_SIZ, f)) {
4819         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4820     }
4821
4822     fclose(f);
4823 }
4824
4825
4826 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4827 void
4828 AlphaRank(char *move, int n)
4829 {
4830 //    char *p = move, c; int x, y;
4831
4832     if (appData.debugMode) {
4833         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4834     }
4835
4836     if(move[1]=='*' &&
4837        move[2]>='0' && move[2]<='9' &&
4838        move[3]>='a' && move[3]<='x'    ) {
4839         move[1] = '@';
4840         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4841         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4842     } else
4843     if(move[0]>='0' && move[0]<='9' &&
4844        move[1]>='a' && move[1]<='x' &&
4845        move[2]>='0' && move[2]<='9' &&
4846        move[3]>='a' && move[3]<='x'    ) {
4847         /* input move, Shogi -> normal */
4848         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4849         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4850         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4851         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4852     } else
4853     if(move[1]=='@' &&
4854        move[3]>='0' && move[3]<='9' &&
4855        move[2]>='a' && move[2]<='x'    ) {
4856         move[1] = '*';
4857         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4858         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4859     } else
4860     if(
4861        move[0]>='a' && move[0]<='x' &&
4862        move[3]>='0' && move[3]<='9' &&
4863        move[2]>='a' && move[2]<='x'    ) {
4864          /* output move, normal -> Shogi */
4865         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4866         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4867         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4868         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4869         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4870     }
4871     if (appData.debugMode) {
4872         fprintf(debugFP, "   out = '%s'\n", move);
4873     }
4874 }
4875
4876 char yy_textstr[8000];
4877
4878 /* Parser for moves from gnuchess, ICS, or user typein box */
4879 Boolean
4880 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4881      char *move;
4882      int moveNum;
4883      ChessMove *moveType;
4884      int *fromX, *fromY, *toX, *toY;
4885      char *promoChar;
4886 {
4887     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4888
4889     switch (*moveType) {
4890       case WhitePromotion:
4891       case BlackPromotion:
4892       case WhiteNonPromotion:
4893       case BlackNonPromotion:
4894       case NormalMove:
4895       case WhiteCapturesEnPassant:
4896       case BlackCapturesEnPassant:
4897       case WhiteKingSideCastle:
4898       case WhiteQueenSideCastle:
4899       case BlackKingSideCastle:
4900       case BlackQueenSideCastle:
4901       case WhiteKingSideCastleWild:
4902       case WhiteQueenSideCastleWild:
4903       case BlackKingSideCastleWild:
4904       case BlackQueenSideCastleWild:
4905       /* Code added by Tord: */
4906       case WhiteHSideCastleFR:
4907       case WhiteASideCastleFR:
4908       case BlackHSideCastleFR:
4909       case BlackASideCastleFR:
4910       /* End of code added by Tord */
4911       case IllegalMove:         /* bug or odd chess variant */
4912         *fromX = currentMoveString[0] - AAA;
4913         *fromY = currentMoveString[1] - ONE;
4914         *toX = currentMoveString[2] - AAA;
4915         *toY = currentMoveString[3] - ONE;
4916         *promoChar = currentMoveString[4];
4917         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4918             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4919     if (appData.debugMode) {
4920         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4921     }
4922             *fromX = *fromY = *toX = *toY = 0;
4923             return FALSE;
4924         }
4925         if (appData.testLegality) {
4926           return (*moveType != IllegalMove);
4927         } else {
4928           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4929                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4930         }
4931
4932       case WhiteDrop:
4933       case BlackDrop:
4934         *fromX = *moveType == WhiteDrop ?
4935           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4936           (int) CharToPiece(ToLower(currentMoveString[0]));
4937         *fromY = DROP_RANK;
4938         *toX = currentMoveString[2] - AAA;
4939         *toY = currentMoveString[3] - ONE;
4940         *promoChar = NULLCHAR;
4941         return TRUE;
4942
4943       case AmbiguousMove:
4944       case ImpossibleMove:
4945       case EndOfFile:
4946       case ElapsedTime:
4947       case Comment:
4948       case PGNTag:
4949       case NAG:
4950       case WhiteWins:
4951       case BlackWins:
4952       case GameIsDrawn:
4953       default:
4954     if (appData.debugMode) {
4955         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4956     }
4957         /* bug? */
4958         *fromX = *fromY = *toX = *toY = 0;
4959         *promoChar = NULLCHAR;
4960         return FALSE;
4961     }
4962 }
4963
4964
4965 void
4966 ParsePV(char *pv, Boolean storeComments)
4967 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4968   int fromX, fromY, toX, toY; char promoChar;
4969   ChessMove moveType;
4970   Boolean valid;
4971   int nr = 0;
4972
4973   endPV = forwardMostMove;
4974   do {
4975     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4976     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4977     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4978 if(appData.debugMode){
4979 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);
4980 }
4981     if(!valid && nr == 0 &&
4982        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4983         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4984         // Hande case where played move is different from leading PV move
4985         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4986         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4987         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4988         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4989           endPV += 2; // if position different, keep this
4990           moveList[endPV-1][0] = fromX + AAA;
4991           moveList[endPV-1][1] = fromY + ONE;
4992           moveList[endPV-1][2] = toX + AAA;
4993           moveList[endPV-1][3] = toY + ONE;
4994           parseList[endPV-1][0] = NULLCHAR;
4995           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4996         }
4997       }
4998     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4999     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5000     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5001     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5002         valid++; // allow comments in PV
5003         continue;
5004     }
5005     nr++;
5006     if(endPV+1 > framePtr) break; // no space, truncate
5007     if(!valid) break;
5008     endPV++;
5009     CopyBoard(boards[endPV], boards[endPV-1]);
5010     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5011     moveList[endPV-1][0] = fromX + AAA;
5012     moveList[endPV-1][1] = fromY + ONE;
5013     moveList[endPV-1][2] = toX + AAA;
5014     moveList[endPV-1][3] = toY + ONE;
5015     if(storeComments)
5016         CoordsToAlgebraic(boards[endPV - 1],
5017                              PosFlags(endPV - 1),
5018                              fromY, fromX, toY, toX, promoChar,
5019                              parseList[endPV - 1]);
5020     else
5021         parseList[endPV-1][0] = NULLCHAR;
5022   } while(valid);
5023   currentMove = endPV;
5024   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5025   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5026                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5027   DrawPosition(TRUE, boards[currentMove]);
5028 }
5029
5030 static int lastX, lastY;
5031
5032 Boolean
5033 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5034 {
5035         int startPV;
5036         char *p;
5037
5038         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5039         lastX = x; lastY = y;
5040         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5041         startPV = index;
5042         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5043         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5044         index = startPV;
5045         do{ while(buf[index] && buf[index] != '\n') index++;
5046         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5047         buf[index] = 0;
5048         ParsePV(buf+startPV, FALSE);
5049         *start = startPV; *end = index-1;
5050         return TRUE;
5051 }
5052
5053 Boolean
5054 LoadPV(int x, int y)
5055 { // called on right mouse click to load PV
5056   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5057   lastX = x; lastY = y;
5058   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5059   return TRUE;
5060 }
5061
5062 void
5063 UnLoadPV()
5064 {
5065   if(endPV < 0) return;
5066   endPV = -1;
5067   currentMove = forwardMostMove;
5068   ClearPremoveHighlights();
5069   DrawPosition(TRUE, boards[currentMove]);
5070 }
5071
5072 void
5073 MovePV(int x, int y, int h)
5074 { // step through PV based on mouse coordinates (called on mouse move)
5075   int margin = h>>3, step = 0;
5076
5077   if(endPV < 0) return;
5078   // we must somehow check if right button is still down (might be released off board!)
5079   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5080   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5081   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5082   if(!step) return;
5083   lastX = x; lastY = y;
5084   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5085   currentMove += step;
5086   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5087   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5088                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5089   DrawPosition(FALSE, boards[currentMove]);
5090 }
5091
5092
5093 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5094 // All positions will have equal probability, but the current method will not provide a unique
5095 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5096 #define DARK 1
5097 #define LITE 2
5098 #define ANY 3
5099
5100 int squaresLeft[4];
5101 int piecesLeft[(int)BlackPawn];
5102 int seed, nrOfShuffles;
5103
5104 void GetPositionNumber()
5105 {       // sets global variable seed
5106         int i;
5107
5108         seed = appData.defaultFrcPosition;
5109         if(seed < 0) { // randomize based on time for negative FRC position numbers
5110                 for(i=0; i<50; i++) seed += random();
5111                 seed = random() ^ random() >> 8 ^ random() << 8;
5112                 if(seed<0) seed = -seed;
5113         }
5114 }
5115
5116 int put(Board board, int pieceType, int rank, int n, int shade)
5117 // put the piece on the (n-1)-th empty squares of the given shade
5118 {
5119         int i;
5120
5121         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5122                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5123                         board[rank][i] = (ChessSquare) pieceType;
5124                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5125                         squaresLeft[ANY]--;
5126                         piecesLeft[pieceType]--;
5127                         return i;
5128                 }
5129         }
5130         return -1;
5131 }
5132
5133
5134 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5135 // calculate where the next piece goes, (any empty square), and put it there
5136 {
5137         int i;
5138
5139         i = seed % squaresLeft[shade];
5140         nrOfShuffles *= squaresLeft[shade];
5141         seed /= squaresLeft[shade];
5142         put(board, pieceType, rank, i, shade);
5143 }
5144
5145 void AddTwoPieces(Board board, int pieceType, int rank)
5146 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5147 {
5148         int i, n=squaresLeft[ANY], j=n-1, k;
5149
5150         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5151         i = seed % k;  // pick one
5152         nrOfShuffles *= k;
5153         seed /= k;
5154         while(i >= j) i -= j--;
5155         j = n - 1 - j; i += j;
5156         put(board, pieceType, rank, j, ANY);
5157         put(board, pieceType, rank, i, ANY);
5158 }
5159
5160 void SetUpShuffle(Board board, int number)
5161 {
5162         int i, p, first=1;
5163
5164         GetPositionNumber(); nrOfShuffles = 1;
5165
5166         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5167         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5168         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5169
5170         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5171
5172         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5173             p = (int) board[0][i];
5174             if(p < (int) BlackPawn) piecesLeft[p] ++;
5175             board[0][i] = EmptySquare;
5176         }
5177
5178         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5179             // shuffles restricted to allow normal castling put KRR first
5180             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5181                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5182             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5183                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5184             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5185                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5186             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5187                 put(board, WhiteRook, 0, 0, ANY);
5188             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5189         }
5190
5191         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5192             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5193             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5194                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5195                 while(piecesLeft[p] >= 2) {
5196                     AddOnePiece(board, p, 0, LITE);
5197                     AddOnePiece(board, p, 0, DARK);
5198                 }
5199                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5200             }
5201
5202         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5203             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5204             // but we leave King and Rooks for last, to possibly obey FRC restriction
5205             if(p == (int)WhiteRook) continue;
5206             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5207             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5208         }
5209
5210         // now everything is placed, except perhaps King (Unicorn) and Rooks
5211
5212         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5213             // Last King gets castling rights
5214             while(piecesLeft[(int)WhiteUnicorn]) {
5215                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5216                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5217             }
5218
5219             while(piecesLeft[(int)WhiteKing]) {
5220                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5221                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5222             }
5223
5224
5225         } else {
5226             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5227             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5228         }
5229
5230         // Only Rooks can be left; simply place them all
5231         while(piecesLeft[(int)WhiteRook]) {
5232                 i = put(board, WhiteRook, 0, 0, ANY);
5233                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5234                         if(first) {
5235                                 first=0;
5236                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5237                         }
5238                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5239                 }
5240         }
5241         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5242             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5243         }
5244
5245         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5246 }
5247
5248 int SetCharTable( char *table, const char * map )
5249 /* [HGM] moved here from winboard.c because of its general usefulness */
5250 /*       Basically a safe strcpy that uses the last character as King */
5251 {
5252     int result = FALSE; int NrPieces;
5253
5254     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5255                     && NrPieces >= 12 && !(NrPieces&1)) {
5256         int i; /* [HGM] Accept even length from 12 to 34 */
5257
5258         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5259         for( i=0; i<NrPieces/2-1; i++ ) {
5260             table[i] = map[i];
5261             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5262         }
5263         table[(int) WhiteKing]  = map[NrPieces/2-1];
5264         table[(int) BlackKing]  = map[NrPieces-1];
5265
5266         result = TRUE;
5267     }
5268
5269     return result;
5270 }
5271
5272 void Prelude(Board board)
5273 {       // [HGM] superchess: random selection of exo-pieces
5274         int i, j, k; ChessSquare p;
5275         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5276
5277         GetPositionNumber(); // use FRC position number
5278
5279         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5280             SetCharTable(pieceToChar, appData.pieceToCharTable);
5281             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5282                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5283         }
5284
5285         j = seed%4;                 seed /= 4;
5286         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5287         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5288         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5289         j = seed%3 + (seed%3 >= j); seed /= 3;
5290         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5291         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5292         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5293         j = seed%3;                 seed /= 3;
5294         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5295         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5296         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5297         j = seed%2 + (seed%2 >= j); seed /= 2;
5298         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5299         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5300         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5301         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5302         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5303         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5304         put(board, exoPieces[0],    0, 0, ANY);
5305         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5306 }
5307
5308 void
5309 InitPosition(redraw)
5310      int redraw;
5311 {
5312     ChessSquare (* pieces)[BOARD_FILES];
5313     int i, j, pawnRow, overrule,
5314     oldx = gameInfo.boardWidth,
5315     oldy = gameInfo.boardHeight,
5316     oldh = gameInfo.holdingsWidth,
5317     oldv = gameInfo.variant;
5318
5319     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5320
5321     /* [AS] Initialize pv info list [HGM] and game status */
5322     {
5323         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5324             pvInfoList[i].depth = 0;
5325             boards[i][EP_STATUS] = EP_NONE;
5326             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5327         }
5328
5329         initialRulePlies = 0; /* 50-move counter start */
5330
5331         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5332         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5333     }
5334
5335
5336     /* [HGM] logic here is completely changed. In stead of full positions */
5337     /* the initialized data only consist of the two backranks. The switch */
5338     /* selects which one we will use, which is than copied to the Board   */
5339     /* initialPosition, which for the rest is initialized by Pawns and    */
5340     /* empty squares. This initial position is then copied to boards[0],  */
5341     /* possibly after shuffling, so that it remains available.            */
5342
5343     gameInfo.holdingsWidth = 0; /* default board sizes */
5344     gameInfo.boardWidth    = 8;
5345     gameInfo.boardHeight   = 8;
5346     gameInfo.holdingsSize  = 0;
5347     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5348     for(i=0; i<BOARD_FILES-2; i++)
5349       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5350     initialPosition[EP_STATUS] = EP_NONE;
5351     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5352     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5353          SetCharTable(pieceNickName, appData.pieceNickNames);
5354     else SetCharTable(pieceNickName, "............");
5355
5356     switch (gameInfo.variant) {
5357     case VariantFischeRandom:
5358       shuffleOpenings = TRUE;
5359     default:
5360       pieces = FIDEArray;
5361       break;
5362     case VariantShatranj:
5363       pieces = ShatranjArray;
5364       nrCastlingRights = 0;
5365       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5366       break;
5367     case VariantMakruk:
5368       pieces = makrukArray;
5369       nrCastlingRights = 0;
5370       startedFromSetupPosition = TRUE;
5371       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5372       break;
5373     case VariantTwoKings:
5374       pieces = twoKingsArray;
5375       break;
5376     case VariantCapaRandom:
5377       shuffleOpenings = TRUE;
5378     case VariantCapablanca:
5379       pieces = CapablancaArray;
5380       gameInfo.boardWidth = 10;
5381       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5382       break;
5383     case VariantGothic:
5384       pieces = GothicArray;
5385       gameInfo.boardWidth = 10;
5386       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5387       break;
5388     case VariantJanus:
5389       pieces = JanusArray;
5390       gameInfo.boardWidth = 10;
5391       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5392       nrCastlingRights = 6;
5393         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5394         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5395         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5396         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5397         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5398         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5399       break;
5400     case VariantFalcon:
5401       pieces = FalconArray;
5402       gameInfo.boardWidth = 10;
5403       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5404       break;
5405     case VariantXiangqi:
5406       pieces = XiangqiArray;
5407       gameInfo.boardWidth  = 9;
5408       gameInfo.boardHeight = 10;
5409       nrCastlingRights = 0;
5410       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5411       break;
5412     case VariantShogi:
5413       pieces = ShogiArray;
5414       gameInfo.boardWidth  = 9;
5415       gameInfo.boardHeight = 9;
5416       gameInfo.holdingsSize = 7;
5417       nrCastlingRights = 0;
5418       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5419       break;
5420     case VariantCourier:
5421       pieces = CourierArray;
5422       gameInfo.boardWidth  = 12;
5423       nrCastlingRights = 0;
5424       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5425       break;
5426     case VariantKnightmate:
5427       pieces = KnightmateArray;
5428       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5429       break;
5430     case VariantFairy:
5431       pieces = fairyArray;
5432       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5433       break;
5434     case VariantGreat:
5435       pieces = GreatArray;
5436       gameInfo.boardWidth = 10;
5437       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5438       gameInfo.holdingsSize = 8;
5439       break;
5440     case VariantSuper:
5441       pieces = FIDEArray;
5442       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5443       gameInfo.holdingsSize = 8;
5444       startedFromSetupPosition = TRUE;
5445       break;
5446     case VariantCrazyhouse:
5447     case VariantBughouse:
5448       pieces = FIDEArray;
5449       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5450       gameInfo.holdingsSize = 5;
5451       break;
5452     case VariantWildCastle:
5453       pieces = FIDEArray;
5454       /* !!?shuffle with kings guaranteed to be on d or e file */
5455       shuffleOpenings = 1;
5456       break;
5457     case VariantNoCastle:
5458       pieces = FIDEArray;
5459       nrCastlingRights = 0;
5460       /* !!?unconstrained back-rank shuffle */
5461       shuffleOpenings = 1;
5462       break;
5463     }
5464
5465     overrule = 0;
5466     if(appData.NrFiles >= 0) {
5467         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5468         gameInfo.boardWidth = appData.NrFiles;
5469     }
5470     if(appData.NrRanks >= 0) {
5471         gameInfo.boardHeight = appData.NrRanks;
5472     }
5473     if(appData.holdingsSize >= 0) {
5474         i = appData.holdingsSize;
5475         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5476         gameInfo.holdingsSize = i;
5477     }
5478     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5479     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5480         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5481
5482     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5483     if(pawnRow < 1) pawnRow = 1;
5484     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5485
5486     /* User pieceToChar list overrules defaults */
5487     if(appData.pieceToCharTable != NULL)
5488         SetCharTable(pieceToChar, appData.pieceToCharTable);
5489
5490     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5491
5492         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5493             s = (ChessSquare) 0; /* account holding counts in guard band */
5494         for( i=0; i<BOARD_HEIGHT; i++ )
5495             initialPosition[i][j] = s;
5496
5497         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5498         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5499         initialPosition[pawnRow][j] = WhitePawn;
5500         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5501         if(gameInfo.variant == VariantXiangqi) {
5502             if(j&1) {
5503                 initialPosition[pawnRow][j] =
5504                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5505                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5506                    initialPosition[2][j] = WhiteCannon;
5507                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5508                 }
5509             }
5510         }
5511         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5512     }
5513     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5514
5515             j=BOARD_LEFT+1;
5516             initialPosition[1][j] = WhiteBishop;
5517             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5518             j=BOARD_RGHT-2;
5519             initialPosition[1][j] = WhiteRook;
5520             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5521     }
5522
5523     if( nrCastlingRights == -1) {
5524         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5525         /*       This sets default castling rights from none to normal corners   */
5526         /* Variants with other castling rights must set them themselves above    */
5527         nrCastlingRights = 6;
5528
5529         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5530         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5531         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5532         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5533         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5534         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5535      }
5536
5537      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5538      if(gameInfo.variant == VariantGreat) { // promotion commoners
5539         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5540         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5541         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5542         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5543      }
5544   if (appData.debugMode) {
5545     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5546   }
5547     if(shuffleOpenings) {
5548         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5549         startedFromSetupPosition = TRUE;
5550     }
5551     if(startedFromPositionFile) {
5552       /* [HGM] loadPos: use PositionFile for every new game */
5553       CopyBoard(initialPosition, filePosition);
5554       for(i=0; i<nrCastlingRights; i++)
5555           initialRights[i] = filePosition[CASTLING][i];
5556       startedFromSetupPosition = TRUE;
5557     }
5558
5559     CopyBoard(boards[0], initialPosition);
5560
5561     if(oldx != gameInfo.boardWidth ||
5562        oldy != gameInfo.boardHeight ||
5563        oldh != gameInfo.holdingsWidth
5564 #ifdef GOTHIC
5565        || oldv == VariantGothic ||        // For licensing popups
5566        gameInfo.variant == VariantGothic
5567 #endif
5568 #ifdef FALCON
5569        || oldv == VariantFalcon ||
5570        gameInfo.variant == VariantFalcon
5571 #endif
5572                                          )
5573             InitDrawingSizes(-2 ,0);
5574
5575     if (redraw)
5576       DrawPosition(TRUE, boards[currentMove]);
5577 }
5578
5579 void
5580 SendBoard(cps, moveNum)
5581      ChessProgramState *cps;
5582      int moveNum;
5583 {
5584     char message[MSG_SIZ];
5585
5586     if (cps->useSetboard) {
5587       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5588       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5589       SendToProgram(message, cps);
5590       free(fen);
5591
5592     } else {
5593       ChessSquare *bp;
5594       int i, j;
5595       /* Kludge to set black to move, avoiding the troublesome and now
5596        * deprecated "black" command.
5597        */
5598       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5599         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5600
5601       SendToProgram("edit\n", cps);
5602       SendToProgram("#\n", cps);
5603       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5604         bp = &boards[moveNum][i][BOARD_LEFT];
5605         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5606           if ((int) *bp < (int) BlackPawn) {
5607             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5608                     AAA + j, ONE + i);
5609             if(message[0] == '+' || message[0] == '~') {
5610               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5611                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5612                         AAA + j, ONE + i);
5613             }
5614             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5615                 message[1] = BOARD_RGHT   - 1 - j + '1';
5616                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5617             }
5618             SendToProgram(message, cps);
5619           }
5620         }
5621       }
5622
5623       SendToProgram("c\n", cps);
5624       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5625         bp = &boards[moveNum][i][BOARD_LEFT];
5626         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5627           if (((int) *bp != (int) EmptySquare)
5628               && ((int) *bp >= (int) BlackPawn)) {
5629             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5630                     AAA + j, ONE + i);
5631             if(message[0] == '+' || message[0] == '~') {
5632               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5633                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5634                         AAA + j, ONE + i);
5635             }
5636             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5637                 message[1] = BOARD_RGHT   - 1 - j + '1';
5638                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5639             }
5640             SendToProgram(message, cps);
5641           }
5642         }
5643       }
5644
5645       SendToProgram(".\n", cps);
5646     }
5647     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5648 }
5649
5650 static int autoQueen; // [HGM] oneclick
5651
5652 int
5653 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5654 {
5655     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5656     /* [HGM] add Shogi promotions */
5657     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5658     ChessSquare piece;
5659     ChessMove moveType;
5660     Boolean premove;
5661
5662     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5663     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5664
5665     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5666       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5667         return FALSE;
5668
5669     piece = boards[currentMove][fromY][fromX];
5670     if(gameInfo.variant == VariantShogi) {
5671         promotionZoneSize = BOARD_HEIGHT/3;
5672         highestPromotingPiece = (int)WhiteFerz;
5673     } else if(gameInfo.variant == VariantMakruk) {
5674         promotionZoneSize = 3;
5675     }
5676
5677     // next weed out all moves that do not touch the promotion zone at all
5678     if((int)piece >= BlackPawn) {
5679         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5680              return FALSE;
5681         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5682     } else {
5683         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5684            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5685     }
5686
5687     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5688
5689     // weed out mandatory Shogi promotions
5690     if(gameInfo.variant == VariantShogi) {
5691         if(piece >= BlackPawn) {
5692             if(toY == 0 && piece == BlackPawn ||
5693                toY == 0 && piece == BlackQueen ||
5694                toY <= 1 && piece == BlackKnight) {
5695                 *promoChoice = '+';
5696                 return FALSE;
5697             }
5698         } else {
5699             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5700                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5701                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5702                 *promoChoice = '+';
5703                 return FALSE;
5704             }
5705         }
5706     }
5707
5708     // weed out obviously illegal Pawn moves
5709     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5710         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5711         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5712         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5713         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5714         // note we are not allowed to test for valid (non-)capture, due to premove
5715     }
5716
5717     // we either have a choice what to promote to, or (in Shogi) whether to promote
5718     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5719         *promoChoice = PieceToChar(BlackFerz);  // no choice
5720         return FALSE;
5721     }
5722     // no sense asking what we must promote to if it is going to explode...
5723     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5724         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5725         return FALSE;
5726     }
5727     if(autoQueen) { // predetermined
5728         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5729              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5730         else *promoChoice = PieceToChar(BlackQueen);
5731         return FALSE;
5732     }
5733
5734     // suppress promotion popup on illegal moves that are not premoves
5735     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5736               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5737     if(appData.testLegality && !premove) {
5738         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5739                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5740         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5741             return FALSE;
5742     }
5743
5744     return TRUE;
5745 }
5746
5747 int
5748 InPalace(row, column)
5749      int row, column;
5750 {   /* [HGM] for Xiangqi */
5751     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5752          column < (BOARD_WIDTH + 4)/2 &&
5753          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5754     return FALSE;
5755 }
5756
5757 int
5758 PieceForSquare (x, y)
5759      int x;
5760      int y;
5761 {
5762   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5763      return -1;
5764   else
5765      return boards[currentMove][y][x];
5766 }
5767
5768 int
5769 OKToStartUserMove(x, y)
5770      int x, y;
5771 {
5772     ChessSquare from_piece;
5773     int white_piece;
5774
5775     if (matchMode) return FALSE;
5776     if (gameMode == EditPosition) return TRUE;
5777
5778     if (x >= 0 && y >= 0)
5779       from_piece = boards[currentMove][y][x];
5780     else
5781       from_piece = EmptySquare;
5782
5783     if (from_piece == EmptySquare) return FALSE;
5784
5785     white_piece = (int)from_piece >= (int)WhitePawn &&
5786       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5787
5788     switch (gameMode) {
5789       case PlayFromGameFile:
5790       case AnalyzeFile:
5791       case TwoMachinesPlay:
5792       case EndOfGame:
5793         return FALSE;
5794
5795       case IcsObserving:
5796       case IcsIdle:
5797         return FALSE;
5798
5799       case MachinePlaysWhite:
5800       case IcsPlayingBlack:
5801         if (appData.zippyPlay) return FALSE;
5802         if (white_piece) {
5803             DisplayMoveError(_("You are playing Black"));
5804             return FALSE;
5805         }
5806         break;
5807
5808       case MachinePlaysBlack:
5809       case IcsPlayingWhite:
5810         if (appData.zippyPlay) return FALSE;
5811         if (!white_piece) {
5812             DisplayMoveError(_("You are playing White"));
5813             return FALSE;
5814         }
5815         break;
5816
5817       case EditGame:
5818         if (!white_piece && WhiteOnMove(currentMove)) {
5819             DisplayMoveError(_("It is White's turn"));
5820             return FALSE;
5821         }
5822         if (white_piece && !WhiteOnMove(currentMove)) {
5823             DisplayMoveError(_("It is Black's turn"));
5824             return FALSE;
5825         }
5826         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5827             /* Editing correspondence game history */
5828             /* Could disallow this or prompt for confirmation */
5829             cmailOldMove = -1;
5830         }
5831         break;
5832
5833       case BeginningOfGame:
5834         if (appData.icsActive) return FALSE;
5835         if (!appData.noChessProgram) {
5836             if (!white_piece) {
5837                 DisplayMoveError(_("You are playing White"));
5838                 return FALSE;
5839             }
5840         }
5841         break;
5842
5843       case Training:
5844         if (!white_piece && WhiteOnMove(currentMove)) {
5845             DisplayMoveError(_("It is White's turn"));
5846             return FALSE;
5847         }
5848         if (white_piece && !WhiteOnMove(currentMove)) {
5849             DisplayMoveError(_("It is Black's turn"));
5850             return FALSE;
5851         }
5852         break;
5853
5854       default:
5855       case IcsExamining:
5856         break;
5857     }
5858     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5859         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5860         && gameMode != AnalyzeFile && gameMode != Training) {
5861         DisplayMoveError(_("Displayed position is not current"));
5862         return FALSE;
5863     }
5864     return TRUE;
5865 }
5866
5867 Boolean
5868 OnlyMove(int *x, int *y, Boolean captures) {
5869     DisambiguateClosure cl;
5870     if (appData.zippyPlay) return FALSE;
5871     switch(gameMode) {
5872       case MachinePlaysBlack:
5873       case IcsPlayingWhite:
5874       case BeginningOfGame:
5875         if(!WhiteOnMove(currentMove)) return FALSE;
5876         break;
5877       case MachinePlaysWhite:
5878       case IcsPlayingBlack:
5879         if(WhiteOnMove(currentMove)) return FALSE;
5880         break;
5881       case EditGame:
5882         break;
5883       default:
5884         return FALSE;
5885     }
5886     cl.pieceIn = EmptySquare;
5887     cl.rfIn = *y;
5888     cl.ffIn = *x;
5889     cl.rtIn = -1;
5890     cl.ftIn = -1;
5891     cl.promoCharIn = NULLCHAR;
5892     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5893     if( cl.kind == NormalMove ||
5894         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5895         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5896         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5897       fromX = cl.ff;
5898       fromY = cl.rf;
5899       *x = cl.ft;
5900       *y = cl.rt;
5901       return TRUE;
5902     }
5903     if(cl.kind != ImpossibleMove) return FALSE;
5904     cl.pieceIn = EmptySquare;
5905     cl.rfIn = -1;
5906     cl.ffIn = -1;
5907     cl.rtIn = *y;
5908     cl.ftIn = *x;
5909     cl.promoCharIn = NULLCHAR;
5910     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5911     if( cl.kind == NormalMove ||
5912         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5913         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5914         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5915       fromX = cl.ff;
5916       fromY = cl.rf;
5917       *x = cl.ft;
5918       *y = cl.rt;
5919       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5920       return TRUE;
5921     }
5922     return FALSE;
5923 }
5924
5925 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5926 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5927 int lastLoadGameUseList = FALSE;
5928 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5929 ChessMove lastLoadGameStart = EndOfFile;
5930
5931 void
5932 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5933      int fromX, fromY, toX, toY;
5934      int promoChar;
5935 {
5936     ChessMove moveType;
5937     ChessSquare pdown, pup;
5938
5939     /* Check if the user is playing in turn.  This is complicated because we
5940        let the user "pick up" a piece before it is his turn.  So the piece he
5941        tried to pick up may have been captured by the time he puts it down!
5942        Therefore we use the color the user is supposed to be playing in this
5943        test, not the color of the piece that is currently on the starting
5944        square---except in EditGame mode, where the user is playing both
5945        sides; fortunately there the capture race can't happen.  (It can
5946        now happen in IcsExamining mode, but that's just too bad.  The user
5947        will get a somewhat confusing message in that case.)
5948        */
5949
5950     switch (gameMode) {
5951       case PlayFromGameFile:
5952       case AnalyzeFile:
5953       case TwoMachinesPlay:
5954       case EndOfGame:
5955       case IcsObserving:
5956       case IcsIdle:
5957         /* We switched into a game mode where moves are not accepted,
5958            perhaps while the mouse button was down. */
5959         return;
5960
5961       case MachinePlaysWhite:
5962         /* User is moving for Black */
5963         if (WhiteOnMove(currentMove)) {
5964             DisplayMoveError(_("It is White's turn"));
5965             return;
5966         }
5967         break;
5968
5969       case MachinePlaysBlack:
5970         /* User is moving for White */
5971         if (!WhiteOnMove(currentMove)) {
5972             DisplayMoveError(_("It is Black's turn"));
5973             return;
5974         }
5975         break;
5976
5977       case EditGame:
5978       case IcsExamining:
5979       case BeginningOfGame:
5980       case AnalyzeMode:
5981       case Training:
5982         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5983             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5984             /* User is moving for Black */
5985             if (WhiteOnMove(currentMove)) {
5986                 DisplayMoveError(_("It is White's turn"));
5987                 return;
5988             }
5989         } else {
5990             /* User is moving for White */
5991             if (!WhiteOnMove(currentMove)) {
5992                 DisplayMoveError(_("It is Black's turn"));
5993                 return;
5994             }
5995         }
5996         break;
5997
5998       case IcsPlayingBlack:
5999         /* User is moving for Black */
6000         if (WhiteOnMove(currentMove)) {
6001             if (!appData.premove) {
6002                 DisplayMoveError(_("It is White's turn"));
6003             } else if (toX >= 0 && toY >= 0) {
6004                 premoveToX = toX;
6005                 premoveToY = toY;
6006                 premoveFromX = fromX;
6007                 premoveFromY = fromY;
6008                 premovePromoChar = promoChar;
6009                 gotPremove = 1;
6010                 if (appData.debugMode)
6011                     fprintf(debugFP, "Got premove: fromX %d,"
6012                             "fromY %d, toX %d, toY %d\n",
6013                             fromX, fromY, toX, toY);
6014             }
6015             return;
6016         }
6017         break;
6018
6019       case IcsPlayingWhite:
6020         /* User is moving for White */
6021         if (!WhiteOnMove(currentMove)) {
6022             if (!appData.premove) {
6023                 DisplayMoveError(_("It is Black's turn"));
6024             } else if (toX >= 0 && toY >= 0) {
6025                 premoveToX = toX;
6026                 premoveToY = toY;
6027                 premoveFromX = fromX;
6028                 premoveFromY = fromY;
6029                 premovePromoChar = promoChar;
6030                 gotPremove = 1;
6031                 if (appData.debugMode)
6032                     fprintf(debugFP, "Got premove: fromX %d,"
6033                             "fromY %d, toX %d, toY %d\n",
6034                             fromX, fromY, toX, toY);
6035             }
6036             return;
6037         }
6038         break;
6039
6040       default:
6041         break;
6042
6043       case EditPosition:
6044         /* EditPosition, empty square, or different color piece;
6045            click-click move is possible */
6046         if (toX == -2 || toY == -2) {
6047             boards[0][fromY][fromX] = EmptySquare;
6048             DrawPosition(FALSE, boards[currentMove]);
6049             return;
6050         } else if (toX >= 0 && toY >= 0) {
6051             boards[0][toY][toX] = boards[0][fromY][fromX];
6052             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6053                 if(boards[0][fromY][0] != EmptySquare) {
6054                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6055                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6056                 }
6057             } else
6058             if(fromX == BOARD_RGHT+1) {
6059                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6060                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6061                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6062                 }
6063             } else
6064             boards[0][fromY][fromX] = EmptySquare;
6065             DrawPosition(FALSE, boards[currentMove]);
6066             return;
6067         }
6068         return;
6069     }
6070
6071     if(toX < 0 || toY < 0) return;
6072     pdown = boards[currentMove][fromY][fromX];
6073     pup = boards[currentMove][toY][toX];
6074
6075     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6076     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
6077          if( pup != EmptySquare ) return;
6078          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6079            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6080                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6081            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6082            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6083            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6084            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6085          fromY = DROP_RANK;
6086     }
6087
6088     /* [HGM] always test for legality, to get promotion info */
6089     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6090                                          fromY, fromX, toY, toX, promoChar);
6091     /* [HGM] but possibly ignore an IllegalMove result */
6092     if (appData.testLegality) {
6093         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6094             DisplayMoveError(_("Illegal move"));
6095             return;
6096         }
6097     }
6098
6099     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6100 }
6101
6102 /* Common tail of UserMoveEvent and DropMenuEvent */
6103 int
6104 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6105      ChessMove moveType;
6106      int fromX, fromY, toX, toY;
6107      /*char*/int promoChar;
6108 {
6109     char *bookHit = 0;
6110
6111     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6112         // [HGM] superchess: suppress promotions to non-available piece
6113         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6114         if(WhiteOnMove(currentMove)) {
6115             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6116         } else {
6117             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6118         }
6119     }
6120
6121     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6122        move type in caller when we know the move is a legal promotion */
6123     if(moveType == NormalMove && promoChar)
6124         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6125
6126     /* [HGM] <popupFix> The following if has been moved here from
6127        UserMoveEvent(). Because it seemed to belong here (why not allow
6128        piece drops in training games?), and because it can only be
6129        performed after it is known to what we promote. */
6130     if (gameMode == Training) {
6131       /* compare the move played on the board to the next move in the
6132        * game. If they match, display the move and the opponent's response.
6133        * If they don't match, display an error message.
6134        */
6135       int saveAnimate;
6136       Board testBoard;
6137       CopyBoard(testBoard, boards[currentMove]);
6138       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6139
6140       if (CompareBoards(testBoard, boards[currentMove+1])) {
6141         ForwardInner(currentMove+1);
6142
6143         /* Autoplay the opponent's response.
6144          * if appData.animate was TRUE when Training mode was entered,
6145          * the response will be animated.
6146          */
6147         saveAnimate = appData.animate;
6148         appData.animate = animateTraining;
6149         ForwardInner(currentMove+1);
6150         appData.animate = saveAnimate;
6151
6152         /* check for the end of the game */
6153         if (currentMove >= forwardMostMove) {
6154           gameMode = PlayFromGameFile;
6155           ModeHighlight();
6156           SetTrainingModeOff();
6157           DisplayInformation(_("End of game"));
6158         }
6159       } else {
6160         DisplayError(_("Incorrect move"), 0);
6161       }
6162       return 1;
6163     }
6164
6165   /* Ok, now we know that the move is good, so we can kill
6166      the previous line in Analysis Mode */
6167   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6168                                 && currentMove < forwardMostMove) {
6169     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6170     else forwardMostMove = currentMove;
6171   }
6172
6173   /* If we need the chess program but it's dead, restart it */
6174   ResurrectChessProgram();
6175
6176   /* A user move restarts a paused game*/
6177   if (pausing)
6178     PauseEvent();
6179
6180   thinkOutput[0] = NULLCHAR;
6181
6182   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6183
6184   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6185
6186   if (gameMode == BeginningOfGame) {
6187     if (appData.noChessProgram) {
6188       gameMode = EditGame;
6189       SetGameInfo();
6190     } else {
6191       char buf[MSG_SIZ];
6192       gameMode = MachinePlaysBlack;
6193       StartClocks();
6194       SetGameInfo();
6195       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6196       DisplayTitle(buf);
6197       if (first.sendName) {
6198         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6199         SendToProgram(buf, &first);
6200       }
6201       StartClocks();
6202     }
6203     ModeHighlight();
6204   }
6205
6206   /* Relay move to ICS or chess engine */
6207   if (appData.icsActive) {
6208     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6209         gameMode == IcsExamining) {
6210       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6211         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6212         SendToICS("draw ");
6213         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6214       }
6215       // also send plain move, in case ICS does not understand atomic claims
6216       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6217       ics_user_moved = 1;
6218     }
6219   } else {
6220     if (first.sendTime && (gameMode == BeginningOfGame ||
6221                            gameMode == MachinePlaysWhite ||
6222                            gameMode == MachinePlaysBlack)) {
6223       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6224     }
6225     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6226          // [HGM] book: if program might be playing, let it use book
6227         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6228         first.maybeThinking = TRUE;
6229     } else SendMoveToProgram(forwardMostMove-1, &first);
6230     if (currentMove == cmailOldMove + 1) {
6231       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6232     }
6233   }
6234
6235   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6236
6237   switch (gameMode) {
6238   case EditGame:
6239     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6240     case MT_NONE:
6241     case MT_CHECK:
6242       break;
6243     case MT_CHECKMATE:
6244     case MT_STAINMATE:
6245       if (WhiteOnMove(currentMove)) {
6246         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6247       } else {
6248         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6249       }
6250       break;
6251     case MT_STALEMATE:
6252       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6253       break;
6254     }
6255     break;
6256
6257   case MachinePlaysBlack:
6258   case MachinePlaysWhite:
6259     /* disable certain menu options while machine is thinking */
6260     SetMachineThinkingEnables();
6261     break;
6262
6263   default:
6264     break;
6265   }
6266
6267   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6268
6269   if(bookHit) { // [HGM] book: simulate book reply
6270         static char bookMove[MSG_SIZ]; // a bit generous?
6271
6272         programStats.nodes = programStats.depth = programStats.time =
6273         programStats.score = programStats.got_only_move = 0;
6274         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6275
6276         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6277         strcat(bookMove, bookHit);
6278         HandleMachineMove(bookMove, &first);
6279   }
6280   return 1;
6281 }
6282
6283 void
6284 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6285      Board board;
6286      int flags;
6287      ChessMove kind;
6288      int rf, ff, rt, ft;
6289      VOIDSTAR closure;
6290 {
6291     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6292     Markers *m = (Markers *) closure;
6293     if(rf == fromY && ff == fromX)
6294         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6295                          || kind == WhiteCapturesEnPassant
6296                          || kind == BlackCapturesEnPassant);
6297     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6298 }
6299
6300 void
6301 MarkTargetSquares(int clear)
6302 {
6303   int x, y;
6304   if(!appData.markers || !appData.highlightDragging ||
6305      !appData.testLegality || gameMode == EditPosition) return;
6306   if(clear) {
6307     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6308   } else {
6309     int capt = 0;
6310     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6311     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6312       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6313       if(capt)
6314       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6315     }
6316   }
6317   DrawPosition(TRUE, NULL);
6318 }
6319
6320 int
6321 Explode(Board board, int fromX, int fromY, int toX, int toY)
6322 {
6323     if(gameInfo.variant == VariantAtomic &&
6324        (board[toY][toX] != EmptySquare ||                     // capture?
6325         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6326                          board[fromY][fromX] == BlackPawn   )
6327       )) {
6328         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6329         return TRUE;
6330     }
6331     return FALSE;
6332 }
6333
6334 void LeftClick(ClickType clickType, int xPix, int yPix)
6335 {
6336     int x, y;
6337     Boolean saveAnimate;
6338     static int second = 0, promotionChoice = 0, dragging = 0;
6339     char promoChoice = NULLCHAR;
6340
6341     if(appData.seekGraph && appData.icsActive && loggedOn &&
6342         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6343         SeekGraphClick(clickType, xPix, yPix, 0);
6344         return;
6345     }
6346
6347     if (clickType == Press) ErrorPopDown();
6348     MarkTargetSquares(1);
6349
6350     x = EventToSquare(xPix, BOARD_WIDTH);
6351     y = EventToSquare(yPix, BOARD_HEIGHT);
6352     if (!flipView && y >= 0) {
6353         y = BOARD_HEIGHT - 1 - y;
6354     }
6355     if (flipView && x >= 0) {
6356         x = BOARD_WIDTH - 1 - x;
6357     }
6358
6359     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6360         if(clickType == Release) return; // ignore upclick of click-click destination
6361         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6362         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6363         if(gameInfo.holdingsWidth &&
6364                 (WhiteOnMove(currentMove)
6365                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6366                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6367             // click in right holdings, for determining promotion piece
6368             ChessSquare p = boards[currentMove][y][x];
6369             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6370             if(p != EmptySquare) {
6371                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6372                 fromX = fromY = -1;
6373                 return;
6374             }
6375         }
6376         DrawPosition(FALSE, boards[currentMove]);
6377         return;
6378     }
6379
6380     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6381     if(clickType == Press
6382             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6383               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6384               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6385         return;
6386
6387     autoQueen = appData.alwaysPromoteToQueen;
6388
6389     if (fromX == -1) {
6390       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6391         if (clickType == Press) {
6392             /* First square */
6393             if (OKToStartUserMove(x, y)) {
6394                 fromX = x;
6395                 fromY = y;
6396                 second = 0;
6397                 MarkTargetSquares(0);
6398                 DragPieceBegin(xPix, yPix); dragging = 1;
6399                 if (appData.highlightDragging) {
6400                     SetHighlights(x, y, -1, -1);
6401                 }
6402             }
6403         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6404             DragPieceEnd(xPix, yPix); dragging = 0;
6405             DrawPosition(FALSE, NULL);
6406         }
6407         return;
6408       }
6409     }
6410
6411     /* fromX != -1 */
6412     if (clickType == Press && gameMode != EditPosition) {
6413         ChessSquare fromP;
6414         ChessSquare toP;
6415         int frc;
6416
6417         // ignore off-board to clicks
6418         if(y < 0 || x < 0) return;
6419
6420         /* Check if clicking again on the same color piece */
6421         fromP = boards[currentMove][fromY][fromX];
6422         toP = boards[currentMove][y][x];
6423         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6424         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6425              WhitePawn <= toP && toP <= WhiteKing &&
6426              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6427              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6428             (BlackPawn <= fromP && fromP <= BlackKing &&
6429              BlackPawn <= toP && toP <= BlackKing &&
6430              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6431              !(fromP == BlackKing && toP == BlackRook && frc))) {
6432             /* Clicked again on same color piece -- changed his mind */
6433             second = (x == fromX && y == fromY);
6434            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6435             if (appData.highlightDragging) {
6436                 SetHighlights(x, y, -1, -1);
6437             } else {
6438                 ClearHighlights();
6439             }
6440             if (OKToStartUserMove(x, y)) {
6441                 fromX = x;
6442                 fromY = y; dragging = 1;
6443                 MarkTargetSquares(0);
6444                 DragPieceBegin(xPix, yPix);
6445             }
6446             return;
6447            }
6448         }
6449         // ignore clicks on holdings
6450         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6451     }
6452
6453     if (clickType == Release && x == fromX && y == fromY) {
6454         DragPieceEnd(xPix, yPix); dragging = 0;
6455         if (appData.animateDragging) {
6456             /* Undo animation damage if any */
6457             DrawPosition(FALSE, NULL);
6458         }
6459         if (second) {
6460             /* Second up/down in same square; just abort move */
6461             second = 0;
6462             fromX = fromY = -1;
6463             ClearHighlights();
6464             gotPremove = 0;
6465             ClearPremoveHighlights();
6466         } else {
6467             /* First upclick in same square; start click-click mode */
6468             SetHighlights(x, y, -1, -1);
6469         }
6470         return;
6471     }
6472
6473     /* we now have a different from- and (possibly off-board) to-square */
6474     /* Completed move */
6475     toX = x;
6476     toY = y;
6477     saveAnimate = appData.animate;
6478     if (clickType == Press) {
6479         /* Finish clickclick move */
6480         if (appData.animate || appData.highlightLastMove) {
6481             SetHighlights(fromX, fromY, toX, toY);
6482         } else {
6483             ClearHighlights();
6484         }
6485     } else {
6486         /* Finish drag move */
6487         if (appData.highlightLastMove) {
6488             SetHighlights(fromX, fromY, toX, toY);
6489         } else {
6490             ClearHighlights();
6491         }
6492         DragPieceEnd(xPix, yPix); dragging = 0;
6493         /* Don't animate move and drag both */
6494         appData.animate = FALSE;
6495     }
6496
6497     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6498     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6499         ChessSquare piece = boards[currentMove][fromY][fromX];
6500         if(gameMode == EditPosition && piece != EmptySquare &&
6501            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6502             int n;
6503
6504             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6505                 n = PieceToNumber(piece - (int)BlackPawn);
6506                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6507                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6508                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6509             } else
6510             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6511                 n = PieceToNumber(piece);
6512                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6513                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6514                 boards[currentMove][n][BOARD_WIDTH-2]++;
6515             }
6516             boards[currentMove][fromY][fromX] = EmptySquare;
6517         }
6518         ClearHighlights();
6519         fromX = fromY = -1;
6520         DrawPosition(TRUE, boards[currentMove]);
6521         return;
6522     }
6523
6524     // off-board moves should not be highlighted
6525     if(x < 0 || x < 0) ClearHighlights();
6526
6527     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6528         SetHighlights(fromX, fromY, toX, toY);
6529         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6530             // [HGM] super: promotion to captured piece selected from holdings
6531             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6532             promotionChoice = TRUE;
6533             // kludge follows to temporarily execute move on display, without promoting yet
6534             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6535             boards[currentMove][toY][toX] = p;
6536             DrawPosition(FALSE, boards[currentMove]);
6537             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6538             boards[currentMove][toY][toX] = q;
6539             DisplayMessage("Click in holdings to choose piece", "");
6540             return;
6541         }
6542         PromotionPopUp();
6543     } else {
6544         int oldMove = currentMove;
6545         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6546         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6547         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6548         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6549            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6550             DrawPosition(TRUE, boards[currentMove]);
6551         fromX = fromY = -1;
6552     }
6553     appData.animate = saveAnimate;
6554     if (appData.animate || appData.animateDragging) {
6555         /* Undo animation damage if needed */
6556         DrawPosition(FALSE, NULL);
6557     }
6558 }
6559
6560 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6561 {   // front-end-free part taken out of PieceMenuPopup
6562     int whichMenu; int xSqr, ySqr;
6563
6564     if(seekGraphUp) { // [HGM] seekgraph
6565         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6566         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6567         return -2;
6568     }
6569
6570     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6571          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6572         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6573         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6574         if(action == Press)   {
6575             originalFlip = flipView;
6576             flipView = !flipView; // temporarily flip board to see game from partners perspective
6577             DrawPosition(TRUE, partnerBoard);
6578             DisplayMessage(partnerStatus, "");
6579             partnerUp = TRUE;
6580         } else if(action == Release) {
6581             flipView = originalFlip;
6582             DrawPosition(TRUE, boards[currentMove]);
6583             partnerUp = FALSE;
6584         }
6585         return -2;
6586     }
6587
6588     xSqr = EventToSquare(x, BOARD_WIDTH);
6589     ySqr = EventToSquare(y, BOARD_HEIGHT);
6590     if (action == Release) UnLoadPV(); // [HGM] pv
6591     if (action != Press) return -2; // return code to be ignored
6592     switch (gameMode) {
6593       case IcsExamining:
6594         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6595       case EditPosition:
6596         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6597         if (xSqr < 0 || ySqr < 0) return -1;\r
6598         whichMenu = 0; // edit-position menu
6599         break;
6600       case IcsObserving:
6601         if(!appData.icsEngineAnalyze) return -1;
6602       case IcsPlayingWhite:
6603       case IcsPlayingBlack:
6604         if(!appData.zippyPlay) goto noZip;
6605       case AnalyzeMode:
6606       case AnalyzeFile:
6607       case MachinePlaysWhite:
6608       case MachinePlaysBlack:
6609       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6610         if (!appData.dropMenu) {
6611           LoadPV(x, y);
6612           return 2; // flag front-end to grab mouse events
6613         }
6614         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6615            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6616       case EditGame:
6617       noZip:
6618         if (xSqr < 0 || ySqr < 0) return -1;
6619         if (!appData.dropMenu || appData.testLegality &&
6620             gameInfo.variant != VariantBughouse &&
6621             gameInfo.variant != VariantCrazyhouse) return -1;
6622         whichMenu = 1; // drop menu
6623         break;
6624       default:
6625         return -1;
6626     }
6627
6628     if (((*fromX = xSqr) < 0) ||
6629         ((*fromY = ySqr) < 0)) {
6630         *fromX = *fromY = -1;
6631         return -1;
6632     }
6633     if (flipView)
6634       *fromX = BOARD_WIDTH - 1 - *fromX;
6635     else
6636       *fromY = BOARD_HEIGHT - 1 - *fromY;
6637
6638     return whichMenu;
6639 }
6640
6641 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6642 {
6643 //    char * hint = lastHint;
6644     FrontEndProgramStats stats;
6645
6646     stats.which = cps == &first ? 0 : 1;
6647     stats.depth = cpstats->depth;
6648     stats.nodes = cpstats->nodes;
6649     stats.score = cpstats->score;
6650     stats.time = cpstats->time;
6651     stats.pv = cpstats->movelist;
6652     stats.hint = lastHint;
6653     stats.an_move_index = 0;
6654     stats.an_move_count = 0;
6655
6656     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6657         stats.hint = cpstats->move_name;
6658         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6659         stats.an_move_count = cpstats->nr_moves;
6660     }
6661
6662     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
6663
6664     SetProgramStats( &stats );
6665 }
6666
6667 void
6668 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6669 {       // count all piece types
6670         int p, f, r;
6671         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6672         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6673         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6674                 p = board[r][f];
6675                 pCnt[p]++;
6676                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6677                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6678                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6679                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6680                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6681                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6682         }
6683 }
6684
6685 int
6686 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6687 {
6688         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6689         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6690
6691         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6692         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6693         if(myPawns == 2 && nMine == 3) // KPP
6694             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6695         if(myPawns == 1 && nMine == 2) // KP
6696             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6697         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6698             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6699         if(myPawns) return FALSE;
6700         if(pCnt[WhiteRook+side])
6701             return pCnt[BlackRook-side] ||
6702                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6703                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6704                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6705         if(pCnt[WhiteCannon+side]) {
6706             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6707             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6708         }
6709         if(pCnt[WhiteKnight+side])
6710             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6711         return FALSE;
6712 }
6713
6714 int
6715 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6716 {
6717         VariantClass v = gameInfo.variant;
6718
6719         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6720         if(v == VariantShatranj) return TRUE; // always winnable through baring
6721         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6722         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6723
6724         if(v == VariantXiangqi) {
6725                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6726
6727                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6728                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6729                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6730                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6731                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6732                 if(stale) // we have at least one last-rank P plus perhaps C
6733                     return majors // KPKX
6734                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6735                 else // KCA*E*
6736                     return pCnt[WhiteFerz+side] // KCAK
6737                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6738                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6739                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6740
6741         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6742                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6743
6744                 if(nMine == 1) return FALSE; // bare King
6745                 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
6746                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6747                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6748                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6749                 if(pCnt[WhiteKnight+side])
6750                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6751                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6752                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6753                 if(nBishops)
6754                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6755                 if(pCnt[WhiteAlfil+side])
6756                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6757                 if(pCnt[WhiteWazir+side])
6758                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6759         }
6760
6761         return TRUE;
6762 }
6763
6764 int
6765 Adjudicate(ChessProgramState *cps)
6766 {       // [HGM] some adjudications useful with buggy engines
6767         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6768         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6769         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6770         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6771         int k, count = 0; static int bare = 1;
6772         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6773         Boolean canAdjudicate = !appData.icsActive;
6774
6775         // most tests only when we understand the game, i.e. legality-checking on
6776             if( appData.testLegality )
6777             {   /* [HGM] Some more adjudications for obstinate engines */
6778                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6779                 static int moveCount = 6;
6780                 ChessMove result;
6781                 char *reason = NULL;
6782
6783                 /* Count what is on board. */
6784                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6785
6786                 /* Some material-based adjudications that have to be made before stalemate test */
6787                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6788                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6789                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6790                      if(canAdjudicate && appData.checkMates) {
6791                          if(engineOpponent)
6792                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6793                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6794                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6795                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6796                          return 1;
6797                      }
6798                 }
6799
6800                 /* Bare King in Shatranj (loses) or Losers (wins) */
6801                 if( nrW == 1 || nrB == 1) {
6802                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6803                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6804                      if(canAdjudicate && appData.checkMates) {
6805                          if(engineOpponent)
6806                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6807                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6808                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6809                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6810                          return 1;
6811                      }
6812                   } else
6813                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6814                   {    /* bare King */
6815                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6816                         if(canAdjudicate && appData.checkMates) {
6817                             /* but only adjudicate if adjudication enabled */
6818                             if(engineOpponent)
6819                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6820                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6821                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6822                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6823                             return 1;
6824                         }
6825                   }
6826                 } else bare = 1;
6827
6828
6829             // don't wait for engine to announce game end if we can judge ourselves
6830             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6831               case MT_CHECK:
6832                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6833                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6834                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6835                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6836                             checkCnt++;
6837                         if(checkCnt >= 2) {
6838                             reason = "Xboard adjudication: 3rd check";
6839                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6840                             break;
6841                         }
6842                     }
6843                 }
6844               case MT_NONE:
6845               default:
6846                 break;
6847               case MT_STALEMATE:
6848               case MT_STAINMATE:
6849                 reason = "Xboard adjudication: Stalemate";
6850                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6851                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6852                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6853                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6854                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6855                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6856                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6857                                                                         EP_CHECKMATE : EP_WINS);
6858                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6859                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6860                 }
6861                 break;
6862               case MT_CHECKMATE:
6863                 reason = "Xboard adjudication: Checkmate";
6864                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6865                 break;
6866             }
6867
6868                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6869                     case EP_STALEMATE:
6870                         result = GameIsDrawn; break;
6871                     case EP_CHECKMATE:
6872                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6873                     case EP_WINS:
6874                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6875                     default:
6876                         result = EndOfFile;
6877                 }
6878                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6879                     if(engineOpponent)
6880                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6881                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6882                     GameEnds( result, reason, GE_XBOARD );
6883                     return 1;
6884                 }
6885
6886                 /* Next absolutely insufficient mating material. */
6887                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6888                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6889                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6890
6891                      /* always flag draws, for judging claims */
6892                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6893
6894                      if(canAdjudicate && appData.materialDraws) {
6895                          /* but only adjudicate them if adjudication enabled */
6896                          if(engineOpponent) {
6897                            SendToProgram("force\n", engineOpponent); // suppress reply
6898                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6899                          }
6900                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6901                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6902                          return 1;
6903                      }
6904                 }
6905
6906                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6907                 if(gameInfo.variant == VariantXiangqi ?
6908                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6909                  : nrW + nrB == 4 &&
6910                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6911                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6912                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6913                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6914                    ) ) {
6915                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6916                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6917                           if(engineOpponent) {
6918                             SendToProgram("force\n", engineOpponent); // suppress reply
6919                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6920                           }
6921                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6922                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6923                           return 1;
6924                      }
6925                 } else moveCount = 6;
6926             }
6927         if (appData.debugMode) { int i;
6928             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6929                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6930                     appData.drawRepeats);
6931             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6932               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6933
6934         }
6935
6936         // Repetition draws and 50-move rule can be applied independently of legality testing
6937
6938                 /* Check for rep-draws */
6939                 count = 0;
6940                 for(k = forwardMostMove-2;
6941                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6942                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6943                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6944                     k-=2)
6945                 {   int rights=0;
6946                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6947                         /* compare castling rights */
6948                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6949                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6950                                 rights++; /* King lost rights, while rook still had them */
6951                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6952                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6953                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6954                                    rights++; /* but at least one rook lost them */
6955                         }
6956                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6957                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6958                                 rights++;
6959                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6960                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6961                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6962                                    rights++;
6963                         }
6964                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6965                             && appData.drawRepeats > 1) {
6966                              /* adjudicate after user-specified nr of repeats */
6967                              int result = GameIsDrawn;
6968                              char *details = "XBoard adjudication: repetition draw";
6969                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6970                                 // [HGM] xiangqi: check for forbidden perpetuals
6971                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6972                                 for(m=forwardMostMove; m>k; m-=2) {
6973                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6974                                         ourPerpetual = 0; // the current mover did not always check
6975                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6976                                         hisPerpetual = 0; // the opponent did not always check
6977                                 }
6978                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6979                                                                         ourPerpetual, hisPerpetual);
6980                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6981                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6982                                     details = "Xboard adjudication: perpetual checking";
6983                                 } else
6984                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6985                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6986                                 } else
6987                                 // Now check for perpetual chases
6988                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6989                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6990                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6991                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6992                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6993                                         details = "Xboard adjudication: perpetual chasing";
6994                                     } else
6995                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6996                                         break; // Abort repetition-checking loop.
6997                                 }
6998                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6999                              }
7000                              if(engineOpponent) {
7001                                SendToProgram("force\n", engineOpponent); // suppress reply
7002                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7003                              }
7004                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7005                              GameEnds( result, details, GE_XBOARD );
7006                              return 1;
7007                         }
7008                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7009                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7010                     }
7011                 }
7012
7013                 /* Now we test for 50-move draws. Determine ply count */
7014                 count = forwardMostMove;
7015                 /* look for last irreversble move */
7016                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7017                     count--;
7018                 /* if we hit starting position, add initial plies */
7019                 if( count == backwardMostMove )
7020                     count -= initialRulePlies;
7021                 count = forwardMostMove - count;
7022                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7023                         // adjust reversible move counter for checks in Xiangqi
7024                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7025                         if(i < backwardMostMove) i = backwardMostMove;
7026                         while(i <= forwardMostMove) {
7027                                 lastCheck = inCheck; // check evasion does not count
7028                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7029                                 if(inCheck || lastCheck) count--; // check does not count
7030                                 i++;
7031                         }
7032                 }
7033                 if( count >= 100)
7034                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7035                          /* this is used to judge if draw claims are legal */
7036                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7037                          if(engineOpponent) {
7038                            SendToProgram("force\n", engineOpponent); // suppress reply
7039                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7040                          }
7041                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7042                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7043                          return 1;
7044                 }
7045
7046                 /* if draw offer is pending, treat it as a draw claim
7047                  * when draw condition present, to allow engines a way to
7048                  * claim draws before making their move to avoid a race
7049                  * condition occurring after their move
7050                  */
7051                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7052                          char *p = NULL;
7053                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7054                              p = "Draw claim: 50-move rule";
7055                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7056                              p = "Draw claim: 3-fold repetition";
7057                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7058                              p = "Draw claim: insufficient mating material";
7059                          if( p != NULL && canAdjudicate) {
7060                              if(engineOpponent) {
7061                                SendToProgram("force\n", engineOpponent); // suppress reply
7062                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7063                              }
7064                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7065                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7066                              return 1;
7067                          }
7068                 }
7069
7070                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7071                     if(engineOpponent) {
7072                       SendToProgram("force\n", engineOpponent); // suppress reply
7073                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7074                     }
7075                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7076                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7077                     return 1;
7078                 }
7079         return 0;
7080 }
7081
7082 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7083 {   // [HGM] book: this routine intercepts moves to simulate book replies
7084     char *bookHit = NULL;
7085
7086     //first determine if the incoming move brings opponent into his book
7087     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7088         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7089     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7090     if(bookHit != NULL && !cps->bookSuspend) {
7091         // make sure opponent is not going to reply after receiving move to book position
7092         SendToProgram("force\n", cps);
7093         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7094     }
7095     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7096     // now arrange restart after book miss
7097     if(bookHit) {
7098         // after a book hit we never send 'go', and the code after the call to this routine
7099         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7100         char buf[MSG_SIZ];
7101         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7102         SendToProgram(buf, cps);
7103         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7104     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7105         SendToProgram("go\n", cps);
7106         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7107     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7108         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7109             SendToProgram("go\n", cps);
7110         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7111     }
7112     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7113 }
7114
7115 char *savedMessage;
7116 ChessProgramState *savedState;
7117 void DeferredBookMove(void)
7118 {
7119         if(savedState->lastPing != savedState->lastPong)
7120                     ScheduleDelayedEvent(DeferredBookMove, 10);
7121         else
7122         HandleMachineMove(savedMessage, savedState);
7123 }
7124
7125 void
7126 HandleMachineMove(message, cps)
7127      char *message;
7128      ChessProgramState *cps;
7129 {
7130     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7131     char realname[MSG_SIZ];
7132     int fromX, fromY, toX, toY;
7133     ChessMove moveType;
7134     char promoChar;
7135     char *p;
7136     int machineWhite;
7137     char *bookHit;
7138
7139     cps->userError = 0;
7140
7141 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7142     /*
7143      * Kludge to ignore BEL characters
7144      */
7145     while (*message == '\007') message++;
7146
7147     /*
7148      * [HGM] engine debug message: ignore lines starting with '#' character
7149      */
7150     if(cps->debug && *message == '#') return;
7151
7152     /*
7153      * Look for book output
7154      */
7155     if (cps == &first && bookRequested) {
7156         if (message[0] == '\t' || message[0] == ' ') {
7157             /* Part of the book output is here; append it */
7158             strcat(bookOutput, message);
7159             strcat(bookOutput, "  \n");
7160             return;
7161         } else if (bookOutput[0] != NULLCHAR) {
7162             /* All of book output has arrived; display it */
7163             char *p = bookOutput;
7164             while (*p != NULLCHAR) {
7165                 if (*p == '\t') *p = ' ';
7166                 p++;
7167             }
7168             DisplayInformation(bookOutput);
7169             bookRequested = FALSE;
7170             /* Fall through to parse the current output */
7171         }
7172     }
7173
7174     /*
7175      * Look for machine move.
7176      */
7177     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7178         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7179     {
7180         /* This method is only useful on engines that support ping */
7181         if (cps->lastPing != cps->lastPong) {
7182           if (gameMode == BeginningOfGame) {
7183             /* Extra move from before last new; ignore */
7184             if (appData.debugMode) {
7185                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7186             }
7187           } else {
7188             if (appData.debugMode) {
7189                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7190                         cps->which, gameMode);
7191             }
7192
7193             SendToProgram("undo\n", cps);
7194           }
7195           return;
7196         }
7197
7198         switch (gameMode) {
7199           case BeginningOfGame:
7200             /* Extra move from before last reset; ignore */
7201             if (appData.debugMode) {
7202                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7203             }
7204             return;
7205
7206           case EndOfGame:
7207           case IcsIdle:
7208           default:
7209             /* Extra move after we tried to stop.  The mode test is
7210                not a reliable way of detecting this problem, but it's
7211                the best we can do on engines that don't support ping.
7212             */
7213             if (appData.debugMode) {
7214                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7215                         cps->which, gameMode);
7216             }
7217             SendToProgram("undo\n", cps);
7218             return;
7219
7220           case MachinePlaysWhite:
7221           case IcsPlayingWhite:
7222             machineWhite = TRUE;
7223             break;
7224
7225           case MachinePlaysBlack:
7226           case IcsPlayingBlack:
7227             machineWhite = FALSE;
7228             break;
7229
7230           case TwoMachinesPlay:
7231             machineWhite = (cps->twoMachinesColor[0] == 'w');
7232             break;
7233         }
7234         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7235             if (appData.debugMode) {
7236                 fprintf(debugFP,
7237                         "Ignoring move out of turn by %s, gameMode %d"
7238                         ", forwardMost %d\n",
7239                         cps->which, gameMode, forwardMostMove);
7240             }
7241             return;
7242         }
7243
7244     if (appData.debugMode) { int f = forwardMostMove;
7245         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7246                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7247                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7248     }
7249         if(cps->alphaRank) AlphaRank(machineMove, 4);
7250         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7251                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7252             /* Machine move could not be parsed; ignore it. */
7253           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7254                     machineMove, cps->which);
7255             DisplayError(buf1, 0);
7256             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7257                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7258             if (gameMode == TwoMachinesPlay) {
7259               GameEnds(machineWhite ? BlackWins : WhiteWins,
7260                        buf1, GE_XBOARD);
7261             }
7262             return;
7263         }
7264
7265         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7266         /* So we have to redo legality test with true e.p. status here,  */
7267         /* to make sure an illegal e.p. capture does not slip through,   */
7268         /* to cause a forfeit on a justified illegal-move complaint      */
7269         /* of the opponent.                                              */
7270         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7271            ChessMove moveType;
7272            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7273                              fromY, fromX, toY, toX, promoChar);
7274             if (appData.debugMode) {
7275                 int i;
7276                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7277                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7278                 fprintf(debugFP, "castling rights\n");
7279             }
7280             if(moveType == IllegalMove) {
7281               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7282                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7283                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7284                            buf1, GE_XBOARD);
7285                 return;
7286            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7287            /* [HGM] Kludge to handle engines that send FRC-style castling
7288               when they shouldn't (like TSCP-Gothic) */
7289            switch(moveType) {
7290              case WhiteASideCastleFR:
7291              case BlackASideCastleFR:
7292                toX+=2;
7293                currentMoveString[2]++;
7294                break;
7295              case WhiteHSideCastleFR:
7296              case BlackHSideCastleFR:
7297                toX--;
7298                currentMoveString[2]--;
7299                break;
7300              default: ; // nothing to do, but suppresses warning of pedantic compilers
7301            }
7302         }
7303         hintRequested = FALSE;
7304         lastHint[0] = NULLCHAR;
7305         bookRequested = FALSE;
7306         /* Program may be pondering now */
7307         cps->maybeThinking = TRUE;
7308         if (cps->sendTime == 2) cps->sendTime = 1;
7309         if (cps->offeredDraw) cps->offeredDraw--;
7310
7311         /* currentMoveString is set as a side-effect of ParseOneMove */
7312         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7313         strcat(machineMove, "\n");
7314         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7315
7316         /* [AS] Save move info*/
7317         pvInfoList[ forwardMostMove ].score = programStats.score;
7318         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7319         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7320
7321         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7322
7323         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7324         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7325             int count = 0;
7326
7327             while( count < adjudicateLossPlies ) {
7328                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7329
7330                 if( count & 1 ) {
7331                     score = -score; /* Flip score for winning side */
7332                 }
7333
7334                 if( score > adjudicateLossThreshold ) {
7335                     break;
7336                 }
7337
7338                 count++;
7339             }
7340
7341             if( count >= adjudicateLossPlies ) {
7342                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7343
7344                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7345                     "Xboard adjudication",
7346                     GE_XBOARD );
7347
7348                 return;
7349             }
7350         }
7351
7352         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7353
7354 #if ZIPPY
7355         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7356             first.initDone) {
7357           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7358                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7359                 SendToICS("draw ");
7360                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7361           }
7362           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7363           ics_user_moved = 1;
7364           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7365                 char buf[3*MSG_SIZ];
7366
7367                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7368                         programStats.score / 100.,
7369                         programStats.depth,
7370                         programStats.time / 100.,
7371                         (unsigned int)programStats.nodes,
7372                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7373                         programStats.movelist);
7374                 SendToICS(buf);
7375 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7376           }
7377         }
7378 #endif
7379
7380         /* [AS] Clear stats for next move */
7381         ClearProgramStats();
7382         thinkOutput[0] = NULLCHAR;
7383         hiddenThinkOutputState = 0;
7384
7385         bookHit = NULL;
7386         if (gameMode == TwoMachinesPlay) {
7387             /* [HGM] relaying draw offers moved to after reception of move */
7388             /* and interpreting offer as claim if it brings draw condition */
7389             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7390                 SendToProgram("draw\n", cps->other);
7391             }
7392             if (cps->other->sendTime) {
7393                 SendTimeRemaining(cps->other,
7394                                   cps->other->twoMachinesColor[0] == 'w');
7395             }
7396             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7397             if (firstMove && !bookHit) {
7398                 firstMove = FALSE;
7399                 if (cps->other->useColors) {
7400                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7401                 }
7402                 SendToProgram("go\n", cps->other);
7403             }
7404             cps->other->maybeThinking = TRUE;
7405         }
7406
7407         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7408
7409         if (!pausing && appData.ringBellAfterMoves) {
7410             RingBell();
7411         }
7412
7413         /*
7414          * Reenable menu items that were disabled while
7415          * machine was thinking
7416          */
7417         if (gameMode != TwoMachinesPlay)
7418             SetUserThinkingEnables();
7419
7420         // [HGM] book: after book hit opponent has received move and is now in force mode
7421         // force the book reply into it, and then fake that it outputted this move by jumping
7422         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7423         if(bookHit) {
7424                 static char bookMove[MSG_SIZ]; // a bit generous?
7425
7426                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7427                 strcat(bookMove, bookHit);
7428                 message = bookMove;
7429                 cps = cps->other;
7430                 programStats.nodes = programStats.depth = programStats.time =
7431                 programStats.score = programStats.got_only_move = 0;
7432                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7433
7434                 if(cps->lastPing != cps->lastPong) {
7435                     savedMessage = message; // args for deferred call
7436                     savedState = cps;
7437                     ScheduleDelayedEvent(DeferredBookMove, 10);
7438                     return;
7439                 }
7440                 goto FakeBookMove;
7441         }
7442
7443         return;
7444     }
7445
7446     /* Set special modes for chess engines.  Later something general
7447      *  could be added here; for now there is just one kludge feature,
7448      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7449      *  when "xboard" is given as an interactive command.
7450      */
7451     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7452         cps->useSigint = FALSE;
7453         cps->useSigterm = FALSE;
7454     }
7455     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7456       ParseFeatures(message+8, cps);
7457       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7458     }
7459
7460     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7461       int dummy, s=6; char buf[MSG_SIZ];
7462       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7463       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7464       ParseFEN(boards[0], &dummy, message+s);
7465       DrawPosition(TRUE, boards[0]);
7466       startedFromSetupPosition = TRUE;
7467       return;
7468     }
7469     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7470      * want this, I was asked to put it in, and obliged.
7471      */
7472     if (!strncmp(message, "setboard ", 9)) {
7473         Board initial_position;
7474
7475         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7476
7477         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7478             DisplayError(_("Bad FEN received from engine"), 0);
7479             return ;
7480         } else {
7481            Reset(TRUE, FALSE);
7482            CopyBoard(boards[0], initial_position);
7483            initialRulePlies = FENrulePlies;
7484            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7485            else gameMode = MachinePlaysBlack;
7486            DrawPosition(FALSE, boards[currentMove]);
7487         }
7488         return;
7489     }
7490
7491     /*
7492      * Look for communication commands
7493      */
7494     if (!strncmp(message, "telluser ", 9)) {
7495         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7496         DisplayNote(message + 9);
7497         return;
7498     }
7499     if (!strncmp(message, "tellusererror ", 14)) {
7500         cps->userError = 1;
7501         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7502         DisplayError(message + 14, 0);
7503         return;
7504     }
7505     if (!strncmp(message, "tellopponent ", 13)) {
7506       if (appData.icsActive) {
7507         if (loggedOn) {
7508           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7509           SendToICS(buf1);
7510         }
7511       } else {
7512         DisplayNote(message + 13);
7513       }
7514       return;
7515     }
7516     if (!strncmp(message, "tellothers ", 11)) {
7517       if (appData.icsActive) {
7518         if (loggedOn) {
7519           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7520           SendToICS(buf1);
7521         }
7522       }
7523       return;
7524     }
7525     if (!strncmp(message, "tellall ", 8)) {
7526       if (appData.icsActive) {
7527         if (loggedOn) {
7528           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7529           SendToICS(buf1);
7530         }
7531       } else {
7532         DisplayNote(message + 8);
7533       }
7534       return;
7535     }
7536     if (strncmp(message, "warning", 7) == 0) {
7537         /* Undocumented feature, use tellusererror in new code */
7538         DisplayError(message, 0);
7539         return;
7540     }
7541     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7542         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7543         strcat(realname, " query");
7544         AskQuestion(realname, buf2, buf1, cps->pr);
7545         return;
7546     }
7547     /* Commands from the engine directly to ICS.  We don't allow these to be
7548      *  sent until we are logged on. Crafty kibitzes have been known to
7549      *  interfere with the login process.
7550      */
7551     if (loggedOn) {
7552         if (!strncmp(message, "tellics ", 8)) {
7553             SendToICS(message + 8);
7554             SendToICS("\n");
7555             return;
7556         }
7557         if (!strncmp(message, "tellicsnoalias ", 15)) {
7558             SendToICS(ics_prefix);
7559             SendToICS(message + 15);
7560             SendToICS("\n");
7561             return;
7562         }
7563         /* The following are for backward compatibility only */
7564         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7565             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7566             SendToICS(ics_prefix);
7567             SendToICS(message);
7568             SendToICS("\n");
7569             return;
7570         }
7571     }
7572     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7573         return;
7574     }
7575     /*
7576      * If the move is illegal, cancel it and redraw the board.
7577      * Also deal with other error cases.  Matching is rather loose
7578      * here to accommodate engines written before the spec.
7579      */
7580     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7581         strncmp(message, "Error", 5) == 0) {
7582         if (StrStr(message, "name") ||
7583             StrStr(message, "rating") || StrStr(message, "?") ||
7584             StrStr(message, "result") || StrStr(message, "board") ||
7585             StrStr(message, "bk") || StrStr(message, "computer") ||
7586             StrStr(message, "variant") || StrStr(message, "hint") ||
7587             StrStr(message, "random") || StrStr(message, "depth") ||
7588             StrStr(message, "accepted")) {
7589             return;
7590         }
7591         if (StrStr(message, "protover")) {
7592           /* Program is responding to input, so it's apparently done
7593              initializing, and this error message indicates it is
7594              protocol version 1.  So we don't need to wait any longer
7595              for it to initialize and send feature commands. */
7596           FeatureDone(cps, 1);
7597           cps->protocolVersion = 1;
7598           return;
7599         }
7600         cps->maybeThinking = FALSE;
7601
7602         if (StrStr(message, "draw")) {
7603             /* Program doesn't have "draw" command */
7604             cps->sendDrawOffers = 0;
7605             return;
7606         }
7607         if (cps->sendTime != 1 &&
7608             (StrStr(message, "time") || StrStr(message, "otim"))) {
7609           /* Program apparently doesn't have "time" or "otim" command */
7610           cps->sendTime = 0;
7611           return;
7612         }
7613         if (StrStr(message, "analyze")) {
7614             cps->analysisSupport = FALSE;
7615             cps->analyzing = FALSE;
7616             Reset(FALSE, TRUE);
7617             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7618             DisplayError(buf2, 0);
7619             return;
7620         }
7621         if (StrStr(message, "(no matching move)st")) {
7622           /* Special kludge for GNU Chess 4 only */
7623           cps->stKludge = TRUE;
7624           SendTimeControl(cps, movesPerSession, timeControl,
7625                           timeIncrement, appData.searchDepth,
7626                           searchTime);
7627           return;
7628         }
7629         if (StrStr(message, "(no matching move)sd")) {
7630           /* Special kludge for GNU Chess 4 only */
7631           cps->sdKludge = TRUE;
7632           SendTimeControl(cps, movesPerSession, timeControl,
7633                           timeIncrement, appData.searchDepth,
7634                           searchTime);
7635           return;
7636         }
7637         if (!StrStr(message, "llegal")) {
7638             return;
7639         }
7640         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7641             gameMode == IcsIdle) return;
7642         if (forwardMostMove <= backwardMostMove) return;
7643         if (pausing) PauseEvent();
7644       if(appData.forceIllegal) {
7645             // [HGM] illegal: machine refused move; force position after move into it
7646           SendToProgram("force\n", cps);
7647           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7648                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7649                 // when black is to move, while there might be nothing on a2 or black
7650                 // might already have the move. So send the board as if white has the move.
7651                 // But first we must change the stm of the engine, as it refused the last move
7652                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7653                 if(WhiteOnMove(forwardMostMove)) {
7654                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7655                     SendBoard(cps, forwardMostMove); // kludgeless board
7656                 } else {
7657                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7658                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7659                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7660                 }
7661           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7662             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7663                  gameMode == TwoMachinesPlay)
7664               SendToProgram("go\n", cps);
7665             return;
7666       } else
7667         if (gameMode == PlayFromGameFile) {
7668             /* Stop reading this game file */
7669             gameMode = EditGame;
7670             ModeHighlight();
7671         }
7672         currentMove = forwardMostMove-1;
7673         DisplayMove(currentMove-1); /* before DisplayMoveError */
7674         SwitchClocks(forwardMostMove-1); // [HGM] race
7675         DisplayBothClocks();
7676         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7677                 parseList[currentMove], cps->which);
7678         DisplayMoveError(buf1);
7679         DrawPosition(FALSE, boards[currentMove]);
7680
7681         /* [HGM] illegal-move claim should forfeit game when Xboard */
7682         /* only passes fully legal moves                            */
7683         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7684             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7685                                 "False illegal-move claim", GE_XBOARD );
7686         }
7687         return;
7688     }
7689     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7690         /* Program has a broken "time" command that
7691            outputs a string not ending in newline.
7692            Don't use it. */
7693         cps->sendTime = 0;
7694     }
7695
7696     /*
7697      * If chess program startup fails, exit with an error message.
7698      * Attempts to recover here are futile.
7699      */
7700     if ((StrStr(message, "unknown host") != NULL)
7701         || (StrStr(message, "No remote directory") != NULL)
7702         || (StrStr(message, "not found") != NULL)
7703         || (StrStr(message, "No such file") != NULL)
7704         || (StrStr(message, "can't alloc") != NULL)
7705         || (StrStr(message, "Permission denied") != NULL)) {
7706
7707         cps->maybeThinking = FALSE;
7708         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7709                 cps->which, cps->program, cps->host, message);
7710         RemoveInputSource(cps->isr);
7711         DisplayFatalError(buf1, 0, 1);
7712         return;
7713     }
7714
7715     /*
7716      * Look for hint output
7717      */
7718     if (sscanf(message, "Hint: %s", buf1) == 1) {
7719         if (cps == &first && hintRequested) {
7720             hintRequested = FALSE;
7721             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7722                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7723                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7724                                     PosFlags(forwardMostMove),
7725                                     fromY, fromX, toY, toX, promoChar, buf1);
7726                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7727                 DisplayInformation(buf2);
7728             } else {
7729                 /* Hint move could not be parsed!? */
7730               snprintf(buf2, sizeof(buf2),
7731                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7732                         buf1, cps->which);
7733                 DisplayError(buf2, 0);
7734             }
7735         } else {
7736           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7737         }
7738         return;
7739     }
7740
7741     /*
7742      * Ignore other messages if game is not in progress
7743      */
7744     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7745         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7746
7747     /*
7748      * look for win, lose, draw, or draw offer
7749      */
7750     if (strncmp(message, "1-0", 3) == 0) {
7751         char *p, *q, *r = "";
7752         p = strchr(message, '{');
7753         if (p) {
7754             q = strchr(p, '}');
7755             if (q) {
7756                 *q = NULLCHAR;
7757                 r = p + 1;
7758             }
7759         }
7760         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7761         return;
7762     } else if (strncmp(message, "0-1", 3) == 0) {
7763         char *p, *q, *r = "";
7764         p = strchr(message, '{');
7765         if (p) {
7766             q = strchr(p, '}');
7767             if (q) {
7768                 *q = NULLCHAR;
7769                 r = p + 1;
7770             }
7771         }
7772         /* Kludge for Arasan 4.1 bug */
7773         if (strcmp(r, "Black resigns") == 0) {
7774             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7775             return;
7776         }
7777         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7778         return;
7779     } else if (strncmp(message, "1/2", 3) == 0) {
7780         char *p, *q, *r = "";
7781         p = strchr(message, '{');
7782         if (p) {
7783             q = strchr(p, '}');
7784             if (q) {
7785                 *q = NULLCHAR;
7786                 r = p + 1;
7787             }
7788         }
7789
7790         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7791         return;
7792
7793     } else if (strncmp(message, "White resign", 12) == 0) {
7794         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7795         return;
7796     } else if (strncmp(message, "Black resign", 12) == 0) {
7797         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7798         return;
7799     } else if (strncmp(message, "White matches", 13) == 0 ||
7800                strncmp(message, "Black matches", 13) == 0   ) {
7801         /* [HGM] ignore GNUShogi noises */
7802         return;
7803     } else if (strncmp(message, "White", 5) == 0 &&
7804                message[5] != '(' &&
7805                StrStr(message, "Black") == NULL) {
7806         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7807         return;
7808     } else if (strncmp(message, "Black", 5) == 0 &&
7809                message[5] != '(') {
7810         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7811         return;
7812     } else if (strcmp(message, "resign") == 0 ||
7813                strcmp(message, "computer resigns") == 0) {
7814         switch (gameMode) {
7815           case MachinePlaysBlack:
7816           case IcsPlayingBlack:
7817             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7818             break;
7819           case MachinePlaysWhite:
7820           case IcsPlayingWhite:
7821             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7822             break;
7823           case TwoMachinesPlay:
7824             if (cps->twoMachinesColor[0] == 'w')
7825               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7826             else
7827               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7828             break;
7829           default:
7830             /* can't happen */
7831             break;
7832         }
7833         return;
7834     } else if (strncmp(message, "opponent mates", 14) == 0) {
7835         switch (gameMode) {
7836           case MachinePlaysBlack:
7837           case IcsPlayingBlack:
7838             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7839             break;
7840           case MachinePlaysWhite:
7841           case IcsPlayingWhite:
7842             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7843             break;
7844           case TwoMachinesPlay:
7845             if (cps->twoMachinesColor[0] == 'w')
7846               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7847             else
7848               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7849             break;
7850           default:
7851             /* can't happen */
7852             break;
7853         }
7854         return;
7855     } else if (strncmp(message, "computer mates", 14) == 0) {
7856         switch (gameMode) {
7857           case MachinePlaysBlack:
7858           case IcsPlayingBlack:
7859             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7860             break;
7861           case MachinePlaysWhite:
7862           case IcsPlayingWhite:
7863             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7864             break;
7865           case TwoMachinesPlay:
7866             if (cps->twoMachinesColor[0] == 'w')
7867               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7868             else
7869               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7870             break;
7871           default:
7872             /* can't happen */
7873             break;
7874         }
7875         return;
7876     } else if (strncmp(message, "checkmate", 9) == 0) {
7877         if (WhiteOnMove(forwardMostMove)) {
7878             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7879         } else {
7880             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7881         }
7882         return;
7883     } else if (strstr(message, "Draw") != NULL ||
7884                strstr(message, "game is a draw") != NULL) {
7885         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7886         return;
7887     } else if (strstr(message, "offer") != NULL &&
7888                strstr(message, "draw") != NULL) {
7889 #if ZIPPY
7890         if (appData.zippyPlay && first.initDone) {
7891             /* Relay offer to ICS */
7892             SendToICS(ics_prefix);
7893             SendToICS("draw\n");
7894         }
7895 #endif
7896         cps->offeredDraw = 2; /* valid until this engine moves twice */
7897         if (gameMode == TwoMachinesPlay) {
7898             if (cps->other->offeredDraw) {
7899                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7900             /* [HGM] in two-machine mode we delay relaying draw offer      */
7901             /* until after we also have move, to see if it is really claim */
7902             }
7903         } else if (gameMode == MachinePlaysWhite ||
7904                    gameMode == MachinePlaysBlack) {
7905           if (userOfferedDraw) {
7906             DisplayInformation(_("Machine accepts your draw offer"));
7907             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7908           } else {
7909             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7910           }
7911         }
7912     }
7913
7914
7915     /*
7916      * Look for thinking output
7917      */
7918     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7919           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7920                                 ) {
7921         int plylev, mvleft, mvtot, curscore, time;
7922         char mvname[MOVE_LEN];
7923         u64 nodes; // [DM]
7924         char plyext;
7925         int ignore = FALSE;
7926         int prefixHint = FALSE;
7927         mvname[0] = NULLCHAR;
7928
7929         switch (gameMode) {
7930           case MachinePlaysBlack:
7931           case IcsPlayingBlack:
7932             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7933             break;
7934           case MachinePlaysWhite:
7935           case IcsPlayingWhite:
7936             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7937             break;
7938           case AnalyzeMode:
7939           case AnalyzeFile:
7940             break;
7941           case IcsObserving: /* [DM] icsEngineAnalyze */
7942             if (!appData.icsEngineAnalyze) ignore = TRUE;
7943             break;
7944           case TwoMachinesPlay:
7945             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7946                 ignore = TRUE;
7947             }
7948             break;
7949           default:
7950             ignore = TRUE;
7951             break;
7952         }
7953
7954         if (!ignore) {
7955             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7956             buf1[0] = NULLCHAR;
7957             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7958                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7959
7960                 if (plyext != ' ' && plyext != '\t') {
7961                     time *= 100;
7962                 }
7963
7964                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7965                 if( cps->scoreIsAbsolute &&
7966                     ( gameMode == MachinePlaysBlack ||
7967                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7968                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7969                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7970                      !WhiteOnMove(currentMove)
7971                     ) )
7972                 {
7973                     curscore = -curscore;
7974                 }
7975
7976
7977                 tempStats.depth = plylev;
7978                 tempStats.nodes = nodes;
7979                 tempStats.time = time;
7980                 tempStats.score = curscore;
7981                 tempStats.got_only_move = 0;
7982
7983                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7984                         int ticklen;
7985
7986                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7987                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7988                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7989                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7990                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7991                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7992                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7993                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7994                 }
7995
7996                 /* Buffer overflow protection */
7997                 if (buf1[0] != NULLCHAR) {
7998                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7999                         && appData.debugMode) {
8000                         fprintf(debugFP,
8001                                 "PV is too long; using the first %u bytes.\n",
8002                                 (unsigned) sizeof(tempStats.movelist) - 1);
8003                     }
8004
8005                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8006                 } else {
8007                     sprintf(tempStats.movelist, " no PV\n");
8008                 }
8009
8010                 if (tempStats.seen_stat) {
8011                     tempStats.ok_to_send = 1;
8012                 }
8013
8014                 if (strchr(tempStats.movelist, '(') != NULL) {
8015                     tempStats.line_is_book = 1;
8016                     tempStats.nr_moves = 0;
8017                     tempStats.moves_left = 0;
8018                 } else {
8019                     tempStats.line_is_book = 0;
8020                 }
8021
8022                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8023                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8024
8025                 SendProgramStatsToFrontend( cps, &tempStats );
8026
8027                 /*
8028                     [AS] Protect the thinkOutput buffer from overflow... this
8029                     is only useful if buf1 hasn't overflowed first!
8030                 */
8031                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8032                          plylev,
8033                          (gameMode == TwoMachinesPlay ?
8034                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8035                          ((double) curscore) / 100.0,
8036                          prefixHint ? lastHint : "",
8037                          prefixHint ? " " : "" );
8038
8039                 if( buf1[0] != NULLCHAR ) {
8040                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8041
8042                     if( strlen(buf1) > max_len ) {
8043                         if( appData.debugMode) {
8044                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8045                         }
8046                         buf1[max_len+1] = '\0';
8047                     }
8048
8049                     strcat( thinkOutput, buf1 );
8050                 }
8051
8052                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8053                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8054                     DisplayMove(currentMove - 1);
8055                 }
8056                 return;
8057
8058             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8059                 /* crafty (9.25+) says "(only move) <move>"
8060                  * if there is only 1 legal move
8061                  */
8062                 sscanf(p, "(only move) %s", buf1);
8063                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8064                 sprintf(programStats.movelist, "%s (only move)", buf1);
8065                 programStats.depth = 1;
8066                 programStats.nr_moves = 1;
8067                 programStats.moves_left = 1;
8068                 programStats.nodes = 1;
8069                 programStats.time = 1;
8070                 programStats.got_only_move = 1;
8071
8072                 /* Not really, but we also use this member to
8073                    mean "line isn't going to change" (Crafty
8074                    isn't searching, so stats won't change) */
8075                 programStats.line_is_book = 1;
8076
8077                 SendProgramStatsToFrontend( cps, &programStats );
8078
8079                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8080                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8081                     DisplayMove(currentMove - 1);
8082                 }
8083                 return;
8084             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8085                               &time, &nodes, &plylev, &mvleft,
8086                               &mvtot, mvname) >= 5) {
8087                 /* The stat01: line is from Crafty (9.29+) in response
8088                    to the "." command */
8089                 programStats.seen_stat = 1;
8090                 cps->maybeThinking = TRUE;
8091
8092                 if (programStats.got_only_move || !appData.periodicUpdates)
8093                   return;
8094
8095                 programStats.depth = plylev;
8096                 programStats.time = time;
8097                 programStats.nodes = nodes;
8098                 programStats.moves_left = mvleft;
8099                 programStats.nr_moves = mvtot;
8100                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8101                 programStats.ok_to_send = 1;
8102                 programStats.movelist[0] = '\0';
8103
8104                 SendProgramStatsToFrontend( cps, &programStats );
8105
8106                 return;
8107
8108             } else if (strncmp(message,"++",2) == 0) {
8109                 /* Crafty 9.29+ outputs this */
8110                 programStats.got_fail = 2;
8111                 return;
8112
8113             } else if (strncmp(message,"--",2) == 0) {
8114                 /* Crafty 9.29+ outputs this */
8115                 programStats.got_fail = 1;
8116                 return;
8117
8118             } else if (thinkOutput[0] != NULLCHAR &&
8119                        strncmp(message, "    ", 4) == 0) {
8120                 unsigned message_len;
8121
8122                 p = message;
8123                 while (*p && *p == ' ') p++;
8124
8125                 message_len = strlen( p );
8126
8127                 /* [AS] Avoid buffer overflow */
8128                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8129                     strcat(thinkOutput, " ");
8130                     strcat(thinkOutput, p);
8131                 }
8132
8133                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8134                     strcat(programStats.movelist, " ");
8135                     strcat(programStats.movelist, p);
8136                 }
8137
8138                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8139                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8140                     DisplayMove(currentMove - 1);
8141                 }
8142                 return;
8143             }
8144         }
8145         else {
8146             buf1[0] = NULLCHAR;
8147
8148             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8149                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8150             {
8151                 ChessProgramStats cpstats;
8152
8153                 if (plyext != ' ' && plyext != '\t') {
8154                     time *= 100;
8155                 }
8156
8157                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8158                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8159                     curscore = -curscore;
8160                 }
8161
8162                 cpstats.depth = plylev;
8163                 cpstats.nodes = nodes;
8164                 cpstats.time = time;
8165                 cpstats.score = curscore;
8166                 cpstats.got_only_move = 0;
8167                 cpstats.movelist[0] = '\0';
8168
8169                 if (buf1[0] != NULLCHAR) {
8170                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8171                 }
8172
8173                 cpstats.ok_to_send = 0;
8174                 cpstats.line_is_book = 0;
8175                 cpstats.nr_moves = 0;
8176                 cpstats.moves_left = 0;
8177
8178                 SendProgramStatsToFrontend( cps, &cpstats );
8179             }
8180         }
8181     }
8182 }
8183
8184
8185 /* Parse a game score from the character string "game", and
8186    record it as the history of the current game.  The game
8187    score is NOT assumed to start from the standard position.
8188    The display is not updated in any way.
8189    */
8190 void
8191 ParseGameHistory(game)
8192      char *game;
8193 {
8194     ChessMove moveType;
8195     int fromX, fromY, toX, toY, boardIndex;
8196     char promoChar;
8197     char *p, *q;
8198     char buf[MSG_SIZ];
8199
8200     if (appData.debugMode)
8201       fprintf(debugFP, "Parsing game history: %s\n", game);
8202
8203     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8204     gameInfo.site = StrSave(appData.icsHost);
8205     gameInfo.date = PGNDate();
8206     gameInfo.round = StrSave("-");
8207
8208     /* Parse out names of players */
8209     while (*game == ' ') game++;
8210     p = buf;
8211     while (*game != ' ') *p++ = *game++;
8212     *p = NULLCHAR;
8213     gameInfo.white = StrSave(buf);
8214     while (*game == ' ') game++;
8215     p = buf;
8216     while (*game != ' ' && *game != '\n') *p++ = *game++;
8217     *p = NULLCHAR;
8218     gameInfo.black = StrSave(buf);
8219
8220     /* Parse moves */
8221     boardIndex = blackPlaysFirst ? 1 : 0;
8222     yynewstr(game);
8223     for (;;) {
8224         yyboardindex = boardIndex;
8225         moveType = (ChessMove) yylex();
8226         switch (moveType) {
8227           case IllegalMove:             /* maybe suicide chess, etc. */
8228   if (appData.debugMode) {
8229     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8230     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8231     setbuf(debugFP, NULL);
8232   }
8233           case WhitePromotion:
8234           case BlackPromotion:
8235           case WhiteNonPromotion:
8236           case BlackNonPromotion:
8237           case NormalMove:
8238           case WhiteCapturesEnPassant:
8239           case BlackCapturesEnPassant:
8240           case WhiteKingSideCastle:
8241           case WhiteQueenSideCastle:
8242           case BlackKingSideCastle:
8243           case BlackQueenSideCastle:
8244           case WhiteKingSideCastleWild:
8245           case WhiteQueenSideCastleWild:
8246           case BlackKingSideCastleWild:
8247           case BlackQueenSideCastleWild:
8248           /* PUSH Fabien */
8249           case WhiteHSideCastleFR:
8250           case WhiteASideCastleFR:
8251           case BlackHSideCastleFR:
8252           case BlackASideCastleFR:
8253           /* POP Fabien */
8254             fromX = currentMoveString[0] - AAA;
8255             fromY = currentMoveString[1] - ONE;
8256             toX = currentMoveString[2] - AAA;
8257             toY = currentMoveString[3] - ONE;
8258             promoChar = currentMoveString[4];
8259             break;
8260           case WhiteDrop:
8261           case BlackDrop:
8262             fromX = moveType == WhiteDrop ?
8263               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8264             (int) CharToPiece(ToLower(currentMoveString[0]));
8265             fromY = DROP_RANK;
8266             toX = currentMoveString[2] - AAA;
8267             toY = currentMoveString[3] - ONE;
8268             promoChar = NULLCHAR;
8269             break;
8270           case AmbiguousMove:
8271             /* bug? */
8272             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8273   if (appData.debugMode) {
8274     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8275     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8276     setbuf(debugFP, NULL);
8277   }
8278             DisplayError(buf, 0);
8279             return;
8280           case ImpossibleMove:
8281             /* bug? */
8282             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8283   if (appData.debugMode) {
8284     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8285     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8286     setbuf(debugFP, NULL);
8287   }
8288             DisplayError(buf, 0);
8289             return;
8290           case EndOfFile:
8291             if (boardIndex < backwardMostMove) {
8292                 /* Oops, gap.  How did that happen? */
8293                 DisplayError(_("Gap in move list"), 0);
8294                 return;
8295             }
8296             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8297             if (boardIndex > forwardMostMove) {
8298                 forwardMostMove = boardIndex;
8299             }
8300             return;
8301           case ElapsedTime:
8302             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8303                 strcat(parseList[boardIndex-1], " ");
8304                 strcat(parseList[boardIndex-1], yy_text);
8305             }
8306             continue;
8307           case Comment:
8308           case PGNTag:
8309           case NAG:
8310           default:
8311             /* ignore */
8312             continue;
8313           case WhiteWins:
8314           case BlackWins:
8315           case GameIsDrawn:
8316           case GameUnfinished:
8317             if (gameMode == IcsExamining) {
8318                 if (boardIndex < backwardMostMove) {
8319                     /* Oops, gap.  How did that happen? */
8320                     return;
8321                 }
8322                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8323                 return;
8324             }
8325             gameInfo.result = moveType;
8326             p = strchr(yy_text, '{');
8327             if (p == NULL) p = strchr(yy_text, '(');
8328             if (p == NULL) {
8329                 p = yy_text;
8330                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8331             } else {
8332                 q = strchr(p, *p == '{' ? '}' : ')');
8333                 if (q != NULL) *q = NULLCHAR;
8334                 p++;
8335             }
8336             gameInfo.resultDetails = StrSave(p);
8337             continue;
8338         }
8339         if (boardIndex >= forwardMostMove &&
8340             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8341             backwardMostMove = blackPlaysFirst ? 1 : 0;
8342             return;
8343         }
8344         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8345                                  fromY, fromX, toY, toX, promoChar,
8346                                  parseList[boardIndex]);
8347         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8348         /* currentMoveString is set as a side-effect of yylex */
8349         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8350         strcat(moveList[boardIndex], "\n");
8351         boardIndex++;
8352         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8353         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8354           case MT_NONE:
8355           case MT_STALEMATE:
8356           default:
8357             break;
8358           case MT_CHECK:
8359             if(gameInfo.variant != VariantShogi)
8360                 strcat(parseList[boardIndex - 1], "+");
8361             break;
8362           case MT_CHECKMATE:
8363           case MT_STAINMATE:
8364             strcat(parseList[boardIndex - 1], "#");
8365             break;
8366         }
8367     }
8368 }
8369
8370
8371 /* Apply a move to the given board  */
8372 void
8373 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8374      int fromX, fromY, toX, toY;
8375      int promoChar;
8376      Board board;
8377 {
8378   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8379   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8380
8381     /* [HGM] compute & store e.p. status and castling rights for new position */
8382     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8383
8384       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8385       oldEP = (signed char)board[EP_STATUS];
8386       board[EP_STATUS] = EP_NONE;
8387
8388       if( board[toY][toX] != EmptySquare )
8389            board[EP_STATUS] = EP_CAPTURE;
8390
8391   if (fromY == DROP_RANK) {
8392         /* must be first */
8393         piece = board[toY][toX] = (ChessSquare) fromX;
8394   } else {
8395       int i;
8396
8397       if( board[fromY][fromX] == WhitePawn ) {
8398            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8399                board[EP_STATUS] = EP_PAWN_MOVE;
8400            if( toY-fromY==2) {
8401                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8402                         gameInfo.variant != VariantBerolina || toX < fromX)
8403                       board[EP_STATUS] = toX | berolina;
8404                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8405                         gameInfo.variant != VariantBerolina || toX > fromX)
8406                       board[EP_STATUS] = toX;
8407            }
8408       } else
8409       if( board[fromY][fromX] == BlackPawn ) {
8410            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8411                board[EP_STATUS] = EP_PAWN_MOVE;
8412            if( toY-fromY== -2) {
8413                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8414                         gameInfo.variant != VariantBerolina || toX < fromX)
8415                       board[EP_STATUS] = toX | berolina;
8416                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8417                         gameInfo.variant != VariantBerolina || toX > fromX)
8418                       board[EP_STATUS] = toX;
8419            }
8420        }
8421
8422        for(i=0; i<nrCastlingRights; i++) {
8423            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8424               board[CASTLING][i] == toX   && castlingRank[i] == toY
8425              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8426        }
8427
8428      if (fromX == toX && fromY == toY) return;
8429
8430      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8431      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8432      if(gameInfo.variant == VariantKnightmate)
8433          king += (int) WhiteUnicorn - (int) WhiteKing;
8434
8435     /* Code added by Tord: */
8436     /* FRC castling assumed when king captures friendly rook. */
8437     if (board[fromY][fromX] == WhiteKing &&
8438              board[toY][toX] == WhiteRook) {
8439       board[fromY][fromX] = EmptySquare;
8440       board[toY][toX] = EmptySquare;
8441       if(toX > fromX) {
8442         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8443       } else {
8444         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8445       }
8446     } else if (board[fromY][fromX] == BlackKing &&
8447                board[toY][toX] == BlackRook) {
8448       board[fromY][fromX] = EmptySquare;
8449       board[toY][toX] = EmptySquare;
8450       if(toX > fromX) {
8451         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8452       } else {
8453         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8454       }
8455     /* End of code added by Tord */
8456
8457     } else if (board[fromY][fromX] == king
8458         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8459         && toY == fromY && toX > fromX+1) {
8460         board[fromY][fromX] = EmptySquare;
8461         board[toY][toX] = king;
8462         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8463         board[fromY][BOARD_RGHT-1] = EmptySquare;
8464     } else if (board[fromY][fromX] == king
8465         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8466                && toY == fromY && toX < fromX-1) {
8467         board[fromY][fromX] = EmptySquare;
8468         board[toY][toX] = king;
8469         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8470         board[fromY][BOARD_LEFT] = EmptySquare;
8471     } else if (board[fromY][fromX] == WhitePawn
8472                && toY >= BOARD_HEIGHT-promoRank
8473                && gameInfo.variant != VariantXiangqi
8474                ) {
8475         /* white pawn promotion */
8476         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8477         if (board[toY][toX] == EmptySquare) {
8478             board[toY][toX] = WhiteQueen;
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 == BOARD_HEIGHT-4)
8485                && (toX != fromX)
8486                && gameInfo.variant != VariantXiangqi
8487                && gameInfo.variant != VariantBerolina
8488                && (board[fromY][fromX] == WhitePawn)
8489                && (board[toY][toX] == EmptySquare)) {
8490         board[fromY][fromX] = EmptySquare;
8491         board[toY][toX] = WhitePawn;
8492         captured = board[toY - 1][toX];
8493         board[toY - 1][toX] = EmptySquare;
8494     } else if ((fromY == BOARD_HEIGHT-4)
8495                && (toX == fromX)
8496                && gameInfo.variant == VariantBerolina
8497                && (board[fromY][fromX] == WhitePawn)
8498                && (board[toY][toX] == EmptySquare)) {
8499         board[fromY][fromX] = EmptySquare;
8500         board[toY][toX] = WhitePawn;
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 if (board[fromY][fromX] == king
8508         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8509                && toY == fromY && toX > fromX+1) {
8510         board[fromY][fromX] = EmptySquare;
8511         board[toY][toX] = king;
8512         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8513         board[fromY][BOARD_RGHT-1] = EmptySquare;
8514     } else if (board[fromY][fromX] == king
8515         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8516                && toY == fromY && toX < fromX-1) {
8517         board[fromY][fromX] = EmptySquare;
8518         board[toY][toX] = king;
8519         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8520         board[fromY][BOARD_LEFT] = EmptySquare;
8521     } else if (fromY == 7 && fromX == 3
8522                && board[fromY][fromX] == BlackKing
8523                && toY == 7 && toX == 5) {
8524         board[fromY][fromX] = EmptySquare;
8525         board[toY][toX] = BlackKing;
8526         board[fromY][7] = EmptySquare;
8527         board[toY][4] = BlackRook;
8528     } else if (fromY == 7 && fromX == 3
8529                && board[fromY][fromX] == BlackKing
8530                && toY == 7 && toX == 1) {
8531         board[fromY][fromX] = EmptySquare;
8532         board[toY][toX] = BlackKing;
8533         board[fromY][0] = EmptySquare;
8534         board[toY][2] = BlackRook;
8535     } else if (board[fromY][fromX] == BlackPawn
8536                && toY < promoRank
8537                && gameInfo.variant != VariantXiangqi
8538                ) {
8539         /* black pawn promotion */
8540         board[toY][toX] = CharToPiece(ToLower(promoChar));
8541         if (board[toY][toX] == EmptySquare) {
8542             board[toY][toX] = BlackQueen;
8543         }
8544         if(gameInfo.variant==VariantBughouse ||
8545            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8546             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8547         board[fromY][fromX] = EmptySquare;
8548     } else if ((fromY == 3)
8549                && (toX != fromX)
8550                && gameInfo.variant != VariantXiangqi
8551                && gameInfo.variant != VariantBerolina
8552                && (board[fromY][fromX] == BlackPawn)
8553                && (board[toY][toX] == EmptySquare)) {
8554         board[fromY][fromX] = EmptySquare;
8555         board[toY][toX] = BlackPawn;
8556         captured = board[toY + 1][toX];
8557         board[toY + 1][toX] = EmptySquare;
8558     } else if ((fromY == 3)
8559                && (toX == fromX)
8560                && gameInfo.variant == VariantBerolina
8561                && (board[fromY][fromX] == BlackPawn)
8562                && (board[toY][toX] == EmptySquare)) {
8563         board[fromY][fromX] = EmptySquare;
8564         board[toY][toX] = BlackPawn;
8565         if(oldEP & EP_BEROLIN_A) {
8566                 captured = board[fromY][fromX-1];
8567                 board[fromY][fromX-1] = EmptySquare;
8568         }else{  captured = board[fromY][fromX+1];
8569                 board[fromY][fromX+1] = EmptySquare;
8570         }
8571     } else {
8572         board[toY][toX] = board[fromY][fromX];
8573         board[fromY][fromX] = EmptySquare;
8574     }
8575   }
8576
8577     if (gameInfo.holdingsWidth != 0) {
8578
8579       /* !!A lot more code needs to be written to support holdings  */
8580       /* [HGM] OK, so I have written it. Holdings are stored in the */
8581       /* penultimate board files, so they are automaticlly stored   */
8582       /* in the game history.                                       */
8583       if (fromY == DROP_RANK) {
8584         /* Delete from holdings, by decreasing count */
8585         /* and erasing image if necessary            */
8586         p = (int) fromX;
8587         if(p < (int) BlackPawn) { /* white drop */
8588              p -= (int)WhitePawn;
8589                  p = PieceToNumber((ChessSquare)p);
8590              if(p >= gameInfo.holdingsSize) p = 0;
8591              if(--board[p][BOARD_WIDTH-2] <= 0)
8592                   board[p][BOARD_WIDTH-1] = EmptySquare;
8593              if((int)board[p][BOARD_WIDTH-2] < 0)
8594                         board[p][BOARD_WIDTH-2] = 0;
8595         } else {                  /* black drop */
8596              p -= (int)BlackPawn;
8597                  p = PieceToNumber((ChessSquare)p);
8598              if(p >= gameInfo.holdingsSize) p = 0;
8599              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8600                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8601              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8602                         board[BOARD_HEIGHT-1-p][1] = 0;
8603         }
8604       }
8605       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8606           && gameInfo.variant != VariantBughouse        ) {
8607         /* [HGM] holdings: Add to holdings, if holdings exist */
8608         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8609                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8610                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8611         }
8612         p = (int) captured;
8613         if (p >= (int) BlackPawn) {
8614           p -= (int)BlackPawn;
8615           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8616                   /* in Shogi restore piece to its original  first */
8617                   captured = (ChessSquare) (DEMOTED captured);
8618                   p = DEMOTED p;
8619           }
8620           p = PieceToNumber((ChessSquare)p);
8621           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8622           board[p][BOARD_WIDTH-2]++;
8623           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8624         } else {
8625           p -= (int)WhitePawn;
8626           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8627                   captured = (ChessSquare) (DEMOTED captured);
8628                   p = DEMOTED p;
8629           }
8630           p = PieceToNumber((ChessSquare)p);
8631           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8632           board[BOARD_HEIGHT-1-p][1]++;
8633           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8634         }
8635       }
8636     } else if (gameInfo.variant == VariantAtomic) {
8637       if (captured != EmptySquare) {
8638         int y, x;
8639         for (y = toY-1; y <= toY+1; y++) {
8640           for (x = toX-1; x <= toX+1; x++) {
8641             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8642                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8643               board[y][x] = EmptySquare;
8644             }
8645           }
8646         }
8647         board[toY][toX] = EmptySquare;
8648       }
8649     }
8650     if(promoChar == '+') {
8651         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8652         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8653     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8654         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8655     }
8656     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8657                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8658         // [HGM] superchess: take promotion piece out of holdings
8659         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8660         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8661             if(!--board[k][BOARD_WIDTH-2])
8662                 board[k][BOARD_WIDTH-1] = EmptySquare;
8663         } else {
8664             if(!--board[BOARD_HEIGHT-1-k][1])
8665                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8666         }
8667     }
8668
8669 }
8670
8671 /* Updates forwardMostMove */
8672 void
8673 MakeMove(fromX, fromY, toX, toY, promoChar)
8674      int fromX, fromY, toX, toY;
8675      int promoChar;
8676 {
8677 //    forwardMostMove++; // [HGM] bare: moved downstream
8678
8679     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8680         int timeLeft; static int lastLoadFlag=0; int king, piece;
8681         piece = boards[forwardMostMove][fromY][fromX];
8682         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8683         if(gameInfo.variant == VariantKnightmate)
8684             king += (int) WhiteUnicorn - (int) WhiteKing;
8685         if(forwardMostMove == 0) {
8686             if(blackPlaysFirst)
8687                 fprintf(serverMoves, "%s;", second.tidy);
8688             fprintf(serverMoves, "%s;", first.tidy);
8689             if(!blackPlaysFirst)
8690                 fprintf(serverMoves, "%s;", second.tidy);
8691         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8692         lastLoadFlag = loadFlag;
8693         // print base move
8694         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8695         // print castling suffix
8696         if( toY == fromY && piece == king ) {
8697             if(toX-fromX > 1)
8698                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8699             if(fromX-toX >1)
8700                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8701         }
8702         // e.p. suffix
8703         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8704              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8705              boards[forwardMostMove][toY][toX] == EmptySquare
8706              && fromX != toX && fromY != toY)
8707                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8708         // promotion suffix
8709         if(promoChar != NULLCHAR)
8710                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8711         if(!loadFlag) {
8712             fprintf(serverMoves, "/%d/%d",
8713                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8714             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8715             else                      timeLeft = blackTimeRemaining/1000;
8716             fprintf(serverMoves, "/%d", timeLeft);
8717         }
8718         fflush(serverMoves);
8719     }
8720
8721     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8722       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8723                         0, 1);
8724       return;
8725     }
8726     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8727     if (commentList[forwardMostMove+1] != NULL) {
8728         free(commentList[forwardMostMove+1]);
8729         commentList[forwardMostMove+1] = NULL;
8730     }
8731     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8732     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8733     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8734     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8735     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8736     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8737     gameInfo.result = GameUnfinished;
8738     if (gameInfo.resultDetails != NULL) {
8739         free(gameInfo.resultDetails);
8740         gameInfo.resultDetails = NULL;
8741     }
8742     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8743                               moveList[forwardMostMove - 1]);
8744     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8745                              PosFlags(forwardMostMove - 1),
8746                              fromY, fromX, toY, toX, promoChar,
8747                              parseList[forwardMostMove - 1]);
8748     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8749       case MT_NONE:
8750       case MT_STALEMATE:
8751       default:
8752         break;
8753       case MT_CHECK:
8754         if(gameInfo.variant != VariantShogi)
8755             strcat(parseList[forwardMostMove - 1], "+");
8756         break;
8757       case MT_CHECKMATE:
8758       case MT_STAINMATE:
8759         strcat(parseList[forwardMostMove - 1], "#");
8760         break;
8761     }
8762     if (appData.debugMode) {
8763         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8764     }
8765
8766 }
8767
8768 /* Updates currentMove if not pausing */
8769 void
8770 ShowMove(fromX, fromY, toX, toY)
8771 {
8772     int instant = (gameMode == PlayFromGameFile) ?
8773         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8774     if(appData.noGUI) return;
8775     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8776         if (!instant) {
8777             if (forwardMostMove == currentMove + 1) {
8778                 AnimateMove(boards[forwardMostMove - 1],
8779                             fromX, fromY, toX, toY);
8780             }
8781             if (appData.highlightLastMove) {
8782                 SetHighlights(fromX, fromY, toX, toY);
8783             }
8784         }
8785         currentMove = forwardMostMove;
8786     }
8787
8788     if (instant) return;
8789
8790     DisplayMove(currentMove - 1);
8791     DrawPosition(FALSE, boards[currentMove]);
8792     DisplayBothClocks();
8793     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8794 }
8795
8796 void SendEgtPath(ChessProgramState *cps)
8797 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8798         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8799
8800         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8801
8802         while(*p) {
8803             char c, *q = name+1, *r, *s;
8804
8805             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8806             while(*p && *p != ',') *q++ = *p++;
8807             *q++ = ':'; *q = 0;
8808             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8809                 strcmp(name, ",nalimov:") == 0 ) {
8810                 // take nalimov path from the menu-changeable option first, if it is defined
8811               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8812                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8813             } else
8814             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8815                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8816                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8817                 s = r = StrStr(s, ":") + 1; // beginning of path info
8818                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8819                 c = *r; *r = 0;             // temporarily null-terminate path info
8820                     *--q = 0;               // strip of trailig ':' from name
8821                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8822                 *r = c;
8823                 SendToProgram(buf,cps);     // send egtbpath command for this format
8824             }
8825             if(*p == ',') p++; // read away comma to position for next format name
8826         }
8827 }
8828
8829 void
8830 InitChessProgram(cps, setup)
8831      ChessProgramState *cps;
8832      int setup; /* [HGM] needed to setup FRC opening position */
8833 {
8834     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8835     if (appData.noChessProgram) return;
8836     hintRequested = FALSE;
8837     bookRequested = FALSE;
8838
8839     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8840     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8841     if(cps->memSize) { /* [HGM] memory */
8842       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8843         SendToProgram(buf, cps);
8844     }
8845     SendEgtPath(cps); /* [HGM] EGT */
8846     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8847       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8848         SendToProgram(buf, cps);
8849     }
8850
8851     SendToProgram(cps->initString, cps);
8852     if (gameInfo.variant != VariantNormal &&
8853         gameInfo.variant != VariantLoadable
8854         /* [HGM] also send variant if board size non-standard */
8855         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8856                                             ) {
8857       char *v = VariantName(gameInfo.variant);
8858       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8859         /* [HGM] in protocol 1 we have to assume all variants valid */
8860         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8861         DisplayFatalError(buf, 0, 1);
8862         return;
8863       }
8864
8865       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8866       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8867       if( gameInfo.variant == VariantXiangqi )
8868            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8869       if( gameInfo.variant == VariantShogi )
8870            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8871       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8872            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8873       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8874                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8875            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8876       if( gameInfo.variant == VariantCourier )
8877            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8878       if( gameInfo.variant == VariantSuper )
8879            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8880       if( gameInfo.variant == VariantGreat )
8881            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8882
8883       if(overruled) {
8884         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8885                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8886            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8887            if(StrStr(cps->variants, b) == NULL) {
8888                // specific sized variant not known, check if general sizing allowed
8889                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8890                    if(StrStr(cps->variants, "boardsize") == NULL) {
8891                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8892                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8893                        DisplayFatalError(buf, 0, 1);
8894                        return;
8895                    }
8896                    /* [HGM] here we really should compare with the maximum supported board size */
8897                }
8898            }
8899       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8900       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8901       SendToProgram(buf, cps);
8902     }
8903     currentlyInitializedVariant = gameInfo.variant;
8904
8905     /* [HGM] send opening position in FRC to first engine */
8906     if(setup) {
8907           SendToProgram("force\n", cps);
8908           SendBoard(cps, 0);
8909           /* engine is now in force mode! Set flag to wake it up after first move. */
8910           setboardSpoiledMachineBlack = 1;
8911     }
8912
8913     if (cps->sendICS) {
8914       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8915       SendToProgram(buf, cps);
8916     }
8917     cps->maybeThinking = FALSE;
8918     cps->offeredDraw = 0;
8919     if (!appData.icsActive) {
8920         SendTimeControl(cps, movesPerSession, timeControl,
8921                         timeIncrement, appData.searchDepth,
8922                         searchTime);
8923     }
8924     if (appData.showThinking
8925         // [HGM] thinking: four options require thinking output to be sent
8926         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8927                                 ) {
8928         SendToProgram("post\n", cps);
8929     }
8930     SendToProgram("hard\n", cps);
8931     if (!appData.ponderNextMove) {
8932         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8933            it without being sure what state we are in first.  "hard"
8934            is not a toggle, so that one is OK.
8935          */
8936         SendToProgram("easy\n", cps);
8937     }
8938     if (cps->usePing) {
8939       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8940       SendToProgram(buf, cps);
8941     }
8942     cps->initDone = TRUE;
8943 }
8944
8945
8946 void
8947 StartChessProgram(cps)
8948      ChessProgramState *cps;
8949 {
8950     char buf[MSG_SIZ];
8951     int err;
8952
8953     if (appData.noChessProgram) return;
8954     cps->initDone = FALSE;
8955
8956     if (strcmp(cps->host, "localhost") == 0) {
8957         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8958     } else if (*appData.remoteShell == NULLCHAR) {
8959         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8960     } else {
8961         if (*appData.remoteUser == NULLCHAR) {
8962           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8963                     cps->program);
8964         } else {
8965           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8966                     cps->host, appData.remoteUser, cps->program);
8967         }
8968         err = StartChildProcess(buf, "", &cps->pr);
8969     }
8970
8971     if (err != 0) {
8972       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8973         DisplayFatalError(buf, err, 1);
8974         cps->pr = NoProc;
8975         cps->isr = NULL;
8976         return;
8977     }
8978
8979     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8980     if (cps->protocolVersion > 1) {
8981       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8982       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8983       cps->comboCnt = 0;  //                and values of combo boxes
8984       SendToProgram(buf, cps);
8985     } else {
8986       SendToProgram("xboard\n", cps);
8987     }
8988 }
8989
8990
8991 void
8992 TwoMachinesEventIfReady P((void))
8993 {
8994   if (first.lastPing != first.lastPong) {
8995     DisplayMessage("", _("Waiting for first chess program"));
8996     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8997     return;
8998   }
8999   if (second.lastPing != second.lastPong) {
9000     DisplayMessage("", _("Waiting for second chess program"));
9001     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9002     return;
9003   }
9004   ThawUI();
9005   TwoMachinesEvent();
9006 }
9007
9008 void
9009 NextMatchGame P((void))
9010 {
9011     int index; /* [HGM] autoinc: step load index during match */
9012     Reset(FALSE, TRUE);
9013     if (*appData.loadGameFile != NULLCHAR) {
9014         index = appData.loadGameIndex;
9015         if(index < 0) { // [HGM] autoinc
9016             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9017             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9018         }
9019         LoadGameFromFile(appData.loadGameFile,
9020                          index,
9021                          appData.loadGameFile, FALSE);
9022     } else if (*appData.loadPositionFile != NULLCHAR) {
9023         index = appData.loadPositionIndex;
9024         if(index < 0) { // [HGM] autoinc
9025             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9026             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9027         }
9028         LoadPositionFromFile(appData.loadPositionFile,
9029                              index,
9030                              appData.loadPositionFile);
9031     }
9032     TwoMachinesEventIfReady();
9033 }
9034
9035 void UserAdjudicationEvent( int result )
9036 {
9037     ChessMove gameResult = GameIsDrawn;
9038
9039     if( result > 0 ) {
9040         gameResult = WhiteWins;
9041     }
9042     else if( result < 0 ) {
9043         gameResult = BlackWins;
9044     }
9045
9046     if( gameMode == TwoMachinesPlay ) {
9047         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9048     }
9049 }
9050
9051
9052 // [HGM] save: calculate checksum of game to make games easily identifiable
9053 int StringCheckSum(char *s)
9054 {
9055         int i = 0;
9056         if(s==NULL) return 0;
9057         while(*s) i = i*259 + *s++;
9058         return i;
9059 }
9060
9061 int GameCheckSum()
9062 {
9063         int i, sum=0;
9064         for(i=backwardMostMove; i<forwardMostMove; i++) {
9065                 sum += pvInfoList[i].depth;
9066                 sum += StringCheckSum(parseList[i]);
9067                 sum += StringCheckSum(commentList[i]);
9068                 sum *= 261;
9069         }
9070         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9071         return sum + StringCheckSum(commentList[i]);
9072 } // end of save patch
9073
9074 void
9075 GameEnds(result, resultDetails, whosays)
9076      ChessMove result;
9077      char *resultDetails;
9078      int whosays;
9079 {
9080     GameMode nextGameMode;
9081     int isIcsGame;
9082     char buf[MSG_SIZ], popupRequested = 0;
9083
9084     if(endingGame) return; /* [HGM] crash: forbid recursion */
9085     endingGame = 1;
9086     if(twoBoards) { // [HGM] dual: switch back to one board
9087         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9088         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9089     }
9090     if (appData.debugMode) {
9091       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9092               result, resultDetails ? resultDetails : "(null)", whosays);
9093     }
9094
9095     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9096
9097     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9098         /* If we are playing on ICS, the server decides when the
9099            game is over, but the engine can offer to draw, claim
9100            a draw, or resign.
9101          */
9102 #if ZIPPY
9103         if (appData.zippyPlay && first.initDone) {
9104             if (result == GameIsDrawn) {
9105                 /* In case draw still needs to be claimed */
9106                 SendToICS(ics_prefix);
9107                 SendToICS("draw\n");
9108             } else if (StrCaseStr(resultDetails, "resign")) {
9109                 SendToICS(ics_prefix);
9110                 SendToICS("resign\n");
9111             }
9112         }
9113 #endif
9114         endingGame = 0; /* [HGM] crash */
9115         return;
9116     }
9117
9118     /* If we're loading the game from a file, stop */
9119     if (whosays == GE_FILE) {
9120       (void) StopLoadGameTimer();
9121       gameFileFP = NULL;
9122     }
9123
9124     /* Cancel draw offers */
9125     first.offeredDraw = second.offeredDraw = 0;
9126
9127     /* If this is an ICS game, only ICS can really say it's done;
9128        if not, anyone can. */
9129     isIcsGame = (gameMode == IcsPlayingWhite ||
9130                  gameMode == IcsPlayingBlack ||
9131                  gameMode == IcsObserving    ||
9132                  gameMode == IcsExamining);
9133
9134     if (!isIcsGame || whosays == GE_ICS) {
9135         /* OK -- not an ICS game, or ICS said it was done */
9136         StopClocks();
9137         if (!isIcsGame && !appData.noChessProgram)
9138           SetUserThinkingEnables();
9139
9140         /* [HGM] if a machine claims the game end we verify this claim */
9141         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9142             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9143                 char claimer;
9144                 ChessMove trueResult = (ChessMove) -1;
9145
9146                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9147                                             first.twoMachinesColor[0] :
9148                                             second.twoMachinesColor[0] ;
9149
9150                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9151                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9152                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9153                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9154                 } else
9155                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9156                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9157                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9158                 } else
9159                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9160                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9161                 }
9162
9163                 // now verify win claims, but not in drop games, as we don't understand those yet
9164                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9165                                                  || gameInfo.variant == VariantGreat) &&
9166                     (result == WhiteWins && claimer == 'w' ||
9167                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9168                       if (appData.debugMode) {
9169                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9170                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9171                       }
9172                       if(result != trueResult) {
9173                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9174                               result = claimer == 'w' ? BlackWins : WhiteWins;
9175                               resultDetails = buf;
9176                       }
9177                 } else
9178                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9179                     && (forwardMostMove <= backwardMostMove ||
9180                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9181                         (claimer=='b')==(forwardMostMove&1))
9182                                                                                   ) {
9183                       /* [HGM] verify: draws that were not flagged are false claims */
9184                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9185                       result = claimer == 'w' ? BlackWins : WhiteWins;
9186                       resultDetails = buf;
9187                 }
9188                 /* (Claiming a loss is accepted no questions asked!) */
9189             }
9190             /* [HGM] bare: don't allow bare King to win */
9191             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9192                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9193                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9194                && result != GameIsDrawn)
9195             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9196                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9197                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9198                         if(p >= 0 && p <= (int)WhiteKing) k++;
9199                 }
9200                 if (appData.debugMode) {
9201                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9202                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9203                 }
9204                 if(k <= 1) {
9205                         result = GameIsDrawn;
9206                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9207                         resultDetails = buf;
9208                 }
9209             }
9210         }
9211
9212
9213         if(serverMoves != NULL && !loadFlag) { char c = '=';
9214             if(result==WhiteWins) c = '+';
9215             if(result==BlackWins) c = '-';
9216             if(resultDetails != NULL)
9217                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9218         }
9219         if (resultDetails != NULL) {
9220             gameInfo.result = result;
9221             gameInfo.resultDetails = StrSave(resultDetails);
9222
9223             /* display last move only if game was not loaded from file */
9224             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9225                 DisplayMove(currentMove - 1);
9226
9227             if (forwardMostMove != 0) {
9228                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9229                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9230                                                                 ) {
9231                     if (*appData.saveGameFile != NULLCHAR) {
9232                         SaveGameToFile(appData.saveGameFile, TRUE);
9233                     } else if (appData.autoSaveGames) {
9234                         AutoSaveGame();
9235                     }
9236                     if (*appData.savePositionFile != NULLCHAR) {
9237                         SavePositionToFile(appData.savePositionFile);
9238                     }
9239                 }
9240             }
9241
9242             /* Tell program how game ended in case it is learning */
9243             /* [HGM] Moved this to after saving the PGN, just in case */
9244             /* engine died and we got here through time loss. In that */
9245             /* case we will get a fatal error writing the pipe, which */
9246             /* would otherwise lose us the PGN.                       */
9247             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9248             /* output during GameEnds should never be fatal anymore   */
9249             if (gameMode == MachinePlaysWhite ||
9250                 gameMode == MachinePlaysBlack ||
9251                 gameMode == TwoMachinesPlay ||
9252                 gameMode == IcsPlayingWhite ||
9253                 gameMode == IcsPlayingBlack ||
9254                 gameMode == BeginningOfGame) {
9255                 char buf[MSG_SIZ];
9256                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9257                         resultDetails);
9258                 if (first.pr != NoProc) {
9259                     SendToProgram(buf, &first);
9260                 }
9261                 if (second.pr != NoProc &&
9262                     gameMode == TwoMachinesPlay) {
9263                     SendToProgram(buf, &second);
9264                 }
9265             }
9266         }
9267
9268         if (appData.icsActive) {
9269             if (appData.quietPlay &&
9270                 (gameMode == IcsPlayingWhite ||
9271                  gameMode == IcsPlayingBlack)) {
9272                 SendToICS(ics_prefix);
9273                 SendToICS("set shout 1\n");
9274             }
9275             nextGameMode = IcsIdle;
9276             ics_user_moved = FALSE;
9277             /* clean up premove.  It's ugly when the game has ended and the
9278              * premove highlights are still on the board.
9279              */
9280             if (gotPremove) {
9281               gotPremove = FALSE;
9282               ClearPremoveHighlights();
9283               DrawPosition(FALSE, boards[currentMove]);
9284             }
9285             if (whosays == GE_ICS) {
9286                 switch (result) {
9287                 case WhiteWins:
9288                     if (gameMode == IcsPlayingWhite)
9289                         PlayIcsWinSound();
9290                     else if(gameMode == IcsPlayingBlack)
9291                         PlayIcsLossSound();
9292                     break;
9293                 case BlackWins:
9294                     if (gameMode == IcsPlayingBlack)
9295                         PlayIcsWinSound();
9296                     else if(gameMode == IcsPlayingWhite)
9297                         PlayIcsLossSound();
9298                     break;
9299                 case GameIsDrawn:
9300                     PlayIcsDrawSound();
9301                     break;
9302                 default:
9303                     PlayIcsUnfinishedSound();
9304                 }
9305             }
9306         } else if (gameMode == EditGame ||
9307                    gameMode == PlayFromGameFile ||
9308                    gameMode == AnalyzeMode ||
9309                    gameMode == AnalyzeFile) {
9310             nextGameMode = gameMode;
9311         } else {
9312             nextGameMode = EndOfGame;
9313         }
9314         pausing = FALSE;
9315         ModeHighlight();
9316     } else {
9317         nextGameMode = gameMode;
9318     }
9319
9320     if (appData.noChessProgram) {
9321         gameMode = nextGameMode;
9322         ModeHighlight();
9323         endingGame = 0; /* [HGM] crash */
9324         return;
9325     }
9326
9327     if (first.reuse) {
9328         /* Put first chess program into idle state */
9329         if (first.pr != NoProc &&
9330             (gameMode == MachinePlaysWhite ||
9331              gameMode == MachinePlaysBlack ||
9332              gameMode == TwoMachinesPlay ||
9333              gameMode == IcsPlayingWhite ||
9334              gameMode == IcsPlayingBlack ||
9335              gameMode == BeginningOfGame)) {
9336             SendToProgram("force\n", &first);
9337             if (first.usePing) {
9338               char buf[MSG_SIZ];
9339               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9340               SendToProgram(buf, &first);
9341             }
9342         }
9343     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9344         /* Kill off first chess program */
9345         if (first.isr != NULL)
9346           RemoveInputSource(first.isr);
9347         first.isr = NULL;
9348
9349         if (first.pr != NoProc) {
9350             ExitAnalyzeMode();
9351             DoSleep( appData.delayBeforeQuit );
9352             SendToProgram("quit\n", &first);
9353             DoSleep( appData.delayAfterQuit );
9354             DestroyChildProcess(first.pr, first.useSigterm);
9355         }
9356         first.pr = NoProc;
9357     }
9358     if (second.reuse) {
9359         /* Put second chess program into idle state */
9360         if (second.pr != NoProc &&
9361             gameMode == TwoMachinesPlay) {
9362             SendToProgram("force\n", &second);
9363             if (second.usePing) {
9364               char buf[MSG_SIZ];
9365               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9366               SendToProgram(buf, &second);
9367             }
9368         }
9369     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9370         /* Kill off second chess program */
9371         if (second.isr != NULL)
9372           RemoveInputSource(second.isr);
9373         second.isr = NULL;
9374
9375         if (second.pr != NoProc) {
9376             DoSleep( appData.delayBeforeQuit );
9377             SendToProgram("quit\n", &second);
9378             DoSleep( appData.delayAfterQuit );
9379             DestroyChildProcess(second.pr, second.useSigterm);
9380         }
9381         second.pr = NoProc;
9382     }
9383
9384     if (matchMode && gameMode == TwoMachinesPlay) {
9385         switch (result) {
9386         case WhiteWins:
9387           if (first.twoMachinesColor[0] == 'w') {
9388             first.matchWins++;
9389           } else {
9390             second.matchWins++;
9391           }
9392           break;
9393         case BlackWins:
9394           if (first.twoMachinesColor[0] == 'b') {
9395             first.matchWins++;
9396           } else {
9397             second.matchWins++;
9398           }
9399           break;
9400         default:
9401           break;
9402         }
9403         if (matchGame < appData.matchGames) {
9404             char *tmp;
9405             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9406                 tmp = first.twoMachinesColor;
9407                 first.twoMachinesColor = second.twoMachinesColor;
9408                 second.twoMachinesColor = tmp;
9409             }
9410             gameMode = nextGameMode;
9411             matchGame++;
9412             if(appData.matchPause>10000 || appData.matchPause<10)
9413                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9414             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9415             endingGame = 0; /* [HGM] crash */
9416             return;
9417         } else {
9418             gameMode = nextGameMode;
9419             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9420                      first.tidy, second.tidy,
9421                      first.matchWins, second.matchWins,
9422                      appData.matchGames - (first.matchWins + second.matchWins));
9423             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9424         }
9425     }
9426     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9427         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9428       ExitAnalyzeMode();
9429     gameMode = nextGameMode;
9430     ModeHighlight();
9431     endingGame = 0;  /* [HGM] crash */
9432     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9433       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9434         matchMode = FALSE; appData.matchGames = matchGame = 0;
9435         DisplayNote(buf);
9436       }
9437     }
9438 }
9439
9440 /* Assumes program was just initialized (initString sent).
9441    Leaves program in force mode. */
9442 void
9443 FeedMovesToProgram(cps, upto)
9444      ChessProgramState *cps;
9445      int upto;
9446 {
9447     int i;
9448
9449     if (appData.debugMode)
9450       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9451               startedFromSetupPosition ? "position and " : "",
9452               backwardMostMove, upto, cps->which);
9453     if(currentlyInitializedVariant != gameInfo.variant) {
9454       char buf[MSG_SIZ];
9455         // [HGM] variantswitch: make engine aware of new variant
9456         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9457                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9458         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9459         SendToProgram(buf, cps);
9460         currentlyInitializedVariant = gameInfo.variant;
9461     }
9462     SendToProgram("force\n", cps);
9463     if (startedFromSetupPosition) {
9464         SendBoard(cps, backwardMostMove);
9465     if (appData.debugMode) {
9466         fprintf(debugFP, "feedMoves\n");
9467     }
9468     }
9469     for (i = backwardMostMove; i < upto; i++) {
9470         SendMoveToProgram(i, cps);
9471     }
9472 }
9473
9474
9475 void
9476 ResurrectChessProgram()
9477 {
9478      /* The chess program may have exited.
9479         If so, restart it and feed it all the moves made so far. */
9480
9481     if (appData.noChessProgram || first.pr != NoProc) return;
9482
9483     StartChessProgram(&first);
9484     InitChessProgram(&first, FALSE);
9485     FeedMovesToProgram(&first, currentMove);
9486
9487     if (!first.sendTime) {
9488         /* can't tell gnuchess what its clock should read,
9489            so we bow to its notion. */
9490         ResetClocks();
9491         timeRemaining[0][currentMove] = whiteTimeRemaining;
9492         timeRemaining[1][currentMove] = blackTimeRemaining;
9493     }
9494
9495     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9496                 appData.icsEngineAnalyze) && first.analysisSupport) {
9497       SendToProgram("analyze\n", &first);
9498       first.analyzing = TRUE;
9499     }
9500 }
9501
9502 /*
9503  * Button procedures
9504  */
9505 void
9506 Reset(redraw, init)
9507      int redraw, init;
9508 {
9509     int i;
9510
9511     if (appData.debugMode) {
9512         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9513                 redraw, init, gameMode);
9514     }
9515     CleanupTail(); // [HGM] vari: delete any stored variations
9516     pausing = pauseExamInvalid = FALSE;
9517     startedFromSetupPosition = blackPlaysFirst = FALSE;
9518     firstMove = TRUE;
9519     whiteFlag = blackFlag = FALSE;
9520     userOfferedDraw = FALSE;
9521     hintRequested = bookRequested = FALSE;
9522     first.maybeThinking = FALSE;
9523     second.maybeThinking = FALSE;
9524     first.bookSuspend = FALSE; // [HGM] book
9525     second.bookSuspend = FALSE;
9526     thinkOutput[0] = NULLCHAR;
9527     lastHint[0] = NULLCHAR;
9528     ClearGameInfo(&gameInfo);
9529     gameInfo.variant = StringToVariant(appData.variant);
9530     ics_user_moved = ics_clock_paused = FALSE;
9531     ics_getting_history = H_FALSE;
9532     ics_gamenum = -1;
9533     white_holding[0] = black_holding[0] = NULLCHAR;
9534     ClearProgramStats();
9535     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9536
9537     ResetFrontEnd();
9538     ClearHighlights();
9539     flipView = appData.flipView;
9540     ClearPremoveHighlights();
9541     gotPremove = FALSE;
9542     alarmSounded = FALSE;
9543
9544     GameEnds(EndOfFile, NULL, GE_PLAYER);
9545     if(appData.serverMovesName != NULL) {
9546         /* [HGM] prepare to make moves file for broadcasting */
9547         clock_t t = clock();
9548         if(serverMoves != NULL) fclose(serverMoves);
9549         serverMoves = fopen(appData.serverMovesName, "r");
9550         if(serverMoves != NULL) {
9551             fclose(serverMoves);
9552             /* delay 15 sec before overwriting, so all clients can see end */
9553             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9554         }
9555         serverMoves = fopen(appData.serverMovesName, "w");
9556     }
9557
9558     ExitAnalyzeMode();
9559     gameMode = BeginningOfGame;
9560     ModeHighlight();
9561     if(appData.icsActive) gameInfo.variant = VariantNormal;
9562     currentMove = forwardMostMove = backwardMostMove = 0;
9563     InitPosition(redraw);
9564     for (i = 0; i < MAX_MOVES; i++) {
9565         if (commentList[i] != NULL) {
9566             free(commentList[i]);
9567             commentList[i] = NULL;
9568         }
9569     }
9570     ResetClocks();
9571     timeRemaining[0][0] = whiteTimeRemaining;
9572     timeRemaining[1][0] = blackTimeRemaining;
9573     if (first.pr == NULL) {
9574         StartChessProgram(&first);
9575     }
9576     if (init) {
9577             InitChessProgram(&first, startedFromSetupPosition);
9578     }
9579     DisplayTitle("");
9580     DisplayMessage("", "");
9581     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9582     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9583 }
9584
9585 void
9586 AutoPlayGameLoop()
9587 {
9588     for (;;) {
9589         if (!AutoPlayOneMove())
9590           return;
9591         if (matchMode || appData.timeDelay == 0)
9592           continue;
9593         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9594           return;
9595         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9596         break;
9597     }
9598 }
9599
9600
9601 int
9602 AutoPlayOneMove()
9603 {
9604     int fromX, fromY, toX, toY;
9605
9606     if (appData.debugMode) {
9607       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9608     }
9609
9610     if (gameMode != PlayFromGameFile)
9611       return FALSE;
9612
9613     if (currentMove >= forwardMostMove) {
9614       gameMode = EditGame;
9615       ModeHighlight();
9616
9617       /* [AS] Clear current move marker at the end of a game */
9618       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9619
9620       return FALSE;
9621     }
9622
9623     toX = moveList[currentMove][2] - AAA;
9624     toY = moveList[currentMove][3] - ONE;
9625
9626     if (moveList[currentMove][1] == '@') {
9627         if (appData.highlightLastMove) {
9628             SetHighlights(-1, -1, toX, toY);
9629         }
9630     } else {
9631         fromX = moveList[currentMove][0] - AAA;
9632         fromY = moveList[currentMove][1] - ONE;
9633
9634         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9635
9636         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9637
9638         if (appData.highlightLastMove) {
9639             SetHighlights(fromX, fromY, toX, toY);
9640         }
9641     }
9642     DisplayMove(currentMove);
9643     SendMoveToProgram(currentMove++, &first);
9644     DisplayBothClocks();
9645     DrawPosition(FALSE, boards[currentMove]);
9646     // [HGM] PV info: always display, routine tests if empty
9647     DisplayComment(currentMove - 1, commentList[currentMove]);
9648     return TRUE;
9649 }
9650
9651
9652 int
9653 LoadGameOneMove(readAhead)
9654      ChessMove readAhead;
9655 {
9656     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9657     char promoChar = NULLCHAR;
9658     ChessMove moveType;
9659     char move[MSG_SIZ];
9660     char *p, *q;
9661
9662     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9663         gameMode != AnalyzeMode && gameMode != Training) {
9664         gameFileFP = NULL;
9665         return FALSE;
9666     }
9667
9668     yyboardindex = forwardMostMove;
9669     if (readAhead != EndOfFile) {
9670       moveType = readAhead;
9671     } else {
9672       if (gameFileFP == NULL)
9673           return FALSE;
9674       moveType = (ChessMove) yylex();
9675     }
9676
9677     done = FALSE;
9678     switch (moveType) {
9679       case Comment:
9680         if (appData.debugMode)
9681           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9682         p = yy_text;
9683
9684         /* append the comment but don't display it */
9685         AppendComment(currentMove, p, FALSE);
9686         return TRUE;
9687
9688       case WhiteCapturesEnPassant:
9689       case BlackCapturesEnPassant:
9690       case WhitePromotion:
9691       case BlackPromotion:
9692       case WhiteNonPromotion:
9693       case BlackNonPromotion:
9694       case NormalMove:
9695       case WhiteKingSideCastle:
9696       case WhiteQueenSideCastle:
9697       case BlackKingSideCastle:
9698       case BlackQueenSideCastle:
9699       case WhiteKingSideCastleWild:
9700       case WhiteQueenSideCastleWild:
9701       case BlackKingSideCastleWild:
9702       case BlackQueenSideCastleWild:
9703       /* PUSH Fabien */
9704       case WhiteHSideCastleFR:
9705       case WhiteASideCastleFR:
9706       case BlackHSideCastleFR:
9707       case BlackASideCastleFR:
9708       /* POP Fabien */
9709         if (appData.debugMode)
9710           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9711         fromX = currentMoveString[0] - AAA;
9712         fromY = currentMoveString[1] - ONE;
9713         toX = currentMoveString[2] - AAA;
9714         toY = currentMoveString[3] - ONE;
9715         promoChar = currentMoveString[4];
9716         break;
9717
9718       case WhiteDrop:
9719       case BlackDrop:
9720         if (appData.debugMode)
9721           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9722         fromX = moveType == WhiteDrop ?
9723           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9724         (int) CharToPiece(ToLower(currentMoveString[0]));
9725         fromY = DROP_RANK;
9726         toX = currentMoveString[2] - AAA;
9727         toY = currentMoveString[3] - ONE;
9728         break;
9729
9730       case WhiteWins:
9731       case BlackWins:
9732       case GameIsDrawn:
9733       case GameUnfinished:
9734         if (appData.debugMode)
9735           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9736         p = strchr(yy_text, '{');
9737         if (p == NULL) p = strchr(yy_text, '(');
9738         if (p == NULL) {
9739             p = yy_text;
9740             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9741         } else {
9742             q = strchr(p, *p == '{' ? '}' : ')');
9743             if (q != NULL) *q = NULLCHAR;
9744             p++;
9745         }
9746         GameEnds(moveType, p, GE_FILE);
9747         done = TRUE;
9748         if (cmailMsgLoaded) {
9749             ClearHighlights();
9750             flipView = WhiteOnMove(currentMove);
9751             if (moveType == GameUnfinished) flipView = !flipView;
9752             if (appData.debugMode)
9753               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9754         }
9755         break;
9756
9757       case EndOfFile:
9758         if (appData.debugMode)
9759           fprintf(debugFP, "Parser hit end of file\n");
9760         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9761           case MT_NONE:
9762           case MT_CHECK:
9763             break;
9764           case MT_CHECKMATE:
9765           case MT_STAINMATE:
9766             if (WhiteOnMove(currentMove)) {
9767                 GameEnds(BlackWins, "Black mates", GE_FILE);
9768             } else {
9769                 GameEnds(WhiteWins, "White mates", GE_FILE);
9770             }
9771             break;
9772           case MT_STALEMATE:
9773             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9774             break;
9775         }
9776         done = TRUE;
9777         break;
9778
9779       case MoveNumberOne:
9780         if (lastLoadGameStart == GNUChessGame) {
9781             /* GNUChessGames have numbers, but they aren't move numbers */
9782             if (appData.debugMode)
9783               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9784                       yy_text, (int) moveType);
9785             return LoadGameOneMove(EndOfFile); /* tail recursion */
9786         }
9787         /* else fall thru */
9788
9789       case XBoardGame:
9790       case GNUChessGame:
9791       case PGNTag:
9792         /* Reached start of next game in file */
9793         if (appData.debugMode)
9794           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9795         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9796           case MT_NONE:
9797           case MT_CHECK:
9798             break;
9799           case MT_CHECKMATE:
9800           case MT_STAINMATE:
9801             if (WhiteOnMove(currentMove)) {
9802                 GameEnds(BlackWins, "Black mates", GE_FILE);
9803             } else {
9804                 GameEnds(WhiteWins, "White mates", GE_FILE);
9805             }
9806             break;
9807           case MT_STALEMATE:
9808             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9809             break;
9810         }
9811         done = TRUE;
9812         break;
9813
9814       case PositionDiagram:     /* should not happen; ignore */
9815       case ElapsedTime:         /* ignore */
9816       case NAG:                 /* ignore */
9817         if (appData.debugMode)
9818           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9819                   yy_text, (int) moveType);
9820         return LoadGameOneMove(EndOfFile); /* tail recursion */
9821
9822       case IllegalMove:
9823         if (appData.testLegality) {
9824             if (appData.debugMode)
9825               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9826             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9827                     (forwardMostMove / 2) + 1,
9828                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9829             DisplayError(move, 0);
9830             done = TRUE;
9831         } else {
9832             if (appData.debugMode)
9833               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9834                       yy_text, currentMoveString);
9835             fromX = currentMoveString[0] - AAA;
9836             fromY = currentMoveString[1] - ONE;
9837             toX = currentMoveString[2] - AAA;
9838             toY = currentMoveString[3] - ONE;
9839             promoChar = currentMoveString[4];
9840         }
9841         break;
9842
9843       case AmbiguousMove:
9844         if (appData.debugMode)
9845           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9846         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9847                 (forwardMostMove / 2) + 1,
9848                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9849         DisplayError(move, 0);
9850         done = TRUE;
9851         break;
9852
9853       default:
9854       case ImpossibleMove:
9855         if (appData.debugMode)
9856           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9857         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9858                 (forwardMostMove / 2) + 1,
9859                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9860         DisplayError(move, 0);
9861         done = TRUE;
9862         break;
9863     }
9864
9865     if (done) {
9866         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9867             DrawPosition(FALSE, boards[currentMove]);
9868             DisplayBothClocks();
9869             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9870               DisplayComment(currentMove - 1, commentList[currentMove]);
9871         }
9872         (void) StopLoadGameTimer();
9873         gameFileFP = NULL;
9874         cmailOldMove = forwardMostMove;
9875         return FALSE;
9876     } else {
9877         /* currentMoveString is set as a side-effect of yylex */
9878         strcat(currentMoveString, "\n");
9879         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9880
9881         thinkOutput[0] = NULLCHAR;
9882         MakeMove(fromX, fromY, toX, toY, promoChar);
9883         currentMove = forwardMostMove;
9884         return TRUE;
9885     }
9886 }
9887
9888 /* Load the nth game from the given file */
9889 int
9890 LoadGameFromFile(filename, n, title, useList)
9891      char *filename;
9892      int n;
9893      char *title;
9894      /*Boolean*/ int useList;
9895 {
9896     FILE *f;
9897     char buf[MSG_SIZ];
9898
9899     if (strcmp(filename, "-") == 0) {
9900         f = stdin;
9901         title = "stdin";
9902     } else {
9903         f = fopen(filename, "rb");
9904         if (f == NULL) {
9905           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9906             DisplayError(buf, errno);
9907             return FALSE;
9908         }
9909     }
9910     if (fseek(f, 0, 0) == -1) {
9911         /* f is not seekable; probably a pipe */
9912         useList = FALSE;
9913     }
9914     if (useList && n == 0) {
9915         int error = GameListBuild(f);
9916         if (error) {
9917             DisplayError(_("Cannot build game list"), error);
9918         } else if (!ListEmpty(&gameList) &&
9919                    ((ListGame *) gameList.tailPred)->number > 1) {
9920             GameListPopUp(f, title);
9921             return TRUE;
9922         }
9923         GameListDestroy();
9924         n = 1;
9925     }
9926     if (n == 0) n = 1;
9927     return LoadGame(f, n, title, FALSE);
9928 }
9929
9930
9931 void
9932 MakeRegisteredMove()
9933 {
9934     int fromX, fromY, toX, toY;
9935     char promoChar;
9936     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9937         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9938           case CMAIL_MOVE:
9939           case CMAIL_DRAW:
9940             if (appData.debugMode)
9941               fprintf(debugFP, "Restoring %s for game %d\n",
9942                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9943
9944             thinkOutput[0] = NULLCHAR;
9945             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9946             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9947             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9948             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9949             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9950             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9951             MakeMove(fromX, fromY, toX, toY, promoChar);
9952             ShowMove(fromX, fromY, toX, toY);
9953
9954             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9955               case MT_NONE:
9956               case MT_CHECK:
9957                 break;
9958
9959               case MT_CHECKMATE:
9960               case MT_STAINMATE:
9961                 if (WhiteOnMove(currentMove)) {
9962                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9963                 } else {
9964                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9965                 }
9966                 break;
9967
9968               case MT_STALEMATE:
9969                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9970                 break;
9971             }
9972
9973             break;
9974
9975           case CMAIL_RESIGN:
9976             if (WhiteOnMove(currentMove)) {
9977                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9978             } else {
9979                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9980             }
9981             break;
9982
9983           case CMAIL_ACCEPT:
9984             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9985             break;
9986
9987           default:
9988             break;
9989         }
9990     }
9991
9992     return;
9993 }
9994
9995 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9996 int
9997 CmailLoadGame(f, gameNumber, title, useList)
9998      FILE *f;
9999      int gameNumber;
10000      char *title;
10001      int useList;
10002 {
10003     int retVal;
10004
10005     if (gameNumber > nCmailGames) {
10006         DisplayError(_("No more games in this message"), 0);
10007         return FALSE;
10008     }
10009     if (f == lastLoadGameFP) {
10010         int offset = gameNumber - lastLoadGameNumber;
10011         if (offset == 0) {
10012             cmailMsg[0] = NULLCHAR;
10013             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10014                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10015                 nCmailMovesRegistered--;
10016             }
10017             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10018             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10019                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10020             }
10021         } else {
10022             if (! RegisterMove()) return FALSE;
10023         }
10024     }
10025
10026     retVal = LoadGame(f, gameNumber, title, useList);
10027
10028     /* Make move registered during previous look at this game, if any */
10029     MakeRegisteredMove();
10030
10031     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10032         commentList[currentMove]
10033           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10034         DisplayComment(currentMove - 1, commentList[currentMove]);
10035     }
10036
10037     return retVal;
10038 }
10039
10040 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10041 int
10042 ReloadGame(offset)
10043      int offset;
10044 {
10045     int gameNumber = lastLoadGameNumber + offset;
10046     if (lastLoadGameFP == NULL) {
10047         DisplayError(_("No game has been loaded yet"), 0);
10048         return FALSE;
10049     }
10050     if (gameNumber <= 0) {
10051         DisplayError(_("Can't back up any further"), 0);
10052         return FALSE;
10053     }
10054     if (cmailMsgLoaded) {
10055         return CmailLoadGame(lastLoadGameFP, gameNumber,
10056                              lastLoadGameTitle, lastLoadGameUseList);
10057     } else {
10058         return LoadGame(lastLoadGameFP, gameNumber,
10059                         lastLoadGameTitle, lastLoadGameUseList);
10060     }
10061 }
10062
10063
10064
10065 /* Load the nth game from open file f */
10066 int
10067 LoadGame(f, gameNumber, title, useList)
10068      FILE *f;
10069      int gameNumber;
10070      char *title;
10071      int useList;
10072 {
10073     ChessMove cm;
10074     char buf[MSG_SIZ];
10075     int gn = gameNumber;
10076     ListGame *lg = NULL;
10077     int numPGNTags = 0;
10078     int err;
10079     GameMode oldGameMode;
10080     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10081
10082     if (appData.debugMode)
10083         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10084
10085     if (gameMode == Training )
10086         SetTrainingModeOff();
10087
10088     oldGameMode = gameMode;
10089     if (gameMode != BeginningOfGame) {
10090       Reset(FALSE, TRUE);
10091     }
10092
10093     gameFileFP = f;
10094     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10095         fclose(lastLoadGameFP);
10096     }
10097
10098     if (useList) {
10099         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10100
10101         if (lg) {
10102             fseek(f, lg->offset, 0);
10103             GameListHighlight(gameNumber);
10104             gn = 1;
10105         }
10106         else {
10107             DisplayError(_("Game number out of range"), 0);
10108             return FALSE;
10109         }
10110     } else {
10111         GameListDestroy();
10112         if (fseek(f, 0, 0) == -1) {
10113             if (f == lastLoadGameFP ?
10114                 gameNumber == lastLoadGameNumber + 1 :
10115                 gameNumber == 1) {
10116                 gn = 1;
10117             } else {
10118                 DisplayError(_("Can't seek on game file"), 0);
10119                 return FALSE;
10120             }
10121         }
10122     }
10123     lastLoadGameFP = f;
10124     lastLoadGameNumber = gameNumber;
10125     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10126     lastLoadGameUseList = useList;
10127
10128     yynewfile(f);
10129
10130     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10131       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10132                 lg->gameInfo.black);
10133             DisplayTitle(buf);
10134     } else if (*title != NULLCHAR) {
10135         if (gameNumber > 1) {
10136           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10137             DisplayTitle(buf);
10138         } else {
10139             DisplayTitle(title);
10140         }
10141     }
10142
10143     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10144         gameMode = PlayFromGameFile;
10145         ModeHighlight();
10146     }
10147
10148     currentMove = forwardMostMove = backwardMostMove = 0;
10149     CopyBoard(boards[0], initialPosition);
10150     StopClocks();
10151
10152     /*
10153      * Skip the first gn-1 games in the file.
10154      * Also skip over anything that precedes an identifiable
10155      * start of game marker, to avoid being confused by
10156      * garbage at the start of the file.  Currently
10157      * recognized start of game markers are the move number "1",
10158      * the pattern "gnuchess .* game", the pattern
10159      * "^[#;%] [^ ]* game file", and a PGN tag block.
10160      * A game that starts with one of the latter two patterns
10161      * will also have a move number 1, possibly
10162      * following a position diagram.
10163      * 5-4-02: Let's try being more lenient and allowing a game to
10164      * start with an unnumbered move.  Does that break anything?
10165      */
10166     cm = lastLoadGameStart = EndOfFile;
10167     while (gn > 0) {
10168         yyboardindex = forwardMostMove;
10169         cm = (ChessMove) yylex();
10170         switch (cm) {
10171           case EndOfFile:
10172             if (cmailMsgLoaded) {
10173                 nCmailGames = CMAIL_MAX_GAMES - gn;
10174             } else {
10175                 Reset(TRUE, TRUE);
10176                 DisplayError(_("Game not found in file"), 0);
10177             }
10178             return FALSE;
10179
10180           case GNUChessGame:
10181           case XBoardGame:
10182             gn--;
10183             lastLoadGameStart = cm;
10184             break;
10185
10186           case MoveNumberOne:
10187             switch (lastLoadGameStart) {
10188               case GNUChessGame:
10189               case XBoardGame:
10190               case PGNTag:
10191                 break;
10192               case MoveNumberOne:
10193               case EndOfFile:
10194                 gn--;           /* count this game */
10195                 lastLoadGameStart = cm;
10196                 break;
10197               default:
10198                 /* impossible */
10199                 break;
10200             }
10201             break;
10202
10203           case PGNTag:
10204             switch (lastLoadGameStart) {
10205               case GNUChessGame:
10206               case PGNTag:
10207               case MoveNumberOne:
10208               case EndOfFile:
10209                 gn--;           /* count this game */
10210                 lastLoadGameStart = cm;
10211                 break;
10212               case XBoardGame:
10213                 lastLoadGameStart = cm; /* game counted already */
10214                 break;
10215               default:
10216                 /* impossible */
10217                 break;
10218             }
10219             if (gn > 0) {
10220                 do {
10221                     yyboardindex = forwardMostMove;
10222                     cm = (ChessMove) yylex();
10223                 } while (cm == PGNTag || cm == Comment);
10224             }
10225             break;
10226
10227           case WhiteWins:
10228           case BlackWins:
10229           case GameIsDrawn:
10230             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10231                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10232                     != CMAIL_OLD_RESULT) {
10233                     nCmailResults ++ ;
10234                     cmailResult[  CMAIL_MAX_GAMES
10235                                 - gn - 1] = CMAIL_OLD_RESULT;
10236                 }
10237             }
10238             break;
10239
10240           case NormalMove:
10241             /* Only a NormalMove can be at the start of a game
10242              * without a position diagram. */
10243             if (lastLoadGameStart == EndOfFile ) {
10244               gn--;
10245               lastLoadGameStart = MoveNumberOne;
10246             }
10247             break;
10248
10249           default:
10250             break;
10251         }
10252     }
10253
10254     if (appData.debugMode)
10255       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10256
10257     if (cm == XBoardGame) {
10258         /* Skip any header junk before position diagram and/or move 1 */
10259         for (;;) {
10260             yyboardindex = forwardMostMove;
10261             cm = (ChessMove) yylex();
10262
10263             if (cm == EndOfFile ||
10264                 cm == GNUChessGame || cm == XBoardGame) {
10265                 /* Empty game; pretend end-of-file and handle later */
10266                 cm = EndOfFile;
10267                 break;
10268             }
10269
10270             if (cm == MoveNumberOne || cm == PositionDiagram ||
10271                 cm == PGNTag || cm == Comment)
10272               break;
10273         }
10274     } else if (cm == GNUChessGame) {
10275         if (gameInfo.event != NULL) {
10276             free(gameInfo.event);
10277         }
10278         gameInfo.event = StrSave(yy_text);
10279     }
10280
10281     startedFromSetupPosition = FALSE;
10282     while (cm == PGNTag) {
10283         if (appData.debugMode)
10284           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10285         err = ParsePGNTag(yy_text, &gameInfo);
10286         if (!err) numPGNTags++;
10287
10288         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10289         if(gameInfo.variant != oldVariant) {
10290             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10291             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10292             InitPosition(TRUE);
10293             oldVariant = gameInfo.variant;
10294             if (appData.debugMode)
10295               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10296         }
10297
10298
10299         if (gameInfo.fen != NULL) {
10300           Board initial_position;
10301           startedFromSetupPosition = TRUE;
10302           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10303             Reset(TRUE, TRUE);
10304             DisplayError(_("Bad FEN position in file"), 0);
10305             return FALSE;
10306           }
10307           CopyBoard(boards[0], initial_position);
10308           if (blackPlaysFirst) {
10309             currentMove = forwardMostMove = backwardMostMove = 1;
10310             CopyBoard(boards[1], initial_position);
10311             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10312             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10313             timeRemaining[0][1] = whiteTimeRemaining;
10314             timeRemaining[1][1] = blackTimeRemaining;
10315             if (commentList[0] != NULL) {
10316               commentList[1] = commentList[0];
10317               commentList[0] = NULL;
10318             }
10319           } else {
10320             currentMove = forwardMostMove = backwardMostMove = 0;
10321           }
10322           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10323           {   int i;
10324               initialRulePlies = FENrulePlies;
10325               for( i=0; i< nrCastlingRights; i++ )
10326                   initialRights[i] = initial_position[CASTLING][i];
10327           }
10328           yyboardindex = forwardMostMove;
10329           free(gameInfo.fen);
10330           gameInfo.fen = NULL;
10331         }
10332
10333         yyboardindex = forwardMostMove;
10334         cm = (ChessMove) yylex();
10335
10336         /* Handle comments interspersed among the tags */
10337         while (cm == Comment) {
10338             char *p;
10339             if (appData.debugMode)
10340               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10341             p = yy_text;
10342             AppendComment(currentMove, p, FALSE);
10343             yyboardindex = forwardMostMove;
10344             cm = (ChessMove) yylex();
10345         }
10346     }
10347
10348     /* don't rely on existence of Event tag since if game was
10349      * pasted from clipboard the Event tag may not exist
10350      */
10351     if (numPGNTags > 0){
10352         char *tags;
10353         if (gameInfo.variant == VariantNormal) {
10354           VariantClass v = StringToVariant(gameInfo.event);
10355           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10356           if(v < VariantShogi) gameInfo.variant = v;
10357         }
10358         if (!matchMode) {
10359           if( appData.autoDisplayTags ) {
10360             tags = PGNTags(&gameInfo);
10361             TagsPopUp(tags, CmailMsg());
10362             free(tags);
10363           }
10364         }
10365     } else {
10366         /* Make something up, but don't display it now */
10367         SetGameInfo();
10368         TagsPopDown();
10369     }
10370
10371     if (cm == PositionDiagram) {
10372         int i, j;
10373         char *p;
10374         Board initial_position;
10375
10376         if (appData.debugMode)
10377           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10378
10379         if (!startedFromSetupPosition) {
10380             p = yy_text;
10381             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10382               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10383                 switch (*p) {
10384                   case '[':
10385                   case '-':
10386                   case ' ':
10387                   case '\t':
10388                   case '\n':
10389                   case '\r':
10390                     break;
10391                   default:
10392                     initial_position[i][j++] = CharToPiece(*p);
10393                     break;
10394                 }
10395             while (*p == ' ' || *p == '\t' ||
10396                    *p == '\n' || *p == '\r') p++;
10397
10398             if (strncmp(p, "black", strlen("black"))==0)
10399               blackPlaysFirst = TRUE;
10400             else
10401               blackPlaysFirst = FALSE;
10402             startedFromSetupPosition = TRUE;
10403
10404             CopyBoard(boards[0], initial_position);
10405             if (blackPlaysFirst) {
10406                 currentMove = forwardMostMove = backwardMostMove = 1;
10407                 CopyBoard(boards[1], initial_position);
10408                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10409                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10410                 timeRemaining[0][1] = whiteTimeRemaining;
10411                 timeRemaining[1][1] = blackTimeRemaining;
10412                 if (commentList[0] != NULL) {
10413                     commentList[1] = commentList[0];
10414                     commentList[0] = NULL;
10415                 }
10416             } else {
10417                 currentMove = forwardMostMove = backwardMostMove = 0;
10418             }
10419         }
10420         yyboardindex = forwardMostMove;
10421         cm = (ChessMove) yylex();
10422     }
10423
10424     if (first.pr == NoProc) {
10425         StartChessProgram(&first);
10426     }
10427     InitChessProgram(&first, FALSE);
10428     SendToProgram("force\n", &first);
10429     if (startedFromSetupPosition) {
10430         SendBoard(&first, forwardMostMove);
10431     if (appData.debugMode) {
10432         fprintf(debugFP, "Load Game\n");
10433     }
10434         DisplayBothClocks();
10435     }
10436
10437     /* [HGM] server: flag to write setup moves in broadcast file as one */
10438     loadFlag = appData.suppressLoadMoves;
10439
10440     while (cm == Comment) {
10441         char *p;
10442         if (appData.debugMode)
10443           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10444         p = yy_text;
10445         AppendComment(currentMove, p, FALSE);
10446         yyboardindex = forwardMostMove;
10447         cm = (ChessMove) yylex();
10448     }
10449
10450     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10451         cm == WhiteWins || cm == BlackWins ||
10452         cm == GameIsDrawn || cm == GameUnfinished) {
10453         DisplayMessage("", _("No moves in game"));
10454         if (cmailMsgLoaded) {
10455             if (appData.debugMode)
10456               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10457             ClearHighlights();
10458             flipView = FALSE;
10459         }
10460         DrawPosition(FALSE, boards[currentMove]);
10461         DisplayBothClocks();
10462         gameMode = EditGame;
10463         ModeHighlight();
10464         gameFileFP = NULL;
10465         cmailOldMove = 0;
10466         return TRUE;
10467     }
10468
10469     // [HGM] PV info: routine tests if comment empty
10470     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10471         DisplayComment(currentMove - 1, commentList[currentMove]);
10472     }
10473     if (!matchMode && appData.timeDelay != 0)
10474       DrawPosition(FALSE, boards[currentMove]);
10475
10476     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10477       programStats.ok_to_send = 1;
10478     }
10479
10480     /* if the first token after the PGN tags is a move
10481      * and not move number 1, retrieve it from the parser
10482      */
10483     if (cm != MoveNumberOne)
10484         LoadGameOneMove(cm);
10485
10486     /* load the remaining moves from the file */
10487     while (LoadGameOneMove(EndOfFile)) {
10488       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10489       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10490     }
10491
10492     /* rewind to the start of the game */
10493     currentMove = backwardMostMove;
10494
10495     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10496
10497     if (oldGameMode == AnalyzeFile ||
10498         oldGameMode == AnalyzeMode) {
10499       AnalyzeFileEvent();
10500     }
10501
10502     if (matchMode || appData.timeDelay == 0) {
10503       ToEndEvent();
10504       gameMode = EditGame;
10505       ModeHighlight();
10506     } else if (appData.timeDelay > 0) {
10507       AutoPlayGameLoop();
10508     }
10509
10510     if (appData.debugMode)
10511         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10512
10513     loadFlag = 0; /* [HGM] true game starts */
10514     return TRUE;
10515 }
10516
10517 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10518 int
10519 ReloadPosition(offset)
10520      int offset;
10521 {
10522     int positionNumber = lastLoadPositionNumber + offset;
10523     if (lastLoadPositionFP == NULL) {
10524         DisplayError(_("No position has been loaded yet"), 0);
10525         return FALSE;
10526     }
10527     if (positionNumber <= 0) {
10528         DisplayError(_("Can't back up any further"), 0);
10529         return FALSE;
10530     }
10531     return LoadPosition(lastLoadPositionFP, positionNumber,
10532                         lastLoadPositionTitle);
10533 }
10534
10535 /* Load the nth position from the given file */
10536 int
10537 LoadPositionFromFile(filename, n, title)
10538      char *filename;
10539      int n;
10540      char *title;
10541 {
10542     FILE *f;
10543     char buf[MSG_SIZ];
10544
10545     if (strcmp(filename, "-") == 0) {
10546         return LoadPosition(stdin, n, "stdin");
10547     } else {
10548         f = fopen(filename, "rb");
10549         if (f == NULL) {
10550             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10551             DisplayError(buf, errno);
10552             return FALSE;
10553         } else {
10554             return LoadPosition(f, n, title);
10555         }
10556     }
10557 }
10558
10559 /* Load the nth position from the given open file, and close it */
10560 int
10561 LoadPosition(f, positionNumber, title)
10562      FILE *f;
10563      int positionNumber;
10564      char *title;
10565 {
10566     char *p, line[MSG_SIZ];
10567     Board initial_position;
10568     int i, j, fenMode, pn;
10569
10570     if (gameMode == Training )
10571         SetTrainingModeOff();
10572
10573     if (gameMode != BeginningOfGame) {
10574         Reset(FALSE, TRUE);
10575     }
10576     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10577         fclose(lastLoadPositionFP);
10578     }
10579     if (positionNumber == 0) positionNumber = 1;
10580     lastLoadPositionFP = f;
10581     lastLoadPositionNumber = positionNumber;
10582     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10583     if (first.pr == NoProc) {
10584       StartChessProgram(&first);
10585       InitChessProgram(&first, FALSE);
10586     }
10587     pn = positionNumber;
10588     if (positionNumber < 0) {
10589         /* Negative position number means to seek to that byte offset */
10590         if (fseek(f, -positionNumber, 0) == -1) {
10591             DisplayError(_("Can't seek on position file"), 0);
10592             return FALSE;
10593         };
10594         pn = 1;
10595     } else {
10596         if (fseek(f, 0, 0) == -1) {
10597             if (f == lastLoadPositionFP ?
10598                 positionNumber == lastLoadPositionNumber + 1 :
10599                 positionNumber == 1) {
10600                 pn = 1;
10601             } else {
10602                 DisplayError(_("Can't seek on position file"), 0);
10603                 return FALSE;
10604             }
10605         }
10606     }
10607     /* See if this file is FEN or old-style xboard */
10608     if (fgets(line, MSG_SIZ, f) == NULL) {
10609         DisplayError(_("Position not found in file"), 0);
10610         return FALSE;
10611     }
10612     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10613     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10614
10615     if (pn >= 2) {
10616         if (fenMode || line[0] == '#') pn--;
10617         while (pn > 0) {
10618             /* skip positions before number pn */
10619             if (fgets(line, MSG_SIZ, f) == NULL) {
10620                 Reset(TRUE, TRUE);
10621                 DisplayError(_("Position not found in file"), 0);
10622                 return FALSE;
10623             }
10624             if (fenMode || line[0] == '#') pn--;
10625         }
10626     }
10627
10628     if (fenMode) {
10629         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10630             DisplayError(_("Bad FEN position in file"), 0);
10631             return FALSE;
10632         }
10633     } else {
10634         (void) fgets(line, MSG_SIZ, f);
10635         (void) fgets(line, MSG_SIZ, f);
10636
10637         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10638             (void) fgets(line, MSG_SIZ, f);
10639             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10640                 if (*p == ' ')
10641                   continue;
10642                 initial_position[i][j++] = CharToPiece(*p);
10643             }
10644         }
10645
10646         blackPlaysFirst = FALSE;
10647         if (!feof(f)) {
10648             (void) fgets(line, MSG_SIZ, f);
10649             if (strncmp(line, "black", strlen("black"))==0)
10650               blackPlaysFirst = TRUE;
10651         }
10652     }
10653     startedFromSetupPosition = TRUE;
10654
10655     SendToProgram("force\n", &first);
10656     CopyBoard(boards[0], initial_position);
10657     if (blackPlaysFirst) {
10658         currentMove = forwardMostMove = backwardMostMove = 1;
10659         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10660         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10661         CopyBoard(boards[1], initial_position);
10662         DisplayMessage("", _("Black to play"));
10663     } else {
10664         currentMove = forwardMostMove = backwardMostMove = 0;
10665         DisplayMessage("", _("White to play"));
10666     }
10667     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10668     SendBoard(&first, forwardMostMove);
10669     if (appData.debugMode) {
10670 int i, j;
10671   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10672   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10673         fprintf(debugFP, "Load Position\n");
10674     }
10675
10676     if (positionNumber > 1) {
10677       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10678         DisplayTitle(line);
10679     } else {
10680         DisplayTitle(title);
10681     }
10682     gameMode = EditGame;
10683     ModeHighlight();
10684     ResetClocks();
10685     timeRemaining[0][1] = whiteTimeRemaining;
10686     timeRemaining[1][1] = blackTimeRemaining;
10687     DrawPosition(FALSE, boards[currentMove]);
10688
10689     return TRUE;
10690 }
10691
10692
10693 void
10694 CopyPlayerNameIntoFileName(dest, src)
10695      char **dest, *src;
10696 {
10697     while (*src != NULLCHAR && *src != ',') {
10698         if (*src == ' ') {
10699             *(*dest)++ = '_';
10700             src++;
10701         } else {
10702             *(*dest)++ = *src++;
10703         }
10704     }
10705 }
10706
10707 char *DefaultFileName(ext)
10708      char *ext;
10709 {
10710     static char def[MSG_SIZ];
10711     char *p;
10712
10713     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10714         p = def;
10715         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10716         *p++ = '-';
10717         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10718         *p++ = '.';
10719         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10720     } else {
10721         def[0] = NULLCHAR;
10722     }
10723     return def;
10724 }
10725
10726 /* Save the current game to the given file */
10727 int
10728 SaveGameToFile(filename, append)
10729      char *filename;
10730      int append;
10731 {
10732     FILE *f;
10733     char buf[MSG_SIZ];
10734
10735     if (strcmp(filename, "-") == 0) {
10736         return SaveGame(stdout, 0, NULL);
10737     } else {
10738         f = fopen(filename, append ? "a" : "w");
10739         if (f == NULL) {
10740             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10741             DisplayError(buf, errno);
10742             return FALSE;
10743         } else {
10744             return SaveGame(f, 0, NULL);
10745         }
10746     }
10747 }
10748
10749 char *
10750 SavePart(str)
10751      char *str;
10752 {
10753     static char buf[MSG_SIZ];
10754     char *p;
10755
10756     p = strchr(str, ' ');
10757     if (p == NULL) return str;
10758     strncpy(buf, str, p - str);
10759     buf[p - str] = NULLCHAR;
10760     return buf;
10761 }
10762
10763 #define PGN_MAX_LINE 75
10764
10765 #define PGN_SIDE_WHITE  0
10766 #define PGN_SIDE_BLACK  1
10767
10768 /* [AS] */
10769 static int FindFirstMoveOutOfBook( int side )
10770 {
10771     int result = -1;
10772
10773     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10774         int index = backwardMostMove;
10775         int has_book_hit = 0;
10776
10777         if( (index % 2) != side ) {
10778             index++;
10779         }
10780
10781         while( index < forwardMostMove ) {
10782             /* Check to see if engine is in book */
10783             int depth = pvInfoList[index].depth;
10784             int score = pvInfoList[index].score;
10785             int in_book = 0;
10786
10787             if( depth <= 2 ) {
10788                 in_book = 1;
10789             }
10790             else if( score == 0 && depth == 63 ) {
10791                 in_book = 1; /* Zappa */
10792             }
10793             else if( score == 2 && depth == 99 ) {
10794                 in_book = 1; /* Abrok */
10795             }
10796
10797             has_book_hit += in_book;
10798
10799             if( ! in_book ) {
10800                 result = index;
10801
10802                 break;
10803             }
10804
10805             index += 2;
10806         }
10807     }
10808
10809     return result;
10810 }
10811
10812 /* [AS] */
10813 void GetOutOfBookInfo( char * buf )
10814 {
10815     int oob[2];
10816     int i;
10817     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10818
10819     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10820     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10821
10822     *buf = '\0';
10823
10824     if( oob[0] >= 0 || oob[1] >= 0 ) {
10825         for( i=0; i<2; i++ ) {
10826             int idx = oob[i];
10827
10828             if( idx >= 0 ) {
10829                 if( i > 0 && oob[0] >= 0 ) {
10830                     strcat( buf, "   " );
10831                 }
10832
10833                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10834                 sprintf( buf+strlen(buf), "%s%.2f",
10835                     pvInfoList[idx].score >= 0 ? "+" : "",
10836                     pvInfoList[idx].score / 100.0 );
10837             }
10838         }
10839     }
10840 }
10841
10842 /* Save game in PGN style and close the file */
10843 int
10844 SaveGamePGN(f)
10845      FILE *f;
10846 {
10847     int i, offset, linelen, newblock;
10848     time_t tm;
10849 //    char *movetext;
10850     char numtext[32];
10851     int movelen, numlen, blank;
10852     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10853
10854     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10855
10856     tm = time((time_t *) NULL);
10857
10858     PrintPGNTags(f, &gameInfo);
10859
10860     if (backwardMostMove > 0 || startedFromSetupPosition) {
10861         char *fen = PositionToFEN(backwardMostMove, NULL);
10862         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10863         fprintf(f, "\n{--------------\n");
10864         PrintPosition(f, backwardMostMove);
10865         fprintf(f, "--------------}\n");
10866         free(fen);
10867     }
10868     else {
10869         /* [AS] Out of book annotation */
10870         if( appData.saveOutOfBookInfo ) {
10871             char buf[64];
10872
10873             GetOutOfBookInfo( buf );
10874
10875             if( buf[0] != '\0' ) {
10876                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10877             }
10878         }
10879
10880         fprintf(f, "\n");
10881     }
10882
10883     i = backwardMostMove;
10884     linelen = 0;
10885     newblock = TRUE;
10886
10887     while (i < forwardMostMove) {
10888         /* Print comments preceding this move */
10889         if (commentList[i] != NULL) {
10890             if (linelen > 0) fprintf(f, "\n");
10891             fprintf(f, "%s", commentList[i]);
10892             linelen = 0;
10893             newblock = TRUE;
10894         }
10895
10896         /* Format move number */
10897         if ((i % 2) == 0)
10898           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10899         else
10900           if (newblock)
10901             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10902           else
10903             numtext[0] = NULLCHAR;
10904
10905         numlen = strlen(numtext);
10906         newblock = FALSE;
10907
10908         /* Print move number */
10909         blank = linelen > 0 && numlen > 0;
10910         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10911             fprintf(f, "\n");
10912             linelen = 0;
10913             blank = 0;
10914         }
10915         if (blank) {
10916             fprintf(f, " ");
10917             linelen++;
10918         }
10919         fprintf(f, "%s", numtext);
10920         linelen += numlen;
10921
10922         /* Get move */
10923         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10924         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10925
10926         /* Print move */
10927         blank = linelen > 0 && movelen > 0;
10928         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10929             fprintf(f, "\n");
10930             linelen = 0;
10931             blank = 0;
10932         }
10933         if (blank) {
10934             fprintf(f, " ");
10935             linelen++;
10936         }
10937         fprintf(f, "%s", move_buffer);
10938         linelen += movelen;
10939
10940         /* [AS] Add PV info if present */
10941         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10942             /* [HGM] add time */
10943             char buf[MSG_SIZ]; int seconds;
10944
10945             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10946
10947             if( seconds <= 0)
10948               buf[0] = 0;
10949             else
10950               if( seconds < 30 )
10951                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10952               else
10953                 {
10954                   seconds = (seconds + 4)/10; // round to full seconds
10955                   if( seconds < 60 )
10956                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10957                   else
10958                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10959                 }
10960
10961             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10962                       pvInfoList[i].score >= 0 ? "+" : "",
10963                       pvInfoList[i].score / 100.0,
10964                       pvInfoList[i].depth,
10965                       buf );
10966
10967             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10968
10969             /* Print score/depth */
10970             blank = linelen > 0 && movelen > 0;
10971             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10972                 fprintf(f, "\n");
10973                 linelen = 0;
10974                 blank = 0;
10975             }
10976             if (blank) {
10977                 fprintf(f, " ");
10978                 linelen++;
10979             }
10980             fprintf(f, "%s", move_buffer);
10981             linelen += movelen;
10982         }
10983
10984         i++;
10985     }
10986
10987     /* Start a new line */
10988     if (linelen > 0) fprintf(f, "\n");
10989
10990     /* Print comments after last move */
10991     if (commentList[i] != NULL) {
10992         fprintf(f, "%s\n", commentList[i]);
10993     }
10994
10995     /* Print result */
10996     if (gameInfo.resultDetails != NULL &&
10997         gameInfo.resultDetails[0] != NULLCHAR) {
10998         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10999                 PGNResult(gameInfo.result));
11000     } else {
11001         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11002     }
11003
11004     fclose(f);
11005     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11006     return TRUE;
11007 }
11008
11009 /* Save game in old style and close the file */
11010 int
11011 SaveGameOldStyle(f)
11012      FILE *f;
11013 {
11014     int i, offset;
11015     time_t tm;
11016
11017     tm = time((time_t *) NULL);
11018
11019     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11020     PrintOpponents(f);
11021
11022     if (backwardMostMove > 0 || startedFromSetupPosition) {
11023         fprintf(f, "\n[--------------\n");
11024         PrintPosition(f, backwardMostMove);
11025         fprintf(f, "--------------]\n");
11026     } else {
11027         fprintf(f, "\n");
11028     }
11029
11030     i = backwardMostMove;
11031     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11032
11033     while (i < forwardMostMove) {
11034         if (commentList[i] != NULL) {
11035             fprintf(f, "[%s]\n", commentList[i]);
11036         }
11037
11038         if ((i % 2) == 1) {
11039             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11040             i++;
11041         } else {
11042             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11043             i++;
11044             if (commentList[i] != NULL) {
11045                 fprintf(f, "\n");
11046                 continue;
11047             }
11048             if (i >= forwardMostMove) {
11049                 fprintf(f, "\n");
11050                 break;
11051             }
11052             fprintf(f, "%s\n", parseList[i]);
11053             i++;
11054         }
11055     }
11056
11057     if (commentList[i] != NULL) {
11058         fprintf(f, "[%s]\n", commentList[i]);
11059     }
11060
11061     /* This isn't really the old style, but it's close enough */
11062     if (gameInfo.resultDetails != NULL &&
11063         gameInfo.resultDetails[0] != NULLCHAR) {
11064         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11065                 gameInfo.resultDetails);
11066     } else {
11067         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11068     }
11069
11070     fclose(f);
11071     return TRUE;
11072 }
11073
11074 /* Save the current game to open file f and close the file */
11075 int
11076 SaveGame(f, dummy, dummy2)
11077      FILE *f;
11078      int dummy;
11079      char *dummy2;
11080 {
11081     if (gameMode == EditPosition) EditPositionDone(TRUE);
11082     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11083     if (appData.oldSaveStyle)
11084       return SaveGameOldStyle(f);
11085     else
11086       return SaveGamePGN(f);
11087 }
11088
11089 /* Save the current position to the given file */
11090 int
11091 SavePositionToFile(filename)
11092      char *filename;
11093 {
11094     FILE *f;
11095     char buf[MSG_SIZ];
11096
11097     if (strcmp(filename, "-") == 0) {
11098         return SavePosition(stdout, 0, NULL);
11099     } else {
11100         f = fopen(filename, "a");
11101         if (f == NULL) {
11102             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11103             DisplayError(buf, errno);
11104             return FALSE;
11105         } else {
11106             SavePosition(f, 0, NULL);
11107             return TRUE;
11108         }
11109     }
11110 }
11111
11112 /* Save the current position to the given open file and close the file */
11113 int
11114 SavePosition(f, dummy, dummy2)
11115      FILE *f;
11116      int dummy;
11117      char *dummy2;
11118 {
11119     time_t tm;
11120     char *fen;
11121
11122     if (gameMode == EditPosition) EditPositionDone(TRUE);
11123     if (appData.oldSaveStyle) {
11124         tm = time((time_t *) NULL);
11125
11126         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11127         PrintOpponents(f);
11128         fprintf(f, "[--------------\n");
11129         PrintPosition(f, currentMove);
11130         fprintf(f, "--------------]\n");
11131     } else {
11132         fen = PositionToFEN(currentMove, NULL);
11133         fprintf(f, "%s\n", fen);
11134         free(fen);
11135     }
11136     fclose(f);
11137     return TRUE;
11138 }
11139
11140 void
11141 ReloadCmailMsgEvent(unregister)
11142      int unregister;
11143 {
11144 #if !WIN32
11145     static char *inFilename = NULL;
11146     static char *outFilename;
11147     int i;
11148     struct stat inbuf, outbuf;
11149     int status;
11150
11151     /* Any registered moves are unregistered if unregister is set, */
11152     /* i.e. invoked by the signal handler */
11153     if (unregister) {
11154         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11155             cmailMoveRegistered[i] = FALSE;
11156             if (cmailCommentList[i] != NULL) {
11157                 free(cmailCommentList[i]);
11158                 cmailCommentList[i] = NULL;
11159             }
11160         }
11161         nCmailMovesRegistered = 0;
11162     }
11163
11164     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11165         cmailResult[i] = CMAIL_NOT_RESULT;
11166     }
11167     nCmailResults = 0;
11168
11169     if (inFilename == NULL) {
11170         /* Because the filenames are static they only get malloced once  */
11171         /* and they never get freed                                      */
11172         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11173         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11174
11175         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11176         sprintf(outFilename, "%s.out", appData.cmailGameName);
11177     }
11178
11179     status = stat(outFilename, &outbuf);
11180     if (status < 0) {
11181         cmailMailedMove = FALSE;
11182     } else {
11183         status = stat(inFilename, &inbuf);
11184         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11185     }
11186
11187     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11188        counts the games, notes how each one terminated, etc.
11189
11190        It would be nice to remove this kludge and instead gather all
11191        the information while building the game list.  (And to keep it
11192        in the game list nodes instead of having a bunch of fixed-size
11193        parallel arrays.)  Note this will require getting each game's
11194        termination from the PGN tags, as the game list builder does
11195        not process the game moves.  --mann
11196        */
11197     cmailMsgLoaded = TRUE;
11198     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11199
11200     /* Load first game in the file or popup game menu */
11201     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11202
11203 #endif /* !WIN32 */
11204     return;
11205 }
11206
11207 int
11208 RegisterMove()
11209 {
11210     FILE *f;
11211     char string[MSG_SIZ];
11212
11213     if (   cmailMailedMove
11214         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11215         return TRUE;            /* Allow free viewing  */
11216     }
11217
11218     /* Unregister move to ensure that we don't leave RegisterMove        */
11219     /* with the move registered when the conditions for registering no   */
11220     /* longer hold                                                       */
11221     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11222         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11223         nCmailMovesRegistered --;
11224
11225         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11226           {
11227               free(cmailCommentList[lastLoadGameNumber - 1]);
11228               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11229           }
11230     }
11231
11232     if (cmailOldMove == -1) {
11233         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11234         return FALSE;
11235     }
11236
11237     if (currentMove > cmailOldMove + 1) {
11238         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11239         return FALSE;
11240     }
11241
11242     if (currentMove < cmailOldMove) {
11243         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11244         return FALSE;
11245     }
11246
11247     if (forwardMostMove > currentMove) {
11248         /* Silently truncate extra moves */
11249         TruncateGame();
11250     }
11251
11252     if (   (currentMove == cmailOldMove + 1)
11253         || (   (currentMove == cmailOldMove)
11254             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11255                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11256         if (gameInfo.result != GameUnfinished) {
11257             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11258         }
11259
11260         if (commentList[currentMove] != NULL) {
11261             cmailCommentList[lastLoadGameNumber - 1]
11262               = StrSave(commentList[currentMove]);
11263         }
11264         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11265
11266         if (appData.debugMode)
11267           fprintf(debugFP, "Saving %s for game %d\n",
11268                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11269
11270         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11271
11272         f = fopen(string, "w");
11273         if (appData.oldSaveStyle) {
11274             SaveGameOldStyle(f); /* also closes the file */
11275
11276             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11277             f = fopen(string, "w");
11278             SavePosition(f, 0, NULL); /* also closes the file */
11279         } else {
11280             fprintf(f, "{--------------\n");
11281             PrintPosition(f, currentMove);
11282             fprintf(f, "--------------}\n\n");
11283
11284             SaveGame(f, 0, NULL); /* also closes the file*/
11285         }
11286
11287         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11288         nCmailMovesRegistered ++;
11289     } else if (nCmailGames == 1) {
11290         DisplayError(_("You have not made a move yet"), 0);
11291         return FALSE;
11292     }
11293
11294     return TRUE;
11295 }
11296
11297 void
11298 MailMoveEvent()
11299 {
11300 #if !WIN32
11301     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11302     FILE *commandOutput;
11303     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11304     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11305     int nBuffers;
11306     int i;
11307     int archived;
11308     char *arcDir;
11309
11310     if (! cmailMsgLoaded) {
11311         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11312         return;
11313     }
11314
11315     if (nCmailGames == nCmailResults) {
11316         DisplayError(_("No unfinished games"), 0);
11317         return;
11318     }
11319
11320 #if CMAIL_PROHIBIT_REMAIL
11321     if (cmailMailedMove) {
11322       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);
11323         DisplayError(msg, 0);
11324         return;
11325     }
11326 #endif
11327
11328     if (! (cmailMailedMove || RegisterMove())) return;
11329
11330     if (   cmailMailedMove
11331         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11332       snprintf(string, MSG_SIZ, partCommandString,
11333                appData.debugMode ? " -v" : "", appData.cmailGameName);
11334         commandOutput = popen(string, "r");
11335
11336         if (commandOutput == NULL) {
11337             DisplayError(_("Failed to invoke cmail"), 0);
11338         } else {
11339             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11340                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11341             }
11342             if (nBuffers > 1) {
11343                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11344                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11345                 nBytes = MSG_SIZ - 1;
11346             } else {
11347                 (void) memcpy(msg, buffer, nBytes);
11348             }
11349             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11350
11351             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11352                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11353
11354                 archived = TRUE;
11355                 for (i = 0; i < nCmailGames; i ++) {
11356                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11357                         archived = FALSE;
11358                     }
11359                 }
11360                 if (   archived
11361                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11362                         != NULL)) {
11363                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11364                            arcDir,
11365                            appData.cmailGameName,
11366                            gameInfo.date);
11367                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11368                     cmailMsgLoaded = FALSE;
11369                 }
11370             }
11371
11372             DisplayInformation(msg);
11373             pclose(commandOutput);
11374         }
11375     } else {
11376         if ((*cmailMsg) != '\0') {
11377             DisplayInformation(cmailMsg);
11378         }
11379     }
11380
11381     return;
11382 #endif /* !WIN32 */
11383 }
11384
11385 char *
11386 CmailMsg()
11387 {
11388 #if WIN32
11389     return NULL;
11390 #else
11391     int  prependComma = 0;
11392     char number[5];
11393     char string[MSG_SIZ];       /* Space for game-list */
11394     int  i;
11395
11396     if (!cmailMsgLoaded) return "";
11397
11398     if (cmailMailedMove) {
11399       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11400     } else {
11401         /* Create a list of games left */
11402       snprintf(string, MSG_SIZ, "[");
11403         for (i = 0; i < nCmailGames; i ++) {
11404             if (! (   cmailMoveRegistered[i]
11405                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11406                 if (prependComma) {
11407                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11408                 } else {
11409                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11410                     prependComma = 1;
11411                 }
11412
11413                 strcat(string, number);
11414             }
11415         }
11416         strcat(string, "]");
11417
11418         if (nCmailMovesRegistered + nCmailResults == 0) {
11419             switch (nCmailGames) {
11420               case 1:
11421                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11422                 break;
11423
11424               case 2:
11425                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11426                 break;
11427
11428               default:
11429                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11430                          nCmailGames);
11431                 break;
11432             }
11433         } else {
11434             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11435               case 1:
11436                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11437                          string);
11438                 break;
11439
11440               case 0:
11441                 if (nCmailResults == nCmailGames) {
11442                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11443                 } else {
11444                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11445                 }
11446                 break;
11447
11448               default:
11449                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11450                          string);
11451             }
11452         }
11453     }
11454     return cmailMsg;
11455 #endif /* WIN32 */
11456 }
11457
11458 void
11459 ResetGameEvent()
11460 {
11461     if (gameMode == Training)
11462       SetTrainingModeOff();
11463
11464     Reset(TRUE, TRUE);
11465     cmailMsgLoaded = FALSE;
11466     if (appData.icsActive) {
11467       SendToICS(ics_prefix);
11468       SendToICS("refresh\n");
11469     }
11470 }
11471
11472 void
11473 ExitEvent(status)
11474      int status;
11475 {
11476     exiting++;
11477     if (exiting > 2) {
11478       /* Give up on clean exit */
11479       exit(status);
11480     }
11481     if (exiting > 1) {
11482       /* Keep trying for clean exit */
11483       return;
11484     }
11485
11486     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11487
11488     if (telnetISR != NULL) {
11489       RemoveInputSource(telnetISR);
11490     }
11491     if (icsPR != NoProc) {
11492       DestroyChildProcess(icsPR, TRUE);
11493     }
11494
11495     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11496     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11497
11498     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11499     /* make sure this other one finishes before killing it!                  */
11500     if(endingGame) { int count = 0;
11501         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11502         while(endingGame && count++ < 10) DoSleep(1);
11503         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11504     }
11505
11506     /* Kill off chess programs */
11507     if (first.pr != NoProc) {
11508         ExitAnalyzeMode();
11509
11510         DoSleep( appData.delayBeforeQuit );
11511         SendToProgram("quit\n", &first);
11512         DoSleep( appData.delayAfterQuit );
11513         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11514     }
11515     if (second.pr != NoProc) {
11516         DoSleep( appData.delayBeforeQuit );
11517         SendToProgram("quit\n", &second);
11518         DoSleep( appData.delayAfterQuit );
11519         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11520     }
11521     if (first.isr != NULL) {
11522         RemoveInputSource(first.isr);
11523     }
11524     if (second.isr != NULL) {
11525         RemoveInputSource(second.isr);
11526     }
11527
11528     ShutDownFrontEnd();
11529     exit(status);
11530 }
11531
11532 void
11533 PauseEvent()
11534 {
11535     if (appData.debugMode)
11536         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11537     if (pausing) {
11538         pausing = FALSE;
11539         ModeHighlight();
11540         if (gameMode == MachinePlaysWhite ||
11541             gameMode == MachinePlaysBlack) {
11542             StartClocks();
11543         } else {
11544             DisplayBothClocks();
11545         }
11546         if (gameMode == PlayFromGameFile) {
11547             if (appData.timeDelay >= 0)
11548                 AutoPlayGameLoop();
11549         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11550             Reset(FALSE, TRUE);
11551             SendToICS(ics_prefix);
11552             SendToICS("refresh\n");
11553         } else if (currentMove < forwardMostMove) {
11554             ForwardInner(forwardMostMove);
11555         }
11556         pauseExamInvalid = FALSE;
11557     } else {
11558         switch (gameMode) {
11559           default:
11560             return;
11561           case IcsExamining:
11562             pauseExamForwardMostMove = forwardMostMove;
11563             pauseExamInvalid = FALSE;
11564             /* fall through */
11565           case IcsObserving:
11566           case IcsPlayingWhite:
11567           case IcsPlayingBlack:
11568             pausing = TRUE;
11569             ModeHighlight();
11570             return;
11571           case PlayFromGameFile:
11572             (void) StopLoadGameTimer();
11573             pausing = TRUE;
11574             ModeHighlight();
11575             break;
11576           case BeginningOfGame:
11577             if (appData.icsActive) return;
11578             /* else fall through */
11579           case MachinePlaysWhite:
11580           case MachinePlaysBlack:
11581           case TwoMachinesPlay:
11582             if (forwardMostMove == 0)
11583               return;           /* don't pause if no one has moved */
11584             if ((gameMode == MachinePlaysWhite &&
11585                  !WhiteOnMove(forwardMostMove)) ||
11586                 (gameMode == MachinePlaysBlack &&
11587                  WhiteOnMove(forwardMostMove))) {
11588                 StopClocks();
11589             }
11590             pausing = TRUE;
11591             ModeHighlight();
11592             break;
11593         }
11594     }
11595 }
11596
11597 void
11598 EditCommentEvent()
11599 {
11600     char title[MSG_SIZ];
11601
11602     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11603       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11604     } else {
11605       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11606                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11607                parseList[currentMove - 1]);
11608     }
11609
11610     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11611 }
11612
11613
11614 void
11615 EditTagsEvent()
11616 {
11617     char *tags = PGNTags(&gameInfo);
11618     EditTagsPopUp(tags);
11619     free(tags);
11620 }
11621
11622 void
11623 AnalyzeModeEvent()
11624 {
11625     if (appData.noChessProgram || gameMode == AnalyzeMode)
11626       return;
11627
11628     if (gameMode != AnalyzeFile) {
11629         if (!appData.icsEngineAnalyze) {
11630                EditGameEvent();
11631                if (gameMode != EditGame) return;
11632         }
11633         ResurrectChessProgram();
11634         SendToProgram("analyze\n", &first);
11635         first.analyzing = TRUE;
11636         /*first.maybeThinking = TRUE;*/
11637         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11638         EngineOutputPopUp();
11639     }
11640     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11641     pausing = FALSE;
11642     ModeHighlight();
11643     SetGameInfo();
11644
11645     StartAnalysisClock();
11646     GetTimeMark(&lastNodeCountTime);
11647     lastNodeCount = 0;
11648 }
11649
11650 void
11651 AnalyzeFileEvent()
11652 {
11653     if (appData.noChessProgram || gameMode == AnalyzeFile)
11654       return;
11655
11656     if (gameMode != AnalyzeMode) {
11657         EditGameEvent();
11658         if (gameMode != EditGame) return;
11659         ResurrectChessProgram();
11660         SendToProgram("analyze\n", &first);
11661         first.analyzing = TRUE;
11662         /*first.maybeThinking = TRUE;*/
11663         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11664         EngineOutputPopUp();
11665     }
11666     gameMode = AnalyzeFile;
11667     pausing = FALSE;
11668     ModeHighlight();
11669     SetGameInfo();
11670
11671     StartAnalysisClock();
11672     GetTimeMark(&lastNodeCountTime);
11673     lastNodeCount = 0;
11674 }
11675
11676 void
11677 MachineWhiteEvent()
11678 {
11679     char buf[MSG_SIZ];
11680     char *bookHit = NULL;
11681
11682     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11683       return;
11684
11685
11686     if (gameMode == PlayFromGameFile ||
11687         gameMode == TwoMachinesPlay  ||
11688         gameMode == Training         ||
11689         gameMode == AnalyzeMode      ||
11690         gameMode == EndOfGame)
11691         EditGameEvent();
11692
11693     if (gameMode == EditPosition)
11694         EditPositionDone(TRUE);
11695
11696     if (!WhiteOnMove(currentMove)) {
11697         DisplayError(_("It is not White's turn"), 0);
11698         return;
11699     }
11700
11701     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11702       ExitAnalyzeMode();
11703
11704     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11705         gameMode == AnalyzeFile)
11706         TruncateGame();
11707
11708     ResurrectChessProgram();    /* in case it isn't running */
11709     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11710         gameMode = MachinePlaysWhite;
11711         ResetClocks();
11712     } else
11713     gameMode = MachinePlaysWhite;
11714     pausing = FALSE;
11715     ModeHighlight();
11716     SetGameInfo();
11717     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11718     DisplayTitle(buf);
11719     if (first.sendName) {
11720       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11721       SendToProgram(buf, &first);
11722     }
11723     if (first.sendTime) {
11724       if (first.useColors) {
11725         SendToProgram("black\n", &first); /*gnu kludge*/
11726       }
11727       SendTimeRemaining(&first, TRUE);
11728     }
11729     if (first.useColors) {
11730       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11731     }
11732     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11733     SetMachineThinkingEnables();
11734     first.maybeThinking = TRUE;
11735     StartClocks();
11736     firstMove = FALSE;
11737
11738     if (appData.autoFlipView && !flipView) {
11739       flipView = !flipView;
11740       DrawPosition(FALSE, NULL);
11741       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11742     }
11743
11744     if(bookHit) { // [HGM] book: simulate book reply
11745         static char bookMove[MSG_SIZ]; // a bit generous?
11746
11747         programStats.nodes = programStats.depth = programStats.time =
11748         programStats.score = programStats.got_only_move = 0;
11749         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11750
11751         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11752         strcat(bookMove, bookHit);
11753         HandleMachineMove(bookMove, &first);
11754     }
11755 }
11756
11757 void
11758 MachineBlackEvent()
11759 {
11760   char buf[MSG_SIZ];
11761   char *bookHit = NULL;
11762
11763     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11764         return;
11765
11766
11767     if (gameMode == PlayFromGameFile ||
11768         gameMode == TwoMachinesPlay  ||
11769         gameMode == Training         ||
11770         gameMode == AnalyzeMode      ||
11771         gameMode == EndOfGame)
11772         EditGameEvent();
11773
11774     if (gameMode == EditPosition)
11775         EditPositionDone(TRUE);
11776
11777     if (WhiteOnMove(currentMove)) {
11778         DisplayError(_("It is not Black's turn"), 0);
11779         return;
11780     }
11781
11782     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11783       ExitAnalyzeMode();
11784
11785     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11786         gameMode == AnalyzeFile)
11787         TruncateGame();
11788
11789     ResurrectChessProgram();    /* in case it isn't running */
11790     gameMode = MachinePlaysBlack;
11791     pausing = FALSE;
11792     ModeHighlight();
11793     SetGameInfo();
11794     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11795     DisplayTitle(buf);
11796     if (first.sendName) {
11797       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11798       SendToProgram(buf, &first);
11799     }
11800     if (first.sendTime) {
11801       if (first.useColors) {
11802         SendToProgram("white\n", &first); /*gnu kludge*/
11803       }
11804       SendTimeRemaining(&first, FALSE);
11805     }
11806     if (first.useColors) {
11807       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11808     }
11809     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11810     SetMachineThinkingEnables();
11811     first.maybeThinking = TRUE;
11812     StartClocks();
11813
11814     if (appData.autoFlipView && flipView) {
11815       flipView = !flipView;
11816       DrawPosition(FALSE, NULL);
11817       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11818     }
11819     if(bookHit) { // [HGM] book: simulate book reply
11820         static char bookMove[MSG_SIZ]; // a bit generous?
11821
11822         programStats.nodes = programStats.depth = programStats.time =
11823         programStats.score = programStats.got_only_move = 0;
11824         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11825
11826         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11827         strcat(bookMove, bookHit);
11828         HandleMachineMove(bookMove, &first);
11829     }
11830 }
11831
11832
11833 void
11834 DisplayTwoMachinesTitle()
11835 {
11836     char buf[MSG_SIZ];
11837     if (appData.matchGames > 0) {
11838         if (first.twoMachinesColor[0] == 'w') {
11839           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11840                    gameInfo.white, gameInfo.black,
11841                    first.matchWins, second.matchWins,
11842                    matchGame - 1 - (first.matchWins + second.matchWins));
11843         } else {
11844           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11845                    gameInfo.white, gameInfo.black,
11846                    second.matchWins, first.matchWins,
11847                    matchGame - 1 - (first.matchWins + second.matchWins));
11848         }
11849     } else {
11850       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11851     }
11852     DisplayTitle(buf);
11853 }
11854
11855 void
11856 TwoMachinesEvent P((void))
11857 {
11858     int i;
11859     char buf[MSG_SIZ];
11860     ChessProgramState *onmove;
11861     char *bookHit = NULL;
11862
11863     if (appData.noChessProgram) return;
11864
11865     switch (gameMode) {
11866       case TwoMachinesPlay:
11867         return;
11868       case MachinePlaysWhite:
11869       case MachinePlaysBlack:
11870         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11871             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11872             return;
11873         }
11874         /* fall through */
11875       case BeginningOfGame:
11876       case PlayFromGameFile:
11877       case EndOfGame:
11878         EditGameEvent();
11879         if (gameMode != EditGame) return;
11880         break;
11881       case EditPosition:
11882         EditPositionDone(TRUE);
11883         break;
11884       case AnalyzeMode:
11885       case AnalyzeFile:
11886         ExitAnalyzeMode();
11887         break;
11888       case EditGame:
11889       default:
11890         break;
11891     }
11892
11893 //    forwardMostMove = currentMove;
11894     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11895     ResurrectChessProgram();    /* in case first program isn't running */
11896
11897     if (second.pr == NULL) {
11898         StartChessProgram(&second);
11899         if (second.protocolVersion == 1) {
11900           TwoMachinesEventIfReady();
11901         } else {
11902           /* kludge: allow timeout for initial "feature" command */
11903           FreezeUI();
11904           DisplayMessage("", _("Starting second chess program"));
11905           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11906         }
11907         return;
11908     }
11909     DisplayMessage("", "");
11910     InitChessProgram(&second, FALSE);
11911     SendToProgram("force\n", &second);
11912     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11913       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11914       return;
11915     }
11916     if (startedFromSetupPosition) {
11917         SendBoard(&second, backwardMostMove);
11918     if (appData.debugMode) {
11919         fprintf(debugFP, "Two Machines\n");
11920     }
11921     }
11922     for (i = backwardMostMove; i < forwardMostMove; i++) {
11923         SendMoveToProgram(i, &second);
11924     }
11925
11926     gameMode = TwoMachinesPlay;
11927     pausing = FALSE;
11928     ModeHighlight();
11929     SetGameInfo();
11930     DisplayTwoMachinesTitle();
11931     firstMove = TRUE;
11932     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11933         onmove = &first;
11934     } else {
11935         onmove = &second;
11936     }
11937
11938     SendToProgram(first.computerString, &first);
11939     if (first.sendName) {
11940       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11941       SendToProgram(buf, &first);
11942     }
11943     SendToProgram(second.computerString, &second);
11944     if (second.sendName) {
11945       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11946       SendToProgram(buf, &second);
11947     }
11948
11949     ResetClocks();
11950     if (!first.sendTime || !second.sendTime) {
11951         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11952         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11953     }
11954     if (onmove->sendTime) {
11955       if (onmove->useColors) {
11956         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11957       }
11958       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11959     }
11960     if (onmove->useColors) {
11961       SendToProgram(onmove->twoMachinesColor, onmove);
11962     }
11963     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11964 //    SendToProgram("go\n", onmove);
11965     onmove->maybeThinking = TRUE;
11966     SetMachineThinkingEnables();
11967
11968     StartClocks();
11969
11970     if(bookHit) { // [HGM] book: simulate book reply
11971         static char bookMove[MSG_SIZ]; // a bit generous?
11972
11973         programStats.nodes = programStats.depth = programStats.time =
11974         programStats.score = programStats.got_only_move = 0;
11975         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11976
11977         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11978         strcat(bookMove, bookHit);
11979         savedMessage = bookMove; // args for deferred call
11980         savedState = onmove;
11981         ScheduleDelayedEvent(DeferredBookMove, 1);
11982     }
11983 }
11984
11985 void
11986 TrainingEvent()
11987 {
11988     if (gameMode == Training) {
11989       SetTrainingModeOff();
11990       gameMode = PlayFromGameFile;
11991       DisplayMessage("", _("Training mode off"));
11992     } else {
11993       gameMode = Training;
11994       animateTraining = appData.animate;
11995
11996       /* make sure we are not already at the end of the game */
11997       if (currentMove < forwardMostMove) {
11998         SetTrainingModeOn();
11999         DisplayMessage("", _("Training mode on"));
12000       } else {
12001         gameMode = PlayFromGameFile;
12002         DisplayError(_("Already at end of game"), 0);
12003       }
12004     }
12005     ModeHighlight();
12006 }
12007
12008 void
12009 IcsClientEvent()
12010 {
12011     if (!appData.icsActive) return;
12012     switch (gameMode) {
12013       case IcsPlayingWhite:
12014       case IcsPlayingBlack:
12015       case IcsObserving:
12016       case IcsIdle:
12017       case BeginningOfGame:
12018       case IcsExamining:
12019         return;
12020
12021       case EditGame:
12022         break;
12023
12024       case EditPosition:
12025         EditPositionDone(TRUE);
12026         break;
12027
12028       case AnalyzeMode:
12029       case AnalyzeFile:
12030         ExitAnalyzeMode();
12031         break;
12032
12033       default:
12034         EditGameEvent();
12035         break;
12036     }
12037
12038     gameMode = IcsIdle;
12039     ModeHighlight();
12040     return;
12041 }
12042
12043
12044 void
12045 EditGameEvent()
12046 {
12047     int i;
12048
12049     switch (gameMode) {
12050       case Training:
12051         SetTrainingModeOff();
12052         break;
12053       case MachinePlaysWhite:
12054       case MachinePlaysBlack:
12055       case BeginningOfGame:
12056         SendToProgram("force\n", &first);
12057         SetUserThinkingEnables();
12058         break;
12059       case PlayFromGameFile:
12060         (void) StopLoadGameTimer();
12061         if (gameFileFP != NULL) {
12062             gameFileFP = NULL;
12063         }
12064         break;
12065       case EditPosition:
12066         EditPositionDone(TRUE);
12067         break;
12068       case AnalyzeMode:
12069       case AnalyzeFile:
12070         ExitAnalyzeMode();
12071         SendToProgram("force\n", &first);
12072         break;
12073       case TwoMachinesPlay:
12074         GameEnds(EndOfFile, NULL, GE_PLAYER);
12075         ResurrectChessProgram();
12076         SetUserThinkingEnables();
12077         break;
12078       case EndOfGame:
12079         ResurrectChessProgram();
12080         break;
12081       case IcsPlayingBlack:
12082       case IcsPlayingWhite:
12083         DisplayError(_("Warning: You are still playing a game"), 0);
12084         break;
12085       case IcsObserving:
12086         DisplayError(_("Warning: You are still observing a game"), 0);
12087         break;
12088       case IcsExamining:
12089         DisplayError(_("Warning: You are still examining a game"), 0);
12090         break;
12091       case IcsIdle:
12092         break;
12093       case EditGame:
12094       default:
12095         return;
12096     }
12097
12098     pausing = FALSE;
12099     StopClocks();
12100     first.offeredDraw = second.offeredDraw = 0;
12101
12102     if (gameMode == PlayFromGameFile) {
12103         whiteTimeRemaining = timeRemaining[0][currentMove];
12104         blackTimeRemaining = timeRemaining[1][currentMove];
12105         DisplayTitle("");
12106     }
12107
12108     if (gameMode == MachinePlaysWhite ||
12109         gameMode == MachinePlaysBlack ||
12110         gameMode == TwoMachinesPlay ||
12111         gameMode == EndOfGame) {
12112         i = forwardMostMove;
12113         while (i > currentMove) {
12114             SendToProgram("undo\n", &first);
12115             i--;
12116         }
12117         whiteTimeRemaining = timeRemaining[0][currentMove];
12118         blackTimeRemaining = timeRemaining[1][currentMove];
12119         DisplayBothClocks();
12120         if (whiteFlag || blackFlag) {
12121             whiteFlag = blackFlag = 0;
12122         }
12123         DisplayTitle("");
12124     }
12125
12126     gameMode = EditGame;
12127     ModeHighlight();
12128     SetGameInfo();
12129 }
12130
12131
12132 void
12133 EditPositionEvent()
12134 {
12135     if (gameMode == EditPosition) {
12136         EditGameEvent();
12137         return;
12138     }
12139
12140     EditGameEvent();
12141     if (gameMode != EditGame) return;
12142
12143     gameMode = EditPosition;
12144     ModeHighlight();
12145     SetGameInfo();
12146     if (currentMove > 0)
12147       CopyBoard(boards[0], boards[currentMove]);
12148
12149     blackPlaysFirst = !WhiteOnMove(currentMove);
12150     ResetClocks();
12151     currentMove = forwardMostMove = backwardMostMove = 0;
12152     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12153     DisplayMove(-1);
12154 }
12155
12156 void
12157 ExitAnalyzeMode()
12158 {
12159     /* [DM] icsEngineAnalyze - possible call from other functions */
12160     if (appData.icsEngineAnalyze) {
12161         appData.icsEngineAnalyze = FALSE;
12162
12163         DisplayMessage("",_("Close ICS engine analyze..."));
12164     }
12165     if (first.analysisSupport && first.analyzing) {
12166       SendToProgram("exit\n", &first);
12167       first.analyzing = FALSE;
12168     }
12169     thinkOutput[0] = NULLCHAR;
12170 }
12171
12172 void
12173 EditPositionDone(Boolean fakeRights)
12174 {
12175     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12176
12177     startedFromSetupPosition = TRUE;
12178     InitChessProgram(&first, FALSE);
12179     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12180       boards[0][EP_STATUS] = EP_NONE;
12181       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12182     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12183         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12184         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12185       } else boards[0][CASTLING][2] = NoRights;
12186     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12187         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12188         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12189       } else boards[0][CASTLING][5] = NoRights;
12190     }
12191     SendToProgram("force\n", &first);
12192     if (blackPlaysFirst) {
12193         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12194         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12195         currentMove = forwardMostMove = backwardMostMove = 1;
12196         CopyBoard(boards[1], boards[0]);
12197     } else {
12198         currentMove = forwardMostMove = backwardMostMove = 0;
12199     }
12200     SendBoard(&first, forwardMostMove);
12201     if (appData.debugMode) {
12202         fprintf(debugFP, "EditPosDone\n");
12203     }
12204     DisplayTitle("");
12205     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12206     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12207     gameMode = EditGame;
12208     ModeHighlight();
12209     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12210     ClearHighlights(); /* [AS] */
12211 }
12212
12213 /* Pause for `ms' milliseconds */
12214 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12215 void
12216 TimeDelay(ms)
12217      long ms;
12218 {
12219     TimeMark m1, m2;
12220
12221     GetTimeMark(&m1);
12222     do {
12223         GetTimeMark(&m2);
12224     } while (SubtractTimeMarks(&m2, &m1) < ms);
12225 }
12226
12227 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12228 void
12229 SendMultiLineToICS(buf)
12230      char *buf;
12231 {
12232     char temp[MSG_SIZ+1], *p;
12233     int len;
12234
12235     len = strlen(buf);
12236     if (len > MSG_SIZ)
12237       len = MSG_SIZ;
12238
12239     strncpy(temp, buf, len);
12240     temp[len] = 0;
12241
12242     p = temp;
12243     while (*p) {
12244         if (*p == '\n' || *p == '\r')
12245           *p = ' ';
12246         ++p;
12247     }
12248
12249     strcat(temp, "\n");
12250     SendToICS(temp);
12251     SendToPlayer(temp, strlen(temp));
12252 }
12253
12254 void
12255 SetWhiteToPlayEvent()
12256 {
12257     if (gameMode == EditPosition) {
12258         blackPlaysFirst = FALSE;
12259         DisplayBothClocks();    /* works because currentMove is 0 */
12260     } else if (gameMode == IcsExamining) {
12261         SendToICS(ics_prefix);
12262         SendToICS("tomove white\n");
12263     }
12264 }
12265
12266 void
12267 SetBlackToPlayEvent()
12268 {
12269     if (gameMode == EditPosition) {
12270         blackPlaysFirst = TRUE;
12271         currentMove = 1;        /* kludge */
12272         DisplayBothClocks();
12273         currentMove = 0;
12274     } else if (gameMode == IcsExamining) {
12275         SendToICS(ics_prefix);
12276         SendToICS("tomove black\n");
12277     }
12278 }
12279
12280 void
12281 EditPositionMenuEvent(selection, x, y)
12282      ChessSquare selection;
12283      int x, y;
12284 {
12285     char buf[MSG_SIZ];
12286     ChessSquare piece = boards[0][y][x];
12287
12288     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12289
12290     switch (selection) {
12291       case ClearBoard:
12292         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12293             SendToICS(ics_prefix);
12294             SendToICS("bsetup clear\n");
12295         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12296             SendToICS(ics_prefix);
12297             SendToICS("clearboard\n");
12298         } else {
12299             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12300                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12301                 for (y = 0; y < BOARD_HEIGHT; y++) {
12302                     if (gameMode == IcsExamining) {
12303                         if (boards[currentMove][y][x] != EmptySquare) {
12304                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12305                                     AAA + x, ONE + y);
12306                             SendToICS(buf);
12307                         }
12308                     } else {
12309                         boards[0][y][x] = p;
12310                     }
12311                 }
12312             }
12313         }
12314         if (gameMode == EditPosition) {
12315             DrawPosition(FALSE, boards[0]);
12316         }
12317         break;
12318
12319       case WhitePlay:
12320         SetWhiteToPlayEvent();
12321         break;
12322
12323       case BlackPlay:
12324         SetBlackToPlayEvent();
12325         break;
12326
12327       case EmptySquare:
12328         if (gameMode == IcsExamining) {
12329             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12330             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12331             SendToICS(buf);
12332         } else {
12333             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12334                 if(x == BOARD_LEFT-2) {
12335                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12336                     boards[0][y][1] = 0;
12337                 } else
12338                 if(x == BOARD_RGHT+1) {
12339                     if(y >= gameInfo.holdingsSize) break;
12340                     boards[0][y][BOARD_WIDTH-2] = 0;
12341                 } else break;
12342             }
12343             boards[0][y][x] = EmptySquare;
12344             DrawPosition(FALSE, boards[0]);
12345         }
12346         break;
12347
12348       case PromotePiece:
12349         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12350            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12351             selection = (ChessSquare) (PROMOTED piece);
12352         } else if(piece == EmptySquare) selection = WhiteSilver;
12353         else selection = (ChessSquare)((int)piece - 1);
12354         goto defaultlabel;
12355
12356       case DemotePiece:
12357         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12358            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12359             selection = (ChessSquare) (DEMOTED piece);
12360         } else if(piece == EmptySquare) selection = BlackSilver;
12361         else selection = (ChessSquare)((int)piece + 1);
12362         goto defaultlabel;
12363
12364       case WhiteQueen:
12365       case BlackQueen:
12366         if(gameInfo.variant == VariantShatranj ||
12367            gameInfo.variant == VariantXiangqi  ||
12368            gameInfo.variant == VariantCourier  ||
12369            gameInfo.variant == VariantMakruk     )
12370             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12371         goto defaultlabel;
12372
12373       case WhiteKing:
12374       case BlackKing:
12375         if(gameInfo.variant == VariantXiangqi)
12376             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12377         if(gameInfo.variant == VariantKnightmate)
12378             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12379       default:
12380         defaultlabel:
12381         if (gameMode == IcsExamining) {
12382             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12383             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12384                      PieceToChar(selection), AAA + x, ONE + y);
12385             SendToICS(buf);
12386         } else {
12387             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12388                 int n;
12389                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12390                     n = PieceToNumber(selection - BlackPawn);
12391                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12392                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12393                     boards[0][BOARD_HEIGHT-1-n][1]++;
12394                 } else
12395                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12396                     n = PieceToNumber(selection);
12397                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12398                     boards[0][n][BOARD_WIDTH-1] = selection;
12399                     boards[0][n][BOARD_WIDTH-2]++;
12400                 }
12401             } else
12402             boards[0][y][x] = selection;
12403             DrawPosition(TRUE, boards[0]);
12404         }
12405         break;
12406     }
12407 }
12408
12409
12410 void
12411 DropMenuEvent(selection, x, y)
12412      ChessSquare selection;
12413      int x, y;
12414 {
12415     ChessMove moveType;
12416
12417     switch (gameMode) {
12418       case IcsPlayingWhite:
12419       case MachinePlaysBlack:
12420         if (!WhiteOnMove(currentMove)) {
12421             DisplayMoveError(_("It is Black's turn"));
12422             return;
12423         }
12424         moveType = WhiteDrop;
12425         break;
12426       case IcsPlayingBlack:
12427       case MachinePlaysWhite:
12428         if (WhiteOnMove(currentMove)) {
12429             DisplayMoveError(_("It is White's turn"));
12430             return;
12431         }
12432         moveType = BlackDrop;
12433         break;
12434       case EditGame:
12435         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12436         break;
12437       default:
12438         return;
12439     }
12440
12441     if (moveType == BlackDrop && selection < BlackPawn) {
12442       selection = (ChessSquare) ((int) selection
12443                                  + (int) BlackPawn - (int) WhitePawn);
12444     }
12445     if (boards[currentMove][y][x] != EmptySquare) {
12446         DisplayMoveError(_("That square is occupied"));
12447         return;
12448     }
12449
12450     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12451 }
12452
12453 void
12454 AcceptEvent()
12455 {
12456     /* Accept a pending offer of any kind from opponent */
12457
12458     if (appData.icsActive) {
12459         SendToICS(ics_prefix);
12460         SendToICS("accept\n");
12461     } else if (cmailMsgLoaded) {
12462         if (currentMove == cmailOldMove &&
12463             commentList[cmailOldMove] != NULL &&
12464             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12465                    "Black offers a draw" : "White offers a draw")) {
12466             TruncateGame();
12467             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12468             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12469         } else {
12470             DisplayError(_("There is no pending offer on this move"), 0);
12471             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12472         }
12473     } else {
12474         /* Not used for offers from chess program */
12475     }
12476 }
12477
12478 void
12479 DeclineEvent()
12480 {
12481     /* Decline a pending offer of any kind from opponent */
12482
12483     if (appData.icsActive) {
12484         SendToICS(ics_prefix);
12485         SendToICS("decline\n");
12486     } else if (cmailMsgLoaded) {
12487         if (currentMove == cmailOldMove &&
12488             commentList[cmailOldMove] != NULL &&
12489             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12490                    "Black offers a draw" : "White offers a draw")) {
12491 #ifdef NOTDEF
12492             AppendComment(cmailOldMove, "Draw declined", TRUE);
12493             DisplayComment(cmailOldMove - 1, "Draw declined");
12494 #endif /*NOTDEF*/
12495         } else {
12496             DisplayError(_("There is no pending offer on this move"), 0);
12497         }
12498     } else {
12499         /* Not used for offers from chess program */
12500     }
12501 }
12502
12503 void
12504 RematchEvent()
12505 {
12506     /* Issue ICS rematch command */
12507     if (appData.icsActive) {
12508         SendToICS(ics_prefix);
12509         SendToICS("rematch\n");
12510     }
12511 }
12512
12513 void
12514 CallFlagEvent()
12515 {
12516     /* Call your opponent's flag (claim a win on time) */
12517     if (appData.icsActive) {
12518         SendToICS(ics_prefix);
12519         SendToICS("flag\n");
12520     } else {
12521         switch (gameMode) {
12522           default:
12523             return;
12524           case MachinePlaysWhite:
12525             if (whiteFlag) {
12526                 if (blackFlag)
12527                   GameEnds(GameIsDrawn, "Both players ran out of time",
12528                            GE_PLAYER);
12529                 else
12530                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12531             } else {
12532                 DisplayError(_("Your opponent is not out of time"), 0);
12533             }
12534             break;
12535           case MachinePlaysBlack:
12536             if (blackFlag) {
12537                 if (whiteFlag)
12538                   GameEnds(GameIsDrawn, "Both players ran out of time",
12539                            GE_PLAYER);
12540                 else
12541                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12542             } else {
12543                 DisplayError(_("Your opponent is not out of time"), 0);
12544             }
12545             break;
12546         }
12547     }
12548 }
12549
12550 void
12551 DrawEvent()
12552 {
12553     /* Offer draw or accept pending draw offer from opponent */
12554
12555     if (appData.icsActive) {
12556         /* Note: tournament rules require draw offers to be
12557            made after you make your move but before you punch
12558            your clock.  Currently ICS doesn't let you do that;
12559            instead, you immediately punch your clock after making
12560            a move, but you can offer a draw at any time. */
12561
12562         SendToICS(ics_prefix);
12563         SendToICS("draw\n");
12564         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12565     } else if (cmailMsgLoaded) {
12566         if (currentMove == cmailOldMove &&
12567             commentList[cmailOldMove] != NULL &&
12568             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12569                    "Black offers a draw" : "White offers a draw")) {
12570             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12571             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12572         } else if (currentMove == cmailOldMove + 1) {
12573             char *offer = WhiteOnMove(cmailOldMove) ?
12574               "White offers a draw" : "Black offers a draw";
12575             AppendComment(currentMove, offer, TRUE);
12576             DisplayComment(currentMove - 1, offer);
12577             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12578         } else {
12579             DisplayError(_("You must make your move before offering a draw"), 0);
12580             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12581         }
12582     } else if (first.offeredDraw) {
12583         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12584     } else {
12585         if (first.sendDrawOffers) {
12586             SendToProgram("draw\n", &first);
12587             userOfferedDraw = TRUE;
12588         }
12589     }
12590 }
12591
12592 void
12593 AdjournEvent()
12594 {
12595     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12596
12597     if (appData.icsActive) {
12598         SendToICS(ics_prefix);
12599         SendToICS("adjourn\n");
12600     } else {
12601         /* Currently GNU Chess doesn't offer or accept Adjourns */
12602     }
12603 }
12604
12605
12606 void
12607 AbortEvent()
12608 {
12609     /* Offer Abort or accept pending Abort offer from opponent */
12610
12611     if (appData.icsActive) {
12612         SendToICS(ics_prefix);
12613         SendToICS("abort\n");
12614     } else {
12615         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12616     }
12617 }
12618
12619 void
12620 ResignEvent()
12621 {
12622     /* Resign.  You can do this even if it's not your turn. */
12623
12624     if (appData.icsActive) {
12625         SendToICS(ics_prefix);
12626         SendToICS("resign\n");
12627     } else {
12628         switch (gameMode) {
12629           case MachinePlaysWhite:
12630             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12631             break;
12632           case MachinePlaysBlack:
12633             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12634             break;
12635           case EditGame:
12636             if (cmailMsgLoaded) {
12637                 TruncateGame();
12638                 if (WhiteOnMove(cmailOldMove)) {
12639                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12640                 } else {
12641                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12642                 }
12643                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12644             }
12645             break;
12646           default:
12647             break;
12648         }
12649     }
12650 }
12651
12652
12653 void
12654 StopObservingEvent()
12655 {
12656     /* Stop observing current games */
12657     SendToICS(ics_prefix);
12658     SendToICS("unobserve\n");
12659 }
12660
12661 void
12662 StopExaminingEvent()
12663 {
12664     /* Stop observing current game */
12665     SendToICS(ics_prefix);
12666     SendToICS("unexamine\n");
12667 }
12668
12669 void
12670 ForwardInner(target)
12671      int target;
12672 {
12673     int limit;
12674
12675     if (appData.debugMode)
12676         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12677                 target, currentMove, forwardMostMove);
12678
12679     if (gameMode == EditPosition)
12680       return;
12681
12682     if (gameMode == PlayFromGameFile && !pausing)
12683       PauseEvent();
12684
12685     if (gameMode == IcsExamining && pausing)
12686       limit = pauseExamForwardMostMove;
12687     else
12688       limit = forwardMostMove;
12689
12690     if (target > limit) target = limit;
12691
12692     if (target > 0 && moveList[target - 1][0]) {
12693         int fromX, fromY, toX, toY;
12694         toX = moveList[target - 1][2] - AAA;
12695         toY = moveList[target - 1][3] - ONE;
12696         if (moveList[target - 1][1] == '@') {
12697             if (appData.highlightLastMove) {
12698                 SetHighlights(-1, -1, toX, toY);
12699             }
12700         } else {
12701             fromX = moveList[target - 1][0] - AAA;
12702             fromY = moveList[target - 1][1] - ONE;
12703             if (target == currentMove + 1) {
12704                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12705             }
12706             if (appData.highlightLastMove) {
12707                 SetHighlights(fromX, fromY, toX, toY);
12708             }
12709         }
12710     }
12711     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12712         gameMode == Training || gameMode == PlayFromGameFile ||
12713         gameMode == AnalyzeFile) {
12714         while (currentMove < target) {
12715             SendMoveToProgram(currentMove++, &first);
12716         }
12717     } else {
12718         currentMove = target;
12719     }
12720
12721     if (gameMode == EditGame || gameMode == EndOfGame) {
12722         whiteTimeRemaining = timeRemaining[0][currentMove];
12723         blackTimeRemaining = timeRemaining[1][currentMove];
12724     }
12725     DisplayBothClocks();
12726     DisplayMove(currentMove - 1);
12727     DrawPosition(FALSE, boards[currentMove]);
12728     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12729     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12730         DisplayComment(currentMove - 1, commentList[currentMove]);
12731     }
12732 }
12733
12734
12735 void
12736 ForwardEvent()
12737 {
12738     if (gameMode == IcsExamining && !pausing) {
12739         SendToICS(ics_prefix);
12740         SendToICS("forward\n");
12741     } else {
12742         ForwardInner(currentMove + 1);
12743     }
12744 }
12745
12746 void
12747 ToEndEvent()
12748 {
12749     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12750         /* to optimze, we temporarily turn off analysis mode while we feed
12751          * the remaining moves to the engine. Otherwise we get analysis output
12752          * after each move.
12753          */
12754         if (first.analysisSupport) {
12755           SendToProgram("exit\nforce\n", &first);
12756           first.analyzing = FALSE;
12757         }
12758     }
12759
12760     if (gameMode == IcsExamining && !pausing) {
12761         SendToICS(ics_prefix);
12762         SendToICS("forward 999999\n");
12763     } else {
12764         ForwardInner(forwardMostMove);
12765     }
12766
12767     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12768         /* we have fed all the moves, so reactivate analysis mode */
12769         SendToProgram("analyze\n", &first);
12770         first.analyzing = TRUE;
12771         /*first.maybeThinking = TRUE;*/
12772         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12773     }
12774 }
12775
12776 void
12777 BackwardInner(target)
12778      int target;
12779 {
12780     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12781
12782     if (appData.debugMode)
12783         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12784                 target, currentMove, forwardMostMove);
12785
12786     if (gameMode == EditPosition) return;
12787     if (currentMove <= backwardMostMove) {
12788         ClearHighlights();
12789         DrawPosition(full_redraw, boards[currentMove]);
12790         return;
12791     }
12792     if (gameMode == PlayFromGameFile && !pausing)
12793       PauseEvent();
12794
12795     if (moveList[target][0]) {
12796         int fromX, fromY, toX, toY;
12797         toX = moveList[target][2] - AAA;
12798         toY = moveList[target][3] - ONE;
12799         if (moveList[target][1] == '@') {
12800             if (appData.highlightLastMove) {
12801                 SetHighlights(-1, -1, toX, toY);
12802             }
12803         } else {
12804             fromX = moveList[target][0] - AAA;
12805             fromY = moveList[target][1] - ONE;
12806             if (target == currentMove - 1) {
12807                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12808             }
12809             if (appData.highlightLastMove) {
12810                 SetHighlights(fromX, fromY, toX, toY);
12811             }
12812         }
12813     }
12814     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12815         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12816         while (currentMove > target) {
12817             SendToProgram("undo\n", &first);
12818             currentMove--;
12819         }
12820     } else {
12821         currentMove = target;
12822     }
12823
12824     if (gameMode == EditGame || gameMode == EndOfGame) {
12825         whiteTimeRemaining = timeRemaining[0][currentMove];
12826         blackTimeRemaining = timeRemaining[1][currentMove];
12827     }
12828     DisplayBothClocks();
12829     DisplayMove(currentMove - 1);
12830     DrawPosition(full_redraw, boards[currentMove]);
12831     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12832     // [HGM] PV info: routine tests if comment empty
12833     DisplayComment(currentMove - 1, commentList[currentMove]);
12834 }
12835
12836 void
12837 BackwardEvent()
12838 {
12839     if (gameMode == IcsExamining && !pausing) {
12840         SendToICS(ics_prefix);
12841         SendToICS("backward\n");
12842     } else {
12843         BackwardInner(currentMove - 1);
12844     }
12845 }
12846
12847 void
12848 ToStartEvent()
12849 {
12850     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12851         /* to optimize, we temporarily turn off analysis mode while we undo
12852          * all the moves. Otherwise we get analysis output after each undo.
12853          */
12854         if (first.analysisSupport) {
12855           SendToProgram("exit\nforce\n", &first);
12856           first.analyzing = FALSE;
12857         }
12858     }
12859
12860     if (gameMode == IcsExamining && !pausing) {
12861         SendToICS(ics_prefix);
12862         SendToICS("backward 999999\n");
12863     } else {
12864         BackwardInner(backwardMostMove);
12865     }
12866
12867     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12868         /* we have fed all the moves, so reactivate analysis mode */
12869         SendToProgram("analyze\n", &first);
12870         first.analyzing = TRUE;
12871         /*first.maybeThinking = TRUE;*/
12872         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12873     }
12874 }
12875
12876 void
12877 ToNrEvent(int to)
12878 {
12879   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12880   if (to >= forwardMostMove) to = forwardMostMove;
12881   if (to <= backwardMostMove) to = backwardMostMove;
12882   if (to < currentMove) {
12883     BackwardInner(to);
12884   } else {
12885     ForwardInner(to);
12886   }
12887 }
12888
12889 void
12890 RevertEvent(Boolean annotate)
12891 {
12892     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12893         return;
12894     }
12895     if (gameMode != IcsExamining) {
12896         DisplayError(_("You are not examining a game"), 0);
12897         return;
12898     }
12899     if (pausing) {
12900         DisplayError(_("You can't revert while pausing"), 0);
12901         return;
12902     }
12903     SendToICS(ics_prefix);
12904     SendToICS("revert\n");
12905 }
12906
12907 void
12908 RetractMoveEvent()
12909 {
12910     switch (gameMode) {
12911       case MachinePlaysWhite:
12912       case MachinePlaysBlack:
12913         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12914             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12915             return;
12916         }
12917         if (forwardMostMove < 2) return;
12918         currentMove = forwardMostMove = forwardMostMove - 2;
12919         whiteTimeRemaining = timeRemaining[0][currentMove];
12920         blackTimeRemaining = timeRemaining[1][currentMove];
12921         DisplayBothClocks();
12922         DisplayMove(currentMove - 1);
12923         ClearHighlights();/*!! could figure this out*/
12924         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12925         SendToProgram("remove\n", &first);
12926         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12927         break;
12928
12929       case BeginningOfGame:
12930       default:
12931         break;
12932
12933       case IcsPlayingWhite:
12934       case IcsPlayingBlack:
12935         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12936             SendToICS(ics_prefix);
12937             SendToICS("takeback 2\n");
12938         } else {
12939             SendToICS(ics_prefix);
12940             SendToICS("takeback 1\n");
12941         }
12942         break;
12943     }
12944 }
12945
12946 void
12947 MoveNowEvent()
12948 {
12949     ChessProgramState *cps;
12950
12951     switch (gameMode) {
12952       case MachinePlaysWhite:
12953         if (!WhiteOnMove(forwardMostMove)) {
12954             DisplayError(_("It is your turn"), 0);
12955             return;
12956         }
12957         cps = &first;
12958         break;
12959       case MachinePlaysBlack:
12960         if (WhiteOnMove(forwardMostMove)) {
12961             DisplayError(_("It is your turn"), 0);
12962             return;
12963         }
12964         cps = &first;
12965         break;
12966       case TwoMachinesPlay:
12967         if (WhiteOnMove(forwardMostMove) ==
12968             (first.twoMachinesColor[0] == 'w')) {
12969             cps = &first;
12970         } else {
12971             cps = &second;
12972         }
12973         break;
12974       case BeginningOfGame:
12975       default:
12976         return;
12977     }
12978     SendToProgram("?\n", cps);
12979 }
12980
12981 void
12982 TruncateGameEvent()
12983 {
12984     EditGameEvent();
12985     if (gameMode != EditGame) return;
12986     TruncateGame();
12987 }
12988
12989 void
12990 TruncateGame()
12991 {
12992     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12993     if (forwardMostMove > currentMove) {
12994         if (gameInfo.resultDetails != NULL) {
12995             free(gameInfo.resultDetails);
12996             gameInfo.resultDetails = NULL;
12997             gameInfo.result = GameUnfinished;
12998         }
12999         forwardMostMove = currentMove;
13000         HistorySet(parseList, backwardMostMove, forwardMostMove,
13001                    currentMove-1);
13002     }
13003 }
13004
13005 void
13006 HintEvent()
13007 {
13008     if (appData.noChessProgram) return;
13009     switch (gameMode) {
13010       case MachinePlaysWhite:
13011         if (WhiteOnMove(forwardMostMove)) {
13012             DisplayError(_("Wait until your turn"), 0);
13013             return;
13014         }
13015         break;
13016       case BeginningOfGame:
13017       case MachinePlaysBlack:
13018         if (!WhiteOnMove(forwardMostMove)) {
13019             DisplayError(_("Wait until your turn"), 0);
13020             return;
13021         }
13022         break;
13023       default:
13024         DisplayError(_("No hint available"), 0);
13025         return;
13026     }
13027     SendToProgram("hint\n", &first);
13028     hintRequested = TRUE;
13029 }
13030
13031 void
13032 BookEvent()
13033 {
13034     if (appData.noChessProgram) return;
13035     switch (gameMode) {
13036       case MachinePlaysWhite:
13037         if (WhiteOnMove(forwardMostMove)) {
13038             DisplayError(_("Wait until your turn"), 0);
13039             return;
13040         }
13041         break;
13042       case BeginningOfGame:
13043       case MachinePlaysBlack:
13044         if (!WhiteOnMove(forwardMostMove)) {
13045             DisplayError(_("Wait until your turn"), 0);
13046             return;
13047         }
13048         break;
13049       case EditPosition:
13050         EditPositionDone(TRUE);
13051         break;
13052       case TwoMachinesPlay:
13053         return;
13054       default:
13055         break;
13056     }
13057     SendToProgram("bk\n", &first);
13058     bookOutput[0] = NULLCHAR;
13059     bookRequested = TRUE;
13060 }
13061
13062 void
13063 AboutGameEvent()
13064 {
13065     char *tags = PGNTags(&gameInfo);
13066     TagsPopUp(tags, CmailMsg());
13067     free(tags);
13068 }
13069
13070 /* end button procedures */
13071
13072 void
13073 PrintPosition(fp, move)
13074      FILE *fp;
13075      int move;
13076 {
13077     int i, j;
13078
13079     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13080         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13081             char c = PieceToChar(boards[move][i][j]);
13082             fputc(c == 'x' ? '.' : c, fp);
13083             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13084         }
13085     }
13086     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13087       fprintf(fp, "white to play\n");
13088     else
13089       fprintf(fp, "black to play\n");
13090 }
13091
13092 void
13093 PrintOpponents(fp)
13094      FILE *fp;
13095 {
13096     if (gameInfo.white != NULL) {
13097         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13098     } else {
13099         fprintf(fp, "\n");
13100     }
13101 }
13102
13103 /* Find last component of program's own name, using some heuristics */
13104 void
13105 TidyProgramName(prog, host, buf)
13106      char *prog, *host, buf[MSG_SIZ];
13107 {
13108     char *p, *q;
13109     int local = (strcmp(host, "localhost") == 0);
13110     while (!local && (p = strchr(prog, ';')) != NULL) {
13111         p++;
13112         while (*p == ' ') p++;
13113         prog = p;
13114     }
13115     if (*prog == '"' || *prog == '\'') {
13116         q = strchr(prog + 1, *prog);
13117     } else {
13118         q = strchr(prog, ' ');
13119     }
13120     if (q == NULL) q = prog + strlen(prog);
13121     p = q;
13122     while (p >= prog && *p != '/' && *p != '\\') p--;
13123     p++;
13124     if(p == prog && *p == '"') p++;
13125     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13126     memcpy(buf, p, q - p);
13127     buf[q - p] = NULLCHAR;
13128     if (!local) {
13129         strcat(buf, "@");
13130         strcat(buf, host);
13131     }
13132 }
13133
13134 char *
13135 TimeControlTagValue()
13136 {
13137     char buf[MSG_SIZ];
13138     if (!appData.clockMode) {
13139       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13140     } else if (movesPerSession > 0) {
13141       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13142     } else if (timeIncrement == 0) {
13143       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13144     } else {
13145       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13146     }
13147     return StrSave(buf);
13148 }
13149
13150 void
13151 SetGameInfo()
13152 {
13153     /* This routine is used only for certain modes */
13154     VariantClass v = gameInfo.variant;
13155     ChessMove r = GameUnfinished;
13156     char *p = NULL;
13157
13158     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13159         r = gameInfo.result;
13160         p = gameInfo.resultDetails;
13161         gameInfo.resultDetails = NULL;
13162     }
13163     ClearGameInfo(&gameInfo);
13164     gameInfo.variant = v;
13165
13166     switch (gameMode) {
13167       case MachinePlaysWhite:
13168         gameInfo.event = StrSave( appData.pgnEventHeader );
13169         gameInfo.site = StrSave(HostName());
13170         gameInfo.date = PGNDate();
13171         gameInfo.round = StrSave("-");
13172         gameInfo.white = StrSave(first.tidy);
13173         gameInfo.black = StrSave(UserName());
13174         gameInfo.timeControl = TimeControlTagValue();
13175         break;
13176
13177       case MachinePlaysBlack:
13178         gameInfo.event = StrSave( appData.pgnEventHeader );
13179         gameInfo.site = StrSave(HostName());
13180         gameInfo.date = PGNDate();
13181         gameInfo.round = StrSave("-");
13182         gameInfo.white = StrSave(UserName());
13183         gameInfo.black = StrSave(first.tidy);
13184         gameInfo.timeControl = TimeControlTagValue();
13185         break;
13186
13187       case TwoMachinesPlay:
13188         gameInfo.event = StrSave( appData.pgnEventHeader );
13189         gameInfo.site = StrSave(HostName());
13190         gameInfo.date = PGNDate();
13191         if (matchGame > 0) {
13192             char buf[MSG_SIZ];
13193             snprintf(buf, MSG_SIZ, "%d", matchGame);
13194             gameInfo.round = StrSave(buf);
13195         } else {
13196             gameInfo.round = StrSave("-");
13197         }
13198         if (first.twoMachinesColor[0] == 'w') {
13199             gameInfo.white = StrSave(first.tidy);
13200             gameInfo.black = StrSave(second.tidy);
13201         } else {
13202             gameInfo.white = StrSave(second.tidy);
13203             gameInfo.black = StrSave(first.tidy);
13204         }
13205         gameInfo.timeControl = TimeControlTagValue();
13206         break;
13207
13208       case EditGame:
13209         gameInfo.event = StrSave("Edited game");
13210         gameInfo.site = StrSave(HostName());
13211         gameInfo.date = PGNDate();
13212         gameInfo.round = StrSave("-");
13213         gameInfo.white = StrSave("-");
13214         gameInfo.black = StrSave("-");
13215         gameInfo.result = r;
13216         gameInfo.resultDetails = p;
13217         break;
13218
13219       case EditPosition:
13220         gameInfo.event = StrSave("Edited position");
13221         gameInfo.site = StrSave(HostName());
13222         gameInfo.date = PGNDate();
13223         gameInfo.round = StrSave("-");
13224         gameInfo.white = StrSave("-");
13225         gameInfo.black = StrSave("-");
13226         break;
13227
13228       case IcsPlayingWhite:
13229       case IcsPlayingBlack:
13230       case IcsObserving:
13231       case IcsExamining:
13232         break;
13233
13234       case PlayFromGameFile:
13235         gameInfo.event = StrSave("Game from non-PGN file");
13236         gameInfo.site = StrSave(HostName());
13237         gameInfo.date = PGNDate();
13238         gameInfo.round = StrSave("-");
13239         gameInfo.white = StrSave("?");
13240         gameInfo.black = StrSave("?");
13241         break;
13242
13243       default:
13244         break;
13245     }
13246 }
13247
13248 void
13249 ReplaceComment(index, text)
13250      int index;
13251      char *text;
13252 {
13253     int len;
13254
13255     while (*text == '\n') text++;
13256     len = strlen(text);
13257     while (len > 0 && text[len - 1] == '\n') len--;
13258
13259     if (commentList[index] != NULL)
13260       free(commentList[index]);
13261
13262     if (len == 0) {
13263         commentList[index] = NULL;
13264         return;
13265     }
13266   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13267       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13268       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13269     commentList[index] = (char *) malloc(len + 2);
13270     strncpy(commentList[index], text, len);
13271     commentList[index][len] = '\n';
13272     commentList[index][len + 1] = NULLCHAR;
13273   } else {
13274     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13275     char *p;
13276     commentList[index] = (char *) malloc(len + 7);
13277     safeStrCpy(commentList[index], "{\n", 3);
13278     safeStrCpy(commentList[index]+2, text, len+1);
13279     commentList[index][len+2] = NULLCHAR;
13280     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13281     strcat(commentList[index], "\n}\n");
13282   }
13283 }
13284
13285 void
13286 CrushCRs(text)
13287      char *text;
13288 {
13289   char *p = text;
13290   char *q = text;
13291   char ch;
13292
13293   do {
13294     ch = *p++;
13295     if (ch == '\r') continue;
13296     *q++ = ch;
13297   } while (ch != '\0');
13298 }
13299
13300 void
13301 AppendComment(index, text, addBraces)
13302      int index;
13303      char *text;
13304      Boolean addBraces; // [HGM] braces: tells if we should add {}
13305 {
13306     int oldlen, len;
13307     char *old;
13308
13309 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13310     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13311
13312     CrushCRs(text);
13313     while (*text == '\n') text++;
13314     len = strlen(text);
13315     while (len > 0 && text[len - 1] == '\n') len--;
13316
13317     if (len == 0) return;
13318
13319     if (commentList[index] != NULL) {
13320         old = commentList[index];
13321         oldlen = strlen(old);
13322         while(commentList[index][oldlen-1] ==  '\n')
13323           commentList[index][--oldlen] = NULLCHAR;
13324         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13325         safeStrCpy(commentList[index], old, oldlen);
13326         free(old);
13327         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13328         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13329           if(addBraces) addBraces = FALSE; else { text++; len--; }
13330           while (*text == '\n') { text++; len--; }
13331           commentList[index][--oldlen] = NULLCHAR;
13332       }
13333         if(addBraces) strcat(commentList[index], "\n{\n");
13334         else          strcat(commentList[index], "\n");
13335         strcat(commentList[index], text);
13336         if(addBraces) strcat(commentList[index], "\n}\n");
13337         else          strcat(commentList[index], "\n");
13338     } else {
13339         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13340         if(addBraces)
13341           safeStrCpy(commentList[index], "{\n", 3);
13342         else commentList[index][0] = NULLCHAR;
13343         strcat(commentList[index], text);
13344         strcat(commentList[index], "\n");
13345         if(addBraces) strcat(commentList[index], "}\n");
13346     }
13347 }
13348
13349 static char * FindStr( char * text, char * sub_text )
13350 {
13351     char * result = strstr( text, sub_text );
13352
13353     if( result != NULL ) {
13354         result += strlen( sub_text );
13355     }
13356
13357     return result;
13358 }
13359
13360 /* [AS] Try to extract PV info from PGN comment */
13361 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13362 char *GetInfoFromComment( int index, char * text )
13363 {
13364     char * sep = text;
13365
13366     if( text != NULL && index > 0 ) {
13367         int score = 0;
13368         int depth = 0;
13369         int time = -1, sec = 0, deci;
13370         char * s_eval = FindStr( text, "[%eval " );
13371         char * s_emt = FindStr( text, "[%emt " );
13372
13373         if( s_eval != NULL || s_emt != NULL ) {
13374             /* New style */
13375             char delim;
13376
13377             if( s_eval != NULL ) {
13378                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13379                     return text;
13380                 }
13381
13382                 if( delim != ']' ) {
13383                     return text;
13384                 }
13385             }
13386
13387             if( s_emt != NULL ) {
13388             }
13389                 return text;
13390         }
13391         else {
13392             /* We expect something like: [+|-]nnn.nn/dd */
13393             int score_lo = 0;
13394
13395             if(*text != '{') return text; // [HGM] braces: must be normal comment
13396
13397             sep = strchr( text, '/' );
13398             if( sep == NULL || sep < (text+4) ) {
13399                 return text;
13400             }
13401
13402             time = -1; sec = -1; deci = -1;
13403             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13404                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13405                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13406                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13407                 return text;
13408             }
13409
13410             if( score_lo < 0 || score_lo >= 100 ) {
13411                 return text;
13412             }
13413
13414             if(sec >= 0) time = 600*time + 10*sec; else
13415             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13416
13417             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13418
13419             /* [HGM] PV time: now locate end of PV info */
13420             while( *++sep >= '0' && *sep <= '9'); // strip depth
13421             if(time >= 0)
13422             while( *++sep >= '0' && *sep <= '9'); // strip time
13423             if(sec >= 0)
13424             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13425             if(deci >= 0)
13426             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13427             while(*sep == ' ') sep++;
13428         }
13429
13430         if( depth <= 0 ) {
13431             return text;
13432         }
13433
13434         if( time < 0 ) {
13435             time = -1;
13436         }
13437
13438         pvInfoList[index-1].depth = depth;
13439         pvInfoList[index-1].score = score;
13440         pvInfoList[index-1].time  = 10*time; // centi-sec
13441         if(*sep == '}') *sep = 0; else *--sep = '{';
13442     }
13443     return sep;
13444 }
13445
13446 void
13447 SendToProgram(message, cps)
13448      char *message;
13449      ChessProgramState *cps;
13450 {
13451     int count, outCount, error;
13452     char buf[MSG_SIZ];
13453
13454     if (cps->pr == NULL) return;
13455     Attention(cps);
13456
13457     if (appData.debugMode) {
13458         TimeMark now;
13459         GetTimeMark(&now);
13460         fprintf(debugFP, "%ld >%-6s: %s",
13461                 SubtractTimeMarks(&now, &programStartTime),
13462                 cps->which, message);
13463     }
13464
13465     count = strlen(message);
13466     outCount = OutputToProcess(cps->pr, message, count, &error);
13467     if (outCount < count && !exiting
13468                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13469       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13470         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13471             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13472                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13473                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13474             } else {
13475                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13476             }
13477             gameInfo.resultDetails = StrSave(buf);
13478         }
13479         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13480     }
13481 }
13482
13483 void
13484 ReceiveFromProgram(isr, closure, message, count, error)
13485      InputSourceRef isr;
13486      VOIDSTAR closure;
13487      char *message;
13488      int count;
13489      int error;
13490 {
13491     char *end_str;
13492     char buf[MSG_SIZ];
13493     ChessProgramState *cps = (ChessProgramState *)closure;
13494
13495     if (isr != cps->isr) return; /* Killed intentionally */
13496     if (count <= 0) {
13497         if (count == 0) {
13498             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13499                     cps->which, cps->program);
13500         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13501                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13502                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13503                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13504                 } else {
13505                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13506                 }
13507                 gameInfo.resultDetails = StrSave(buf);
13508             }
13509             RemoveInputSource(cps->isr);
13510             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13511         } else {
13512             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13513                     cps->which, cps->program);
13514             RemoveInputSource(cps->isr);
13515
13516             /* [AS] Program is misbehaving badly... kill it */
13517             if( count == -2 ) {
13518                 DestroyChildProcess( cps->pr, 9 );
13519                 cps->pr = NoProc;
13520             }
13521
13522             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13523         }
13524         return;
13525     }
13526
13527     if ((end_str = strchr(message, '\r')) != NULL)
13528       *end_str = NULLCHAR;
13529     if ((end_str = strchr(message, '\n')) != NULL)
13530       *end_str = NULLCHAR;
13531
13532     if (appData.debugMode) {
13533         TimeMark now; int print = 1;
13534         char *quote = ""; char c; int i;
13535
13536         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13537                 char start = message[0];
13538                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13539                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13540                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13541                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13542                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13543                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13544                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13545                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13546                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13547                     print = (appData.engineComments >= 2);
13548                 }
13549                 message[0] = start; // restore original message
13550         }
13551         if(print) {
13552                 GetTimeMark(&now);
13553                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13554                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13555                         quote,
13556                         message);
13557         }
13558     }
13559
13560     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13561     if (appData.icsEngineAnalyze) {
13562         if (strstr(message, "whisper") != NULL ||
13563              strstr(message, "kibitz") != NULL ||
13564             strstr(message, "tellics") != NULL) return;
13565     }
13566
13567     HandleMachineMove(message, cps);
13568 }
13569
13570
13571 void
13572 SendTimeControl(cps, mps, tc, inc, sd, st)
13573      ChessProgramState *cps;
13574      int mps, inc, sd, st;
13575      long tc;
13576 {
13577     char buf[MSG_SIZ];
13578     int seconds;
13579
13580     if( timeControl_2 > 0 ) {
13581         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13582             tc = timeControl_2;
13583         }
13584     }
13585     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13586     inc /= cps->timeOdds;
13587     st  /= cps->timeOdds;
13588
13589     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13590
13591     if (st > 0) {
13592       /* Set exact time per move, normally using st command */
13593       if (cps->stKludge) {
13594         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13595         seconds = st % 60;
13596         if (seconds == 0) {
13597           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13598         } else {
13599           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13600         }
13601       } else {
13602         snprintf(buf, MSG_SIZ, "st %d\n", st);
13603       }
13604     } else {
13605       /* Set conventional or incremental time control, using level command */
13606       if (seconds == 0) {
13607         /* Note old gnuchess bug -- minutes:seconds used to not work.
13608            Fixed in later versions, but still avoid :seconds
13609            when seconds is 0. */
13610         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13611       } else {
13612         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13613                  seconds, inc/1000.);
13614       }
13615     }
13616     SendToProgram(buf, cps);
13617
13618     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13619     /* Orthogonally, limit search to given depth */
13620     if (sd > 0) {
13621       if (cps->sdKludge) {
13622         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13623       } else {
13624         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13625       }
13626       SendToProgram(buf, cps);
13627     }
13628
13629     if(cps->nps > 0) { /* [HGM] nps */
13630         if(cps->supportsNPS == FALSE)
13631           cps->nps = -1; // don't use if engine explicitly says not supported!
13632         else {
13633           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13634           SendToProgram(buf, cps);
13635         }
13636     }
13637 }
13638
13639 ChessProgramState *WhitePlayer()
13640 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13641 {
13642     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13643        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13644         return &second;
13645     return &first;
13646 }
13647
13648 void
13649 SendTimeRemaining(cps, machineWhite)
13650      ChessProgramState *cps;
13651      int /*boolean*/ machineWhite;
13652 {
13653     char message[MSG_SIZ];
13654     long time, otime;
13655
13656     /* Note: this routine must be called when the clocks are stopped
13657        or when they have *just* been set or switched; otherwise
13658        it will be off by the time since the current tick started.
13659     */
13660     if (machineWhite) {
13661         time = whiteTimeRemaining / 10;
13662         otime = blackTimeRemaining / 10;
13663     } else {
13664         time = blackTimeRemaining / 10;
13665         otime = whiteTimeRemaining / 10;
13666     }
13667     /* [HGM] translate opponent's time by time-odds factor */
13668     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13669     if (appData.debugMode) {
13670         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13671     }
13672
13673     if (time <= 0) time = 1;
13674     if (otime <= 0) otime = 1;
13675
13676     snprintf(message, MSG_SIZ, "time %ld\n", time);
13677     SendToProgram(message, cps);
13678
13679     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13680     SendToProgram(message, cps);
13681 }
13682
13683 int
13684 BoolFeature(p, name, loc, cps)
13685      char **p;
13686      char *name;
13687      int *loc;
13688      ChessProgramState *cps;
13689 {
13690   char buf[MSG_SIZ];
13691   int len = strlen(name);
13692   int val;
13693
13694   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13695     (*p) += len + 1;
13696     sscanf(*p, "%d", &val);
13697     *loc = (val != 0);
13698     while (**p && **p != ' ')
13699       (*p)++;
13700     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13701     SendToProgram(buf, cps);
13702     return TRUE;
13703   }
13704   return FALSE;
13705 }
13706
13707 int
13708 IntFeature(p, name, loc, cps)
13709      char **p;
13710      char *name;
13711      int *loc;
13712      ChessProgramState *cps;
13713 {
13714   char buf[MSG_SIZ];
13715   int len = strlen(name);
13716   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13717     (*p) += len + 1;
13718     sscanf(*p, "%d", loc);
13719     while (**p && **p != ' ') (*p)++;
13720     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13721     SendToProgram(buf, cps);
13722     return TRUE;
13723   }
13724   return FALSE;
13725 }
13726
13727 int
13728 StringFeature(p, name, loc, cps)
13729      char **p;
13730      char *name;
13731      char loc[];
13732      ChessProgramState *cps;
13733 {
13734   char buf[MSG_SIZ];
13735   int len = strlen(name);
13736   if (strncmp((*p), name, len) == 0
13737       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13738     (*p) += len + 2;
13739     sscanf(*p, "%[^\"]", loc);
13740     while (**p && **p != '\"') (*p)++;
13741     if (**p == '\"') (*p)++;
13742     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13743     SendToProgram(buf, cps);
13744     return TRUE;
13745   }
13746   return FALSE;
13747 }
13748
13749 int
13750 ParseOption(Option *opt, ChessProgramState *cps)
13751 // [HGM] options: process the string that defines an engine option, and determine
13752 // name, type, default value, and allowed value range
13753 {
13754         char *p, *q, buf[MSG_SIZ];
13755         int n, min = (-1)<<31, max = 1<<31, def;
13756
13757         if(p = strstr(opt->name, " -spin ")) {
13758             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13759             if(max < min) max = min; // enforce consistency
13760             if(def < min) def = min;
13761             if(def > max) def = max;
13762             opt->value = def;
13763             opt->min = min;
13764             opt->max = max;
13765             opt->type = Spin;
13766         } else if((p = strstr(opt->name, " -slider "))) {
13767             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13768             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13769             if(max < min) max = min; // enforce consistency
13770             if(def < min) def = min;
13771             if(def > max) def = max;
13772             opt->value = def;
13773             opt->min = min;
13774             opt->max = max;
13775             opt->type = Spin; // Slider;
13776         } else if((p = strstr(opt->name, " -string "))) {
13777             opt->textValue = p+9;
13778             opt->type = TextBox;
13779         } else if((p = strstr(opt->name, " -file "))) {
13780             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13781             opt->textValue = p+7;
13782             opt->type = TextBox; // FileName;
13783         } else if((p = strstr(opt->name, " -path "))) {
13784             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13785             opt->textValue = p+7;
13786             opt->type = TextBox; // PathName;
13787         } else if(p = strstr(opt->name, " -check ")) {
13788             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13789             opt->value = (def != 0);
13790             opt->type = CheckBox;
13791         } else if(p = strstr(opt->name, " -combo ")) {
13792             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13793             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13794             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13795             opt->value = n = 0;
13796             while(q = StrStr(q, " /// ")) {
13797                 n++; *q = 0;    // count choices, and null-terminate each of them
13798                 q += 5;
13799                 if(*q == '*') { // remember default, which is marked with * prefix
13800                     q++;
13801                     opt->value = n;
13802                 }
13803                 cps->comboList[cps->comboCnt++] = q;
13804             }
13805             cps->comboList[cps->comboCnt++] = NULL;
13806             opt->max = n + 1;
13807             opt->type = ComboBox;
13808         } else if(p = strstr(opt->name, " -button")) {
13809             opt->type = Button;
13810         } else if(p = strstr(opt->name, " -save")) {
13811             opt->type = SaveButton;
13812         } else return FALSE;
13813         *p = 0; // terminate option name
13814         // now look if the command-line options define a setting for this engine option.
13815         if(cps->optionSettings && cps->optionSettings[0])
13816             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13817         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13818           snprintf(buf, MSG_SIZ, "option %s", p);
13819                 if(p = strstr(buf, ",")) *p = 0;
13820                 if(q = strchr(buf, '=')) switch(opt->type) {
13821                     case ComboBox:
13822                         for(n=0; n<opt->max; n++)
13823                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13824                         break;
13825                     case TextBox:
13826                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13827                         break;
13828                     case Spin:
13829                     case CheckBox:
13830                         opt->value = atoi(q+1);
13831                     default:
13832                         break;
13833                 }
13834                 strcat(buf, "\n");
13835                 SendToProgram(buf, cps);
13836         }
13837         return TRUE;
13838 }
13839
13840 void
13841 FeatureDone(cps, val)
13842      ChessProgramState* cps;
13843      int val;
13844 {
13845   DelayedEventCallback cb = GetDelayedEvent();
13846   if ((cb == InitBackEnd3 && cps == &first) ||
13847       (cb == TwoMachinesEventIfReady && cps == &second)) {
13848     CancelDelayedEvent();
13849     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13850   }
13851   cps->initDone = val;
13852 }
13853
13854 /* Parse feature command from engine */
13855 void
13856 ParseFeatures(args, cps)
13857      char* args;
13858      ChessProgramState *cps;
13859 {
13860   char *p = args;
13861   char *q;
13862   int val;
13863   char buf[MSG_SIZ];
13864
13865   for (;;) {
13866     while (*p == ' ') p++;
13867     if (*p == NULLCHAR) return;
13868
13869     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13870     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13871     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13872     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13873     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13874     if (BoolFeature(&p, "reuse", &val, cps)) {
13875       /* Engine can disable reuse, but can't enable it if user said no */
13876       if (!val) cps->reuse = FALSE;
13877       continue;
13878     }
13879     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13880     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13881       if (gameMode == TwoMachinesPlay) {
13882         DisplayTwoMachinesTitle();
13883       } else {
13884         DisplayTitle("");
13885       }
13886       continue;
13887     }
13888     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13889     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13890     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13891     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13892     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13893     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13894     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13895     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13896     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13897     if (IntFeature(&p, "done", &val, cps)) {
13898       FeatureDone(cps, val);
13899       continue;
13900     }
13901     /* Added by Tord: */
13902     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13903     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13904     /* End of additions by Tord */
13905
13906     /* [HGM] added features: */
13907     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13908     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13909     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13910     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13911     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13912     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13913     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13914         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13915           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13916             SendToProgram(buf, cps);
13917             continue;
13918         }
13919         if(cps->nrOptions >= MAX_OPTIONS) {
13920             cps->nrOptions--;
13921             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13922             DisplayError(buf, 0);
13923         }
13924         continue;
13925     }
13926     /* End of additions by HGM */
13927
13928     /* unknown feature: complain and skip */
13929     q = p;
13930     while (*q && *q != '=') q++;
13931     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13932     SendToProgram(buf, cps);
13933     p = q;
13934     if (*p == '=') {
13935       p++;
13936       if (*p == '\"') {
13937         p++;
13938         while (*p && *p != '\"') p++;
13939         if (*p == '\"') p++;
13940       } else {
13941         while (*p && *p != ' ') p++;
13942       }
13943     }
13944   }
13945
13946 }
13947
13948 void
13949 PeriodicUpdatesEvent(newState)
13950      int newState;
13951 {
13952     if (newState == appData.periodicUpdates)
13953       return;
13954
13955     appData.periodicUpdates=newState;
13956
13957     /* Display type changes, so update it now */
13958 //    DisplayAnalysis();
13959
13960     /* Get the ball rolling again... */
13961     if (newState) {
13962         AnalysisPeriodicEvent(1);
13963         StartAnalysisClock();
13964     }
13965 }
13966
13967 void
13968 PonderNextMoveEvent(newState)
13969      int newState;
13970 {
13971     if (newState == appData.ponderNextMove) return;
13972     if (gameMode == EditPosition) EditPositionDone(TRUE);
13973     if (newState) {
13974         SendToProgram("hard\n", &first);
13975         if (gameMode == TwoMachinesPlay) {
13976             SendToProgram("hard\n", &second);
13977         }
13978     } else {
13979         SendToProgram("easy\n", &first);
13980         thinkOutput[0] = NULLCHAR;
13981         if (gameMode == TwoMachinesPlay) {
13982             SendToProgram("easy\n", &second);
13983         }
13984     }
13985     appData.ponderNextMove = newState;
13986 }
13987
13988 void
13989 NewSettingEvent(option, feature, command, value)
13990      char *command;
13991      int option, value, *feature;
13992 {
13993     char buf[MSG_SIZ];
13994
13995     if (gameMode == EditPosition) EditPositionDone(TRUE);
13996     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
13997     if(feature == NULL || *feature) SendToProgram(buf, &first);
13998     if (gameMode == TwoMachinesPlay) {
13999         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14000     }
14001 }
14002
14003 void
14004 ShowThinkingEvent()
14005 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14006 {
14007     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14008     int newState = appData.showThinking
14009         // [HGM] thinking: other features now need thinking output as well
14010         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14011
14012     if (oldState == newState) return;
14013     oldState = newState;
14014     if (gameMode == EditPosition) EditPositionDone(TRUE);
14015     if (oldState) {
14016         SendToProgram("post\n", &first);
14017         if (gameMode == TwoMachinesPlay) {
14018             SendToProgram("post\n", &second);
14019         }
14020     } else {
14021         SendToProgram("nopost\n", &first);
14022         thinkOutput[0] = NULLCHAR;
14023         if (gameMode == TwoMachinesPlay) {
14024             SendToProgram("nopost\n", &second);
14025         }
14026     }
14027 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14028 }
14029
14030 void
14031 AskQuestionEvent(title, question, replyPrefix, which)
14032      char *title; char *question; char *replyPrefix; char *which;
14033 {
14034   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14035   if (pr == NoProc) return;
14036   AskQuestion(title, question, replyPrefix, pr);
14037 }
14038
14039 void
14040 DisplayMove(moveNumber)
14041      int moveNumber;
14042 {
14043     char message[MSG_SIZ];
14044     char res[MSG_SIZ];
14045     char cpThinkOutput[MSG_SIZ];
14046
14047     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14048
14049     if (moveNumber == forwardMostMove - 1 ||
14050         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14051
14052         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14053
14054         if (strchr(cpThinkOutput, '\n')) {
14055             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14056         }
14057     } else {
14058         *cpThinkOutput = NULLCHAR;
14059     }
14060
14061     /* [AS] Hide thinking from human user */
14062     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14063         *cpThinkOutput = NULLCHAR;
14064         if( thinkOutput[0] != NULLCHAR ) {
14065             int i;
14066
14067             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14068                 cpThinkOutput[i] = '.';
14069             }
14070             cpThinkOutput[i] = NULLCHAR;
14071             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14072         }
14073     }
14074
14075     if (moveNumber == forwardMostMove - 1 &&
14076         gameInfo.resultDetails != NULL) {
14077         if (gameInfo.resultDetails[0] == NULLCHAR) {
14078           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14079         } else {
14080           snprintf(res, MSG_SIZ, " {%s} %s",
14081                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14082         }
14083     } else {
14084         res[0] = NULLCHAR;
14085     }
14086
14087     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14088         DisplayMessage(res, cpThinkOutput);
14089     } else {
14090       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14091                 WhiteOnMove(moveNumber) ? " " : ".. ",
14092                 parseList[moveNumber], res);
14093         DisplayMessage(message, cpThinkOutput);
14094     }
14095 }
14096
14097 void
14098 DisplayComment(moveNumber, text)
14099      int moveNumber;
14100      char *text;
14101 {
14102     char title[MSG_SIZ];
14103     char buf[8000]; // comment can be long!
14104     int score, depth;
14105
14106     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14107       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14108     } else {
14109       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14110               WhiteOnMove(moveNumber) ? " " : ".. ",
14111               parseList[moveNumber]);
14112     }
14113     // [HGM] PV info: display PV info together with (or as) comment
14114     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14115       if(text == NULL) text = "";
14116       score = pvInfoList[moveNumber].score;
14117       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14118               depth, (pvInfoList[moveNumber].time+50)/100, text);
14119       text = buf;
14120     }
14121     if (text != NULL && (appData.autoDisplayComment || commentUp))
14122         CommentPopUp(title, text);
14123 }
14124
14125 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14126  * might be busy thinking or pondering.  It can be omitted if your
14127  * gnuchess is configured to stop thinking immediately on any user
14128  * input.  However, that gnuchess feature depends on the FIONREAD
14129  * ioctl, which does not work properly on some flavors of Unix.
14130  */
14131 void
14132 Attention(cps)
14133      ChessProgramState *cps;
14134 {
14135 #if ATTENTION
14136     if (!cps->useSigint) return;
14137     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14138     switch (gameMode) {
14139       case MachinePlaysWhite:
14140       case MachinePlaysBlack:
14141       case TwoMachinesPlay:
14142       case IcsPlayingWhite:
14143       case IcsPlayingBlack:
14144       case AnalyzeMode:
14145       case AnalyzeFile:
14146         /* Skip if we know it isn't thinking */
14147         if (!cps->maybeThinking) return;
14148         if (appData.debugMode)
14149           fprintf(debugFP, "Interrupting %s\n", cps->which);
14150         InterruptChildProcess(cps->pr);
14151         cps->maybeThinking = FALSE;
14152         break;
14153       default:
14154         break;
14155     }
14156 #endif /*ATTENTION*/
14157 }
14158
14159 int
14160 CheckFlags()
14161 {
14162     if (whiteTimeRemaining <= 0) {
14163         if (!whiteFlag) {
14164             whiteFlag = TRUE;
14165             if (appData.icsActive) {
14166                 if (appData.autoCallFlag &&
14167                     gameMode == IcsPlayingBlack && !blackFlag) {
14168                   SendToICS(ics_prefix);
14169                   SendToICS("flag\n");
14170                 }
14171             } else {
14172                 if (blackFlag) {
14173                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14174                 } else {
14175                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14176                     if (appData.autoCallFlag) {
14177                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14178                         return TRUE;
14179                     }
14180                 }
14181             }
14182         }
14183     }
14184     if (blackTimeRemaining <= 0) {
14185         if (!blackFlag) {
14186             blackFlag = TRUE;
14187             if (appData.icsActive) {
14188                 if (appData.autoCallFlag &&
14189                     gameMode == IcsPlayingWhite && !whiteFlag) {
14190                   SendToICS(ics_prefix);
14191                   SendToICS("flag\n");
14192                 }
14193             } else {
14194                 if (whiteFlag) {
14195                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14196                 } else {
14197                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14198                     if (appData.autoCallFlag) {
14199                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14200                         return TRUE;
14201                     }
14202                 }
14203             }
14204         }
14205     }
14206     return FALSE;
14207 }
14208
14209 void
14210 CheckTimeControl()
14211 {
14212     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14213         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14214
14215     /*
14216      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14217      */
14218     if ( !WhiteOnMove(forwardMostMove) ) {
14219         /* White made time control */
14220         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14221         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14222         /* [HGM] time odds: correct new time quota for time odds! */
14223                                             / WhitePlayer()->timeOdds;
14224         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14225     } else {
14226         lastBlack -= blackTimeRemaining;
14227         /* Black made time control */
14228         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14229                                             / WhitePlayer()->other->timeOdds;
14230         lastWhite = whiteTimeRemaining;
14231     }
14232 }
14233
14234 void
14235 DisplayBothClocks()
14236 {
14237     int wom = gameMode == EditPosition ?
14238       !blackPlaysFirst : WhiteOnMove(currentMove);
14239     DisplayWhiteClock(whiteTimeRemaining, wom);
14240     DisplayBlackClock(blackTimeRemaining, !wom);
14241 }
14242
14243
14244 /* Timekeeping seems to be a portability nightmare.  I think everyone
14245    has ftime(), but I'm really not sure, so I'm including some ifdefs
14246    to use other calls if you don't.  Clocks will be less accurate if
14247    you have neither ftime nor gettimeofday.
14248 */
14249
14250 /* VS 2008 requires the #include outside of the function */
14251 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14252 #include <sys/timeb.h>
14253 #endif
14254
14255 /* Get the current time as a TimeMark */
14256 void
14257 GetTimeMark(tm)
14258      TimeMark *tm;
14259 {
14260 #if HAVE_GETTIMEOFDAY
14261
14262     struct timeval timeVal;
14263     struct timezone timeZone;
14264
14265     gettimeofday(&timeVal, &timeZone);
14266     tm->sec = (long) timeVal.tv_sec;
14267     tm->ms = (int) (timeVal.tv_usec / 1000L);
14268
14269 #else /*!HAVE_GETTIMEOFDAY*/
14270 #if HAVE_FTIME
14271
14272 // include <sys/timeb.h> / moved to just above start of function
14273     struct timeb timeB;
14274
14275     ftime(&timeB);
14276     tm->sec = (long) timeB.time;
14277     tm->ms = (int) timeB.millitm;
14278
14279 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14280     tm->sec = (long) time(NULL);
14281     tm->ms = 0;
14282 #endif
14283 #endif
14284 }
14285
14286 /* Return the difference in milliseconds between two
14287    time marks.  We assume the difference will fit in a long!
14288 */
14289 long
14290 SubtractTimeMarks(tm2, tm1)
14291      TimeMark *tm2, *tm1;
14292 {
14293     return 1000L*(tm2->sec - tm1->sec) +
14294            (long) (tm2->ms - tm1->ms);
14295 }
14296
14297
14298 /*
14299  * Code to manage the game clocks.
14300  *
14301  * In tournament play, black starts the clock and then white makes a move.
14302  * We give the human user a slight advantage if he is playing white---the
14303  * clocks don't run until he makes his first move, so it takes zero time.
14304  * Also, we don't account for network lag, so we could get out of sync
14305  * with GNU Chess's clock -- but then, referees are always right.
14306  */
14307
14308 static TimeMark tickStartTM;
14309 static long intendedTickLength;
14310
14311 long
14312 NextTickLength(timeRemaining)
14313      long timeRemaining;
14314 {
14315     long nominalTickLength, nextTickLength;
14316
14317     if (timeRemaining > 0L && timeRemaining <= 10000L)
14318       nominalTickLength = 100L;
14319     else
14320       nominalTickLength = 1000L;
14321     nextTickLength = timeRemaining % nominalTickLength;
14322     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14323
14324     return nextTickLength;
14325 }
14326
14327 /* Adjust clock one minute up or down */
14328 void
14329 AdjustClock(Boolean which, int dir)
14330 {
14331     if(which) blackTimeRemaining += 60000*dir;
14332     else      whiteTimeRemaining += 60000*dir;
14333     DisplayBothClocks();
14334 }
14335
14336 /* Stop clocks and reset to a fresh time control */
14337 void
14338 ResetClocks()
14339 {
14340     (void) StopClockTimer();
14341     if (appData.icsActive) {
14342         whiteTimeRemaining = blackTimeRemaining = 0;
14343     } else if (searchTime) {
14344         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14345         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14346     } else { /* [HGM] correct new time quote for time odds */
14347         whiteTC = blackTC = fullTimeControlString;
14348         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14349         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14350     }
14351     if (whiteFlag || blackFlag) {
14352         DisplayTitle("");
14353         whiteFlag = blackFlag = FALSE;
14354     }
14355     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14356     DisplayBothClocks();
14357 }
14358
14359 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14360
14361 /* Decrement running clock by amount of time that has passed */
14362 void
14363 DecrementClocks()
14364 {
14365     long timeRemaining;
14366     long lastTickLength, fudge;
14367     TimeMark now;
14368
14369     if (!appData.clockMode) return;
14370     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14371
14372     GetTimeMark(&now);
14373
14374     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14375
14376     /* Fudge if we woke up a little too soon */
14377     fudge = intendedTickLength - lastTickLength;
14378     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14379
14380     if (WhiteOnMove(forwardMostMove)) {
14381         if(whiteNPS >= 0) lastTickLength = 0;
14382         timeRemaining = whiteTimeRemaining -= lastTickLength;
14383         if(timeRemaining < 0 && !appData.icsActive) {
14384             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14385             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14386                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14387                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14388             }
14389         }
14390         DisplayWhiteClock(whiteTimeRemaining - fudge,
14391                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14392     } else {
14393         if(blackNPS >= 0) lastTickLength = 0;
14394         timeRemaining = blackTimeRemaining -= lastTickLength;
14395         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14396             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14397             if(suddenDeath) {
14398                 blackStartMove = forwardMostMove;
14399                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14400             }
14401         }
14402         DisplayBlackClock(blackTimeRemaining - fudge,
14403                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14404     }
14405     if (CheckFlags()) return;
14406
14407     tickStartTM = now;
14408     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14409     StartClockTimer(intendedTickLength);
14410
14411     /* if the time remaining has fallen below the alarm threshold, sound the
14412      * alarm. if the alarm has sounded and (due to a takeback or time control
14413      * with increment) the time remaining has increased to a level above the
14414      * threshold, reset the alarm so it can sound again.
14415      */
14416
14417     if (appData.icsActive && appData.icsAlarm) {
14418
14419         /* make sure we are dealing with the user's clock */
14420         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14421                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14422            )) return;
14423
14424         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14425             alarmSounded = FALSE;
14426         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14427             PlayAlarmSound();
14428             alarmSounded = TRUE;
14429         }
14430     }
14431 }
14432
14433
14434 /* A player has just moved, so stop the previously running
14435    clock and (if in clock mode) start the other one.
14436    We redisplay both clocks in case we're in ICS mode, because
14437    ICS gives us an update to both clocks after every move.
14438    Note that this routine is called *after* forwardMostMove
14439    is updated, so the last fractional tick must be subtracted
14440    from the color that is *not* on move now.
14441 */
14442 void
14443 SwitchClocks(int newMoveNr)
14444 {
14445     long lastTickLength;
14446     TimeMark now;
14447     int flagged = FALSE;
14448
14449     GetTimeMark(&now);
14450
14451     if (StopClockTimer() && appData.clockMode) {
14452         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14453         if (!WhiteOnMove(forwardMostMove)) {
14454             if(blackNPS >= 0) lastTickLength = 0;
14455             blackTimeRemaining -= lastTickLength;
14456            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14457 //         if(pvInfoList[forwardMostMove-1].time == -1)
14458                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14459                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14460         } else {
14461            if(whiteNPS >= 0) lastTickLength = 0;
14462            whiteTimeRemaining -= lastTickLength;
14463            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14464 //         if(pvInfoList[forwardMostMove-1].time == -1)
14465                  pvInfoList[forwardMostMove-1].time =
14466                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14467         }
14468         flagged = CheckFlags();
14469     }
14470     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14471     CheckTimeControl();
14472
14473     if (flagged || !appData.clockMode) return;
14474
14475     switch (gameMode) {
14476       case MachinePlaysBlack:
14477       case MachinePlaysWhite:
14478       case BeginningOfGame:
14479         if (pausing) return;
14480         break;
14481
14482       case EditGame:
14483       case PlayFromGameFile:
14484       case IcsExamining:
14485         return;
14486
14487       default:
14488         break;
14489     }
14490
14491     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14492         if(WhiteOnMove(forwardMostMove))
14493              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14494         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14495     }
14496
14497     tickStartTM = now;
14498     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14499       whiteTimeRemaining : blackTimeRemaining);
14500     StartClockTimer(intendedTickLength);
14501 }
14502
14503
14504 /* Stop both clocks */
14505 void
14506 StopClocks()
14507 {
14508     long lastTickLength;
14509     TimeMark now;
14510
14511     if (!StopClockTimer()) return;
14512     if (!appData.clockMode) return;
14513
14514     GetTimeMark(&now);
14515
14516     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14517     if (WhiteOnMove(forwardMostMove)) {
14518         if(whiteNPS >= 0) lastTickLength = 0;
14519         whiteTimeRemaining -= lastTickLength;
14520         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14521     } else {
14522         if(blackNPS >= 0) lastTickLength = 0;
14523         blackTimeRemaining -= lastTickLength;
14524         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14525     }
14526     CheckFlags();
14527 }
14528
14529 /* Start clock of player on move.  Time may have been reset, so
14530    if clock is already running, stop and restart it. */
14531 void
14532 StartClocks()
14533 {
14534     (void) StopClockTimer(); /* in case it was running already */
14535     DisplayBothClocks();
14536     if (CheckFlags()) return;
14537
14538     if (!appData.clockMode) return;
14539     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14540
14541     GetTimeMark(&tickStartTM);
14542     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14543       whiteTimeRemaining : blackTimeRemaining);
14544
14545    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14546     whiteNPS = blackNPS = -1;
14547     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14548        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14549         whiteNPS = first.nps;
14550     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14551        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14552         blackNPS = first.nps;
14553     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14554         whiteNPS = second.nps;
14555     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14556         blackNPS = second.nps;
14557     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14558
14559     StartClockTimer(intendedTickLength);
14560 }
14561
14562 char *
14563 TimeString(ms)
14564      long ms;
14565 {
14566     long second, minute, hour, day;
14567     char *sign = "";
14568     static char buf[32];
14569
14570     if (ms > 0 && ms <= 9900) {
14571       /* convert milliseconds to tenths, rounding up */
14572       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14573
14574       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14575       return buf;
14576     }
14577
14578     /* convert milliseconds to seconds, rounding up */
14579     /* use floating point to avoid strangeness of integer division
14580        with negative dividends on many machines */
14581     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14582
14583     if (second < 0) {
14584         sign = "-";
14585         second = -second;
14586     }
14587
14588     day = second / (60 * 60 * 24);
14589     second = second % (60 * 60 * 24);
14590     hour = second / (60 * 60);
14591     second = second % (60 * 60);
14592     minute = second / 60;
14593     second = second % 60;
14594
14595     if (day > 0)
14596       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14597               sign, day, hour, minute, second);
14598     else if (hour > 0)
14599       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14600     else
14601       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14602
14603     return buf;
14604 }
14605
14606
14607 /*
14608  * This is necessary because some C libraries aren't ANSI C compliant yet.
14609  */
14610 char *
14611 StrStr(string, match)
14612      char *string, *match;
14613 {
14614     int i, length;
14615
14616     length = strlen(match);
14617
14618     for (i = strlen(string) - length; i >= 0; i--, string++)
14619       if (!strncmp(match, string, length))
14620         return string;
14621
14622     return NULL;
14623 }
14624
14625 char *
14626 StrCaseStr(string, match)
14627      char *string, *match;
14628 {
14629     int i, j, length;
14630
14631     length = strlen(match);
14632
14633     for (i = strlen(string) - length; i >= 0; i--, string++) {
14634         for (j = 0; j < length; j++) {
14635             if (ToLower(match[j]) != ToLower(string[j]))
14636               break;
14637         }
14638         if (j == length) return string;
14639     }
14640
14641     return NULL;
14642 }
14643
14644 #ifndef _amigados
14645 int
14646 StrCaseCmp(s1, s2)
14647      char *s1, *s2;
14648 {
14649     char c1, c2;
14650
14651     for (;;) {
14652         c1 = ToLower(*s1++);
14653         c2 = ToLower(*s2++);
14654         if (c1 > c2) return 1;
14655         if (c1 < c2) return -1;
14656         if (c1 == NULLCHAR) return 0;
14657     }
14658 }
14659
14660
14661 int
14662 ToLower(c)
14663      int c;
14664 {
14665     return isupper(c) ? tolower(c) : c;
14666 }
14667
14668
14669 int
14670 ToUpper(c)
14671      int c;
14672 {
14673     return islower(c) ? toupper(c) : c;
14674 }
14675 #endif /* !_amigados    */
14676
14677 char *
14678 StrSave(s)
14679      char *s;
14680 {
14681   char *ret;
14682
14683   if ((ret = (char *) malloc(strlen(s) + 1)))
14684     {
14685       safeStrCpy(ret, s, strlen(s)+1);
14686     }
14687   return ret;
14688 }
14689
14690 char *
14691 StrSavePtr(s, savePtr)
14692      char *s, **savePtr;
14693 {
14694     if (*savePtr) {
14695         free(*savePtr);
14696     }
14697     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14698       safeStrCpy(*savePtr, s, strlen(s)+1);
14699     }
14700     return(*savePtr);
14701 }
14702
14703 char *
14704 PGNDate()
14705 {
14706     time_t clock;
14707     struct tm *tm;
14708     char buf[MSG_SIZ];
14709
14710     clock = time((time_t *)NULL);
14711     tm = localtime(&clock);
14712     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14713             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14714     return StrSave(buf);
14715 }
14716
14717
14718 char *
14719 PositionToFEN(move, overrideCastling)
14720      int move;
14721      char *overrideCastling;
14722 {
14723     int i, j, fromX, fromY, toX, toY;
14724     int whiteToPlay;
14725     char buf[128];
14726     char *p, *q;
14727     int emptycount;
14728     ChessSquare piece;
14729
14730     whiteToPlay = (gameMode == EditPosition) ?
14731       !blackPlaysFirst : (move % 2 == 0);
14732     p = buf;
14733
14734     /* Piece placement data */
14735     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14736         emptycount = 0;
14737         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14738             if (boards[move][i][j] == EmptySquare) {
14739                 emptycount++;
14740             } else { ChessSquare piece = boards[move][i][j];
14741                 if (emptycount > 0) {
14742                     if(emptycount<10) /* [HGM] can be >= 10 */
14743                         *p++ = '0' + emptycount;
14744                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14745                     emptycount = 0;
14746                 }
14747                 if(PieceToChar(piece) == '+') {
14748                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14749                     *p++ = '+';
14750                     piece = (ChessSquare)(DEMOTED piece);
14751                 }
14752                 *p++ = PieceToChar(piece);
14753                 if(p[-1] == '~') {
14754                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14755                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14756                     *p++ = '~';
14757                 }
14758             }
14759         }
14760         if (emptycount > 0) {
14761             if(emptycount<10) /* [HGM] can be >= 10 */
14762                 *p++ = '0' + emptycount;
14763             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14764             emptycount = 0;
14765         }
14766         *p++ = '/';
14767     }
14768     *(p - 1) = ' ';
14769
14770     /* [HGM] print Crazyhouse or Shogi holdings */
14771     if( gameInfo.holdingsWidth ) {
14772         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14773         q = p;
14774         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14775             piece = boards[move][i][BOARD_WIDTH-1];
14776             if( piece != EmptySquare )
14777               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14778                   *p++ = PieceToChar(piece);
14779         }
14780         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14781             piece = boards[move][BOARD_HEIGHT-i-1][0];
14782             if( piece != EmptySquare )
14783               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14784                   *p++ = PieceToChar(piece);
14785         }
14786
14787         if( q == p ) *p++ = '-';
14788         *p++ = ']';
14789         *p++ = ' ';
14790     }
14791
14792     /* Active color */
14793     *p++ = whiteToPlay ? 'w' : 'b';
14794     *p++ = ' ';
14795
14796   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14797     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14798   } else {
14799   if(nrCastlingRights) {
14800      q = p;
14801      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14802        /* [HGM] write directly from rights */
14803            if(boards[move][CASTLING][2] != NoRights &&
14804               boards[move][CASTLING][0] != NoRights   )
14805                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14806            if(boards[move][CASTLING][2] != NoRights &&
14807               boards[move][CASTLING][1] != NoRights   )
14808                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14809            if(boards[move][CASTLING][5] != NoRights &&
14810               boards[move][CASTLING][3] != NoRights   )
14811                 *p++ = boards[move][CASTLING][3] + AAA;
14812            if(boards[move][CASTLING][5] != NoRights &&
14813               boards[move][CASTLING][4] != NoRights   )
14814                 *p++ = boards[move][CASTLING][4] + AAA;
14815      } else {
14816
14817         /* [HGM] write true castling rights */
14818         if( nrCastlingRights == 6 ) {
14819             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14820                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14821             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14822                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14823             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14824                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14825             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14826                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14827         }
14828      }
14829      if (q == p) *p++ = '-'; /* No castling rights */
14830      *p++ = ' ';
14831   }
14832
14833   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14834      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14835     /* En passant target square */
14836     if (move > backwardMostMove) {
14837         fromX = moveList[move - 1][0] - AAA;
14838         fromY = moveList[move - 1][1] - ONE;
14839         toX = moveList[move - 1][2] - AAA;
14840         toY = moveList[move - 1][3] - ONE;
14841         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14842             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14843             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14844             fromX == toX) {
14845             /* 2-square pawn move just happened */
14846             *p++ = toX + AAA;
14847             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14848         } else {
14849             *p++ = '-';
14850         }
14851     } else if(move == backwardMostMove) {
14852         // [HGM] perhaps we should always do it like this, and forget the above?
14853         if((signed char)boards[move][EP_STATUS] >= 0) {
14854             *p++ = boards[move][EP_STATUS] + AAA;
14855             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14856         } else {
14857             *p++ = '-';
14858         }
14859     } else {
14860         *p++ = '-';
14861     }
14862     *p++ = ' ';
14863   }
14864   }
14865
14866     /* [HGM] find reversible plies */
14867     {   int i = 0, j=move;
14868
14869         if (appData.debugMode) { int k;
14870             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14871             for(k=backwardMostMove; k<=forwardMostMove; k++)
14872                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14873
14874         }
14875
14876         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14877         if( j == backwardMostMove ) i += initialRulePlies;
14878         sprintf(p, "%d ", i);
14879         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14880     }
14881     /* Fullmove number */
14882     sprintf(p, "%d", (move / 2) + 1);
14883
14884     return StrSave(buf);
14885 }
14886
14887 Boolean
14888 ParseFEN(board, blackPlaysFirst, fen)
14889     Board board;
14890      int *blackPlaysFirst;
14891      char *fen;
14892 {
14893     int i, j;
14894     char *p, c;
14895     int emptycount;
14896     ChessSquare piece;
14897
14898     p = fen;
14899
14900     /* [HGM] by default clear Crazyhouse holdings, if present */
14901     if(gameInfo.holdingsWidth) {
14902        for(i=0; i<BOARD_HEIGHT; i++) {
14903            board[i][0]             = EmptySquare; /* black holdings */
14904            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14905            board[i][1]             = (ChessSquare) 0; /* black counts */
14906            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14907        }
14908     }
14909
14910     /* Piece placement data */
14911     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14912         j = 0;
14913         for (;;) {
14914             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14915                 if (*p == '/') p++;
14916                 emptycount = gameInfo.boardWidth - j;
14917                 while (emptycount--)
14918                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14919                 break;
14920 #if(BOARD_FILES >= 10)
14921             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14922                 p++; emptycount=10;
14923                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14924                 while (emptycount--)
14925                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14926 #endif
14927             } else if (isdigit(*p)) {
14928                 emptycount = *p++ - '0';
14929                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14930                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14931                 while (emptycount--)
14932                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14933             } else if (*p == '+' || isalpha(*p)) {
14934                 if (j >= gameInfo.boardWidth) return FALSE;
14935                 if(*p=='+') {
14936                     piece = CharToPiece(*++p);
14937                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14938                     piece = (ChessSquare) (PROMOTED piece ); p++;
14939                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14940                 } else piece = CharToPiece(*p++);
14941
14942                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14943                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14944                     piece = (ChessSquare) (PROMOTED piece);
14945                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14946                     p++;
14947                 }
14948                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14949             } else {
14950                 return FALSE;
14951             }
14952         }
14953     }
14954     while (*p == '/' || *p == ' ') p++;
14955
14956     /* [HGM] look for Crazyhouse holdings here */
14957     while(*p==' ') p++;
14958     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14959         if(*p == '[') p++;
14960         if(*p == '-' ) p++; /* empty holdings */ else {
14961             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14962             /* if we would allow FEN reading to set board size, we would   */
14963             /* have to add holdings and shift the board read so far here   */
14964             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14965                 p++;
14966                 if((int) piece >= (int) BlackPawn ) {
14967                     i = (int)piece - (int)BlackPawn;
14968                     i = PieceToNumber((ChessSquare)i);
14969                     if( i >= gameInfo.holdingsSize ) return FALSE;
14970                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14971                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14972                 } else {
14973                     i = (int)piece - (int)WhitePawn;
14974                     i = PieceToNumber((ChessSquare)i);
14975                     if( i >= gameInfo.holdingsSize ) return FALSE;
14976                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14977                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14978                 }
14979             }
14980         }
14981         if(*p == ']') p++;
14982     }
14983
14984     while(*p == ' ') p++;
14985
14986     /* Active color */
14987     c = *p++;
14988     if(appData.colorNickNames) {
14989       if( c == appData.colorNickNames[0] ) c = 'w'; else
14990       if( c == appData.colorNickNames[1] ) c = 'b';
14991     }
14992     switch (c) {
14993       case 'w':
14994         *blackPlaysFirst = FALSE;
14995         break;
14996       case 'b':
14997         *blackPlaysFirst = TRUE;
14998         break;
14999       default:
15000         return FALSE;
15001     }
15002
15003     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15004     /* return the extra info in global variiables             */
15005
15006     /* set defaults in case FEN is incomplete */
15007     board[EP_STATUS] = EP_UNKNOWN;
15008     for(i=0; i<nrCastlingRights; i++ ) {
15009         board[CASTLING][i] =
15010             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15011     }   /* assume possible unless obviously impossible */
15012     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15013     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15014     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15015                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15016     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15017     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15018     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15019                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15020     FENrulePlies = 0;
15021
15022     while(*p==' ') p++;
15023     if(nrCastlingRights) {
15024       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15025           /* castling indicator present, so default becomes no castlings */
15026           for(i=0; i<nrCastlingRights; i++ ) {
15027                  board[CASTLING][i] = NoRights;
15028           }
15029       }
15030       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15031              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15032              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15033              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15034         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15035
15036         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15037             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15038             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15039         }
15040         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15041             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15042         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15043                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15044         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15045                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15046         switch(c) {
15047           case'K':
15048               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15049               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15050               board[CASTLING][2] = whiteKingFile;
15051               break;
15052           case'Q':
15053               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15054               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15055               board[CASTLING][2] = whiteKingFile;
15056               break;
15057           case'k':
15058               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15059               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15060               board[CASTLING][5] = blackKingFile;
15061               break;
15062           case'q':
15063               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15064               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15065               board[CASTLING][5] = blackKingFile;
15066           case '-':
15067               break;
15068           default: /* FRC castlings */
15069               if(c >= 'a') { /* black rights */
15070                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15071                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15072                   if(i == BOARD_RGHT) break;
15073                   board[CASTLING][5] = i;
15074                   c -= AAA;
15075                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15076                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15077                   if(c > i)
15078                       board[CASTLING][3] = c;
15079                   else
15080                       board[CASTLING][4] = c;
15081               } else { /* white rights */
15082                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15083                     if(board[0][i] == WhiteKing) break;
15084                   if(i == BOARD_RGHT) break;
15085                   board[CASTLING][2] = i;
15086                   c -= AAA - 'a' + 'A';
15087                   if(board[0][c] >= WhiteKing) break;
15088                   if(c > i)
15089                       board[CASTLING][0] = c;
15090                   else
15091                       board[CASTLING][1] = c;
15092               }
15093         }
15094       }
15095       for(i=0; i<nrCastlingRights; i++)
15096         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15097     if (appData.debugMode) {
15098         fprintf(debugFP, "FEN castling rights:");
15099         for(i=0; i<nrCastlingRights; i++)
15100         fprintf(debugFP, " %d", board[CASTLING][i]);
15101         fprintf(debugFP, "\n");
15102     }
15103
15104       while(*p==' ') p++;
15105     }
15106
15107     /* read e.p. field in games that know e.p. capture */
15108     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15109        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15110       if(*p=='-') {
15111         p++; board[EP_STATUS] = EP_NONE;
15112       } else {
15113          char c = *p++ - AAA;
15114
15115          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15116          if(*p >= '0' && *p <='9') p++;
15117          board[EP_STATUS] = c;
15118       }
15119     }
15120
15121
15122     if(sscanf(p, "%d", &i) == 1) {
15123         FENrulePlies = i; /* 50-move ply counter */
15124         /* (The move number is still ignored)    */
15125     }
15126
15127     return TRUE;
15128 }
15129
15130 void
15131 EditPositionPasteFEN(char *fen)
15132 {
15133   if (fen != NULL) {
15134     Board initial_position;
15135
15136     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15137       DisplayError(_("Bad FEN position in clipboard"), 0);
15138       return ;
15139     } else {
15140       int savedBlackPlaysFirst = blackPlaysFirst;
15141       EditPositionEvent();
15142       blackPlaysFirst = savedBlackPlaysFirst;
15143       CopyBoard(boards[0], initial_position);
15144       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15145       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15146       DisplayBothClocks();
15147       DrawPosition(FALSE, boards[currentMove]);
15148     }
15149   }
15150 }
15151
15152 static char cseq[12] = "\\   ";
15153
15154 Boolean set_cont_sequence(char *new_seq)
15155 {
15156     int len;
15157     Boolean ret;
15158
15159     // handle bad attempts to set the sequence
15160         if (!new_seq)
15161                 return 0; // acceptable error - no debug
15162
15163     len = strlen(new_seq);
15164     ret = (len > 0) && (len < sizeof(cseq));
15165     if (ret)
15166       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15167     else if (appData.debugMode)
15168       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15169     return ret;
15170 }
15171
15172 /*
15173     reformat a source message so words don't cross the width boundary.  internal
15174     newlines are not removed.  returns the wrapped size (no null character unless
15175     included in source message).  If dest is NULL, only calculate the size required
15176     for the dest buffer.  lp argument indicats line position upon entry, and it's
15177     passed back upon exit.
15178 */
15179 int wrap(char *dest, char *src, int count, int width, int *lp)
15180 {
15181     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15182
15183     cseq_len = strlen(cseq);
15184     old_line = line = *lp;
15185     ansi = len = clen = 0;
15186
15187     for (i=0; i < count; i++)
15188     {
15189         if (src[i] == '\033')
15190             ansi = 1;
15191
15192         // if we hit the width, back up
15193         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15194         {
15195             // store i & len in case the word is too long
15196             old_i = i, old_len = len;
15197
15198             // find the end of the last word
15199             while (i && src[i] != ' ' && src[i] != '\n')
15200             {
15201                 i--;
15202                 len--;
15203             }
15204
15205             // word too long?  restore i & len before splitting it
15206             if ((old_i-i+clen) >= width)
15207             {
15208                 i = old_i;
15209                 len = old_len;
15210             }
15211
15212             // extra space?
15213             if (i && src[i-1] == ' ')
15214                 len--;
15215
15216             if (src[i] != ' ' && src[i] != '\n')
15217             {
15218                 i--;
15219                 if (len)
15220                     len--;
15221             }
15222
15223             // now append the newline and continuation sequence
15224             if (dest)
15225                 dest[len] = '\n';
15226             len++;
15227             if (dest)
15228                 strncpy(dest+len, cseq, cseq_len);
15229             len += cseq_len;
15230             line = cseq_len;
15231             clen = cseq_len;
15232             continue;
15233         }
15234
15235         if (dest)
15236             dest[len] = src[i];
15237         len++;
15238         if (!ansi)
15239             line++;
15240         if (src[i] == '\n')
15241             line = 0;
15242         if (src[i] == 'm')
15243             ansi = 0;
15244     }
15245     if (dest && appData.debugMode)
15246     {
15247         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15248             count, width, line, len, *lp);
15249         show_bytes(debugFP, src, count);
15250         fprintf(debugFP, "\ndest: ");
15251         show_bytes(debugFP, dest, len);
15252         fprintf(debugFP, "\n");
15253     }
15254     *lp = dest ? line : old_line;
15255
15256     return len;
15257 }
15258
15259 // [HGM] vari: routines for shelving variations
15260
15261 void
15262 PushTail(int firstMove, int lastMove)
15263 {
15264         int i, j, nrMoves = lastMove - firstMove;
15265
15266         if(appData.icsActive) { // only in local mode
15267                 forwardMostMove = currentMove; // mimic old ICS behavior
15268                 return;
15269         }
15270         if(storedGames >= MAX_VARIATIONS-1) return;
15271
15272         // push current tail of game on stack
15273         savedResult[storedGames] = gameInfo.result;
15274         savedDetails[storedGames] = gameInfo.resultDetails;
15275         gameInfo.resultDetails = NULL;
15276         savedFirst[storedGames] = firstMove;
15277         savedLast [storedGames] = lastMove;
15278         savedFramePtr[storedGames] = framePtr;
15279         framePtr -= nrMoves; // reserve space for the boards
15280         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15281             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15282             for(j=0; j<MOVE_LEN; j++)
15283                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15284             for(j=0; j<2*MOVE_LEN; j++)
15285                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15286             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15287             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15288             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15289             pvInfoList[firstMove+i-1].depth = 0;
15290             commentList[framePtr+i] = commentList[firstMove+i];
15291             commentList[firstMove+i] = NULL;
15292         }
15293
15294         storedGames++;
15295         forwardMostMove = firstMove; // truncate game so we can start variation
15296         if(storedGames == 1) GreyRevert(FALSE);
15297 }
15298
15299 Boolean
15300 PopTail(Boolean annotate)
15301 {
15302         int i, j, nrMoves;
15303         char buf[8000], moveBuf[20];
15304
15305         if(appData.icsActive) return FALSE; // only in local mode
15306         if(!storedGames) return FALSE; // sanity
15307         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15308
15309         storedGames--;
15310         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15311         nrMoves = savedLast[storedGames] - currentMove;
15312         if(annotate) {
15313                 int cnt = 10;
15314                 if(!WhiteOnMove(currentMove))
15315                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15316                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15317                 for(i=currentMove; i<forwardMostMove; i++) {
15318                         if(WhiteOnMove(i))
15319                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15320                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15321                         strcat(buf, moveBuf);
15322                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15323                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15324                 }
15325                 strcat(buf, ")");
15326         }
15327         for(i=1; i<=nrMoves; i++) { // copy last variation back
15328             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15329             for(j=0; j<MOVE_LEN; j++)
15330                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15331             for(j=0; j<2*MOVE_LEN; j++)
15332                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15333             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15334             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15335             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15336             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15337             commentList[currentMove+i] = commentList[framePtr+i];
15338             commentList[framePtr+i] = NULL;
15339         }
15340         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15341         framePtr = savedFramePtr[storedGames];
15342         gameInfo.result = savedResult[storedGames];
15343         if(gameInfo.resultDetails != NULL) {
15344             free(gameInfo.resultDetails);
15345       }
15346         gameInfo.resultDetails = savedDetails[storedGames];
15347         forwardMostMove = currentMove + nrMoves;
15348         if(storedGames == 0) GreyRevert(TRUE);
15349         return TRUE;
15350 }
15351
15352 void
15353 CleanupTail()
15354 {       // remove all shelved variations
15355         int i;
15356         for(i=0; i<storedGames; i++) {
15357             if(savedDetails[i])
15358                 free(savedDetails[i]);
15359             savedDetails[i] = NULL;
15360         }
15361         for(i=framePtr; i<MAX_MOVES; i++) {
15362                 if(commentList[i]) free(commentList[i]);
15363                 commentList[i] = NULL;
15364         }
15365         framePtr = MAX_MOVES-1;
15366         storedGames = 0;
15367 }
15368
15369 void
15370 LoadVariation(int index, char *text)
15371 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15372         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15373         int level = 0, move;
15374
15375         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15376         // first find outermost bracketing variation
15377         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15378             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15379                 if(*p == '{') wait = '}'; else
15380                 if(*p == '[') wait = ']'; else
15381                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15382                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15383             }
15384             if(*p == wait) wait = NULLCHAR; // closing ]} found
15385             p++;
15386         }
15387         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15388         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15389         end[1] = NULLCHAR; // clip off comment beyond variation
15390         ToNrEvent(currentMove-1);
15391         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15392         // kludge: use ParsePV() to append variation to game
15393         move = currentMove;
15394         ParsePV(start, TRUE);
15395         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15396         ClearPremoveHighlights();
15397         CommentPopDown();
15398         ToNrEvent(currentMove+1);
15399 }
15400