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