2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
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.
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
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
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.
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.
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/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
147 /* A point in time */
149 long sec; /* Assuming this is >= 32 bits */
150 int ms; /* Assuming this is >= 16 bits */
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,
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));
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);
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
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 */
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 */
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];
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;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 /* States for ics_getting_history */
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
279 /* whosays values for GameEnds */
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
291 /* Different types of move when calling RegisterMove */
293 #define CMAIL_RESIGN 1
295 #define CMAIL_ACCEPT 3
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
302 /* Telnet protocol constants */
313 safeStrCpy( char *dst, const char *src, size_t count )
316 assert( dst != NULL );
317 assert( src != NULL );
320 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
321 if( i == count && dst[count-1] != NULLCHAR)
323 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
324 if(appData.debugMode)
325 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
331 /* Some compiler can't cast u64 to double
332 * This function do the job for us:
334 * We use the highest bit for cast, this only
335 * works if the highest bit is not
336 * in use (This should not happen)
338 * We used this for all compiler
341 u64ToDouble(u64 value)
344 u64 tmp = value & u64Const(0x7fffffffffffffff);
345 r = (double)(s64)tmp;
346 if (value & u64Const(0x8000000000000000))
347 r += 9.2233720368547758080e18; /* 2^63 */
351 /* Fake up flags for now, as we aren't keeping track of castling
352 availability yet. [HGM] Change of logic: the flag now only
353 indicates the type of castlings allowed by the rule of the game.
354 The actual rights themselves are maintained in the array
355 castlingRights, as part of the game history, and are not probed
361 int flags = F_ALL_CASTLE_OK;
362 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
363 switch (gameInfo.variant) {
365 flags &= ~F_ALL_CASTLE_OK;
366 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
367 flags |= F_IGNORE_CHECK;
369 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
374 case VariantKriegspiel:
375 flags |= F_KRIEGSPIEL_CAPTURE;
377 case VariantCapaRandom:
378 case VariantFischeRandom:
379 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
380 case VariantNoCastle:
381 case VariantShatranj:
384 flags &= ~F_ALL_CASTLE_OK;
392 FILE *gameFileFP, *debugFP;
395 [AS] Note: sometimes, the sscanf() function is used to parse the input
396 into a fixed-size buffer. Because of this, we must be prepared to
397 receive strings as long as the size of the input buffer, which is currently
398 set to 4K for Windows and 8K for the rest.
399 So, we must either allocate sufficiently large buffers here, or
400 reduce the size of the input buffer in the input reading part.
403 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
404 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
405 char thinkOutput1[MSG_SIZ*10];
407 ChessProgramState first, second;
409 /* premove variables */
412 int premoveFromX = 0;
413 int premoveFromY = 0;
414 int premovePromoChar = 0;
416 Boolean alarmSounded;
417 /* end premove variables */
419 char *ics_prefix = "$";
420 int ics_type = ICS_GENERIC;
422 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
423 int pauseExamForwardMostMove = 0;
424 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
425 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
426 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
427 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
428 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
429 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
430 int whiteFlag = FALSE, blackFlag = FALSE;
431 int userOfferedDraw = FALSE;
432 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
433 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
434 int cmailMoveType[CMAIL_MAX_GAMES];
435 long ics_clock_paused = 0;
436 ProcRef icsPR = NoProc, cmailPR = NoProc;
437 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
438 GameMode gameMode = BeginningOfGame;
439 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
440 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
441 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
442 int hiddenThinkOutputState = 0; /* [AS] */
443 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
444 int adjudicateLossPlies = 6;
445 char white_holding[64], black_holding[64];
446 TimeMark lastNodeCountTime;
447 long lastNodeCount=0;
448 int shiftKey; // [HGM] set by mouse handler
450 int have_sent_ICS_logon = 0;
452 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
453 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
454 long timeControl_2; /* [AS] Allow separate time controls */
455 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
456 long timeRemaining[2][MAX_MOVES];
458 TimeMark programStartTime;
459 char ics_handle[MSG_SIZ];
460 int have_set_title = 0;
462 /* animateTraining preserves the state of appData.animate
463 * when Training mode is activated. This allows the
464 * response to be animated when appData.animate == TRUE and
465 * appData.animateDragging == TRUE.
467 Boolean animateTraining;
473 Board boards[MAX_MOVES];
474 /* [HGM] Following 7 needed for accurate legality tests: */
475 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
476 signed char initialRights[BOARD_FILES];
477 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
478 int initialRulePlies, FENrulePlies;
479 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
482 int mute; // mute all sounds
484 // [HGM] vari: next 12 to save and restore variations
485 #define MAX_VARIATIONS 10
486 int framePtr = MAX_MOVES-1; // points to free stack entry
488 int savedFirst[MAX_VARIATIONS];
489 int savedLast[MAX_VARIATIONS];
490 int savedFramePtr[MAX_VARIATIONS];
491 char *savedDetails[MAX_VARIATIONS];
492 ChessMove savedResult[MAX_VARIATIONS];
494 void PushTail P((int firstMove, int lastMove));
495 Boolean PopTail P((Boolean annotate));
496 void CleanupTail P((void));
498 ChessSquare FIDEArray[2][BOARD_FILES] = {
499 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
500 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
501 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
502 BlackKing, BlackBishop, BlackKnight, BlackRook }
505 ChessSquare twoKingsArray[2][BOARD_FILES] = {
506 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
508 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509 BlackKing, BlackKing, BlackKnight, BlackRook }
512 ChessSquare KnightmateArray[2][BOARD_FILES] = {
513 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
514 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
515 { BlackRook, BlackMan, BlackBishop, BlackQueen,
516 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
519 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
520 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
522 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
523 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
526 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
527 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
528 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
529 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
530 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
533 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
535 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
536 { BlackRook, BlackKnight, BlackMan, BlackFerz,
537 BlackKing, BlackMan, BlackKnight, BlackRook }
541 #if (BOARD_FILES>=10)
542 ChessSquare ShogiArray[2][BOARD_FILES] = {
543 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
544 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
545 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
546 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
549 ChessSquare XiangqiArray[2][BOARD_FILES] = {
550 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
551 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
553 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 ChessSquare CapablancaArray[2][BOARD_FILES] = {
557 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
558 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
559 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
560 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
563 ChessSquare GreatArray[2][BOARD_FILES] = {
564 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
565 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
566 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
567 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
570 ChessSquare JanusArray[2][BOARD_FILES] = {
571 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
572 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
573 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
574 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
578 ChessSquare GothicArray[2][BOARD_FILES] = {
579 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
580 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
582 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
585 #define GothicArray CapablancaArray
589 ChessSquare FalconArray[2][BOARD_FILES] = {
590 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
591 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
592 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
593 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
596 #define FalconArray CapablancaArray
599 #else // !(BOARD_FILES>=10)
600 #define XiangqiPosition FIDEArray
601 #define CapablancaArray FIDEArray
602 #define GothicArray FIDEArray
603 #define GreatArray FIDEArray
604 #endif // !(BOARD_FILES>=10)
606 #if (BOARD_FILES>=12)
607 ChessSquare CourierArray[2][BOARD_FILES] = {
608 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
609 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
610 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
611 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
613 #else // !(BOARD_FILES>=12)
614 #define CourierArray CapablancaArray
615 #endif // !(BOARD_FILES>=12)
618 Board initialPosition;
621 /* Convert str to a rating. Checks for special cases of "----",
623 "++++", etc. Also strips ()'s */
625 string_to_rating(str)
628 while(*str && !isdigit(*str)) ++str;
630 return 0; /* One of the special "no rating" cases */
638 /* Init programStats */
639 programStats.movelist[0] = 0;
640 programStats.depth = 0;
641 programStats.nr_moves = 0;
642 programStats.moves_left = 0;
643 programStats.nodes = 0;
644 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
645 programStats.score = 0;
646 programStats.got_only_move = 0;
647 programStats.got_fail = 0;
648 programStats.line_is_book = 0;
654 int matched, min, sec;
656 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
657 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
659 GetTimeMark(&programStartTime);
660 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
663 programStats.ok_to_send = 1;
664 programStats.seen_stat = 0;
667 * Initialize game list
673 * Internet chess server status
675 if (appData.icsActive) {
676 appData.matchMode = FALSE;
677 appData.matchGames = 0;
679 appData.noChessProgram = !appData.zippyPlay;
681 appData.zippyPlay = FALSE;
682 appData.zippyTalk = FALSE;
683 appData.noChessProgram = TRUE;
685 if (*appData.icsHelper != NULLCHAR) {
686 appData.useTelnet = TRUE;
687 appData.telnetProgram = appData.icsHelper;
690 appData.zippyTalk = appData.zippyPlay = FALSE;
693 /* [AS] Initialize pv info list [HGM] and game state */
697 for( i=0; i<=framePtr; i++ ) {
698 pvInfoList[i].depth = -1;
699 boards[i][EP_STATUS] = EP_NONE;
700 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
705 * Parse timeControl resource
707 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
708 appData.movesPerSession)) {
710 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
711 DisplayFatalError(buf, 0, 2);
715 * Parse searchTime resource
717 if (*appData.searchTime != NULLCHAR) {
718 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
720 searchTime = min * 60;
721 } else if (matched == 2) {
722 searchTime = min * 60 + sec;
725 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
726 DisplayFatalError(buf, 0, 2);
730 /* [AS] Adjudication threshold */
731 adjudicateLossThreshold = appData.adjudicateLossThreshold;
733 first.which = _("first");
734 second.which = _("second");
735 first.maybeThinking = second.maybeThinking = FALSE;
736 first.pr = second.pr = NoProc;
737 first.isr = second.isr = NULL;
738 first.sendTime = second.sendTime = 2;
739 first.sendDrawOffers = 1;
740 if (appData.firstPlaysBlack) {
741 first.twoMachinesColor = "black\n";
742 second.twoMachinesColor = "white\n";
744 first.twoMachinesColor = "white\n";
745 second.twoMachinesColor = "black\n";
747 first.program = appData.firstChessProgram;
748 second.program = appData.secondChessProgram;
749 first.host = appData.firstHost;
750 second.host = appData.secondHost;
751 first.dir = appData.firstDirectory;
752 second.dir = appData.secondDirectory;
753 first.other = &second;
754 second.other = &first;
755 first.initString = appData.initString;
756 second.initString = appData.secondInitString;
757 first.computerString = appData.firstComputerString;
758 second.computerString = appData.secondComputerString;
759 first.useSigint = second.useSigint = TRUE;
760 first.useSigterm = second.useSigterm = TRUE;
761 first.reuse = appData.reuseFirst;
762 second.reuse = appData.reuseSecond;
763 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
764 second.nps = appData.secondNPS;
765 first.useSetboard = second.useSetboard = FALSE;
766 first.useSAN = second.useSAN = FALSE;
767 first.usePing = second.usePing = FALSE;
768 first.lastPing = second.lastPing = 0;
769 first.lastPong = second.lastPong = 0;
770 first.usePlayother = second.usePlayother = FALSE;
771 first.useColors = second.useColors = TRUE;
772 first.useUsermove = second.useUsermove = FALSE;
773 first.sendICS = second.sendICS = FALSE;
774 first.sendName = second.sendName = appData.icsActive;
775 first.sdKludge = second.sdKludge = FALSE;
776 first.stKludge = second.stKludge = FALSE;
777 TidyProgramName(first.program, first.host, first.tidy);
778 TidyProgramName(second.program, second.host, second.tidy);
779 first.matchWins = second.matchWins = 0;
780 safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
781 safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
782 first.analysisSupport = second.analysisSupport = 2; /* detect */
783 first.analyzing = second.analyzing = FALSE;
784 first.initDone = second.initDone = FALSE;
786 /* New features added by Tord: */
787 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
788 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
789 /* End of new features added by Tord. */
790 first.fenOverride = appData.fenOverride1;
791 second.fenOverride = appData.fenOverride2;
793 /* [HGM] time odds: set factor for each machine */
794 first.timeOdds = appData.firstTimeOdds;
795 second.timeOdds = appData.secondTimeOdds;
797 if(appData.timeOddsMode) {
798 norm = first.timeOdds;
799 if(norm > second.timeOdds) norm = second.timeOdds;
801 first.timeOdds /= norm;
802 second.timeOdds /= norm;
805 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
806 first.accumulateTC = appData.firstAccumulateTC;
807 second.accumulateTC = appData.secondAccumulateTC;
808 first.maxNrOfSessions = second.maxNrOfSessions = 1;
811 first.debug = second.debug = FALSE;
812 first.supportsNPS = second.supportsNPS = UNKNOWN;
815 first.optionSettings = appData.firstOptions;
816 second.optionSettings = appData.secondOptions;
818 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
819 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
820 first.isUCI = appData.firstIsUCI; /* [AS] */
821 second.isUCI = appData.secondIsUCI; /* [AS] */
822 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
823 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
825 if (appData.firstProtocolVersion > PROTOVER
826 || appData.firstProtocolVersion < 1)
831 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
832 appData.firstProtocolVersion);
833 if( (len > MSG_SIZ) && appData.debugMode )
834 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
836 DisplayFatalError(buf, 0, 2);
840 first.protocolVersion = appData.firstProtocolVersion;
843 if (appData.secondProtocolVersion > PROTOVER
844 || appData.secondProtocolVersion < 1)
849 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
850 appData.secondProtocolVersion);
851 if( (len > MSG_SIZ) && appData.debugMode )
852 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
854 DisplayFatalError(buf, 0, 2);
858 second.protocolVersion = appData.secondProtocolVersion;
861 if (appData.icsActive) {
862 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
863 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
864 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
865 appData.clockMode = FALSE;
866 first.sendTime = second.sendTime = 0;
870 /* Override some settings from environment variables, for backward
871 compatibility. Unfortunately it's not feasible to have the env
872 vars just set defaults, at least in xboard. Ugh.
874 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
879 if (appData.noChessProgram) {
880 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
881 sprintf(programVersion, "%s", PACKAGE_STRING);
883 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
884 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
885 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
888 if (!appData.icsActive) {
892 /* Check for variants that are supported only in ICS mode,
893 or not at all. Some that are accepted here nevertheless
894 have bugs; see comments below.
896 VariantClass variant = StringToVariant(appData.variant);
898 case VariantBughouse: /* need four players and two boards */
899 case VariantKriegspiel: /* need to hide pieces and move details */
900 /* case VariantFischeRandom: (Fabien: moved below) */
901 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
902 if( (len > MSG_SIZ) && appData.debugMode )
903 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
905 DisplayFatalError(buf, 0, 2);
909 case VariantLoadable:
919 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
920 if( (len > MSG_SIZ) && appData.debugMode )
921 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
923 DisplayFatalError(buf, 0, 2);
926 case VariantXiangqi: /* [HGM] repetition rules not implemented */
927 case VariantFairy: /* [HGM] TestLegality definitely off! */
928 case VariantGothic: /* [HGM] should work */
929 case VariantCapablanca: /* [HGM] should work */
930 case VariantCourier: /* [HGM] initial forced moves not implemented */
931 case VariantShogi: /* [HGM] could still mate with pawn drop */
932 case VariantKnightmate: /* [HGM] should work */
933 case VariantCylinder: /* [HGM] untested */
934 case VariantFalcon: /* [HGM] untested */
935 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
936 offboard interposition not understood */
937 case VariantNormal: /* definitely works! */
938 case VariantWildCastle: /* pieces not automatically shuffled */
939 case VariantNoCastle: /* pieces not automatically shuffled */
940 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
941 case VariantLosers: /* should work except for win condition,
942 and doesn't know captures are mandatory */
943 case VariantSuicide: /* should work except for win condition,
944 and doesn't know captures are mandatory */
945 case VariantGiveaway: /* should work except for win condition,
946 and doesn't know captures are mandatory */
947 case VariantTwoKings: /* should work */
948 case VariantAtomic: /* should work except for win condition */
949 case Variant3Check: /* should work except for win condition */
950 case VariantShatranj: /* should work except for all win conditions */
951 case VariantMakruk: /* should work except for daw countdown */
952 case VariantBerolina: /* might work if TestLegality is off */
953 case VariantCapaRandom: /* should work */
954 case VariantJanus: /* should work */
955 case VariantSuper: /* experimental */
956 case VariantGreat: /* experimental, requires legality testing to be off */
957 case VariantSChess: /* S-Chess, should work */
962 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
963 InitEngineUCI( installDir, &second );
966 int NextIntegerFromString( char ** str, long * value )
971 while( *s == ' ' || *s == '\t' ) {
977 if( *s >= '0' && *s <= '9' ) {
978 while( *s >= '0' && *s <= '9' ) {
979 *value = *value * 10 + (*s - '0');
991 int NextTimeControlFromString( char ** str, long * value )
994 int result = NextIntegerFromString( str, &temp );
997 *value = temp * 60; /* Minutes */
1000 result = NextIntegerFromString( str, &temp );
1001 *value += temp; /* Seconds */
1008 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1009 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1010 int result = -1, type = 0; long temp, temp2;
1012 if(**str != ':') return -1; // old params remain in force!
1014 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1015 if( NextIntegerFromString( str, &temp ) ) return -1;
1016 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1019 /* time only: incremental or sudden-death time control */
1020 if(**str == '+') { /* increment follows; read it */
1022 if(**str == '!') type = *(*str)++; // Bronstein TC
1023 if(result = NextIntegerFromString( str, &temp2)) return -1;
1024 *inc = temp2 * 1000;
1025 if(**str == '.') { // read fraction of increment
1026 char *start = ++(*str);
1027 if(result = NextIntegerFromString( str, &temp2)) return -1;
1029 while(start++ < *str) temp2 /= 10;
1033 *moves = 0; *tc = temp * 1000; *incType = type;
1037 (*str)++; /* classical time control */
1038 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1049 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1050 { /* [HGM] get time to add from the multi-session time-control string */
1051 int incType, moves=1; /* kludge to force reading of first session */
1052 long time, increment;
1055 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1056 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1058 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1059 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1060 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1061 if(movenr == -1) return time; /* last move before new session */
1062 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1063 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1064 if(!moves) return increment; /* current session is incremental */
1065 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1066 } while(movenr >= -1); /* try again for next session */
1068 return 0; // no new time quota on this move
1072 ParseTimeControl(tc, ti, mps)
1079 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1082 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1083 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1084 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1088 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1090 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1093 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1095 snprintf(buf, MSG_SIZ, ":%s", mytc);
1097 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1099 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1104 /* Parse second time control */
1107 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1115 timeControl_2 = tc2 * 1000;
1125 timeControl = tc1 * 1000;
1128 timeIncrement = ti * 1000; /* convert to ms */
1129 movesPerSession = 0;
1132 movesPerSession = mps;
1140 if (appData.debugMode) {
1141 fprintf(debugFP, "%s\n", programVersion);
1144 set_cont_sequence(appData.wrapContSeq);
1145 if (appData.matchGames > 0) {
1146 appData.matchMode = TRUE;
1147 } else if (appData.matchMode) {
1148 appData.matchGames = 1;
1150 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1151 appData.matchGames = appData.sameColorGames;
1152 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1153 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1154 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1157 if (appData.noChessProgram || first.protocolVersion == 1) {
1160 /* kludge: allow timeout for initial "feature" commands */
1162 DisplayMessage("", _("Starting chess program"));
1163 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1168 InitBackEnd3 P((void))
1170 GameMode initialMode;
1174 InitChessProgram(&first, startedFromSetupPosition);
1176 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1177 free(programVersion);
1178 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1179 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1182 if (appData.icsActive) {
1184 /* [DM] Make a console window if needed [HGM] merged ifs */
1190 if (*appData.icsCommPort != NULLCHAR)
1191 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1192 appData.icsCommPort);
1194 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1195 appData.icsHost, appData.icsPort);
1197 if( (len > MSG_SIZ) && appData.debugMode )
1198 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1200 DisplayFatalError(buf, err, 1);
1205 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1207 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1208 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1209 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1210 } else if (appData.noChessProgram) {
1216 if (*appData.cmailGameName != NULLCHAR) {
1218 OpenLoopback(&cmailPR);
1220 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1224 DisplayMessage("", "");
1225 if (StrCaseCmp(appData.initialMode, "") == 0) {
1226 initialMode = BeginningOfGame;
1227 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1228 initialMode = TwoMachinesPlay;
1229 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1230 initialMode = AnalyzeFile;
1231 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1232 initialMode = AnalyzeMode;
1233 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1234 initialMode = MachinePlaysWhite;
1235 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1236 initialMode = MachinePlaysBlack;
1237 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1238 initialMode = EditGame;
1239 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1240 initialMode = EditPosition;
1241 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1242 initialMode = Training;
1244 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1245 if( (len > MSG_SIZ) && appData.debugMode )
1246 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1248 DisplayFatalError(buf, 0, 2);
1252 if (appData.matchMode) {
1253 /* Set up machine vs. machine match */
1254 if (appData.noChessProgram) {
1255 DisplayFatalError(_("Can't have a match with no chess programs"),
1261 if (*appData.loadGameFile != NULLCHAR) {
1262 int index = appData.loadGameIndex; // [HGM] autoinc
1263 if(index<0) lastIndex = index = 1;
1264 if (!LoadGameFromFile(appData.loadGameFile,
1266 appData.loadGameFile, FALSE)) {
1267 DisplayFatalError(_("Bad game file"), 0, 1);
1270 } else if (*appData.loadPositionFile != NULLCHAR) {
1271 int index = appData.loadPositionIndex; // [HGM] autoinc
1272 if(index<0) lastIndex = index = 1;
1273 if (!LoadPositionFromFile(appData.loadPositionFile,
1275 appData.loadPositionFile)) {
1276 DisplayFatalError(_("Bad position file"), 0, 1);
1281 } else if (*appData.cmailGameName != NULLCHAR) {
1282 /* Set up cmail mode */
1283 ReloadCmailMsgEvent(TRUE);
1285 /* Set up other modes */
1286 if (initialMode == AnalyzeFile) {
1287 if (*appData.loadGameFile == NULLCHAR) {
1288 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1292 if (*appData.loadGameFile != NULLCHAR) {
1293 (void) LoadGameFromFile(appData.loadGameFile,
1294 appData.loadGameIndex,
1295 appData.loadGameFile, TRUE);
1296 } else if (*appData.loadPositionFile != NULLCHAR) {
1297 (void) LoadPositionFromFile(appData.loadPositionFile,
1298 appData.loadPositionIndex,
1299 appData.loadPositionFile);
1300 /* [HGM] try to make self-starting even after FEN load */
1301 /* to allow automatic setup of fairy variants with wtm */
1302 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1303 gameMode = BeginningOfGame;
1304 setboardSpoiledMachineBlack = 1;
1306 /* [HGM] loadPos: make that every new game uses the setup */
1307 /* from file as long as we do not switch variant */
1308 if(!blackPlaysFirst) {
1309 startedFromPositionFile = TRUE;
1310 CopyBoard(filePosition, boards[0]);
1313 if (initialMode == AnalyzeMode) {
1314 if (appData.noChessProgram) {
1315 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1318 if (appData.icsActive) {
1319 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1323 } else if (initialMode == AnalyzeFile) {
1324 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1325 ShowThinkingEvent();
1327 AnalysisPeriodicEvent(1);
1328 } else if (initialMode == MachinePlaysWhite) {
1329 if (appData.noChessProgram) {
1330 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1334 if (appData.icsActive) {
1335 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1339 MachineWhiteEvent();
1340 } else if (initialMode == MachinePlaysBlack) {
1341 if (appData.noChessProgram) {
1342 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1346 if (appData.icsActive) {
1347 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1351 MachineBlackEvent();
1352 } else if (initialMode == TwoMachinesPlay) {
1353 if (appData.noChessProgram) {
1354 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1358 if (appData.icsActive) {
1359 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1364 } else if (initialMode == EditGame) {
1366 } else if (initialMode == EditPosition) {
1367 EditPositionEvent();
1368 } else if (initialMode == Training) {
1369 if (*appData.loadGameFile == NULLCHAR) {
1370 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1379 * Establish will establish a contact to a remote host.port.
1380 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1381 * used to talk to the host.
1382 * Returns 0 if okay, error code if not.
1389 if (*appData.icsCommPort != NULLCHAR) {
1390 /* Talk to the host through a serial comm port */
1391 return OpenCommPort(appData.icsCommPort, &icsPR);
1393 } else if (*appData.gateway != NULLCHAR) {
1394 if (*appData.remoteShell == NULLCHAR) {
1395 /* Use the rcmd protocol to run telnet program on a gateway host */
1396 snprintf(buf, sizeof(buf), "%s %s %s",
1397 appData.telnetProgram, appData.icsHost, appData.icsPort);
1398 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1401 /* Use the rsh program to run telnet program on a gateway host */
1402 if (*appData.remoteUser == NULLCHAR) {
1403 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1404 appData.gateway, appData.telnetProgram,
1405 appData.icsHost, appData.icsPort);
1407 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1408 appData.remoteShell, appData.gateway,
1409 appData.remoteUser, appData.telnetProgram,
1410 appData.icsHost, appData.icsPort);
1412 return StartChildProcess(buf, "", &icsPR);
1415 } else if (appData.useTelnet) {
1416 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1419 /* TCP socket interface differs somewhat between
1420 Unix and NT; handle details in the front end.
1422 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1426 void EscapeExpand(char *p, char *q)
1427 { // [HGM] initstring: routine to shape up string arguments
1428 while(*p++ = *q++) if(p[-1] == '\\')
1430 case 'n': p[-1] = '\n'; break;
1431 case 'r': p[-1] = '\r'; break;
1432 case 't': p[-1] = '\t'; break;
1433 case '\\': p[-1] = '\\'; break;
1434 case 0: *p = 0; return;
1435 default: p[-1] = q[-1]; break;
1440 show_bytes(fp, buf, count)
1446 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1447 fprintf(fp, "\\%03o", *buf & 0xff);
1456 /* Returns an errno value */
1458 OutputMaybeTelnet(pr, message, count, outError)
1464 char buf[8192], *p, *q, *buflim;
1465 int left, newcount, outcount;
1467 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1468 *appData.gateway != NULLCHAR) {
1469 if (appData.debugMode) {
1470 fprintf(debugFP, ">ICS: ");
1471 show_bytes(debugFP, message, count);
1472 fprintf(debugFP, "\n");
1474 return OutputToProcess(pr, message, count, outError);
1477 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1484 if (appData.debugMode) {
1485 fprintf(debugFP, ">ICS: ");
1486 show_bytes(debugFP, buf, newcount);
1487 fprintf(debugFP, "\n");
1489 outcount = OutputToProcess(pr, buf, newcount, outError);
1490 if (outcount < newcount) return -1; /* to be sure */
1497 } else if (((unsigned char) *p) == TN_IAC) {
1498 *q++ = (char) TN_IAC;
1505 if (appData.debugMode) {
1506 fprintf(debugFP, ">ICS: ");
1507 show_bytes(debugFP, buf, newcount);
1508 fprintf(debugFP, "\n");
1510 outcount = OutputToProcess(pr, buf, newcount, outError);
1511 if (outcount < newcount) return -1; /* to be sure */
1516 read_from_player(isr, closure, message, count, error)
1523 int outError, outCount;
1524 static int gotEof = 0;
1526 /* Pass data read from player on to ICS */
1529 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1530 if (outCount < count) {
1531 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1533 } else if (count < 0) {
1534 RemoveInputSource(isr);
1535 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1536 } else if (gotEof++ > 0) {
1537 RemoveInputSource(isr);
1538 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1544 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1545 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1546 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1547 SendToICS("date\n");
1548 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1551 /* added routine for printf style output to ics */
1552 void ics_printf(char *format, ...)
1554 char buffer[MSG_SIZ];
1557 va_start(args, format);
1558 vsnprintf(buffer, sizeof(buffer), format, args);
1559 buffer[sizeof(buffer)-1] = '\0';
1568 int count, outCount, outError;
1570 if (icsPR == NULL) return;
1573 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1574 if (outCount < count) {
1575 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1579 /* This is used for sending logon scripts to the ICS. Sending
1580 without a delay causes problems when using timestamp on ICC
1581 (at least on my machine). */
1583 SendToICSDelayed(s,msdelay)
1587 int count, outCount, outError;
1589 if (icsPR == NULL) return;
1592 if (appData.debugMode) {
1593 fprintf(debugFP, ">ICS: ");
1594 show_bytes(debugFP, s, count);
1595 fprintf(debugFP, "\n");
1597 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1599 if (outCount < count) {
1600 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1605 /* Remove all highlighting escape sequences in s
1606 Also deletes any suffix starting with '('
1609 StripHighlightAndTitle(s)
1612 static char retbuf[MSG_SIZ];
1615 while (*s != NULLCHAR) {
1616 while (*s == '\033') {
1617 while (*s != NULLCHAR && !isalpha(*s)) s++;
1618 if (*s != NULLCHAR) s++;
1620 while (*s != NULLCHAR && *s != '\033') {
1621 if (*s == '(' || *s == '[') {
1632 /* Remove all highlighting escape sequences in s */
1637 static char retbuf[MSG_SIZ];
1640 while (*s != NULLCHAR) {
1641 while (*s == '\033') {
1642 while (*s != NULLCHAR && !isalpha(*s)) s++;
1643 if (*s != NULLCHAR) s++;
1645 while (*s != NULLCHAR && *s != '\033') {
1653 char *variantNames[] = VARIANT_NAMES;
1658 return variantNames[v];
1662 /* Identify a variant from the strings the chess servers use or the
1663 PGN Variant tag names we use. */
1670 VariantClass v = VariantNormal;
1671 int i, found = FALSE;
1677 /* [HGM] skip over optional board-size prefixes */
1678 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1679 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1680 while( *e++ != '_');
1683 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1687 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1688 if (StrCaseStr(e, variantNames[i])) {
1689 v = (VariantClass) i;
1696 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1697 || StrCaseStr(e, "wild/fr")
1698 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1699 v = VariantFischeRandom;
1700 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1701 (i = 1, p = StrCaseStr(e, "w"))) {
1703 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1710 case 0: /* FICS only, actually */
1712 /* Castling legal even if K starts on d-file */
1713 v = VariantWildCastle;
1718 /* Castling illegal even if K & R happen to start in
1719 normal positions. */
1720 v = VariantNoCastle;
1733 /* Castling legal iff K & R start in normal positions */
1739 /* Special wilds for position setup; unclear what to do here */
1740 v = VariantLoadable;
1743 /* Bizarre ICC game */
1744 v = VariantTwoKings;
1747 v = VariantKriegspiel;
1753 v = VariantFischeRandom;
1756 v = VariantCrazyhouse;
1759 v = VariantBughouse;
1765 /* Not quite the same as FICS suicide! */
1766 v = VariantGiveaway;
1772 v = VariantShatranj;
1775 /* Temporary names for future ICC types. The name *will* change in
1776 the next xboard/WinBoard release after ICC defines it. */
1814 v = VariantCapablanca;
1817 v = VariantKnightmate;
1823 v = VariantCylinder;
1829 v = VariantCapaRandom;
1832 v = VariantBerolina;
1844 /* Found "wild" or "w" in the string but no number;
1845 must assume it's normal chess. */
1849 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1850 if( (len > MSG_SIZ) && appData.debugMode )
1851 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1853 DisplayError(buf, 0);
1859 if (appData.debugMode) {
1860 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1861 e, wnum, VariantName(v));
1866 static int leftover_start = 0, leftover_len = 0;
1867 char star_match[STAR_MATCH_N][MSG_SIZ];
1869 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1870 advance *index beyond it, and set leftover_start to the new value of
1871 *index; else return FALSE. If pattern contains the character '*', it
1872 matches any sequence of characters not containing '\r', '\n', or the
1873 character following the '*' (if any), and the matched sequence(s) are
1874 copied into star_match.
1877 looking_at(buf, index, pattern)
1882 char *bufp = &buf[*index], *patternp = pattern;
1884 char *matchp = star_match[0];
1887 if (*patternp == NULLCHAR) {
1888 *index = leftover_start = bufp - buf;
1892 if (*bufp == NULLCHAR) return FALSE;
1893 if (*patternp == '*') {
1894 if (*bufp == *(patternp + 1)) {
1896 matchp = star_match[++star_count];
1900 } else if (*bufp == '\n' || *bufp == '\r') {
1902 if (*patternp == NULLCHAR)
1907 *matchp++ = *bufp++;
1911 if (*patternp != *bufp) return FALSE;
1918 SendToPlayer(data, length)
1922 int error, outCount;
1923 outCount = OutputToProcess(NoProc, data, length, &error);
1924 if (outCount < length) {
1925 DisplayFatalError(_("Error writing to display"), error, 1);
1930 PackHolding(packed, holding)
1942 switch (runlength) {
1953 sprintf(q, "%d", runlength);
1965 /* Telnet protocol requests from the front end */
1967 TelnetRequest(ddww, option)
1968 unsigned char ddww, option;
1970 unsigned char msg[3];
1971 int outCount, outError;
1973 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1975 if (appData.debugMode) {
1976 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1992 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2001 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2004 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2009 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2011 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2018 if (!appData.icsActive) return;
2019 TelnetRequest(TN_DO, TN_ECHO);
2025 if (!appData.icsActive) return;
2026 TelnetRequest(TN_DONT, TN_ECHO);
2030 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2032 /* put the holdings sent to us by the server on the board holdings area */
2033 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2037 if(gameInfo.holdingsWidth < 2) return;
2038 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2039 return; // prevent overwriting by pre-board holdings
2041 if( (int)lowestPiece >= BlackPawn ) {
2044 holdingsStartRow = BOARD_HEIGHT-1;
2047 holdingsColumn = BOARD_WIDTH-1;
2048 countsColumn = BOARD_WIDTH-2;
2049 holdingsStartRow = 0;
2053 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2054 board[i][holdingsColumn] = EmptySquare;
2055 board[i][countsColumn] = (ChessSquare) 0;
2057 while( (p=*holdings++) != NULLCHAR ) {
2058 piece = CharToPiece( ToUpper(p) );
2059 if(piece == EmptySquare) continue;
2060 /*j = (int) piece - (int) WhitePawn;*/
2061 j = PieceToNumber(piece);
2062 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2063 if(j < 0) continue; /* should not happen */
2064 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2065 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2066 board[holdingsStartRow+j*direction][countsColumn]++;
2072 VariantSwitch(Board board, VariantClass newVariant)
2074 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2075 static Board oldBoard;
2077 startedFromPositionFile = FALSE;
2078 if(gameInfo.variant == newVariant) return;
2080 /* [HGM] This routine is called each time an assignment is made to
2081 * gameInfo.variant during a game, to make sure the board sizes
2082 * are set to match the new variant. If that means adding or deleting
2083 * holdings, we shift the playing board accordingly
2084 * This kludge is needed because in ICS observe mode, we get boards
2085 * of an ongoing game without knowing the variant, and learn about the
2086 * latter only later. This can be because of the move list we requested,
2087 * in which case the game history is refilled from the beginning anyway,
2088 * but also when receiving holdings of a crazyhouse game. In the latter
2089 * case we want to add those holdings to the already received position.
2093 if (appData.debugMode) {
2094 fprintf(debugFP, "Switch board from %s to %s\n",
2095 VariantName(gameInfo.variant), VariantName(newVariant));
2096 setbuf(debugFP, NULL);
2098 shuffleOpenings = 0; /* [HGM] shuffle */
2099 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2103 newWidth = 9; newHeight = 9;
2104 gameInfo.holdingsSize = 7;
2105 case VariantBughouse:
2106 case VariantCrazyhouse:
2107 newHoldingsWidth = 2; break;
2111 newHoldingsWidth = 2;
2112 gameInfo.holdingsSize = 8;
2115 case VariantCapablanca:
2116 case VariantCapaRandom:
2119 newHoldingsWidth = gameInfo.holdingsSize = 0;
2122 if(newWidth != gameInfo.boardWidth ||
2123 newHeight != gameInfo.boardHeight ||
2124 newHoldingsWidth != gameInfo.holdingsWidth ) {
2126 /* shift position to new playing area, if needed */
2127 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2128 for(i=0; i<BOARD_HEIGHT; i++)
2129 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2130 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2132 for(i=0; i<newHeight; i++) {
2133 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2134 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2136 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2137 for(i=0; i<BOARD_HEIGHT; i++)
2138 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2139 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2142 gameInfo.boardWidth = newWidth;
2143 gameInfo.boardHeight = newHeight;
2144 gameInfo.holdingsWidth = newHoldingsWidth;
2145 gameInfo.variant = newVariant;
2146 InitDrawingSizes(-2, 0);
2147 } else gameInfo.variant = newVariant;
2148 CopyBoard(oldBoard, board); // remember correctly formatted board
2149 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2150 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2153 static int loggedOn = FALSE;
2155 /*-- Game start info cache: --*/
2157 char gs_kind[MSG_SIZ];
2158 static char player1Name[128] = "";
2159 static char player2Name[128] = "";
2160 static char cont_seq[] = "\n\\ ";
2161 static int player1Rating = -1;
2162 static int player2Rating = -1;
2163 /*----------------------------*/
2165 ColorClass curColor = ColorNormal;
2166 int suppressKibitz = 0;
2169 Boolean soughtPending = FALSE;
2170 Boolean seekGraphUp;
2171 #define MAX_SEEK_ADS 200
2173 char *seekAdList[MAX_SEEK_ADS];
2174 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2175 float tcList[MAX_SEEK_ADS];
2176 char colorList[MAX_SEEK_ADS];
2177 int nrOfSeekAds = 0;
2178 int minRating = 1010, maxRating = 2800;
2179 int hMargin = 10, vMargin = 20, h, w;
2180 extern int squareSize, lineGap;
2185 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2186 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2187 if(r < minRating+100 && r >=0 ) r = minRating+100;
2188 if(r > maxRating) r = maxRating;
2189 if(tc < 1.) tc = 1.;
2190 if(tc > 95.) tc = 95.;
2191 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2192 y = ((double)r - minRating)/(maxRating - minRating)
2193 * (h-vMargin-squareSize/8-1) + vMargin;
2194 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2195 if(strstr(seekAdList[i], " u ")) color = 1;
2196 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2197 !strstr(seekAdList[i], "bullet") &&
2198 !strstr(seekAdList[i], "blitz") &&
2199 !strstr(seekAdList[i], "standard") ) color = 2;
2200 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2201 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2205 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2207 char buf[MSG_SIZ], *ext = "";
2208 VariantClass v = StringToVariant(type);
2209 if(strstr(type, "wild")) {
2210 ext = type + 4; // append wild number
2211 if(v == VariantFischeRandom) type = "chess960"; else
2212 if(v == VariantLoadable) type = "setup"; else
2213 type = VariantName(v);
2215 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2216 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2217 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2218 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2219 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2220 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2221 seekNrList[nrOfSeekAds] = nr;
2222 zList[nrOfSeekAds] = 0;
2223 seekAdList[nrOfSeekAds++] = StrSave(buf);
2224 if(plot) PlotSeekAd(nrOfSeekAds-1);
2231 int x = xList[i], y = yList[i], d=squareSize/4, k;
2232 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2233 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2234 // now replot every dot that overlapped
2235 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2236 int xx = xList[k], yy = yList[k];
2237 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2238 DrawSeekDot(xx, yy, colorList[k]);
2243 RemoveSeekAd(int nr)
2246 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2248 if(seekAdList[i]) free(seekAdList[i]);
2249 seekAdList[i] = seekAdList[--nrOfSeekAds];
2250 seekNrList[i] = seekNrList[nrOfSeekAds];
2251 ratingList[i] = ratingList[nrOfSeekAds];
2252 colorList[i] = colorList[nrOfSeekAds];
2253 tcList[i] = tcList[nrOfSeekAds];
2254 xList[i] = xList[nrOfSeekAds];
2255 yList[i] = yList[nrOfSeekAds];
2256 zList[i] = zList[nrOfSeekAds];
2257 seekAdList[nrOfSeekAds] = NULL;
2263 MatchSoughtLine(char *line)
2265 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2266 int nr, base, inc, u=0; char dummy;
2268 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2269 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2271 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2272 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2273 // match: compact and save the line
2274 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2284 if(!seekGraphUp) return FALSE;
2285 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2286 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2288 DrawSeekBackground(0, 0, w, h);
2289 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2290 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2291 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2292 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2294 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2297 snprintf(buf, MSG_SIZ, "%d", i);
2298 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2301 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2302 for(i=1; i<100; i+=(i<10?1:5)) {
2303 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2304 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2305 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2307 snprintf(buf, MSG_SIZ, "%d", i);
2308 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2311 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2315 int SeekGraphClick(ClickType click, int x, int y, int moving)
2317 static int lastDown = 0, displayed = 0, lastSecond;
2318 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2319 if(click == Release || moving) return FALSE;
2321 soughtPending = TRUE;
2322 SendToICS(ics_prefix);
2323 SendToICS("sought\n"); // should this be "sought all"?
2324 } else { // issue challenge based on clicked ad
2325 int dist = 10000; int i, closest = 0, second = 0;
2326 for(i=0; i<nrOfSeekAds; i++) {
2327 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2328 if(d < dist) { dist = d; closest = i; }
2329 second += (d - zList[i] < 120); // count in-range ads
2330 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2334 second = (second > 1);
2335 if(displayed != closest || second != lastSecond) {
2336 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2337 lastSecond = second; displayed = closest;
2339 if(click == Press) {
2340 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2343 } // on press 'hit', only show info
2344 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2345 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2346 SendToICS(ics_prefix);
2348 return TRUE; // let incoming board of started game pop down the graph
2349 } else if(click == Release) { // release 'miss' is ignored
2350 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2351 if(moving == 2) { // right up-click
2352 nrOfSeekAds = 0; // refresh graph
2353 soughtPending = TRUE;
2354 SendToICS(ics_prefix);
2355 SendToICS("sought\n"); // should this be "sought all"?
2358 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2359 // press miss or release hit 'pop down' seek graph
2360 seekGraphUp = FALSE;
2361 DrawPosition(TRUE, NULL);
2367 read_from_ics(isr, closure, data, count, error)
2374 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2375 #define STARTED_NONE 0
2376 #define STARTED_MOVES 1
2377 #define STARTED_BOARD 2
2378 #define STARTED_OBSERVE 3
2379 #define STARTED_HOLDINGS 4
2380 #define STARTED_CHATTER 5
2381 #define STARTED_COMMENT 6
2382 #define STARTED_MOVES_NOHIDE 7
2384 static int started = STARTED_NONE;
2385 static char parse[20000];
2386 static int parse_pos = 0;
2387 static char buf[BUF_SIZE + 1];
2388 static int firstTime = TRUE, intfSet = FALSE;
2389 static ColorClass prevColor = ColorNormal;
2390 static int savingComment = FALSE;
2391 static int cmatch = 0; // continuation sequence match
2398 int backup; /* [DM] For zippy color lines */
2400 char talker[MSG_SIZ]; // [HGM] chat
2403 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2405 if (appData.debugMode) {
2407 fprintf(debugFP, "<ICS: ");
2408 show_bytes(debugFP, data, count);
2409 fprintf(debugFP, "\n");
2413 if (appData.debugMode) { int f = forwardMostMove;
2414 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2415 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2416 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2419 /* If last read ended with a partial line that we couldn't parse,
2420 prepend it to the new read and try again. */
2421 if (leftover_len > 0) {
2422 for (i=0; i<leftover_len; i++)
2423 buf[i] = buf[leftover_start + i];
2426 /* copy new characters into the buffer */
2427 bp = buf + leftover_len;
2428 buf_len=leftover_len;
2429 for (i=0; i<count; i++)
2432 if (data[i] == '\r')
2435 // join lines split by ICS?
2436 if (!appData.noJoin)
2439 Joining just consists of finding matches against the
2440 continuation sequence, and discarding that sequence
2441 if found instead of copying it. So, until a match
2442 fails, there's nothing to do since it might be the
2443 complete sequence, and thus, something we don't want
2446 if (data[i] == cont_seq[cmatch])
2449 if (cmatch == strlen(cont_seq))
2451 cmatch = 0; // complete match. just reset the counter
2454 it's possible for the ICS to not include the space
2455 at the end of the last word, making our [correct]
2456 join operation fuse two separate words. the server
2457 does this when the space occurs at the width setting.
2459 if (!buf_len || buf[buf_len-1] != ' ')
2470 match failed, so we have to copy what matched before
2471 falling through and copying this character. In reality,
2472 this will only ever be just the newline character, but
2473 it doesn't hurt to be precise.
2475 strncpy(bp, cont_seq, cmatch);
2487 buf[buf_len] = NULLCHAR;
2488 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2493 while (i < buf_len) {
2494 /* Deal with part of the TELNET option negotiation
2495 protocol. We refuse to do anything beyond the
2496 defaults, except that we allow the WILL ECHO option,
2497 which ICS uses to turn off password echoing when we are
2498 directly connected to it. We reject this option
2499 if localLineEditing mode is on (always on in xboard)
2500 and we are talking to port 23, which might be a real
2501 telnet server that will try to keep WILL ECHO on permanently.
2503 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2504 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2505 unsigned char option;
2507 switch ((unsigned char) buf[++i]) {
2509 if (appData.debugMode)
2510 fprintf(debugFP, "\n<WILL ");
2511 switch (option = (unsigned char) buf[++i]) {
2513 if (appData.debugMode)
2514 fprintf(debugFP, "ECHO ");
2515 /* Reply only if this is a change, according
2516 to the protocol rules. */
2517 if (remoteEchoOption) break;
2518 if (appData.localLineEditing &&
2519 atoi(appData.icsPort) == TN_PORT) {
2520 TelnetRequest(TN_DONT, TN_ECHO);
2523 TelnetRequest(TN_DO, TN_ECHO);
2524 remoteEchoOption = TRUE;
2528 if (appData.debugMode)
2529 fprintf(debugFP, "%d ", option);
2530 /* Whatever this is, we don't want it. */
2531 TelnetRequest(TN_DONT, option);
2536 if (appData.debugMode)
2537 fprintf(debugFP, "\n<WONT ");
2538 switch (option = (unsigned char) buf[++i]) {
2540 if (appData.debugMode)
2541 fprintf(debugFP, "ECHO ");
2542 /* Reply only if this is a change, according
2543 to the protocol rules. */
2544 if (!remoteEchoOption) break;
2546 TelnetRequest(TN_DONT, TN_ECHO);
2547 remoteEchoOption = FALSE;
2550 if (appData.debugMode)
2551 fprintf(debugFP, "%d ", (unsigned char) option);
2552 /* Whatever this is, it must already be turned
2553 off, because we never agree to turn on
2554 anything non-default, so according to the
2555 protocol rules, we don't reply. */
2560 if (appData.debugMode)
2561 fprintf(debugFP, "\n<DO ");
2562 switch (option = (unsigned char) buf[++i]) {
2564 /* Whatever this is, we refuse to do it. */
2565 if (appData.debugMode)
2566 fprintf(debugFP, "%d ", option);
2567 TelnetRequest(TN_WONT, option);
2572 if (appData.debugMode)
2573 fprintf(debugFP, "\n<DONT ");
2574 switch (option = (unsigned char) buf[++i]) {
2576 if (appData.debugMode)
2577 fprintf(debugFP, "%d ", option);
2578 /* Whatever this is, we are already not doing
2579 it, because we never agree to do anything
2580 non-default, so according to the protocol
2581 rules, we don't reply. */
2586 if (appData.debugMode)
2587 fprintf(debugFP, "\n<IAC ");
2588 /* Doubled IAC; pass it through */
2592 if (appData.debugMode)
2593 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2594 /* Drop all other telnet commands on the floor */
2597 if (oldi > next_out)
2598 SendToPlayer(&buf[next_out], oldi - next_out);
2604 /* OK, this at least will *usually* work */
2605 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2609 if (loggedOn && !intfSet) {
2610 if (ics_type == ICS_ICC) {
2611 snprintf(str, MSG_SIZ,
2612 "/set-quietly interface %s\n/set-quietly style 12\n",
2614 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2615 strcat(str, "/set-2 51 1\n/set seek 1\n");
2616 } else if (ics_type == ICS_CHESSNET) {
2617 snprintf(str, MSG_SIZ, "/style 12\n");
2619 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2620 strcat(str, programVersion);
2621 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2622 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2623 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2625 strcat(str, "$iset nohighlight 1\n");
2627 strcat(str, "$iset lock 1\n$style 12\n");
2630 NotifyFrontendLogin();
2634 if (started == STARTED_COMMENT) {
2635 /* Accumulate characters in comment */
2636 parse[parse_pos++] = buf[i];
2637 if (buf[i] == '\n') {
2638 parse[parse_pos] = NULLCHAR;
2639 if(chattingPartner>=0) {
2641 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2642 OutputChatMessage(chattingPartner, mess);
2643 chattingPartner = -1;
2644 next_out = i+1; // [HGM] suppress printing in ICS window
2646 if(!suppressKibitz) // [HGM] kibitz
2647 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2648 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2649 int nrDigit = 0, nrAlph = 0, j;
2650 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2651 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2652 parse[parse_pos] = NULLCHAR;
2653 // try to be smart: if it does not look like search info, it should go to
2654 // ICS interaction window after all, not to engine-output window.
2655 for(j=0; j<parse_pos; j++) { // count letters and digits
2656 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2657 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2658 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2660 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2661 int depth=0; float score;
2662 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2663 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2664 pvInfoList[forwardMostMove-1].depth = depth;
2665 pvInfoList[forwardMostMove-1].score = 100*score;
2667 OutputKibitz(suppressKibitz, parse);
2670 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2671 SendToPlayer(tmp, strlen(tmp));
2673 next_out = i+1; // [HGM] suppress printing in ICS window
2675 started = STARTED_NONE;
2677 /* Don't match patterns against characters in comment */
2682 if (started == STARTED_CHATTER) {
2683 if (buf[i] != '\n') {
2684 /* Don't match patterns against characters in chatter */
2688 started = STARTED_NONE;
2689 if(suppressKibitz) next_out = i+1;
2692 /* Kludge to deal with rcmd protocol */
2693 if (firstTime && looking_at(buf, &i, "\001*")) {
2694 DisplayFatalError(&buf[1], 0, 1);
2700 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2703 if (appData.debugMode)
2704 fprintf(debugFP, "ics_type %d\n", ics_type);
2707 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2708 ics_type = ICS_FICS;
2710 if (appData.debugMode)
2711 fprintf(debugFP, "ics_type %d\n", ics_type);
2714 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2715 ics_type = ICS_CHESSNET;
2717 if (appData.debugMode)
2718 fprintf(debugFP, "ics_type %d\n", ics_type);
2723 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2724 looking_at(buf, &i, "Logging you in as \"*\"") ||
2725 looking_at(buf, &i, "will be \"*\""))) {
2726 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2730 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2732 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2733 DisplayIcsInteractionTitle(buf);
2734 have_set_title = TRUE;
2737 /* skip finger notes */
2738 if (started == STARTED_NONE &&
2739 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2740 (buf[i] == '1' && buf[i+1] == '0')) &&
2741 buf[i+2] == ':' && buf[i+3] == ' ') {
2742 started = STARTED_CHATTER;
2748 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2749 if(appData.seekGraph) {
2750 if(soughtPending && MatchSoughtLine(buf+i)) {
2751 i = strstr(buf+i, "rated") - buf;
2752 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2753 next_out = leftover_start = i;
2754 started = STARTED_CHATTER;
2755 suppressKibitz = TRUE;
2758 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2759 && looking_at(buf, &i, "* ads displayed")) {
2760 soughtPending = FALSE;
2765 if(appData.autoRefresh) {
2766 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2767 int s = (ics_type == ICS_ICC); // ICC format differs
2769 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2770 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2771 looking_at(buf, &i, "*% "); // eat prompt
2772 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2773 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2774 next_out = i; // suppress
2777 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2778 char *p = star_match[0];
2780 if(seekGraphUp) RemoveSeekAd(atoi(p));
2781 while(*p && *p++ != ' '); // next
2783 looking_at(buf, &i, "*% "); // eat prompt
2784 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2791 /* skip formula vars */
2792 if (started == STARTED_NONE &&
2793 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2794 started = STARTED_CHATTER;
2799 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2800 if (appData.autoKibitz && started == STARTED_NONE &&
2801 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2802 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2803 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2804 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2805 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2806 suppressKibitz = TRUE;
2807 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2809 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2810 && (gameMode == IcsPlayingWhite)) ||
2811 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2812 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2813 started = STARTED_CHATTER; // own kibitz we simply discard
2815 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2816 parse_pos = 0; parse[0] = NULLCHAR;
2817 savingComment = TRUE;
2818 suppressKibitz = gameMode != IcsObserving ? 2 :
2819 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2823 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2824 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2825 && atoi(star_match[0])) {
2826 // suppress the acknowledgements of our own autoKibitz
2828 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2829 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2830 SendToPlayer(star_match[0], strlen(star_match[0]));
2831 if(looking_at(buf, &i, "*% ")) // eat prompt
2832 suppressKibitz = FALSE;
2836 } // [HGM] kibitz: end of patch
2838 // [HGM] chat: intercept tells by users for which we have an open chat window
2840 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2841 looking_at(buf, &i, "* whispers:") ||
2842 looking_at(buf, &i, "* kibitzes:") ||
2843 looking_at(buf, &i, "* shouts:") ||
2844 looking_at(buf, &i, "* c-shouts:") ||
2845 looking_at(buf, &i, "--> * ") ||
2846 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2847 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2848 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2849 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2851 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2852 chattingPartner = -1;
2854 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2855 for(p=0; p<MAX_CHAT; p++) {
2856 if(channel == atoi(chatPartner[p])) {
2857 talker[0] = '['; strcat(talker, "] ");
2858 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2859 chattingPartner = p; break;
2862 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2863 for(p=0; p<MAX_CHAT; p++) {
2864 if(!strcmp("kibitzes", chatPartner[p])) {
2865 talker[0] = '['; strcat(talker, "] ");
2866 chattingPartner = p; break;
2869 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2870 for(p=0; p<MAX_CHAT; p++) {
2871 if(!strcmp("whispers", chatPartner[p])) {
2872 talker[0] = '['; strcat(talker, "] ");
2873 chattingPartner = p; break;
2876 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2877 if(buf[i-8] == '-' && buf[i-3] == 't')
2878 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2879 if(!strcmp("c-shouts", chatPartner[p])) {
2880 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2881 chattingPartner = p; break;
2884 if(chattingPartner < 0)
2885 for(p=0; p<MAX_CHAT; p++) {
2886 if(!strcmp("shouts", chatPartner[p])) {
2887 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2888 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2889 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2890 chattingPartner = p; break;
2894 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2895 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2896 talker[0] = 0; Colorize(ColorTell, FALSE);
2897 chattingPartner = p; break;
2899 if(chattingPartner<0) i = oldi; else {
2900 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2901 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2902 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2903 started = STARTED_COMMENT;
2904 parse_pos = 0; parse[0] = NULLCHAR;
2905 savingComment = 3 + chattingPartner; // counts as TRUE
2906 suppressKibitz = TRUE;
2909 } // [HGM] chat: end of patch
2911 if (appData.zippyTalk || appData.zippyPlay) {
2912 /* [DM] Backup address for color zippy lines */
2915 if (loggedOn == TRUE)
2916 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2917 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2919 } // [DM] 'else { ' deleted
2921 /* Regular tells and says */
2922 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2923 looking_at(buf, &i, "* (your partner) tells you: ") ||
2924 looking_at(buf, &i, "* says: ") ||
2925 /* Don't color "message" or "messages" output */
2926 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2927 looking_at(buf, &i, "*. * at *:*: ") ||
2928 looking_at(buf, &i, "--* (*:*): ") ||
2929 /* Message notifications (same color as tells) */
2930 looking_at(buf, &i, "* has left a message ") ||
2931 looking_at(buf, &i, "* just sent you a message:\n") ||
2932 /* Whispers and kibitzes */
2933 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2934 looking_at(buf, &i, "* kibitzes: ") ||
2936 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2938 if (tkind == 1 && strchr(star_match[0], ':')) {
2939 /* Avoid "tells you:" spoofs in channels */
2942 if (star_match[0][0] == NULLCHAR ||
2943 strchr(star_match[0], ' ') ||
2944 (tkind == 3 && strchr(star_match[1], ' '))) {
2945 /* Reject bogus matches */
2948 if (appData.colorize) {
2949 if (oldi > next_out) {
2950 SendToPlayer(&buf[next_out], oldi - next_out);
2955 Colorize(ColorTell, FALSE);
2956 curColor = ColorTell;
2959 Colorize(ColorKibitz, FALSE);
2960 curColor = ColorKibitz;
2963 p = strrchr(star_match[1], '(');
2970 Colorize(ColorChannel1, FALSE);
2971 curColor = ColorChannel1;
2973 Colorize(ColorChannel, FALSE);
2974 curColor = ColorChannel;
2978 curColor = ColorNormal;
2982 if (started == STARTED_NONE && appData.autoComment &&
2983 (gameMode == IcsObserving ||
2984 gameMode == IcsPlayingWhite ||
2985 gameMode == IcsPlayingBlack)) {
2986 parse_pos = i - oldi;
2987 memcpy(parse, &buf[oldi], parse_pos);
2988 parse[parse_pos] = NULLCHAR;
2989 started = STARTED_COMMENT;
2990 savingComment = TRUE;
2992 started = STARTED_CHATTER;
2993 savingComment = FALSE;
3000 if (looking_at(buf, &i, "* s-shouts: ") ||
3001 looking_at(buf, &i, "* c-shouts: ")) {
3002 if (appData.colorize) {
3003 if (oldi > next_out) {
3004 SendToPlayer(&buf[next_out], oldi - next_out);
3007 Colorize(ColorSShout, FALSE);
3008 curColor = ColorSShout;
3011 started = STARTED_CHATTER;
3015 if (looking_at(buf, &i, "--->")) {
3020 if (looking_at(buf, &i, "* shouts: ") ||
3021 looking_at(buf, &i, "--> ")) {
3022 if (appData.colorize) {
3023 if (oldi > next_out) {
3024 SendToPlayer(&buf[next_out], oldi - next_out);
3027 Colorize(ColorShout, FALSE);
3028 curColor = ColorShout;
3031 started = STARTED_CHATTER;
3035 if (looking_at( buf, &i, "Challenge:")) {
3036 if (appData.colorize) {
3037 if (oldi > next_out) {
3038 SendToPlayer(&buf[next_out], oldi - next_out);
3041 Colorize(ColorChallenge, FALSE);
3042 curColor = ColorChallenge;
3048 if (looking_at(buf, &i, "* offers you") ||
3049 looking_at(buf, &i, "* offers to be") ||
3050 looking_at(buf, &i, "* would like to") ||
3051 looking_at(buf, &i, "* requests to") ||
3052 looking_at(buf, &i, "Your opponent offers") ||
3053 looking_at(buf, &i, "Your opponent requests")) {
3055 if (appData.colorize) {
3056 if (oldi > next_out) {
3057 SendToPlayer(&buf[next_out], oldi - next_out);
3060 Colorize(ColorRequest, FALSE);
3061 curColor = ColorRequest;
3066 if (looking_at(buf, &i, "* (*) seeking")) {
3067 if (appData.colorize) {
3068 if (oldi > next_out) {
3069 SendToPlayer(&buf[next_out], oldi - next_out);
3072 Colorize(ColorSeek, FALSE);
3073 curColor = ColorSeek;
3078 if (looking_at(buf, &i, "\\ ")) {
3079 if (prevColor != ColorNormal) {
3080 if (oldi > next_out) {
3081 SendToPlayer(&buf[next_out], oldi - next_out);
3084 Colorize(prevColor, TRUE);
3085 curColor = prevColor;
3087 if (savingComment) {
3088 parse_pos = i - oldi;
3089 memcpy(parse, &buf[oldi], parse_pos);
3090 parse[parse_pos] = NULLCHAR;
3091 started = STARTED_COMMENT;
3092 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3093 chattingPartner = savingComment - 3; // kludge to remember the box
3095 started = STARTED_CHATTER;
3100 if (looking_at(buf, &i, "Black Strength :") ||
3101 looking_at(buf, &i, "<<< style 10 board >>>") ||
3102 looking_at(buf, &i, "<10>") ||
3103 looking_at(buf, &i, "#@#")) {
3104 /* Wrong board style */
3106 SendToICS(ics_prefix);
3107 SendToICS("set style 12\n");
3108 SendToICS(ics_prefix);
3109 SendToICS("refresh\n");
3113 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3115 have_sent_ICS_logon = 1;
3119 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3120 (looking_at(buf, &i, "\n<12> ") ||
3121 looking_at(buf, &i, "<12> "))) {
3123 if (oldi > next_out) {
3124 SendToPlayer(&buf[next_out], oldi - next_out);
3127 started = STARTED_BOARD;
3132 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3133 looking_at(buf, &i, "<b1> ")) {
3134 if (oldi > next_out) {
3135 SendToPlayer(&buf[next_out], oldi - next_out);
3138 started = STARTED_HOLDINGS;
3143 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3145 /* Header for a move list -- first line */
3147 switch (ics_getting_history) {
3151 case BeginningOfGame:
3152 /* User typed "moves" or "oldmoves" while we
3153 were idle. Pretend we asked for these
3154 moves and soak them up so user can step
3155 through them and/or save them.
3158 gameMode = IcsObserving;
3161 ics_getting_history = H_GOT_UNREQ_HEADER;
3163 case EditGame: /*?*/
3164 case EditPosition: /*?*/
3165 /* Should above feature work in these modes too? */
3166 /* For now it doesn't */
3167 ics_getting_history = H_GOT_UNWANTED_HEADER;
3170 ics_getting_history = H_GOT_UNWANTED_HEADER;
3175 /* Is this the right one? */
3176 if (gameInfo.white && gameInfo.black &&
3177 strcmp(gameInfo.white, star_match[0]) == 0 &&
3178 strcmp(gameInfo.black, star_match[2]) == 0) {
3180 ics_getting_history = H_GOT_REQ_HEADER;
3183 case H_GOT_REQ_HEADER:
3184 case H_GOT_UNREQ_HEADER:
3185 case H_GOT_UNWANTED_HEADER:
3186 case H_GETTING_MOVES:
3187 /* Should not happen */
3188 DisplayError(_("Error gathering move list: two headers"), 0);
3189 ics_getting_history = H_FALSE;
3193 /* Save player ratings into gameInfo if needed */
3194 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3195 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3196 (gameInfo.whiteRating == -1 ||
3197 gameInfo.blackRating == -1)) {
3199 gameInfo.whiteRating = string_to_rating(star_match[1]);
3200 gameInfo.blackRating = string_to_rating(star_match[3]);
3201 if (appData.debugMode)
3202 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3203 gameInfo.whiteRating, gameInfo.blackRating);
3208 if (looking_at(buf, &i,
3209 "* * match, initial time: * minute*, increment: * second")) {
3210 /* Header for a move list -- second line */
3211 /* Initial board will follow if this is a wild game */
3212 if (gameInfo.event != NULL) free(gameInfo.event);
3213 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3214 gameInfo.event = StrSave(str);
3215 /* [HGM] we switched variant. Translate boards if needed. */
3216 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3220 if (looking_at(buf, &i, "Move ")) {
3221 /* Beginning of a move list */
3222 switch (ics_getting_history) {
3224 /* Normally should not happen */
3225 /* Maybe user hit reset while we were parsing */
3228 /* Happens if we are ignoring a move list that is not
3229 * the one we just requested. Common if the user
3230 * tries to observe two games without turning off
3233 case H_GETTING_MOVES:
3234 /* Should not happen */
3235 DisplayError(_("Error gathering move list: nested"), 0);
3236 ics_getting_history = H_FALSE;
3238 case H_GOT_REQ_HEADER:
3239 ics_getting_history = H_GETTING_MOVES;
3240 started = STARTED_MOVES;
3242 if (oldi > next_out) {
3243 SendToPlayer(&buf[next_out], oldi - next_out);
3246 case H_GOT_UNREQ_HEADER:
3247 ics_getting_history = H_GETTING_MOVES;
3248 started = STARTED_MOVES_NOHIDE;
3251 case H_GOT_UNWANTED_HEADER:
3252 ics_getting_history = H_FALSE;
3258 if (looking_at(buf, &i, "% ") ||
3259 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3260 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3261 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3262 soughtPending = FALSE;
3266 if(suppressKibitz) next_out = i;
3267 savingComment = FALSE;
3271 case STARTED_MOVES_NOHIDE:
3272 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3273 parse[parse_pos + i - oldi] = NULLCHAR;
3274 ParseGameHistory(parse);
3276 if (appData.zippyPlay && first.initDone) {
3277 FeedMovesToProgram(&first, forwardMostMove);
3278 if (gameMode == IcsPlayingWhite) {
3279 if (WhiteOnMove(forwardMostMove)) {
3280 if (first.sendTime) {
3281 if (first.useColors) {
3282 SendToProgram("black\n", &first);
3284 SendTimeRemaining(&first, TRUE);
3286 if (first.useColors) {
3287 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3289 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3290 first.maybeThinking = TRUE;
3292 if (first.usePlayother) {
3293 if (first.sendTime) {
3294 SendTimeRemaining(&first, TRUE);
3296 SendToProgram("playother\n", &first);
3302 } else if (gameMode == IcsPlayingBlack) {
3303 if (!WhiteOnMove(forwardMostMove)) {
3304 if (first.sendTime) {
3305 if (first.useColors) {
3306 SendToProgram("white\n", &first);
3308 SendTimeRemaining(&first, FALSE);
3310 if (first.useColors) {
3311 SendToProgram("black\n", &first);
3313 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3314 first.maybeThinking = TRUE;
3316 if (first.usePlayother) {
3317 if (first.sendTime) {
3318 SendTimeRemaining(&first, FALSE);
3320 SendToProgram("playother\n", &first);
3329 if (gameMode == IcsObserving && ics_gamenum == -1) {
3330 /* Moves came from oldmoves or moves command
3331 while we weren't doing anything else.
3333 currentMove = forwardMostMove;
3334 ClearHighlights();/*!!could figure this out*/
3335 flipView = appData.flipView;
3336 DrawPosition(TRUE, boards[currentMove]);
3337 DisplayBothClocks();
3338 snprintf(str, MSG_SIZ, "%s vs. %s",
3339 gameInfo.white, gameInfo.black);
3343 /* Moves were history of an active game */
3344 if (gameInfo.resultDetails != NULL) {
3345 free(gameInfo.resultDetails);
3346 gameInfo.resultDetails = NULL;
3349 HistorySet(parseList, backwardMostMove,
3350 forwardMostMove, currentMove-1);
3351 DisplayMove(currentMove - 1);
3352 if (started == STARTED_MOVES) next_out = i;
3353 started = STARTED_NONE;
3354 ics_getting_history = H_FALSE;
3357 case STARTED_OBSERVE:
3358 started = STARTED_NONE;
3359 SendToICS(ics_prefix);
3360 SendToICS("refresh\n");
3366 if(bookHit) { // [HGM] book: simulate book reply
3367 static char bookMove[MSG_SIZ]; // a bit generous?
3369 programStats.nodes = programStats.depth = programStats.time =
3370 programStats.score = programStats.got_only_move = 0;
3371 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3373 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3374 strcat(bookMove, bookHit);
3375 HandleMachineMove(bookMove, &first);
3380 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3381 started == STARTED_HOLDINGS ||
3382 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3383 /* Accumulate characters in move list or board */
3384 parse[parse_pos++] = buf[i];
3387 /* Start of game messages. Mostly we detect start of game
3388 when the first board image arrives. On some versions
3389 of the ICS, though, we need to do a "refresh" after starting
3390 to observe in order to get the current board right away. */
3391 if (looking_at(buf, &i, "Adding game * to observation list")) {
3392 started = STARTED_OBSERVE;
3396 /* Handle auto-observe */
3397 if (appData.autoObserve &&
3398 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3399 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3401 /* Choose the player that was highlighted, if any. */
3402 if (star_match[0][0] == '\033' ||
3403 star_match[1][0] != '\033') {
3404 player = star_match[0];
3406 player = star_match[2];
3408 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3409 ics_prefix, StripHighlightAndTitle(player));
3412 /* Save ratings from notify string */
3413 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3414 player1Rating = string_to_rating(star_match[1]);
3415 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3416 player2Rating = string_to_rating(star_match[3]);
3418 if (appData.debugMode)
3420 "Ratings from 'Game notification:' %s %d, %s %d\n",
3421 player1Name, player1Rating,
3422 player2Name, player2Rating);
3427 /* Deal with automatic examine mode after a game,
3428 and with IcsObserving -> IcsExamining transition */
3429 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3430 looking_at(buf, &i, "has made you an examiner of game *")) {
3432 int gamenum = atoi(star_match[0]);
3433 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3434 gamenum == ics_gamenum) {
3435 /* We were already playing or observing this game;
3436 no need to refetch history */
3437 gameMode = IcsExamining;
3439 pauseExamForwardMostMove = forwardMostMove;
3440 } else if (currentMove < forwardMostMove) {
3441 ForwardInner(forwardMostMove);
3444 /* I don't think this case really can happen */
3445 SendToICS(ics_prefix);
3446 SendToICS("refresh\n");
3451 /* Error messages */
3452 // if (ics_user_moved) {
3453 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3454 if (looking_at(buf, &i, "Illegal move") ||
3455 looking_at(buf, &i, "Not a legal move") ||
3456 looking_at(buf, &i, "Your king is in check") ||
3457 looking_at(buf, &i, "It isn't your turn") ||
3458 looking_at(buf, &i, "It is not your move")) {
3460 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3461 currentMove = forwardMostMove-1;
3462 DisplayMove(currentMove - 1); /* before DMError */
3463 DrawPosition(FALSE, boards[currentMove]);
3464 SwitchClocks(forwardMostMove-1); // [HGM] race
3465 DisplayBothClocks();
3467 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3473 if (looking_at(buf, &i, "still have time") ||
3474 looking_at(buf, &i, "not out of time") ||
3475 looking_at(buf, &i, "either player is out of time") ||
3476 looking_at(buf, &i, "has timeseal; checking")) {
3477 /* We must have called his flag a little too soon */
3478 whiteFlag = blackFlag = FALSE;
3482 if (looking_at(buf, &i, "added * seconds to") ||
3483 looking_at(buf, &i, "seconds were added to")) {
3484 /* Update the clocks */
3485 SendToICS(ics_prefix);
3486 SendToICS("refresh\n");
3490 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3491 ics_clock_paused = TRUE;
3496 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3497 ics_clock_paused = FALSE;
3502 /* Grab player ratings from the Creating: message.
3503 Note we have to check for the special case when
3504 the ICS inserts things like [white] or [black]. */
3505 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3506 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3508 0 player 1 name (not necessarily white)
3510 2 empty, white, or black (IGNORED)
3511 3 player 2 name (not necessarily black)
3514 The names/ratings are sorted out when the game
3515 actually starts (below).
3517 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3518 player1Rating = string_to_rating(star_match[1]);
3519 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3520 player2Rating = string_to_rating(star_match[4]);
3522 if (appData.debugMode)
3524 "Ratings from 'Creating:' %s %d, %s %d\n",
3525 player1Name, player1Rating,
3526 player2Name, player2Rating);
3531 /* Improved generic start/end-of-game messages */
3532 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3533 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3534 /* If tkind == 0: */
3535 /* star_match[0] is the game number */
3536 /* [1] is the white player's name */
3537 /* [2] is the black player's name */
3538 /* For end-of-game: */
3539 /* [3] is the reason for the game end */
3540 /* [4] is a PGN end game-token, preceded by " " */
3541 /* For start-of-game: */
3542 /* [3] begins with "Creating" or "Continuing" */
3543 /* [4] is " *" or empty (don't care). */
3544 int gamenum = atoi(star_match[0]);
3545 char *whitename, *blackname, *why, *endtoken;
3546 ChessMove endtype = EndOfFile;
3549 whitename = star_match[1];
3550 blackname = star_match[2];
3551 why = star_match[3];
3552 endtoken = star_match[4];
3554 whitename = star_match[1];
3555 blackname = star_match[3];
3556 why = star_match[5];
3557 endtoken = star_match[6];
3560 /* Game start messages */
3561 if (strncmp(why, "Creating ", 9) == 0 ||
3562 strncmp(why, "Continuing ", 11) == 0) {
3563 gs_gamenum = gamenum;
3564 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3565 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3567 if (appData.zippyPlay) {
3568 ZippyGameStart(whitename, blackname);
3571 partnerBoardValid = FALSE; // [HGM] bughouse
3575 /* Game end messages */
3576 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3577 ics_gamenum != gamenum) {
3580 while (endtoken[0] == ' ') endtoken++;
3581 switch (endtoken[0]) {
3584 endtype = GameUnfinished;
3587 endtype = BlackWins;
3590 if (endtoken[1] == '/')
3591 endtype = GameIsDrawn;
3593 endtype = WhiteWins;
3596 GameEnds(endtype, why, GE_ICS);
3598 if (appData.zippyPlay && first.initDone) {
3599 ZippyGameEnd(endtype, why);
3600 if (first.pr == NULL) {
3601 /* Start the next process early so that we'll
3602 be ready for the next challenge */
3603 StartChessProgram(&first);
3605 /* Send "new" early, in case this command takes
3606 a long time to finish, so that we'll be ready
3607 for the next challenge. */
3608 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3612 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3616 if (looking_at(buf, &i, "Removing game * from observation") ||
3617 looking_at(buf, &i, "no longer observing game *") ||
3618 looking_at(buf, &i, "Game * (*) has no examiners")) {
3619 if (gameMode == IcsObserving &&
3620 atoi(star_match[0]) == ics_gamenum)
3622 /* icsEngineAnalyze */
3623 if (appData.icsEngineAnalyze) {
3630 ics_user_moved = FALSE;
3635 if (looking_at(buf, &i, "no longer examining game *")) {
3636 if (gameMode == IcsExamining &&
3637 atoi(star_match[0]) == ics_gamenum)
3641 ics_user_moved = FALSE;
3646 /* Advance leftover_start past any newlines we find,
3647 so only partial lines can get reparsed */
3648 if (looking_at(buf, &i, "\n")) {
3649 prevColor = curColor;
3650 if (curColor != ColorNormal) {
3651 if (oldi > next_out) {
3652 SendToPlayer(&buf[next_out], oldi - next_out);
3655 Colorize(ColorNormal, FALSE);
3656 curColor = ColorNormal;
3658 if (started == STARTED_BOARD) {
3659 started = STARTED_NONE;
3660 parse[parse_pos] = NULLCHAR;
3661 ParseBoard12(parse);
3664 /* Send premove here */
3665 if (appData.premove) {
3667 if (currentMove == 0 &&
3668 gameMode == IcsPlayingWhite &&
3669 appData.premoveWhite) {
3670 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3671 if (appData.debugMode)
3672 fprintf(debugFP, "Sending premove:\n");
3674 } else if (currentMove == 1 &&
3675 gameMode == IcsPlayingBlack &&
3676 appData.premoveBlack) {
3677 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3678 if (appData.debugMode)
3679 fprintf(debugFP, "Sending premove:\n");
3681 } else if (gotPremove) {
3683 ClearPremoveHighlights();
3684 if (appData.debugMode)
3685 fprintf(debugFP, "Sending premove:\n");
3686 UserMoveEvent(premoveFromX, premoveFromY,
3687 premoveToX, premoveToY,
3692 /* Usually suppress following prompt */
3693 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3694 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3695 if (looking_at(buf, &i, "*% ")) {
3696 savingComment = FALSE;
3701 } else if (started == STARTED_HOLDINGS) {
3703 char new_piece[MSG_SIZ];
3704 started = STARTED_NONE;
3705 parse[parse_pos] = NULLCHAR;
3706 if (appData.debugMode)
3707 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3708 parse, currentMove);
3709 if (sscanf(parse, " game %d", &gamenum) == 1) {
3710 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3711 if (gameInfo.variant == VariantNormal) {
3712 /* [HGM] We seem to switch variant during a game!
3713 * Presumably no holdings were displayed, so we have
3714 * to move the position two files to the right to
3715 * create room for them!
3717 VariantClass newVariant;
3718 switch(gameInfo.boardWidth) { // base guess on board width
3719 case 9: newVariant = VariantShogi; break;
3720 case 10: newVariant = VariantGreat; break;
3721 default: newVariant = VariantCrazyhouse; break;
3723 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3724 /* Get a move list just to see the header, which
3725 will tell us whether this is really bug or zh */
3726 if (ics_getting_history == H_FALSE) {
3727 ics_getting_history = H_REQUESTED;
3728 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3732 new_piece[0] = NULLCHAR;
3733 sscanf(parse, "game %d white [%s black [%s <- %s",
3734 &gamenum, white_holding, black_holding,
3736 white_holding[strlen(white_holding)-1] = NULLCHAR;
3737 black_holding[strlen(black_holding)-1] = NULLCHAR;
3738 /* [HGM] copy holdings to board holdings area */
3739 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3740 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3741 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3743 if (appData.zippyPlay && first.initDone) {
3744 ZippyHoldings(white_holding, black_holding,
3748 if (tinyLayout || smallLayout) {
3749 char wh[16], bh[16];
3750 PackHolding(wh, white_holding);
3751 PackHolding(bh, black_holding);
3752 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3753 gameInfo.white, gameInfo.black);
3755 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3756 gameInfo.white, white_holding,
3757 gameInfo.black, black_holding);
3759 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3760 DrawPosition(FALSE, boards[currentMove]);
3762 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3763 sscanf(parse, "game %d white [%s black [%s <- %s",
3764 &gamenum, white_holding, black_holding,
3766 white_holding[strlen(white_holding)-1] = NULLCHAR;
3767 black_holding[strlen(black_holding)-1] = NULLCHAR;
3768 /* [HGM] copy holdings to partner-board holdings area */
3769 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3770 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3771 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3772 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3773 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3776 /* Suppress following prompt */
3777 if (looking_at(buf, &i, "*% ")) {
3778 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3779 savingComment = FALSE;
3787 i++; /* skip unparsed character and loop back */
3790 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3791 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3792 // SendToPlayer(&buf[next_out], i - next_out);
3793 started != STARTED_HOLDINGS && leftover_start > next_out) {
3794 SendToPlayer(&buf[next_out], leftover_start - next_out);
3798 leftover_len = buf_len - leftover_start;
3799 /* if buffer ends with something we couldn't parse,
3800 reparse it after appending the next read */
3802 } else if (count == 0) {
3803 RemoveInputSource(isr);
3804 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3806 DisplayFatalError(_("Error reading from ICS"), error, 1);
3811 /* Board style 12 looks like this:
3813 <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
3815 * The "<12> " is stripped before it gets to this routine. The two
3816 * trailing 0's (flip state and clock ticking) are later addition, and
3817 * some chess servers may not have them, or may have only the first.
3818 * Additional trailing fields may be added in the future.
3821 #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"
3823 #define RELATION_OBSERVING_PLAYED 0
3824 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3825 #define RELATION_PLAYING_MYMOVE 1
3826 #define RELATION_PLAYING_NOTMYMOVE -1
3827 #define RELATION_EXAMINING 2
3828 #define RELATION_ISOLATED_BOARD -3
3829 #define RELATION_STARTING_POSITION -4 /* FICS only */
3832 ParseBoard12(string)
3835 GameMode newGameMode;
3836 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3837 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3838 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3839 char to_play, board_chars[200];
3840 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3841 char black[32], white[32];
3843 int prevMove = currentMove;
3846 int fromX, fromY, toX, toY;
3848 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3849 char *bookHit = NULL; // [HGM] book
3850 Boolean weird = FALSE, reqFlag = FALSE;
3852 fromX = fromY = toX = toY = -1;
3856 if (appData.debugMode)
3857 fprintf(debugFP, _("Parsing board: %s\n"), string);
3859 move_str[0] = NULLCHAR;
3860 elapsed_time[0] = NULLCHAR;
3861 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3863 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3864 if(string[i] == ' ') { ranks++; files = 0; }
3866 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3869 for(j = 0; j <i; j++) board_chars[j] = string[j];
3870 board_chars[i] = '\0';
3873 n = sscanf(string, PATTERN, &to_play, &double_push,
3874 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3875 &gamenum, white, black, &relation, &basetime, &increment,
3876 &white_stren, &black_stren, &white_time, &black_time,
3877 &moveNum, str, elapsed_time, move_str, &ics_flip,
3881 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3882 DisplayError(str, 0);
3886 /* Convert the move number to internal form */
3887 moveNum = (moveNum - 1) * 2;
3888 if (to_play == 'B') moveNum++;
3889 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3890 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3896 case RELATION_OBSERVING_PLAYED:
3897 case RELATION_OBSERVING_STATIC:
3898 if (gamenum == -1) {
3899 /* Old ICC buglet */
3900 relation = RELATION_OBSERVING_STATIC;
3902 newGameMode = IcsObserving;
3904 case RELATION_PLAYING_MYMOVE:
3905 case RELATION_PLAYING_NOTMYMOVE:
3907 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3908 IcsPlayingWhite : IcsPlayingBlack;
3910 case RELATION_EXAMINING:
3911 newGameMode = IcsExamining;
3913 case RELATION_ISOLATED_BOARD:
3915 /* Just display this board. If user was doing something else,
3916 we will forget about it until the next board comes. */
3917 newGameMode = IcsIdle;
3919 case RELATION_STARTING_POSITION:
3920 newGameMode = gameMode;
3924 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3925 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3926 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3928 for (k = 0; k < ranks; k++) {
3929 for (j = 0; j < files; j++)
3930 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3931 if(gameInfo.holdingsWidth > 1) {
3932 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3933 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3936 CopyBoard(partnerBoard, board);
3937 if(toSqr = strchr(str, '/')) { // extract highlights from long move
3938 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3939 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3940 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3941 if(toSqr = strchr(str, '-')) {
3942 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3943 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3944 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3945 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3946 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3947 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3948 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3949 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3950 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3951 DisplayMessage(partnerStatus, "");
3952 partnerBoardValid = TRUE;
3956 /* Modify behavior for initial board display on move listing
3959 switch (ics_getting_history) {
3963 case H_GOT_REQ_HEADER:
3964 case H_GOT_UNREQ_HEADER:
3965 /* This is the initial position of the current game */
3966 gamenum = ics_gamenum;
3967 moveNum = 0; /* old ICS bug workaround */
3968 if (to_play == 'B') {
3969 startedFromSetupPosition = TRUE;
3970 blackPlaysFirst = TRUE;
3972 if (forwardMostMove == 0) forwardMostMove = 1;
3973 if (backwardMostMove == 0) backwardMostMove = 1;
3974 if (currentMove == 0) currentMove = 1;
3976 newGameMode = gameMode;
3977 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3979 case H_GOT_UNWANTED_HEADER:
3980 /* This is an initial board that we don't want */
3982 case H_GETTING_MOVES:
3983 /* Should not happen */
3984 DisplayError(_("Error gathering move list: extra board"), 0);
3985 ics_getting_history = H_FALSE;
3989 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3990 weird && (int)gameInfo.variant < (int)VariantShogi) {
3991 /* [HGM] We seem to have switched variant unexpectedly
3992 * Try to guess new variant from board size
3994 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3995 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3996 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3997 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3998 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3999 if(!weird) newVariant = VariantNormal;
4000 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4001 /* Get a move list just to see the header, which
4002 will tell us whether this is really bug or zh */
4003 if (ics_getting_history == H_FALSE) {
4004 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4005 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4010 /* Take action if this is the first board of a new game, or of a
4011 different game than is currently being displayed. */
4012 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4013 relation == RELATION_ISOLATED_BOARD) {
4015 /* Forget the old game and get the history (if any) of the new one */
4016 if (gameMode != BeginningOfGame) {
4020 if (appData.autoRaiseBoard) BoardToTop();
4022 if (gamenum == -1) {
4023 newGameMode = IcsIdle;
4024 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4025 appData.getMoveList && !reqFlag) {
4026 /* Need to get game history */
4027 ics_getting_history = H_REQUESTED;
4028 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4032 /* Initially flip the board to have black on the bottom if playing
4033 black or if the ICS flip flag is set, but let the user change
4034 it with the Flip View button. */
4035 flipView = appData.autoFlipView ?
4036 (newGameMode == IcsPlayingBlack) || ics_flip :
4039 /* Done with values from previous mode; copy in new ones */
4040 gameMode = newGameMode;
4042 ics_gamenum = gamenum;
4043 if (gamenum == gs_gamenum) {
4044 int klen = strlen(gs_kind);
4045 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4046 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4047 gameInfo.event = StrSave(str);
4049 gameInfo.event = StrSave("ICS game");
4051 gameInfo.site = StrSave(appData.icsHost);
4052 gameInfo.date = PGNDate();
4053 gameInfo.round = StrSave("-");
4054 gameInfo.white = StrSave(white);
4055 gameInfo.black = StrSave(black);
4056 timeControl = basetime * 60 * 1000;
4058 timeIncrement = increment * 1000;
4059 movesPerSession = 0;
4060 gameInfo.timeControl = TimeControlTagValue();
4061 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4062 if (appData.debugMode) {
4063 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4064 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4065 setbuf(debugFP, NULL);
4068 gameInfo.outOfBook = NULL;
4070 /* Do we have the ratings? */
4071 if (strcmp(player1Name, white) == 0 &&
4072 strcmp(player2Name, black) == 0) {
4073 if (appData.debugMode)
4074 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4075 player1Rating, player2Rating);
4076 gameInfo.whiteRating = player1Rating;
4077 gameInfo.blackRating = player2Rating;
4078 } else if (strcmp(player2Name, white) == 0 &&
4079 strcmp(player1Name, black) == 0) {
4080 if (appData.debugMode)
4081 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4082 player2Rating, player1Rating);
4083 gameInfo.whiteRating = player2Rating;
4084 gameInfo.blackRating = player1Rating;
4086 player1Name[0] = player2Name[0] = NULLCHAR;
4088 /* Silence shouts if requested */
4089 if (appData.quietPlay &&
4090 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4091 SendToICS(ics_prefix);
4092 SendToICS("set shout 0\n");
4096 /* Deal with midgame name changes */
4098 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4099 if (gameInfo.white) free(gameInfo.white);
4100 gameInfo.white = StrSave(white);
4102 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4103 if (gameInfo.black) free(gameInfo.black);
4104 gameInfo.black = StrSave(black);
4108 /* Throw away game result if anything actually changes in examine mode */
4109 if (gameMode == IcsExamining && !newGame) {
4110 gameInfo.result = GameUnfinished;
4111 if (gameInfo.resultDetails != NULL) {
4112 free(gameInfo.resultDetails);
4113 gameInfo.resultDetails = NULL;
4117 /* In pausing && IcsExamining mode, we ignore boards coming
4118 in if they are in a different variation than we are. */
4119 if (pauseExamInvalid) return;
4120 if (pausing && gameMode == IcsExamining) {
4121 if (moveNum <= pauseExamForwardMostMove) {
4122 pauseExamInvalid = TRUE;
4123 forwardMostMove = pauseExamForwardMostMove;
4128 if (appData.debugMode) {
4129 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4131 /* Parse the board */
4132 for (k = 0; k < ranks; k++) {
4133 for (j = 0; j < files; j++)
4134 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4135 if(gameInfo.holdingsWidth > 1) {
4136 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4137 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4140 CopyBoard(boards[moveNum], board);
4141 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4143 startedFromSetupPosition =
4144 !CompareBoards(board, initialPosition);
4145 if(startedFromSetupPosition)
4146 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4149 /* [HGM] Set castling rights. Take the outermost Rooks,
4150 to make it also work for FRC opening positions. Note that board12
4151 is really defective for later FRC positions, as it has no way to
4152 indicate which Rook can castle if they are on the same side of King.
4153 For the initial position we grant rights to the outermost Rooks,
4154 and remember thos rights, and we then copy them on positions
4155 later in an FRC game. This means WB might not recognize castlings with
4156 Rooks that have moved back to their original position as illegal,
4157 but in ICS mode that is not its job anyway.
4159 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4160 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4162 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4163 if(board[0][i] == WhiteRook) j = i;
4164 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4165 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4166 if(board[0][i] == WhiteRook) j = i;
4167 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4168 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4169 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4170 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4171 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4172 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4173 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4175 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4176 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4177 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4178 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4179 if(board[BOARD_HEIGHT-1][k] == bKing)
4180 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4181 if(gameInfo.variant == VariantTwoKings) {
4182 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4183 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4184 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4187 r = boards[moveNum][CASTLING][0] = initialRights[0];
4188 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4189 r = boards[moveNum][CASTLING][1] = initialRights[1];
4190 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4191 r = boards[moveNum][CASTLING][3] = initialRights[3];
4192 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4193 r = boards[moveNum][CASTLING][4] = initialRights[4];
4194 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4195 /* wildcastle kludge: always assume King has rights */
4196 r = boards[moveNum][CASTLING][2] = initialRights[2];
4197 r = boards[moveNum][CASTLING][5] = initialRights[5];
4199 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4200 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4203 if (ics_getting_history == H_GOT_REQ_HEADER ||
4204 ics_getting_history == H_GOT_UNREQ_HEADER) {
4205 /* This was an initial position from a move list, not
4206 the current position */
4210 /* Update currentMove and known move number limits */
4211 newMove = newGame || moveNum > forwardMostMove;
4214 forwardMostMove = backwardMostMove = currentMove = moveNum;
4215 if (gameMode == IcsExamining && moveNum == 0) {
4216 /* Workaround for ICS limitation: we are not told the wild
4217 type when starting to examine a game. But if we ask for
4218 the move list, the move list header will tell us */
4219 ics_getting_history = H_REQUESTED;
4220 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4223 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4224 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4226 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4227 /* [HGM] applied this also to an engine that is silently watching */
4228 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4229 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4230 gameInfo.variant == currentlyInitializedVariant) {
4231 takeback = forwardMostMove - moveNum;
4232 for (i = 0; i < takeback; i++) {
4233 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4234 SendToProgram("undo\n", &first);
4239 forwardMostMove = moveNum;
4240 if (!pausing || currentMove > forwardMostMove)
4241 currentMove = forwardMostMove;
4243 /* New part of history that is not contiguous with old part */
4244 if (pausing && gameMode == IcsExamining) {
4245 pauseExamInvalid = TRUE;
4246 forwardMostMove = pauseExamForwardMostMove;
4249 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4251 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4252 // [HGM] when we will receive the move list we now request, it will be
4253 // fed to the engine from the first move on. So if the engine is not
4254 // in the initial position now, bring it there.
4255 InitChessProgram(&first, 0);
4258 ics_getting_history = H_REQUESTED;
4259 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4262 forwardMostMove = backwardMostMove = currentMove = moveNum;
4265 /* Update the clocks */
4266 if (strchr(elapsed_time, '.')) {
4268 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4269 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4271 /* Time is in seconds */
4272 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4273 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4278 if (appData.zippyPlay && newGame &&
4279 gameMode != IcsObserving && gameMode != IcsIdle &&
4280 gameMode != IcsExamining)
4281 ZippyFirstBoard(moveNum, basetime, increment);
4284 /* Put the move on the move list, first converting
4285 to canonical algebraic form. */
4287 if (appData.debugMode) {
4288 if (appData.debugMode) { int f = forwardMostMove;
4289 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4290 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4291 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4293 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4294 fprintf(debugFP, "moveNum = %d\n", moveNum);
4295 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4296 setbuf(debugFP, NULL);
4298 if (moveNum <= backwardMostMove) {
4299 /* We don't know what the board looked like before
4301 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4302 strcat(parseList[moveNum - 1], " ");
4303 strcat(parseList[moveNum - 1], elapsed_time);
4304 moveList[moveNum - 1][0] = NULLCHAR;
4305 } else if (strcmp(move_str, "none") == 0) {
4306 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4307 /* Again, we don't know what the board looked like;
4308 this is really the start of the game. */
4309 parseList[moveNum - 1][0] = NULLCHAR;
4310 moveList[moveNum - 1][0] = NULLCHAR;
4311 backwardMostMove = moveNum;
4312 startedFromSetupPosition = TRUE;
4313 fromX = fromY = toX = toY = -1;
4315 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4316 // So we parse the long-algebraic move string in stead of the SAN move
4317 int valid; char buf[MSG_SIZ], *prom;
4319 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4320 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4321 // str looks something like "Q/a1-a2"; kill the slash
4323 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4324 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4325 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4326 strcat(buf, prom); // long move lacks promo specification!
4327 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4328 if(appData.debugMode)
4329 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4330 safeStrCpy(move_str, buf, MSG_SIZ);
4332 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4333 &fromX, &fromY, &toX, &toY, &promoChar)
4334 || ParseOneMove(buf, moveNum - 1, &moveType,
4335 &fromX, &fromY, &toX, &toY, &promoChar);
4336 // end of long SAN patch
4338 (void) CoordsToAlgebraic(boards[moveNum - 1],
4339 PosFlags(moveNum - 1),
4340 fromY, fromX, toY, toX, promoChar,
4341 parseList[moveNum-1]);
4342 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4348 if(gameInfo.variant != VariantShogi)
4349 strcat(parseList[moveNum - 1], "+");
4352 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4353 strcat(parseList[moveNum - 1], "#");
4356 strcat(parseList[moveNum - 1], " ");
4357 strcat(parseList[moveNum - 1], elapsed_time);
4358 /* currentMoveString is set as a side-effect of ParseOneMove */
4359 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4360 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4361 strcat(moveList[moveNum - 1], "\n");
4363 if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4364 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4365 ChessSquare old, new = boards[moveNum][k][j];
4366 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4367 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4368 if(old == new) continue;
4369 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4370 else if(new == WhiteWazir || new == BlackWazir) {
4371 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4372 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4373 else boards[moveNum][k][j] = old; // preserve type of Gold
4374 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4375 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4378 /* Move from ICS was illegal!? Punt. */
4379 if (appData.debugMode) {
4380 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4381 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4383 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4384 strcat(parseList[moveNum - 1], " ");
4385 strcat(parseList[moveNum - 1], elapsed_time);
4386 moveList[moveNum - 1][0] = NULLCHAR;
4387 fromX = fromY = toX = toY = -1;
4390 if (appData.debugMode) {
4391 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4392 setbuf(debugFP, NULL);
4396 /* Send move to chess program (BEFORE animating it). */
4397 if (appData.zippyPlay && !newGame && newMove &&
4398 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4400 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4401 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4402 if (moveList[moveNum - 1][0] == NULLCHAR) {
4403 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4405 DisplayError(str, 0);
4407 if (first.sendTime) {
4408 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4410 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4411 if (firstMove && !bookHit) {
4413 if (first.useColors) {
4414 SendToProgram(gameMode == IcsPlayingWhite ?
4416 "black\ngo\n", &first);
4418 SendToProgram("go\n", &first);
4420 first.maybeThinking = TRUE;
4423 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4424 if (moveList[moveNum - 1][0] == NULLCHAR) {
4425 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4426 DisplayError(str, 0);
4428 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4429 SendMoveToProgram(moveNum - 1, &first);
4436 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4437 /* If move comes from a remote source, animate it. If it
4438 isn't remote, it will have already been animated. */
4439 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4440 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4442 if (!pausing && appData.highlightLastMove) {
4443 SetHighlights(fromX, fromY, toX, toY);
4447 /* Start the clocks */
4448 whiteFlag = blackFlag = FALSE;
4449 appData.clockMode = !(basetime == 0 && increment == 0);
4451 ics_clock_paused = TRUE;
4453 } else if (ticking == 1) {
4454 ics_clock_paused = FALSE;
4456 if (gameMode == IcsIdle ||
4457 relation == RELATION_OBSERVING_STATIC ||
4458 relation == RELATION_EXAMINING ||
4460 DisplayBothClocks();
4464 /* Display opponents and material strengths */
4465 if (gameInfo.variant != VariantBughouse &&
4466 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4467 if (tinyLayout || smallLayout) {
4468 if(gameInfo.variant == VariantNormal)
4469 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4470 gameInfo.white, white_stren, gameInfo.black, black_stren,
4471 basetime, increment);
4473 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4474 gameInfo.white, white_stren, gameInfo.black, black_stren,
4475 basetime, increment, (int) gameInfo.variant);
4477 if(gameInfo.variant == VariantNormal)
4478 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4479 gameInfo.white, white_stren, gameInfo.black, black_stren,
4480 basetime, increment);
4482 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4483 gameInfo.white, white_stren, gameInfo.black, black_stren,
4484 basetime, increment, VariantName(gameInfo.variant));
4487 if (appData.debugMode) {
4488 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4493 /* Display the board */
4494 if (!pausing && !appData.noGUI) {
4496 if (appData.premove)
4498 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4499 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4500 ClearPremoveHighlights();
4502 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4503 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4504 DrawPosition(j, boards[currentMove]);
4506 DisplayMove(moveNum - 1);
4507 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4508 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4509 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4510 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4514 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4516 if(bookHit) { // [HGM] book: simulate book reply
4517 static char bookMove[MSG_SIZ]; // a bit generous?
4519 programStats.nodes = programStats.depth = programStats.time =
4520 programStats.score = programStats.got_only_move = 0;
4521 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4523 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4524 strcat(bookMove, bookHit);
4525 HandleMachineMove(bookMove, &first);
4534 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4535 ics_getting_history = H_REQUESTED;
4536 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4542 AnalysisPeriodicEvent(force)
4545 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4546 && !force) || !appData.periodicUpdates)
4549 /* Send . command to Crafty to collect stats */
4550 SendToProgram(".\n", &first);
4552 /* Don't send another until we get a response (this makes
4553 us stop sending to old Crafty's which don't understand
4554 the "." command (sending illegal cmds resets node count & time,
4555 which looks bad)) */
4556 programStats.ok_to_send = 0;
4559 void ics_update_width(new_width)
4562 ics_printf("set width %d\n", new_width);
4566 SendMoveToProgram(moveNum, cps)
4568 ChessProgramState *cps;
4572 if (cps->useUsermove) {
4573 SendToProgram("usermove ", cps);
4577 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4578 int len = space - parseList[moveNum];
4579 memcpy(buf, parseList[moveNum], len);
4581 buf[len] = NULLCHAR;
4583 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4585 SendToProgram(buf, cps);
4587 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4588 AlphaRank(moveList[moveNum], 4);
4589 SendToProgram(moveList[moveNum], cps);
4590 AlphaRank(moveList[moveNum], 4); // and back
4592 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4593 * the engine. It would be nice to have a better way to identify castle
4595 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4596 && cps->useOOCastle) {
4597 int fromX = moveList[moveNum][0] - AAA;
4598 int fromY = moveList[moveNum][1] - ONE;
4599 int toX = moveList[moveNum][2] - AAA;
4600 int toY = moveList[moveNum][3] - ONE;
4601 if((boards[moveNum][fromY][fromX] == WhiteKing
4602 && boards[moveNum][toY][toX] == WhiteRook)
4603 || (boards[moveNum][fromY][fromX] == BlackKing
4604 && boards[moveNum][toY][toX] == BlackRook)) {
4605 if(toX > fromX) SendToProgram("O-O\n", cps);
4606 else SendToProgram("O-O-O\n", cps);
4608 else SendToProgram(moveList[moveNum], cps);
4610 else SendToProgram(moveList[moveNum], cps);
4611 /* End of additions by Tord */
4614 /* [HGM] setting up the opening has brought engine in force mode! */
4615 /* Send 'go' if we are in a mode where machine should play. */
4616 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4617 (gameMode == TwoMachinesPlay ||
4619 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4621 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4622 SendToProgram("go\n", cps);
4623 if (appData.debugMode) {
4624 fprintf(debugFP, "(extra)\n");
4627 setboardSpoiledMachineBlack = 0;
4631 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4633 int fromX, fromY, toX, toY;
4636 char user_move[MSG_SIZ];
4640 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4641 (int)moveType, fromX, fromY, toX, toY);
4642 DisplayError(user_move + strlen("say "), 0);
4644 case WhiteKingSideCastle:
4645 case BlackKingSideCastle:
4646 case WhiteQueenSideCastleWild:
4647 case BlackQueenSideCastleWild:
4649 case WhiteHSideCastleFR:
4650 case BlackHSideCastleFR:
4652 snprintf(user_move, MSG_SIZ, "o-o\n");
4654 case WhiteQueenSideCastle:
4655 case BlackQueenSideCastle:
4656 case WhiteKingSideCastleWild:
4657 case BlackKingSideCastleWild:
4659 case WhiteASideCastleFR:
4660 case BlackASideCastleFR:
4662 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4664 case WhiteNonPromotion:
4665 case BlackNonPromotion:
4666 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4668 case WhitePromotion:
4669 case BlackPromotion:
4670 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4671 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4672 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4673 PieceToChar(WhiteFerz));
4674 else if(gameInfo.variant == VariantGreat)
4675 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4676 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4677 PieceToChar(WhiteMan));
4679 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4680 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4686 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4687 ToUpper(PieceToChar((ChessSquare) fromX)),
4688 AAA + toX, ONE + toY);
4690 case IllegalMove: /* could be a variant we don't quite understand */
4691 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4693 case WhiteCapturesEnPassant:
4694 case BlackCapturesEnPassant:
4695 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4696 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4699 SendToICS(user_move);
4700 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4701 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4706 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4707 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4708 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4709 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4710 DisplayError("You cannot do this while you are playing or observing", 0);
4713 if(gameMode != IcsExamining) { // is this ever not the case?
4714 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4716 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4717 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4718 } else { // on FICS we must first go to general examine mode
4719 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4721 if(gameInfo.variant != VariantNormal) {
4722 // try figure out wild number, as xboard names are not always valid on ICS
4723 for(i=1; i<=36; i++) {
4724 snprintf(buf, MSG_SIZ, "wild/%d", i);
4725 if(StringToVariant(buf) == gameInfo.variant) break;
4727 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4728 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4729 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4730 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4731 SendToICS(ics_prefix);
4733 if(startedFromSetupPosition || backwardMostMove != 0) {
4734 fen = PositionToFEN(backwardMostMove, NULL);
4735 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4736 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4738 } else { // FICS: everything has to set by separate bsetup commands
4739 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4740 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4742 if(!WhiteOnMove(backwardMostMove)) {
4743 SendToICS("bsetup tomove black\n");
4745 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4746 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4748 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4749 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4751 i = boards[backwardMostMove][EP_STATUS];
4752 if(i >= 0) { // set e.p.
4753 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4759 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4760 SendToICS("bsetup done\n"); // switch to normal examining.
4762 for(i = backwardMostMove; i<last; i++) {
4764 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4767 SendToICS(ics_prefix);
4768 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4772 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4777 if (rf == DROP_RANK) {
4778 sprintf(move, "%c@%c%c\n",
4779 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4781 if (promoChar == 'x' || promoChar == NULLCHAR) {
4782 sprintf(move, "%c%c%c%c\n",
4783 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4785 sprintf(move, "%c%c%c%c%c\n",
4786 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4792 ProcessICSInitScript(f)
4797 while (fgets(buf, MSG_SIZ, f)) {
4798 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4805 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4807 AlphaRank(char *move, int n)
4809 // char *p = move, c; int x, y;
4811 if (appData.debugMode) {
4812 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4816 move[2]>='0' && move[2]<='9' &&
4817 move[3]>='a' && move[3]<='x' ) {
4819 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4820 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4822 if(move[0]>='0' && move[0]<='9' &&
4823 move[1]>='a' && move[1]<='x' &&
4824 move[2]>='0' && move[2]<='9' &&
4825 move[3]>='a' && move[3]<='x' ) {
4826 /* input move, Shogi -> normal */
4827 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4828 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4829 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4830 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4833 move[3]>='0' && move[3]<='9' &&
4834 move[2]>='a' && move[2]<='x' ) {
4836 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4837 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4840 move[0]>='a' && move[0]<='x' &&
4841 move[3]>='0' && move[3]<='9' &&
4842 move[2]>='a' && move[2]<='x' ) {
4843 /* output move, normal -> Shogi */
4844 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4845 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4846 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4847 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4848 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4850 if (appData.debugMode) {
4851 fprintf(debugFP, " out = '%s'\n", move);
4855 char yy_textstr[8000];
4857 /* Parser for moves from gnuchess, ICS, or user typein box */
4859 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4862 ChessMove *moveType;
4863 int *fromX, *fromY, *toX, *toY;
4866 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4868 switch (*moveType) {
4869 case WhitePromotion:
4870 case BlackPromotion:
4871 case WhiteNonPromotion:
4872 case BlackNonPromotion:
4874 case WhiteCapturesEnPassant:
4875 case BlackCapturesEnPassant:
4876 case WhiteKingSideCastle:
4877 case WhiteQueenSideCastle:
4878 case BlackKingSideCastle:
4879 case BlackQueenSideCastle:
4880 case WhiteKingSideCastleWild:
4881 case WhiteQueenSideCastleWild:
4882 case BlackKingSideCastleWild:
4883 case BlackQueenSideCastleWild:
4884 /* Code added by Tord: */
4885 case WhiteHSideCastleFR:
4886 case WhiteASideCastleFR:
4887 case BlackHSideCastleFR:
4888 case BlackASideCastleFR:
4889 /* End of code added by Tord */
4890 case IllegalMove: /* bug or odd chess variant */
4891 *fromX = currentMoveString[0] - AAA;
4892 *fromY = currentMoveString[1] - ONE;
4893 *toX = currentMoveString[2] - AAA;
4894 *toY = currentMoveString[3] - ONE;
4895 *promoChar = currentMoveString[4];
4896 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4897 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4898 if (appData.debugMode) {
4899 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4901 *fromX = *fromY = *toX = *toY = 0;
4904 if (appData.testLegality) {
4905 return (*moveType != IllegalMove);
4907 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4908 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4913 *fromX = *moveType == WhiteDrop ?
4914 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4915 (int) CharToPiece(ToLower(currentMoveString[0]));
4917 *toX = currentMoveString[2] - AAA;
4918 *toY = currentMoveString[3] - ONE;
4919 *promoChar = NULLCHAR;
4923 case ImpossibleMove:
4933 if (appData.debugMode) {
4934 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4937 *fromX = *fromY = *toX = *toY = 0;
4938 *promoChar = NULLCHAR;
4945 ParsePV(char *pv, Boolean storeComments)
4946 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4947 int fromX, fromY, toX, toY; char promoChar;
4952 endPV = forwardMostMove;
4954 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4955 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4956 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4957 if(appData.debugMode){
4958 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);
4960 if(!valid && nr == 0 &&
4961 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4962 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4963 // Hande case where played move is different from leading PV move
4964 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4965 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4966 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4967 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4968 endPV += 2; // if position different, keep this
4969 moveList[endPV-1][0] = fromX + AAA;
4970 moveList[endPV-1][1] = fromY + ONE;
4971 moveList[endPV-1][2] = toX + AAA;
4972 moveList[endPV-1][3] = toY + ONE;
4973 parseList[endPV-1][0] = NULLCHAR;
4974 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4977 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4978 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4979 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4980 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4981 valid++; // allow comments in PV
4985 if(endPV+1 > framePtr) break; // no space, truncate
4988 CopyBoard(boards[endPV], boards[endPV-1]);
4989 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4990 moveList[endPV-1][0] = fromX + AAA;
4991 moveList[endPV-1][1] = fromY + ONE;
4992 moveList[endPV-1][2] = toX + AAA;
4993 moveList[endPV-1][3] = toY + ONE;
4995 CoordsToAlgebraic(boards[endPV - 1],
4996 PosFlags(endPV - 1),
4997 fromY, fromX, toY, toX, promoChar,
4998 parseList[endPV - 1]);
5000 parseList[endPV-1][0] = NULLCHAR;
5002 currentMove = endPV;
5003 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5004 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5005 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5006 DrawPosition(TRUE, boards[currentMove]);
5009 static int lastX, lastY;
5012 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5017 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5018 lastX = x; lastY = y;
5019 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5021 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5022 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5024 do{ while(buf[index] && buf[index] != '\n') index++;
5025 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5027 ParsePV(buf+startPV, FALSE);
5028 *start = startPV; *end = index-1;
5033 LoadPV(int x, int y)
5034 { // called on right mouse click to load PV
5035 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5036 lastX = x; lastY = y;
5037 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5044 if(endPV < 0) return;
5046 currentMove = forwardMostMove;
5047 ClearPremoveHighlights();
5048 DrawPosition(TRUE, boards[currentMove]);
5052 MovePV(int x, int y, int h)
5053 { // step through PV based on mouse coordinates (called on mouse move)
5054 int margin = h>>3, step = 0;
5056 if(endPV < 0) return;
5057 // we must somehow check if right button is still down (might be released off board!)
5058 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5059 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5060 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5062 lastX = x; lastY = y;
5063 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5064 currentMove += step;
5065 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5066 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5067 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5068 DrawPosition(FALSE, boards[currentMove]);
5072 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5073 // All positions will have equal probability, but the current method will not provide a unique
5074 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5080 int piecesLeft[(int)BlackPawn];
5081 int seed, nrOfShuffles;
5083 void GetPositionNumber()
5084 { // sets global variable seed
5087 seed = appData.defaultFrcPosition;
5088 if(seed < 0) { // randomize based on time for negative FRC position numbers
5089 for(i=0; i<50; i++) seed += random();
5090 seed = random() ^ random() >> 8 ^ random() << 8;
5091 if(seed<0) seed = -seed;
5095 int put(Board board, int pieceType, int rank, int n, int shade)
5096 // put the piece on the (n-1)-th empty squares of the given shade
5100 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5101 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5102 board[rank][i] = (ChessSquare) pieceType;
5103 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5105 piecesLeft[pieceType]--;
5113 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5114 // calculate where the next piece goes, (any empty square), and put it there
5118 i = seed % squaresLeft[shade];
5119 nrOfShuffles *= squaresLeft[shade];
5120 seed /= squaresLeft[shade];
5121 put(board, pieceType, rank, i, shade);
5124 void AddTwoPieces(Board board, int pieceType, int rank)
5125 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5127 int i, n=squaresLeft[ANY], j=n-1, k;
5129 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5130 i = seed % k; // pick one
5133 while(i >= j) i -= j--;
5134 j = n - 1 - j; i += j;
5135 put(board, pieceType, rank, j, ANY);
5136 put(board, pieceType, rank, i, ANY);
5139 void SetUpShuffle(Board board, int number)
5143 GetPositionNumber(); nrOfShuffles = 1;
5145 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5146 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5147 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5149 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5151 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5152 p = (int) board[0][i];
5153 if(p < (int) BlackPawn) piecesLeft[p] ++;
5154 board[0][i] = EmptySquare;
5157 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5158 // shuffles restricted to allow normal castling put KRR first
5159 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5160 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5161 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5162 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5163 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5164 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5165 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5166 put(board, WhiteRook, 0, 0, ANY);
5167 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5170 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5171 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5172 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5173 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5174 while(piecesLeft[p] >= 2) {
5175 AddOnePiece(board, p, 0, LITE);
5176 AddOnePiece(board, p, 0, DARK);
5178 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5181 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5182 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5183 // but we leave King and Rooks for last, to possibly obey FRC restriction
5184 if(p == (int)WhiteRook) continue;
5185 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5186 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5189 // now everything is placed, except perhaps King (Unicorn) and Rooks
5191 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5192 // Last King gets castling rights
5193 while(piecesLeft[(int)WhiteUnicorn]) {
5194 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5195 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5198 while(piecesLeft[(int)WhiteKing]) {
5199 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5200 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5205 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5206 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5209 // Only Rooks can be left; simply place them all
5210 while(piecesLeft[(int)WhiteRook]) {
5211 i = put(board, WhiteRook, 0, 0, ANY);
5212 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5215 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5217 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5220 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5221 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5224 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5227 int SetCharTable( char *table, const char * map )
5228 /* [HGM] moved here from winboard.c because of its general usefulness */
5229 /* Basically a safe strcpy that uses the last character as King */
5231 int result = FALSE; int NrPieces;
5233 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5234 && NrPieces >= 12 && !(NrPieces&1)) {
5235 int i; /* [HGM] Accept even length from 12 to 34 */
5237 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5238 for( i=0; i<NrPieces/2-1; i++ ) {
5240 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5242 table[(int) WhiteKing] = map[NrPieces/2-1];
5243 table[(int) BlackKing] = map[NrPieces-1];
5251 void Prelude(Board board)
5252 { // [HGM] superchess: random selection of exo-pieces
5253 int i, j, k; ChessSquare p;
5254 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5256 GetPositionNumber(); // use FRC position number
5258 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5259 SetCharTable(pieceToChar, appData.pieceToCharTable);
5260 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5261 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5264 j = seed%4; seed /= 4;
5265 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5266 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5267 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5268 j = seed%3 + (seed%3 >= j); seed /= 3;
5269 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5270 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5271 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5272 j = seed%3; seed /= 3;
5273 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5274 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5275 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5276 j = seed%2 + (seed%2 >= j); seed /= 2;
5277 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5278 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5279 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5280 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5281 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5282 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5283 put(board, exoPieces[0], 0, 0, ANY);
5284 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5288 InitPosition(redraw)
5291 ChessSquare (* pieces)[BOARD_FILES];
5292 int i, j, pawnRow, overrule,
5293 oldx = gameInfo.boardWidth,
5294 oldy = gameInfo.boardHeight,
5295 oldh = gameInfo.holdingsWidth,
5296 oldv = gameInfo.variant;
5298 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5300 /* [AS] Initialize pv info list [HGM] and game status */
5302 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5303 pvInfoList[i].depth = 0;
5304 boards[i][EP_STATUS] = EP_NONE;
5305 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5308 initialRulePlies = 0; /* 50-move counter start */
5310 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5311 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5315 /* [HGM] logic here is completely changed. In stead of full positions */
5316 /* the initialized data only consist of the two backranks. The switch */
5317 /* selects which one we will use, which is than copied to the Board */
5318 /* initialPosition, which for the rest is initialized by Pawns and */
5319 /* empty squares. This initial position is then copied to boards[0], */
5320 /* possibly after shuffling, so that it remains available. */
5322 gameInfo.holdingsWidth = 0; /* default board sizes */
5323 gameInfo.boardWidth = 8;
5324 gameInfo.boardHeight = 8;
5325 gameInfo.holdingsSize = 0;
5326 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5327 for(i=0; i<BOARD_FILES-2; i++)
5328 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5329 initialPosition[EP_STATUS] = EP_NONE;
5330 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5331 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5332 SetCharTable(pieceNickName, appData.pieceNickNames);
5333 else SetCharTable(pieceNickName, "............");
5336 switch (gameInfo.variant) {
5337 case VariantFischeRandom:
5338 shuffleOpenings = TRUE;
5341 case VariantShatranj:
5342 pieces = ShatranjArray;
5343 nrCastlingRights = 0;
5344 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5347 pieces = makrukArray;
5348 nrCastlingRights = 0;
5349 startedFromSetupPosition = TRUE;
5350 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5352 case VariantTwoKings:
5353 pieces = twoKingsArray;
5355 case VariantCapaRandom:
5356 shuffleOpenings = TRUE;
5357 case VariantCapablanca:
5358 pieces = CapablancaArray;
5359 gameInfo.boardWidth = 10;
5360 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5363 pieces = GothicArray;
5364 gameInfo.boardWidth = 10;
5365 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5368 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5369 gameInfo.holdingsSize = 7;
5372 pieces = JanusArray;
5373 gameInfo.boardWidth = 10;
5374 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5375 nrCastlingRights = 6;
5376 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5377 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5378 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5379 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5380 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5381 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5384 pieces = FalconArray;
5385 gameInfo.boardWidth = 10;
5386 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5388 case VariantXiangqi:
5389 pieces = XiangqiArray;
5390 gameInfo.boardWidth = 9;
5391 gameInfo.boardHeight = 10;
5392 nrCastlingRights = 0;
5393 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5396 pieces = ShogiArray;
5397 gameInfo.boardWidth = 9;
5398 gameInfo.boardHeight = 9;
5399 gameInfo.holdingsSize = 7;
5400 nrCastlingRights = 0;
5401 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5403 case VariantCourier:
5404 pieces = CourierArray;
5405 gameInfo.boardWidth = 12;
5406 nrCastlingRights = 0;
5407 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5409 case VariantKnightmate:
5410 pieces = KnightmateArray;
5411 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5414 pieces = fairyArray;
5415 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5418 pieces = GreatArray;
5419 gameInfo.boardWidth = 10;
5420 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5421 gameInfo.holdingsSize = 8;
5425 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5426 gameInfo.holdingsSize = 8;
5427 startedFromSetupPosition = TRUE;
5429 case VariantCrazyhouse:
5430 case VariantBughouse:
5432 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5433 gameInfo.holdingsSize = 5;
5435 case VariantWildCastle:
5437 /* !!?shuffle with kings guaranteed to be on d or e file */
5438 shuffleOpenings = 1;
5440 case VariantNoCastle:
5442 nrCastlingRights = 0;
5443 /* !!?unconstrained back-rank shuffle */
5444 shuffleOpenings = 1;
5449 if(appData.NrFiles >= 0) {
5450 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5451 gameInfo.boardWidth = appData.NrFiles;
5453 if(appData.NrRanks >= 0) {
5454 gameInfo.boardHeight = appData.NrRanks;
5456 if(appData.holdingsSize >= 0) {
5457 i = appData.holdingsSize;
5458 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5459 gameInfo.holdingsSize = i;
5461 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5462 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5463 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5465 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5466 if(pawnRow < 1) pawnRow = 1;
5467 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5469 /* User pieceToChar list overrules defaults */
5470 if(appData.pieceToCharTable != NULL)
5471 SetCharTable(pieceToChar, appData.pieceToCharTable);
5473 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5475 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5476 s = (ChessSquare) 0; /* account holding counts in guard band */
5477 for( i=0; i<BOARD_HEIGHT; i++ )
5478 initialPosition[i][j] = s;
5480 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5481 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5482 initialPosition[pawnRow][j] = WhitePawn;
5483 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5484 if(gameInfo.variant == VariantXiangqi) {
5486 initialPosition[pawnRow][j] =
5487 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5488 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5489 initialPosition[2][j] = WhiteCannon;
5490 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5494 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5496 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5499 initialPosition[1][j] = WhiteBishop;
5500 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5502 initialPosition[1][j] = WhiteRook;
5503 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5506 if( nrCastlingRights == -1) {
5507 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5508 /* This sets default castling rights from none to normal corners */
5509 /* Variants with other castling rights must set them themselves above */
5510 nrCastlingRights = 6;
5512 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5513 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5514 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5515 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5516 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5517 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5520 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5521 if(gameInfo.variant == VariantGreat) { // promotion commoners
5522 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5523 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5524 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5525 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5527 if( gameInfo.variant == VariantSChess ) {
5528 initialPosition[1][0] = BlackMarshall;
5529 initialPosition[2][0] = BlackAngel;
5530 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5531 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5532 initialPosition[1][1] = initialPosition[2][1] =
5533 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5535 if (appData.debugMode) {
5536 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5538 if(shuffleOpenings) {
5539 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5540 startedFromSetupPosition = TRUE;
5542 if(startedFromPositionFile) {
5543 /* [HGM] loadPos: use PositionFile for every new game */
5544 CopyBoard(initialPosition, filePosition);
5545 for(i=0; i<nrCastlingRights; i++)
5546 initialRights[i] = filePosition[CASTLING][i];
5547 startedFromSetupPosition = TRUE;
5550 CopyBoard(boards[0], initialPosition);
5552 if(oldx != gameInfo.boardWidth ||
5553 oldy != gameInfo.boardHeight ||
5554 oldh != gameInfo.holdingsWidth
5556 || oldv == VariantGothic || // For licensing popups
5557 gameInfo.variant == VariantGothic
5560 || oldv == VariantFalcon ||
5561 gameInfo.variant == VariantFalcon
5564 InitDrawingSizes(-2 ,0);
5567 DrawPosition(TRUE, boards[currentMove]);
5571 SendBoard(cps, moveNum)
5572 ChessProgramState *cps;
5575 char message[MSG_SIZ];
5577 if (cps->useSetboard) {
5578 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5579 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5580 SendToProgram(message, cps);
5586 /* Kludge to set black to move, avoiding the troublesome and now
5587 * deprecated "black" command.
5589 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5590 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5592 SendToProgram("edit\n", cps);
5593 SendToProgram("#\n", cps);
5594 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5595 bp = &boards[moveNum][i][BOARD_LEFT];
5596 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5597 if ((int) *bp < (int) BlackPawn) {
5598 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5600 if(message[0] == '+' || message[0] == '~') {
5601 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5602 PieceToChar((ChessSquare)(DEMOTED *bp)),
5605 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5606 message[1] = BOARD_RGHT - 1 - j + '1';
5607 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5609 SendToProgram(message, cps);
5614 SendToProgram("c\n", cps);
5615 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5616 bp = &boards[moveNum][i][BOARD_LEFT];
5617 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5618 if (((int) *bp != (int) EmptySquare)
5619 && ((int) *bp >= (int) BlackPawn)) {
5620 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5622 if(message[0] == '+' || message[0] == '~') {
5623 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5624 PieceToChar((ChessSquare)(DEMOTED *bp)),
5627 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5628 message[1] = BOARD_RGHT - 1 - j + '1';
5629 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5631 SendToProgram(message, cps);
5636 SendToProgram(".\n", cps);
5638 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5641 static int autoQueen; // [HGM] oneclick
5644 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5646 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5647 /* [HGM] add Shogi promotions */
5648 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5653 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5654 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5656 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5657 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5660 piece = boards[currentMove][fromY][fromX];
5661 if(gameInfo.variant == VariantShogi) {
5662 promotionZoneSize = BOARD_HEIGHT/3;
5663 highestPromotingPiece = (int)WhiteFerz;
5664 } else if(gameInfo.variant == VariantMakruk) {
5665 promotionZoneSize = 3;
5668 // next weed out all moves that do not touch the promotion zone at all
5669 if((int)piece >= BlackPawn) {
5670 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5672 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5674 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5675 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5678 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5680 // weed out mandatory Shogi promotions
5681 if(gameInfo.variant == VariantShogi) {
5682 if(piece >= BlackPawn) {
5683 if(toY == 0 && piece == BlackPawn ||
5684 toY == 0 && piece == BlackQueen ||
5685 toY <= 1 && piece == BlackKnight) {
5690 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5691 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5692 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5699 // weed out obviously illegal Pawn moves
5700 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5701 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5702 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5703 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5704 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5705 // note we are not allowed to test for valid (non-)capture, due to premove
5708 // we either have a choice what to promote to, or (in Shogi) whether to promote
5709 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5710 *promoChoice = PieceToChar(BlackFerz); // no choice
5713 // no sense asking what we must promote to if it is going to explode...
5714 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5715 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5718 if(autoQueen) { // predetermined
5719 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5720 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5721 else *promoChoice = PieceToChar(BlackQueen);
5725 // suppress promotion popup on illegal moves that are not premoves
5726 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5727 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5728 if(appData.testLegality && !premove) {
5729 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5730 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5731 if(moveType != WhitePromotion && moveType != BlackPromotion)
5739 InPalace(row, column)
5741 { /* [HGM] for Xiangqi */
5742 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5743 column < (BOARD_WIDTH + 4)/2 &&
5744 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5749 PieceForSquare (x, y)
5753 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5756 return boards[currentMove][y][x];
5760 OKToStartUserMove(x, y)
5763 ChessSquare from_piece;
5766 if (matchMode) return FALSE;
5767 if (gameMode == EditPosition) return TRUE;
5769 if (x >= 0 && y >= 0)
5770 from_piece = boards[currentMove][y][x];
5772 from_piece = EmptySquare;
5774 if (from_piece == EmptySquare) return FALSE;
5776 white_piece = (int)from_piece >= (int)WhitePawn &&
5777 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5780 case PlayFromGameFile:
5782 case TwoMachinesPlay:
5790 case MachinePlaysWhite:
5791 case IcsPlayingBlack:
5792 if (appData.zippyPlay) return FALSE;
5794 DisplayMoveError(_("You are playing Black"));
5799 case MachinePlaysBlack:
5800 case IcsPlayingWhite:
5801 if (appData.zippyPlay) return FALSE;
5803 DisplayMoveError(_("You are playing White"));
5809 if (!white_piece && WhiteOnMove(currentMove)) {
5810 DisplayMoveError(_("It is White's turn"));
5813 if (white_piece && !WhiteOnMove(currentMove)) {
5814 DisplayMoveError(_("It is Black's turn"));
5817 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5818 /* Editing correspondence game history */
5819 /* Could disallow this or prompt for confirmation */
5824 case BeginningOfGame:
5825 if (appData.icsActive) return FALSE;
5826 if (!appData.noChessProgram) {
5828 DisplayMoveError(_("You are playing White"));
5835 if (!white_piece && WhiteOnMove(currentMove)) {
5836 DisplayMoveError(_("It is White's turn"));
5839 if (white_piece && !WhiteOnMove(currentMove)) {
5840 DisplayMoveError(_("It is Black's turn"));
5849 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5850 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5851 && gameMode != AnalyzeFile && gameMode != Training) {
5852 DisplayMoveError(_("Displayed position is not current"));
5859 OnlyMove(int *x, int *y, Boolean captures) {
5860 DisambiguateClosure cl;
5861 if (appData.zippyPlay) return FALSE;
5863 case MachinePlaysBlack:
5864 case IcsPlayingWhite:
5865 case BeginningOfGame:
5866 if(!WhiteOnMove(currentMove)) return FALSE;
5868 case MachinePlaysWhite:
5869 case IcsPlayingBlack:
5870 if(WhiteOnMove(currentMove)) return FALSE;
5877 cl.pieceIn = EmptySquare;
5882 cl.promoCharIn = NULLCHAR;
5883 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5884 if( cl.kind == NormalMove ||
5885 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5886 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5887 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5894 if(cl.kind != ImpossibleMove) return FALSE;
5895 cl.pieceIn = EmptySquare;
5900 cl.promoCharIn = NULLCHAR;
5901 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5902 if( cl.kind == NormalMove ||
5903 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5904 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5905 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5910 autoQueen = TRUE; // act as if autoQueen on when we click to-square
5916 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5917 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5918 int lastLoadGameUseList = FALSE;
5919 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5920 ChessMove lastLoadGameStart = EndOfFile;
5923 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5924 int fromX, fromY, toX, toY;
5928 ChessSquare pdown, pup;
5930 /* Check if the user is playing in turn. This is complicated because we
5931 let the user "pick up" a piece before it is his turn. So the piece he
5932 tried to pick up may have been captured by the time he puts it down!
5933 Therefore we use the color the user is supposed to be playing in this
5934 test, not the color of the piece that is currently on the starting
5935 square---except in EditGame mode, where the user is playing both
5936 sides; fortunately there the capture race can't happen. (It can
5937 now happen in IcsExamining mode, but that's just too bad. The user
5938 will get a somewhat confusing message in that case.)
5942 case PlayFromGameFile:
5944 case TwoMachinesPlay:
5948 /* We switched into a game mode where moves are not accepted,
5949 perhaps while the mouse button was down. */
5952 case MachinePlaysWhite:
5953 /* User is moving for Black */
5954 if (WhiteOnMove(currentMove)) {
5955 DisplayMoveError(_("It is White's turn"));
5960 case MachinePlaysBlack:
5961 /* User is moving for White */
5962 if (!WhiteOnMove(currentMove)) {
5963 DisplayMoveError(_("It is Black's turn"));
5970 case BeginningOfGame:
5973 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5974 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5975 /* User is moving for Black */
5976 if (WhiteOnMove(currentMove)) {
5977 DisplayMoveError(_("It is White's turn"));
5981 /* User is moving for White */
5982 if (!WhiteOnMove(currentMove)) {
5983 DisplayMoveError(_("It is Black's turn"));
5989 case IcsPlayingBlack:
5990 /* User is moving for Black */
5991 if (WhiteOnMove(currentMove)) {
5992 if (!appData.premove) {
5993 DisplayMoveError(_("It is White's turn"));
5994 } else if (toX >= 0 && toY >= 0) {
5997 premoveFromX = fromX;
5998 premoveFromY = fromY;
5999 premovePromoChar = promoChar;
6001 if (appData.debugMode)
6002 fprintf(debugFP, "Got premove: fromX %d,"
6003 "fromY %d, toX %d, toY %d\n",
6004 fromX, fromY, toX, toY);
6010 case IcsPlayingWhite:
6011 /* User is moving for White */
6012 if (!WhiteOnMove(currentMove)) {
6013 if (!appData.premove) {
6014 DisplayMoveError(_("It is Black's turn"));
6015 } else if (toX >= 0 && toY >= 0) {
6018 premoveFromX = fromX;
6019 premoveFromY = fromY;
6020 premovePromoChar = promoChar;
6022 if (appData.debugMode)
6023 fprintf(debugFP, "Got premove: fromX %d,"
6024 "fromY %d, toX %d, toY %d\n",
6025 fromX, fromY, toX, toY);
6035 /* EditPosition, empty square, or different color piece;
6036 click-click move is possible */
6037 if (toX == -2 || toY == -2) {
6038 boards[0][fromY][fromX] = EmptySquare;
6039 DrawPosition(FALSE, boards[currentMove]);
6041 } else if (toX >= 0 && toY >= 0) {
6042 boards[0][toY][toX] = boards[0][fromY][fromX];
6043 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6044 if(boards[0][fromY][0] != EmptySquare) {
6045 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6046 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6049 if(fromX == BOARD_RGHT+1) {
6050 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6051 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6052 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6055 boards[0][fromY][fromX] = EmptySquare;
6056 DrawPosition(FALSE, boards[currentMove]);
6062 if(toX < 0 || toY < 0) return;
6063 pdown = boards[currentMove][fromY][fromX];
6064 pup = boards[currentMove][toY][toX];
6066 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6067 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
6068 if( pup != EmptySquare ) return;
6069 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6070 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6071 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6072 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6073 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6074 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6075 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6079 /* [HGM] always test for legality, to get promotion info */
6080 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6081 fromY, fromX, toY, toX, promoChar);
6082 /* [HGM] but possibly ignore an IllegalMove result */
6083 if (appData.testLegality) {
6084 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6085 DisplayMoveError(_("Illegal move"));
6090 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6093 /* Common tail of UserMoveEvent and DropMenuEvent */
6095 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6097 int fromX, fromY, toX, toY;
6098 /*char*/int promoChar;
6102 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6103 // [HGM] superchess: suppress promotions to non-available piece
6104 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6105 if(WhiteOnMove(currentMove)) {
6106 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6108 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6112 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6113 move type in caller when we know the move is a legal promotion */
6114 if(moveType == NormalMove && promoChar)
6115 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6117 /* [HGM] <popupFix> The following if has been moved here from
6118 UserMoveEvent(). Because it seemed to belong here (why not allow
6119 piece drops in training games?), and because it can only be
6120 performed after it is known to what we promote. */
6121 if (gameMode == Training) {
6122 /* compare the move played on the board to the next move in the
6123 * game. If they match, display the move and the opponent's response.
6124 * If they don't match, display an error message.
6128 CopyBoard(testBoard, boards[currentMove]);
6129 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6131 if (CompareBoards(testBoard, boards[currentMove+1])) {
6132 ForwardInner(currentMove+1);
6134 /* Autoplay the opponent's response.
6135 * if appData.animate was TRUE when Training mode was entered,
6136 * the response will be animated.
6138 saveAnimate = appData.animate;
6139 appData.animate = animateTraining;
6140 ForwardInner(currentMove+1);
6141 appData.animate = saveAnimate;
6143 /* check for the end of the game */
6144 if (currentMove >= forwardMostMove) {
6145 gameMode = PlayFromGameFile;
6147 SetTrainingModeOff();
6148 DisplayInformation(_("End of game"));
6151 DisplayError(_("Incorrect move"), 0);
6156 /* Ok, now we know that the move is good, so we can kill
6157 the previous line in Analysis Mode */
6158 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6159 && currentMove < forwardMostMove) {
6160 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6161 else forwardMostMove = currentMove;
6164 /* If we need the chess program but it's dead, restart it */
6165 ResurrectChessProgram();
6167 /* A user move restarts a paused game*/
6171 thinkOutput[0] = NULLCHAR;
6173 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6175 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6177 if (gameMode == BeginningOfGame) {
6178 if (appData.noChessProgram) {
6179 gameMode = EditGame;
6183 gameMode = MachinePlaysBlack;
6186 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6188 if (first.sendName) {
6189 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6190 SendToProgram(buf, &first);
6197 /* Relay move to ICS or chess engine */
6198 if (appData.icsActive) {
6199 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6200 gameMode == IcsExamining) {
6201 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6202 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6204 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6206 // also send plain move, in case ICS does not understand atomic claims
6207 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6211 if (first.sendTime && (gameMode == BeginningOfGame ||
6212 gameMode == MachinePlaysWhite ||
6213 gameMode == MachinePlaysBlack)) {
6214 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6216 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6217 // [HGM] book: if program might be playing, let it use book
6218 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6219 first.maybeThinking = TRUE;
6220 } else SendMoveToProgram(forwardMostMove-1, &first);
6221 if (currentMove == cmailOldMove + 1) {
6222 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6226 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6230 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6236 if (WhiteOnMove(currentMove)) {
6237 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6239 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6243 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6248 case MachinePlaysBlack:
6249 case MachinePlaysWhite:
6250 /* disable certain menu options while machine is thinking */
6251 SetMachineThinkingEnables();
6258 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6260 if(bookHit) { // [HGM] book: simulate book reply
6261 static char bookMove[MSG_SIZ]; // a bit generous?
6263 programStats.nodes = programStats.depth = programStats.time =
6264 programStats.score = programStats.got_only_move = 0;
6265 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6267 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6268 strcat(bookMove, bookHit);
6269 HandleMachineMove(bookMove, &first);
6275 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6282 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6283 Markers *m = (Markers *) closure;
6284 if(rf == fromY && ff == fromX)
6285 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6286 || kind == WhiteCapturesEnPassant
6287 || kind == BlackCapturesEnPassant);
6288 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6292 MarkTargetSquares(int clear)
6295 if(!appData.markers || !appData.highlightDragging ||
6296 !appData.testLegality || gameMode == EditPosition) return;
6298 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6301 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6302 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6303 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6305 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6308 DrawPosition(TRUE, NULL);
6312 Explode(Board board, int fromX, int fromY, int toX, int toY)
6314 if(gameInfo.variant == VariantAtomic &&
6315 (board[toY][toX] != EmptySquare || // capture?
6316 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6317 board[fromY][fromX] == BlackPawn )
6319 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6325 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6327 void LeftClick(ClickType clickType, int xPix, int yPix)
6330 Boolean saveAnimate;
6331 static int second = 0, promotionChoice = 0, dragging = 0;
6332 char promoChoice = NULLCHAR;
6334 if(appData.seekGraph && appData.icsActive && loggedOn &&
6335 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6336 SeekGraphClick(clickType, xPix, yPix, 0);
6340 if (clickType == Press) ErrorPopDown();
6341 MarkTargetSquares(1);
6343 x = EventToSquare(xPix, BOARD_WIDTH);
6344 y = EventToSquare(yPix, BOARD_HEIGHT);
6345 if (!flipView && y >= 0) {
6346 y = BOARD_HEIGHT - 1 - y;
6348 if (flipView && x >= 0) {
6349 x = BOARD_WIDTH - 1 - x;
6352 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6353 if(clickType == Release) return; // ignore upclick of click-click destination
6354 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6355 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6356 if(gameInfo.holdingsWidth &&
6357 (WhiteOnMove(currentMove)
6358 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6359 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6360 // click in right holdings, for determining promotion piece
6361 ChessSquare p = boards[currentMove][y][x];
6362 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6363 if(p != EmptySquare) {
6364 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6369 DrawPosition(FALSE, boards[currentMove]);
6373 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6374 if(clickType == Press
6375 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6376 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6377 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6380 autoQueen = appData.alwaysPromoteToQueen;
6383 gatingPiece = EmptySquare;
6384 if (clickType != Press) {
6385 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6386 DragPieceEnd(xPix, yPix); dragging = 0;
6387 DrawPosition(FALSE, NULL);
6391 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6393 if (OKToStartUserMove(x, y)) {
6397 MarkTargetSquares(0);
6398 DragPieceBegin(xPix, yPix); dragging = 1;
6399 if (appData.highlightDragging) {
6400 SetHighlights(x, y, -1, -1);
6408 if (clickType == Press && gameMode != EditPosition) {
6413 // ignore off-board to clicks
6414 if(y < 0 || x < 0) return;
6416 /* Check if clicking again on the same color piece */
6417 fromP = boards[currentMove][fromY][fromX];
6418 toP = boards[currentMove][y][x];
6419 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6420 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6421 WhitePawn <= toP && toP <= WhiteKing &&
6422 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6423 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6424 (BlackPawn <= fromP && fromP <= BlackKing &&
6425 BlackPawn <= toP && toP <= BlackKing &&
6426 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6427 !(fromP == BlackKing && toP == BlackRook && frc))) {
6428 /* Clicked again on same color piece -- changed his mind */
6429 second = (x == fromX && y == fromY);
6430 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6431 if (appData.highlightDragging) {
6432 SetHighlights(x, y, -1, -1);
6436 if (OKToStartUserMove(x, y)) {
6437 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6438 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6439 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6440 gatingPiece = boards[currentMove][fromY][fromX];
6441 else gatingPiece = EmptySquare;
6443 fromY = y; dragging = 1;
6444 MarkTargetSquares(0);
6445 DragPieceBegin(xPix, yPix);
6448 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6451 // ignore clicks on holdings
6452 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6455 if (clickType == Release && x == fromX && y == fromY) {
6456 DragPieceEnd(xPix, yPix); dragging = 0;
6457 if (appData.animateDragging) {
6458 /* Undo animation damage if any */
6459 DrawPosition(FALSE, NULL);
6462 /* Second up/down in same square; just abort move */
6465 gatingPiece = EmptySquare;
6468 ClearPremoveHighlights();
6470 /* First upclick in same square; start click-click mode */
6471 SetHighlights(x, y, -1, -1);
6476 /* we now have a different from- and (possibly off-board) to-square */
6477 /* Completed move */
6480 saveAnimate = appData.animate;
6481 if (clickType == Press) {
6482 /* Finish clickclick move */
6483 if (appData.animate || appData.highlightLastMove) {
6484 SetHighlights(fromX, fromY, toX, toY);
6489 /* Finish drag move */
6490 if (appData.highlightLastMove) {
6491 SetHighlights(fromX, fromY, toX, toY);
6495 DragPieceEnd(xPix, yPix); dragging = 0;
6496 /* Don't animate move and drag both */
6497 appData.animate = FALSE;
6500 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6501 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6502 ChessSquare piece = boards[currentMove][fromY][fromX];
6503 if(gameMode == EditPosition && piece != EmptySquare &&
6504 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6507 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6508 n = PieceToNumber(piece - (int)BlackPawn);
6509 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6510 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6511 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6513 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6514 n = PieceToNumber(piece);
6515 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6516 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6517 boards[currentMove][n][BOARD_WIDTH-2]++;
6519 boards[currentMove][fromY][fromX] = EmptySquare;
6523 DrawPosition(TRUE, boards[currentMove]);
6527 // off-board moves should not be highlighted
6528 if(x < 0 || y < 0) ClearHighlights();
6530 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6532 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6533 SetHighlights(fromX, fromY, toX, toY);
6534 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6535 // [HGM] super: promotion to captured piece selected from holdings
6536 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6537 promotionChoice = TRUE;
6538 // kludge follows to temporarily execute move on display, without promoting yet
6539 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6540 boards[currentMove][toY][toX] = p;
6541 DrawPosition(FALSE, boards[currentMove]);
6542 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6543 boards[currentMove][toY][toX] = q;
6544 DisplayMessage("Click in holdings to choose piece", "");
6549 int oldMove = currentMove;
6550 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6551 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6552 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6553 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6554 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6555 DrawPosition(TRUE, boards[currentMove]);
6558 appData.animate = saveAnimate;
6559 if (appData.animate || appData.animateDragging) {
6560 /* Undo animation damage if needed */
6561 DrawPosition(FALSE, NULL);
6565 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6566 { // front-end-free part taken out of PieceMenuPopup
6567 int whichMenu; int xSqr, ySqr;
6569 if(seekGraphUp) { // [HGM] seekgraph
6570 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6571 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6575 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6576 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6577 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6578 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6579 if(action == Press) {
6580 originalFlip = flipView;
6581 flipView = !flipView; // temporarily flip board to see game from partners perspective
6582 DrawPosition(TRUE, partnerBoard);
6583 DisplayMessage(partnerStatus, "");
6585 } else if(action == Release) {
6586 flipView = originalFlip;
6587 DrawPosition(TRUE, boards[currentMove]);
6593 xSqr = EventToSquare(x, BOARD_WIDTH);
6594 ySqr = EventToSquare(y, BOARD_HEIGHT);
6595 if (action == Release) UnLoadPV(); // [HGM] pv
6596 if (action != Press) return -2; // return code to be ignored
6599 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6601 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6602 if (xSqr < 0 || ySqr < 0) return -1;
\r
6603 whichMenu = 0; // edit-position menu
6606 if(!appData.icsEngineAnalyze) return -1;
6607 case IcsPlayingWhite:
6608 case IcsPlayingBlack:
6609 if(!appData.zippyPlay) goto noZip;
6612 case MachinePlaysWhite:
6613 case MachinePlaysBlack:
6614 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6615 if (!appData.dropMenu) {
6617 return 2; // flag front-end to grab mouse events
6619 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6620 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6623 if (xSqr < 0 || ySqr < 0) return -1;
6624 if (!appData.dropMenu || appData.testLegality &&
6625 gameInfo.variant != VariantBughouse &&
6626 gameInfo.variant != VariantCrazyhouse) return -1;
6627 whichMenu = 1; // drop menu
6633 if (((*fromX = xSqr) < 0) ||
6634 ((*fromY = ySqr) < 0)) {
6635 *fromX = *fromY = -1;
6639 *fromX = BOARD_WIDTH - 1 - *fromX;
6641 *fromY = BOARD_HEIGHT - 1 - *fromY;
6646 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6648 // char * hint = lastHint;
6649 FrontEndProgramStats stats;
6651 stats.which = cps == &first ? 0 : 1;
6652 stats.depth = cpstats->depth;
6653 stats.nodes = cpstats->nodes;
6654 stats.score = cpstats->score;
6655 stats.time = cpstats->time;
6656 stats.pv = cpstats->movelist;
6657 stats.hint = lastHint;
6658 stats.an_move_index = 0;
6659 stats.an_move_count = 0;
6661 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6662 stats.hint = cpstats->move_name;
6663 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6664 stats.an_move_count = cpstats->nr_moves;
6667 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
6669 SetProgramStats( &stats );
6673 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6674 { // count all piece types
6676 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6677 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6678 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6681 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6682 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6683 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6684 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6685 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
6686 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6691 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6693 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6694 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6696 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6697 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6698 if(myPawns == 2 && nMine == 3) // KPP
6699 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6700 if(myPawns == 1 && nMine == 2) // KP
6701 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6702 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6703 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6704 if(myPawns) return FALSE;
6705 if(pCnt[WhiteRook+side])
6706 return pCnt[BlackRook-side] ||
6707 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6708 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6709 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6710 if(pCnt[WhiteCannon+side]) {
6711 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6712 return majorDefense || pCnt[BlackAlfil-side] >= 2;
6714 if(pCnt[WhiteKnight+side])
6715 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6720 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6722 VariantClass v = gameInfo.variant;
6724 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6725 if(v == VariantShatranj) return TRUE; // always winnable through baring
6726 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6727 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6729 if(v == VariantXiangqi) {
6730 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6732 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6733 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6734 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6735 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6736 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6737 if(stale) // we have at least one last-rank P plus perhaps C
6738 return majors // KPKX
6739 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6741 return pCnt[WhiteFerz+side] // KCAK
6742 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6743 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6744 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6746 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6747 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6749 if(nMine == 1) return FALSE; // bare King
6750 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
6751 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6752 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6753 // by now we have King + 1 piece (or multiple Bishops on the same color)
6754 if(pCnt[WhiteKnight+side])
6755 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6756 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6757 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6759 return (pCnt[BlackKnight-side]); // KBKN, KFKN
6760 if(pCnt[WhiteAlfil+side])
6761 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6762 if(pCnt[WhiteWazir+side])
6763 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6770 Adjudicate(ChessProgramState *cps)
6771 { // [HGM] some adjudications useful with buggy engines
6772 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6773 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6774 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6775 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6776 int k, count = 0; static int bare = 1;
6777 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6778 Boolean canAdjudicate = !appData.icsActive;
6780 // most tests only when we understand the game, i.e. legality-checking on
6781 if( appData.testLegality )
6782 { /* [HGM] Some more adjudications for obstinate engines */
6783 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6784 static int moveCount = 6;
6786 char *reason = NULL;
6788 /* Count what is on board. */
6789 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6791 /* Some material-based adjudications that have to be made before stalemate test */
6792 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6793 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6794 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6795 if(canAdjudicate && appData.checkMates) {
6797 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6798 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6799 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6800 "Xboard adjudication: King destroyed", GE_XBOARD );
6805 /* Bare King in Shatranj (loses) or Losers (wins) */
6806 if( nrW == 1 || nrB == 1) {
6807 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6808 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6809 if(canAdjudicate && appData.checkMates) {
6811 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6812 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6813 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6814 "Xboard adjudication: Bare king", GE_XBOARD );
6818 if( gameInfo.variant == VariantShatranj && --bare < 0)
6820 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6821 if(canAdjudicate && appData.checkMates) {
6822 /* but only adjudicate if adjudication enabled */
6824 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6825 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6826 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6827 "Xboard adjudication: Bare king", GE_XBOARD );
6834 // don't wait for engine to announce game end if we can judge ourselves
6835 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6837 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6838 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6839 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6840 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6843 reason = "Xboard adjudication: 3rd check";
6844 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6854 reason = "Xboard adjudication: Stalemate";
6855 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6856 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6857 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6858 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6859 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6860 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6861 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6862 EP_CHECKMATE : EP_WINS);
6863 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6864 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6868 reason = "Xboard adjudication: Checkmate";
6869 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6873 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6875 result = GameIsDrawn; break;
6877 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6879 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6883 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6885 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6886 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6887 GameEnds( result, reason, GE_XBOARD );
6891 /* Next absolutely insufficient mating material. */
6892 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6893 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6894 { /* includes KBK, KNK, KK of KBKB with like Bishops */
6896 /* always flag draws, for judging claims */
6897 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6899 if(canAdjudicate && appData.materialDraws) {
6900 /* but only adjudicate them if adjudication enabled */
6901 if(engineOpponent) {
6902 SendToProgram("force\n", engineOpponent); // suppress reply
6903 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6905 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6906 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6911 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6912 if(gameInfo.variant == VariantXiangqi ?
6913 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6915 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6916 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
6917 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
6918 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6920 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6921 { /* if the first 3 moves do not show a tactical win, declare draw */
6922 if(engineOpponent) {
6923 SendToProgram("force\n", engineOpponent); // suppress reply
6924 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6926 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6927 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6930 } else moveCount = 6;
6932 if (appData.debugMode) { int i;
6933 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6934 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6935 appData.drawRepeats);
6936 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6937 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6941 // Repetition draws and 50-move rule can be applied independently of legality testing
6943 /* Check for rep-draws */
6945 for(k = forwardMostMove-2;
6946 k>=backwardMostMove && k>=forwardMostMove-100 &&
6947 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6948 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6951 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6952 /* compare castling rights */
6953 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6954 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6955 rights++; /* King lost rights, while rook still had them */
6956 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6957 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6958 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6959 rights++; /* but at least one rook lost them */
6961 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6962 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6964 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6965 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6966 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6969 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6970 && appData.drawRepeats > 1) {
6971 /* adjudicate after user-specified nr of repeats */
6972 int result = GameIsDrawn;
6973 char *details = "XBoard adjudication: repetition draw";
6974 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6975 // [HGM] xiangqi: check for forbidden perpetuals
6976 int m, ourPerpetual = 1, hisPerpetual = 1;
6977 for(m=forwardMostMove; m>k; m-=2) {
6978 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6979 ourPerpetual = 0; // the current mover did not always check
6980 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6981 hisPerpetual = 0; // the opponent did not always check
6983 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6984 ourPerpetual, hisPerpetual);
6985 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6986 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6987 details = "Xboard adjudication: perpetual checking";
6989 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6990 break; // (or we would have caught him before). Abort repetition-checking loop.
6992 // Now check for perpetual chases
6993 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6994 hisPerpetual = PerpetualChase(k, forwardMostMove);
6995 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6996 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6997 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6998 details = "Xboard adjudication: perpetual chasing";
7000 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7001 break; // Abort repetition-checking loop.
7003 // if neither of us is checking or chasing all the time, or both are, it is draw
7005 if(engineOpponent) {
7006 SendToProgram("force\n", engineOpponent); // suppress reply
7007 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7009 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7010 GameEnds( result, details, GE_XBOARD );
7013 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7014 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7018 /* Now we test for 50-move draws. Determine ply count */
7019 count = forwardMostMove;
7020 /* look for last irreversble move */
7021 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7023 /* if we hit starting position, add initial plies */
7024 if( count == backwardMostMove )
7025 count -= initialRulePlies;
7026 count = forwardMostMove - count;
7027 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7028 // adjust reversible move counter for checks in Xiangqi
7029 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7030 if(i < backwardMostMove) i = backwardMostMove;
7031 while(i <= forwardMostMove) {
7032 lastCheck = inCheck; // check evasion does not count
7033 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7034 if(inCheck || lastCheck) count--; // check does not count
7039 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7040 /* this is used to judge if draw claims are legal */
7041 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7042 if(engineOpponent) {
7043 SendToProgram("force\n", engineOpponent); // suppress reply
7044 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7046 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7047 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7051 /* if draw offer is pending, treat it as a draw claim
7052 * when draw condition present, to allow engines a way to
7053 * claim draws before making their move to avoid a race
7054 * condition occurring after their move
7056 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7058 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7059 p = "Draw claim: 50-move rule";
7060 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7061 p = "Draw claim: 3-fold repetition";
7062 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7063 p = "Draw claim: insufficient mating material";
7064 if( p != NULL && canAdjudicate) {
7065 if(engineOpponent) {
7066 SendToProgram("force\n", engineOpponent); // suppress reply
7067 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7069 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7070 GameEnds( GameIsDrawn, p, GE_XBOARD );
7075 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7076 if(engineOpponent) {
7077 SendToProgram("force\n", engineOpponent); // suppress reply
7078 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7080 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7081 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7087 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7088 { // [HGM] book: this routine intercepts moves to simulate book replies
7089 char *bookHit = NULL;
7091 //first determine if the incoming move brings opponent into his book
7092 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7093 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7094 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7095 if(bookHit != NULL && !cps->bookSuspend) {
7096 // make sure opponent is not going to reply after receiving move to book position
7097 SendToProgram("force\n", cps);
7098 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7100 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7101 // now arrange restart after book miss
7103 // after a book hit we never send 'go', and the code after the call to this routine
7104 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7106 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7107 SendToProgram(buf, cps);
7108 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7109 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7110 SendToProgram("go\n", cps);
7111 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7112 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7113 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7114 SendToProgram("go\n", cps);
7115 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7117 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7121 ChessProgramState *savedState;
7122 void DeferredBookMove(void)
7124 if(savedState->lastPing != savedState->lastPong)
7125 ScheduleDelayedEvent(DeferredBookMove, 10);
7127 HandleMachineMove(savedMessage, savedState);
7131 HandleMachineMove(message, cps)
7133 ChessProgramState *cps;
7135 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7136 char realname[MSG_SIZ];
7137 int fromX, fromY, toX, toY;
7146 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7148 * Kludge to ignore BEL characters
7150 while (*message == '\007') message++;
7153 * [HGM] engine debug message: ignore lines starting with '#' character
7155 if(cps->debug && *message == '#') return;
7158 * Look for book output
7160 if (cps == &first && bookRequested) {
7161 if (message[0] == '\t' || message[0] == ' ') {
7162 /* Part of the book output is here; append it */
7163 strcat(bookOutput, message);
7164 strcat(bookOutput, " \n");
7166 } else if (bookOutput[0] != NULLCHAR) {
7167 /* All of book output has arrived; display it */
7168 char *p = bookOutput;
7169 while (*p != NULLCHAR) {
7170 if (*p == '\t') *p = ' ';
7173 DisplayInformation(bookOutput);
7174 bookRequested = FALSE;
7175 /* Fall through to parse the current output */
7180 * Look for machine move.
7182 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7183 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7185 /* This method is only useful on engines that support ping */
7186 if (cps->lastPing != cps->lastPong) {
7187 if (gameMode == BeginningOfGame) {
7188 /* Extra move from before last new; ignore */
7189 if (appData.debugMode) {
7190 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7193 if (appData.debugMode) {
7194 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7195 cps->which, gameMode);
7198 SendToProgram("undo\n", cps);
7204 case BeginningOfGame:
7205 /* Extra move from before last reset; ignore */
7206 if (appData.debugMode) {
7207 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7214 /* Extra move after we tried to stop. The mode test is
7215 not a reliable way of detecting this problem, but it's
7216 the best we can do on engines that don't support ping.
7218 if (appData.debugMode) {
7219 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7220 cps->which, gameMode);
7222 SendToProgram("undo\n", cps);
7225 case MachinePlaysWhite:
7226 case IcsPlayingWhite:
7227 machineWhite = TRUE;
7230 case MachinePlaysBlack:
7231 case IcsPlayingBlack:
7232 machineWhite = FALSE;
7235 case TwoMachinesPlay:
7236 machineWhite = (cps->twoMachinesColor[0] == 'w');
7239 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7240 if (appData.debugMode) {
7242 "Ignoring move out of turn by %s, gameMode %d"
7243 ", forwardMost %d\n",
7244 cps->which, gameMode, forwardMostMove);
7249 if (appData.debugMode) { int f = forwardMostMove;
7250 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7251 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7252 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7254 if(cps->alphaRank) AlphaRank(machineMove, 4);
7255 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7256 &fromX, &fromY, &toX, &toY, &promoChar)) {
7257 /* Machine move could not be parsed; ignore it. */
7258 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7259 machineMove, cps->which);
7260 DisplayError(buf1, 0);
7261 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7262 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7263 if (gameMode == TwoMachinesPlay) {
7264 GameEnds(machineWhite ? BlackWins : WhiteWins,
7270 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7271 /* So we have to redo legality test with true e.p. status here, */
7272 /* to make sure an illegal e.p. capture does not slip through, */
7273 /* to cause a forfeit on a justified illegal-move complaint */
7274 /* of the opponent. */
7275 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7277 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7278 fromY, fromX, toY, toX, promoChar);
7279 if (appData.debugMode) {
7281 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7282 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7283 fprintf(debugFP, "castling rights\n");
7285 if(moveType == IllegalMove) {
7286 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7287 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7288 GameEnds(machineWhite ? BlackWins : WhiteWins,
7291 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7292 /* [HGM] Kludge to handle engines that send FRC-style castling
7293 when they shouldn't (like TSCP-Gothic) */
7295 case WhiteASideCastleFR:
7296 case BlackASideCastleFR:
7298 currentMoveString[2]++;
7300 case WhiteHSideCastleFR:
7301 case BlackHSideCastleFR:
7303 currentMoveString[2]--;
7305 default: ; // nothing to do, but suppresses warning of pedantic compilers
7308 hintRequested = FALSE;
7309 lastHint[0] = NULLCHAR;
7310 bookRequested = FALSE;
7311 /* Program may be pondering now */
7312 cps->maybeThinking = TRUE;
7313 if (cps->sendTime == 2) cps->sendTime = 1;
7314 if (cps->offeredDraw) cps->offeredDraw--;
7316 /* currentMoveString is set as a side-effect of ParseOneMove */
7317 safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7318 strcat(machineMove, "\n");
7319 safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7321 /* [AS] Save move info*/
7322 pvInfoList[ forwardMostMove ].score = programStats.score;
7323 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7324 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7326 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7328 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7329 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7332 while( count < adjudicateLossPlies ) {
7333 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7336 score = -score; /* Flip score for winning side */
7339 if( score > adjudicateLossThreshold ) {
7346 if( count >= adjudicateLossPlies ) {
7347 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7349 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7350 "Xboard adjudication",
7357 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7360 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7362 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7363 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7365 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7367 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7369 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7370 char buf[3*MSG_SIZ];
7372 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7373 programStats.score / 100.,
7375 programStats.time / 100.,
7376 (unsigned int)programStats.nodes,
7377 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7378 programStats.movelist);
7380 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7385 /* [AS] Clear stats for next move */
7386 ClearProgramStats();
7387 thinkOutput[0] = NULLCHAR;
7388 hiddenThinkOutputState = 0;
7391 if (gameMode == TwoMachinesPlay) {
7392 /* [HGM] relaying draw offers moved to after reception of move */
7393 /* and interpreting offer as claim if it brings draw condition */
7394 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7395 SendToProgram("draw\n", cps->other);
7397 if (cps->other->sendTime) {
7398 SendTimeRemaining(cps->other,
7399 cps->other->twoMachinesColor[0] == 'w');
7401 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7402 if (firstMove && !bookHit) {
7404 if (cps->other->useColors) {
7405 SendToProgram(cps->other->twoMachinesColor, cps->other);
7407 SendToProgram("go\n", cps->other);
7409 cps->other->maybeThinking = TRUE;
7412 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7414 if (!pausing && appData.ringBellAfterMoves) {
7419 * Reenable menu items that were disabled while
7420 * machine was thinking
7422 if (gameMode != TwoMachinesPlay)
7423 SetUserThinkingEnables();
7425 // [HGM] book: after book hit opponent has received move and is now in force mode
7426 // force the book reply into it, and then fake that it outputted this move by jumping
7427 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7429 static char bookMove[MSG_SIZ]; // a bit generous?
7431 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7432 strcat(bookMove, bookHit);
7435 programStats.nodes = programStats.depth = programStats.time =
7436 programStats.score = programStats.got_only_move = 0;
7437 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7439 if(cps->lastPing != cps->lastPong) {
7440 savedMessage = message; // args for deferred call
7442 ScheduleDelayedEvent(DeferredBookMove, 10);
7451 /* Set special modes for chess engines. Later something general
7452 * could be added here; for now there is just one kludge feature,
7453 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7454 * when "xboard" is given as an interactive command.
7456 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7457 cps->useSigint = FALSE;
7458 cps->useSigterm = FALSE;
7460 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7461 ParseFeatures(message+8, cps);
7462 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7465 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7466 int dummy, s=6; char buf[MSG_SIZ];
7467 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7468 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7469 ParseFEN(boards[0], &dummy, message+s);
7470 DrawPosition(TRUE, boards[0]);
7471 startedFromSetupPosition = TRUE;
7474 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7475 * want this, I was asked to put it in, and obliged.
7477 if (!strncmp(message, "setboard ", 9)) {
7478 Board initial_position;
7480 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7482 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7483 DisplayError(_("Bad FEN received from engine"), 0);
7487 CopyBoard(boards[0], initial_position);
7488 initialRulePlies = FENrulePlies;
7489 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7490 else gameMode = MachinePlaysBlack;
7491 DrawPosition(FALSE, boards[currentMove]);
7497 * Look for communication commands
7499 if (!strncmp(message, "telluser ", 9)) {
7500 if(message[9] == '\\' && message[10] == '\\')
7501 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7502 DisplayNote(message + 9);
7505 if (!strncmp(message, "tellusererror ", 14)) {
7507 if(message[14] == '\\' && message[15] == '\\')
7508 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7509 DisplayError(message + 14, 0);
7512 if (!strncmp(message, "tellopponent ", 13)) {
7513 if (appData.icsActive) {
7515 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7519 DisplayNote(message + 13);
7523 if (!strncmp(message, "tellothers ", 11)) {
7524 if (appData.icsActive) {
7526 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7532 if (!strncmp(message, "tellall ", 8)) {
7533 if (appData.icsActive) {
7535 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7539 DisplayNote(message + 8);
7543 if (strncmp(message, "warning", 7) == 0) {
7544 /* Undocumented feature, use tellusererror in new code */
7545 DisplayError(message, 0);
7548 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7549 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7550 strcat(realname, " query");
7551 AskQuestion(realname, buf2, buf1, cps->pr);
7554 /* Commands from the engine directly to ICS. We don't allow these to be
7555 * sent until we are logged on. Crafty kibitzes have been known to
7556 * interfere with the login process.
7559 if (!strncmp(message, "tellics ", 8)) {
7560 SendToICS(message + 8);
7564 if (!strncmp(message, "tellicsnoalias ", 15)) {
7565 SendToICS(ics_prefix);
7566 SendToICS(message + 15);
7570 /* The following are for backward compatibility only */
7571 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7572 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7573 SendToICS(ics_prefix);
7579 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7583 * If the move is illegal, cancel it and redraw the board.
7584 * Also deal with other error cases. Matching is rather loose
7585 * here to accommodate engines written before the spec.
7587 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7588 strncmp(message, "Error", 5) == 0) {
7589 if (StrStr(message, "name") ||
7590 StrStr(message, "rating") || StrStr(message, "?") ||
7591 StrStr(message, "result") || StrStr(message, "board") ||
7592 StrStr(message, "bk") || StrStr(message, "computer") ||
7593 StrStr(message, "variant") || StrStr(message, "hint") ||
7594 StrStr(message, "random") || StrStr(message, "depth") ||
7595 StrStr(message, "accepted")) {
7598 if (StrStr(message, "protover")) {
7599 /* Program is responding to input, so it's apparently done
7600 initializing, and this error message indicates it is
7601 protocol version 1. So we don't need to wait any longer
7602 for it to initialize and send feature commands. */
7603 FeatureDone(cps, 1);
7604 cps->protocolVersion = 1;
7607 cps->maybeThinking = FALSE;
7609 if (StrStr(message, "draw")) {
7610 /* Program doesn't have "draw" command */
7611 cps->sendDrawOffers = 0;
7614 if (cps->sendTime != 1 &&
7615 (StrStr(message, "time") || StrStr(message, "otim"))) {
7616 /* Program apparently doesn't have "time" or "otim" command */
7620 if (StrStr(message, "analyze")) {
7621 cps->analysisSupport = FALSE;
7622 cps->analyzing = FALSE;
7624 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7625 DisplayError(buf2, 0);
7628 if (StrStr(message, "(no matching move)st")) {
7629 /* Special kludge for GNU Chess 4 only */
7630 cps->stKludge = TRUE;
7631 SendTimeControl(cps, movesPerSession, timeControl,
7632 timeIncrement, appData.searchDepth,
7636 if (StrStr(message, "(no matching move)sd")) {
7637 /* Special kludge for GNU Chess 4 only */
7638 cps->sdKludge = TRUE;
7639 SendTimeControl(cps, movesPerSession, timeControl,
7640 timeIncrement, appData.searchDepth,
7644 if (!StrStr(message, "llegal")) {
7647 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7648 gameMode == IcsIdle) return;
7649 if (forwardMostMove <= backwardMostMove) return;
7650 if (pausing) PauseEvent();
7651 if(appData.forceIllegal) {
7652 // [HGM] illegal: machine refused move; force position after move into it
7653 SendToProgram("force\n", cps);
7654 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7655 // we have a real problem now, as SendBoard will use the a2a3 kludge
7656 // when black is to move, while there might be nothing on a2 or black
7657 // might already have the move. So send the board as if white has the move.
7658 // But first we must change the stm of the engine, as it refused the last move
7659 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7660 if(WhiteOnMove(forwardMostMove)) {
7661 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7662 SendBoard(cps, forwardMostMove); // kludgeless board
7664 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7665 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7666 SendBoard(cps, forwardMostMove+1); // kludgeless board
7668 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7669 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7670 gameMode == TwoMachinesPlay)
7671 SendToProgram("go\n", cps);
7674 if (gameMode == PlayFromGameFile) {
7675 /* Stop reading this game file */
7676 gameMode = EditGame;
7679 currentMove = forwardMostMove-1;
7680 DisplayMove(currentMove-1); /* before DisplayMoveError */
7681 SwitchClocks(forwardMostMove-1); // [HGM] race
7682 DisplayBothClocks();
7683 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7684 parseList[currentMove], cps->which);
7685 DisplayMoveError(buf1);
7686 DrawPosition(FALSE, boards[currentMove]);
7688 /* [HGM] illegal-move claim should forfeit game when Xboard */
7689 /* only passes fully legal moves */
7690 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7691 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7692 "False illegal-move claim", GE_XBOARD );
7696 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7697 /* Program has a broken "time" command that
7698 outputs a string not ending in newline.
7704 * If chess program startup fails, exit with an error message.
7705 * Attempts to recover here are futile.
7707 if ((StrStr(message, "unknown host") != NULL)
7708 || (StrStr(message, "No remote directory") != NULL)
7709 || (StrStr(message, "not found") != NULL)
7710 || (StrStr(message, "No such file") != NULL)
7711 || (StrStr(message, "can't alloc") != NULL)
7712 || (StrStr(message, "Permission denied") != NULL)) {
7714 cps->maybeThinking = FALSE;
7715 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7716 cps->which, cps->program, cps->host, message);
7717 RemoveInputSource(cps->isr);
7718 DisplayFatalError(buf1, 0, 1);
7723 * Look for hint output
7725 if (sscanf(message, "Hint: %s", buf1) == 1) {
7726 if (cps == &first && hintRequested) {
7727 hintRequested = FALSE;
7728 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7729 &fromX, &fromY, &toX, &toY, &promoChar)) {
7730 (void) CoordsToAlgebraic(boards[forwardMostMove],
7731 PosFlags(forwardMostMove),
7732 fromY, fromX, toY, toX, promoChar, buf1);
7733 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7734 DisplayInformation(buf2);
7736 /* Hint move could not be parsed!? */
7737 snprintf(buf2, sizeof(buf2),
7738 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7740 DisplayError(buf2, 0);
7743 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7749 * Ignore other messages if game is not in progress
7751 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7752 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7755 * look for win, lose, draw, or draw offer
7757 if (strncmp(message, "1-0", 3) == 0) {
7758 char *p, *q, *r = "";
7759 p = strchr(message, '{');
7767 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7769 } else if (strncmp(message, "0-1", 3) == 0) {
7770 char *p, *q, *r = "";
7771 p = strchr(message, '{');
7779 /* Kludge for Arasan 4.1 bug */
7780 if (strcmp(r, "Black resigns") == 0) {
7781 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7784 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7786 } else if (strncmp(message, "1/2", 3) == 0) {
7787 char *p, *q, *r = "";
7788 p = strchr(message, '{');
7797 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7800 } else if (strncmp(message, "White resign", 12) == 0) {
7801 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7803 } else if (strncmp(message, "Black resign", 12) == 0) {
7804 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7806 } else if (strncmp(message, "White matches", 13) == 0 ||
7807 strncmp(message, "Black matches", 13) == 0 ) {
7808 /* [HGM] ignore GNUShogi noises */
7810 } else if (strncmp(message, "White", 5) == 0 &&
7811 message[5] != '(' &&
7812 StrStr(message, "Black") == NULL) {
7813 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7815 } else if (strncmp(message, "Black", 5) == 0 &&
7816 message[5] != '(') {
7817 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7819 } else if (strcmp(message, "resign") == 0 ||
7820 strcmp(message, "computer resigns") == 0) {
7822 case MachinePlaysBlack:
7823 case IcsPlayingBlack:
7824 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7826 case MachinePlaysWhite:
7827 case IcsPlayingWhite:
7828 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7830 case TwoMachinesPlay:
7831 if (cps->twoMachinesColor[0] == 'w')
7832 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7834 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7841 } else if (strncmp(message, "opponent mates", 14) == 0) {
7843 case MachinePlaysBlack:
7844 case IcsPlayingBlack:
7845 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7847 case MachinePlaysWhite:
7848 case IcsPlayingWhite:
7849 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7851 case TwoMachinesPlay:
7852 if (cps->twoMachinesColor[0] == 'w')
7853 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7855 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7862 } else if (strncmp(message, "computer mates", 14) == 0) {
7864 case MachinePlaysBlack:
7865 case IcsPlayingBlack:
7866 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7868 case MachinePlaysWhite:
7869 case IcsPlayingWhite:
7870 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7872 case TwoMachinesPlay:
7873 if (cps->twoMachinesColor[0] == 'w')
7874 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7876 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7883 } else if (strncmp(message, "checkmate", 9) == 0) {
7884 if (WhiteOnMove(forwardMostMove)) {
7885 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7887 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7890 } else if (strstr(message, "Draw") != NULL ||
7891 strstr(message, "game is a draw") != NULL) {
7892 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7894 } else if (strstr(message, "offer") != NULL &&
7895 strstr(message, "draw") != NULL) {
7897 if (appData.zippyPlay && first.initDone) {
7898 /* Relay offer to ICS */
7899 SendToICS(ics_prefix);
7900 SendToICS("draw\n");
7903 cps->offeredDraw = 2; /* valid until this engine moves twice */
7904 if (gameMode == TwoMachinesPlay) {
7905 if (cps->other->offeredDraw) {
7906 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7907 /* [HGM] in two-machine mode we delay relaying draw offer */
7908 /* until after we also have move, to see if it is really claim */
7910 } else if (gameMode == MachinePlaysWhite ||
7911 gameMode == MachinePlaysBlack) {
7912 if (userOfferedDraw) {
7913 DisplayInformation(_("Machine accepts your draw offer"));
7914 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7916 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7923 * Look for thinking output
7925 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7926 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7928 int plylev, mvleft, mvtot, curscore, time;
7929 char mvname[MOVE_LEN];
7933 int prefixHint = FALSE;
7934 mvname[0] = NULLCHAR;
7937 case MachinePlaysBlack:
7938 case IcsPlayingBlack:
7939 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7941 case MachinePlaysWhite:
7942 case IcsPlayingWhite:
7943 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7948 case IcsObserving: /* [DM] icsEngineAnalyze */
7949 if (!appData.icsEngineAnalyze) ignore = TRUE;
7951 case TwoMachinesPlay:
7952 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7962 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7964 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7965 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7967 if (plyext != ' ' && plyext != '\t') {
7971 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7972 if( cps->scoreIsAbsolute &&
7973 ( gameMode == MachinePlaysBlack ||
7974 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7975 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7976 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7977 !WhiteOnMove(currentMove)
7980 curscore = -curscore;
7984 tempStats.depth = plylev;
7985 tempStats.nodes = nodes;
7986 tempStats.time = time;
7987 tempStats.score = curscore;
7988 tempStats.got_only_move = 0;
7990 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7993 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7994 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7995 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7996 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7997 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7998 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7999 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8000 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8003 /* Buffer overflow protection */
8004 if (buf1[0] != NULLCHAR) {
8005 if (strlen(buf1) >= sizeof(tempStats.movelist)
8006 && appData.debugMode) {
8008 "PV is too long; using the first %u bytes.\n",
8009 (unsigned) sizeof(tempStats.movelist) - 1);
8012 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8014 sprintf(tempStats.movelist, " no PV\n");
8017 if (tempStats.seen_stat) {
8018 tempStats.ok_to_send = 1;
8021 if (strchr(tempStats.movelist, '(') != NULL) {
8022 tempStats.line_is_book = 1;
8023 tempStats.nr_moves = 0;
8024 tempStats.moves_left = 0;
8026 tempStats.line_is_book = 0;
8029 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8030 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8032 SendProgramStatsToFrontend( cps, &tempStats );
8035 [AS] Protect the thinkOutput buffer from overflow... this
8036 is only useful if buf1 hasn't overflowed first!
8038 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8040 (gameMode == TwoMachinesPlay ?
8041 ToUpper(cps->twoMachinesColor[0]) : ' '),
8042 ((double) curscore) / 100.0,
8043 prefixHint ? lastHint : "",
8044 prefixHint ? " " : "" );
8046 if( buf1[0] != NULLCHAR ) {
8047 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8049 if( strlen(buf1) > max_len ) {
8050 if( appData.debugMode) {
8051 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8053 buf1[max_len+1] = '\0';
8056 strcat( thinkOutput, buf1 );
8059 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8060 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8061 DisplayMove(currentMove - 1);
8065 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8066 /* crafty (9.25+) says "(only move) <move>"
8067 * if there is only 1 legal move
8069 sscanf(p, "(only move) %s", buf1);
8070 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8071 sprintf(programStats.movelist, "%s (only move)", buf1);
8072 programStats.depth = 1;
8073 programStats.nr_moves = 1;
8074 programStats.moves_left = 1;
8075 programStats.nodes = 1;
8076 programStats.time = 1;
8077 programStats.got_only_move = 1;
8079 /* Not really, but we also use this member to
8080 mean "line isn't going to change" (Crafty
8081 isn't searching, so stats won't change) */
8082 programStats.line_is_book = 1;
8084 SendProgramStatsToFrontend( cps, &programStats );
8086 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8087 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8088 DisplayMove(currentMove - 1);
8091 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8092 &time, &nodes, &plylev, &mvleft,
8093 &mvtot, mvname) >= 5) {
8094 /* The stat01: line is from Crafty (9.29+) in response
8095 to the "." command */
8096 programStats.seen_stat = 1;
8097 cps->maybeThinking = TRUE;
8099 if (programStats.got_only_move || !appData.periodicUpdates)
8102 programStats.depth = plylev;
8103 programStats.time = time;
8104 programStats.nodes = nodes;
8105 programStats.moves_left = mvleft;
8106 programStats.nr_moves = mvtot;
8107 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8108 programStats.ok_to_send = 1;
8109 programStats.movelist[0] = '\0';
8111 SendProgramStatsToFrontend( cps, &programStats );
8115 } else if (strncmp(message,"++",2) == 0) {
8116 /* Crafty 9.29+ outputs this */
8117 programStats.got_fail = 2;
8120 } else if (strncmp(message,"--",2) == 0) {
8121 /* Crafty 9.29+ outputs this */
8122 programStats.got_fail = 1;
8125 } else if (thinkOutput[0] != NULLCHAR &&
8126 strncmp(message, " ", 4) == 0) {
8127 unsigned message_len;
8130 while (*p && *p == ' ') p++;
8132 message_len = strlen( p );
8134 /* [AS] Avoid buffer overflow */
8135 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8136 strcat(thinkOutput, " ");
8137 strcat(thinkOutput, p);
8140 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8141 strcat(programStats.movelist, " ");
8142 strcat(programStats.movelist, p);
8145 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8146 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8147 DisplayMove(currentMove - 1);
8155 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8156 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8158 ChessProgramStats cpstats;
8160 if (plyext != ' ' && plyext != '\t') {
8164 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8165 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8166 curscore = -curscore;
8169 cpstats.depth = plylev;
8170 cpstats.nodes = nodes;
8171 cpstats.time = time;
8172 cpstats.score = curscore;
8173 cpstats.got_only_move = 0;
8174 cpstats.movelist[0] = '\0';
8176 if (buf1[0] != NULLCHAR) {
8177 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8180 cpstats.ok_to_send = 0;
8181 cpstats.line_is_book = 0;
8182 cpstats.nr_moves = 0;
8183 cpstats.moves_left = 0;
8185 SendProgramStatsToFrontend( cps, &cpstats );
8192 /* Parse a game score from the character string "game", and
8193 record it as the history of the current game. The game
8194 score is NOT assumed to start from the standard position.
8195 The display is not updated in any way.
8198 ParseGameHistory(game)
8202 int fromX, fromY, toX, toY, boardIndex;
8207 if (appData.debugMode)
8208 fprintf(debugFP, "Parsing game history: %s\n", game);
8210 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8211 gameInfo.site = StrSave(appData.icsHost);
8212 gameInfo.date = PGNDate();
8213 gameInfo.round = StrSave("-");
8215 /* Parse out names of players */
8216 while (*game == ' ') game++;
8218 while (*game != ' ') *p++ = *game++;
8220 gameInfo.white = StrSave(buf);
8221 while (*game == ' ') game++;
8223 while (*game != ' ' && *game != '\n') *p++ = *game++;
8225 gameInfo.black = StrSave(buf);
8228 boardIndex = blackPlaysFirst ? 1 : 0;
8231 yyboardindex = boardIndex;
8232 moveType = (ChessMove) Myylex();
8234 case IllegalMove: /* maybe suicide chess, etc. */
8235 if (appData.debugMode) {
8236 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8237 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8238 setbuf(debugFP, NULL);
8240 case WhitePromotion:
8241 case BlackPromotion:
8242 case WhiteNonPromotion:
8243 case BlackNonPromotion:
8245 case WhiteCapturesEnPassant:
8246 case BlackCapturesEnPassant:
8247 case WhiteKingSideCastle:
8248 case WhiteQueenSideCastle:
8249 case BlackKingSideCastle:
8250 case BlackQueenSideCastle:
8251 case WhiteKingSideCastleWild:
8252 case WhiteQueenSideCastleWild:
8253 case BlackKingSideCastleWild:
8254 case BlackQueenSideCastleWild:
8256 case WhiteHSideCastleFR:
8257 case WhiteASideCastleFR:
8258 case BlackHSideCastleFR:
8259 case BlackASideCastleFR:
8261 fromX = currentMoveString[0] - AAA;
8262 fromY = currentMoveString[1] - ONE;
8263 toX = currentMoveString[2] - AAA;
8264 toY = currentMoveString[3] - ONE;
8265 promoChar = currentMoveString[4];
8269 fromX = moveType == WhiteDrop ?
8270 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8271 (int) CharToPiece(ToLower(currentMoveString[0]));
8273 toX = currentMoveString[2] - AAA;
8274 toY = currentMoveString[3] - ONE;
8275 promoChar = NULLCHAR;
8279 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8280 if (appData.debugMode) {
8281 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8282 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8283 setbuf(debugFP, NULL);
8285 DisplayError(buf, 0);
8287 case ImpossibleMove:
8289 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8290 if (appData.debugMode) {
8291 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8292 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8293 setbuf(debugFP, NULL);
8295 DisplayError(buf, 0);
8298 if (boardIndex < backwardMostMove) {
8299 /* Oops, gap. How did that happen? */
8300 DisplayError(_("Gap in move list"), 0);
8303 backwardMostMove = blackPlaysFirst ? 1 : 0;
8304 if (boardIndex > forwardMostMove) {
8305 forwardMostMove = boardIndex;
8309 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8310 strcat(parseList[boardIndex-1], " ");
8311 strcat(parseList[boardIndex-1], yy_text);
8323 case GameUnfinished:
8324 if (gameMode == IcsExamining) {
8325 if (boardIndex < backwardMostMove) {
8326 /* Oops, gap. How did that happen? */
8329 backwardMostMove = blackPlaysFirst ? 1 : 0;
8332 gameInfo.result = moveType;
8333 p = strchr(yy_text, '{');
8334 if (p == NULL) p = strchr(yy_text, '(');
8337 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8339 q = strchr(p, *p == '{' ? '}' : ')');
8340 if (q != NULL) *q = NULLCHAR;
8343 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8344 gameInfo.resultDetails = StrSave(p);
8347 if (boardIndex >= forwardMostMove &&
8348 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8349 backwardMostMove = blackPlaysFirst ? 1 : 0;
8352 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8353 fromY, fromX, toY, toX, promoChar,
8354 parseList[boardIndex]);
8355 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8356 /* currentMoveString is set as a side-effect of yylex */
8357 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8358 strcat(moveList[boardIndex], "\n");
8360 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8361 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8367 if(gameInfo.variant != VariantShogi)
8368 strcat(parseList[boardIndex - 1], "+");
8372 strcat(parseList[boardIndex - 1], "#");
8379 /* Apply a move to the given board */
8381 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8382 int fromX, fromY, toX, toY;
8386 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8387 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8389 /* [HGM] compute & store e.p. status and castling rights for new position */
8390 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8392 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8393 oldEP = (signed char)board[EP_STATUS];
8394 board[EP_STATUS] = EP_NONE;
8396 if( board[toY][toX] != EmptySquare )
8397 board[EP_STATUS] = EP_CAPTURE;
8399 if (fromY == DROP_RANK) {
8401 piece = board[toY][toX] = (ChessSquare) fromX;
8405 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8406 if( gameInfo.variant == VariantFairy ) board[EP_STATUS] = EP_PAWN_MOVE; // Lance in fairy is Pawn-like
8408 if( board[fromY][fromX] == WhitePawn ) {
8409 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8410 board[EP_STATUS] = EP_PAWN_MOVE;
8412 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8413 gameInfo.variant != VariantBerolina || toX < fromX)
8414 board[EP_STATUS] = toX | berolina;
8415 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8416 gameInfo.variant != VariantBerolina || toX > fromX)
8417 board[EP_STATUS] = toX;
8420 if( board[fromY][fromX] == BlackPawn ) {
8421 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8422 board[EP_STATUS] = EP_PAWN_MOVE;
8423 if( toY-fromY== -2) {
8424 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8425 gameInfo.variant != VariantBerolina || toX < fromX)
8426 board[EP_STATUS] = toX | berolina;
8427 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8428 gameInfo.variant != VariantBerolina || toX > fromX)
8429 board[EP_STATUS] = toX;
8433 for(i=0; i<nrCastlingRights; i++) {
8434 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8435 board[CASTLING][i] == toX && castlingRank[i] == toY
8436 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8439 if (fromX == toX && fromY == toY) return;
8441 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8442 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8443 if(gameInfo.variant == VariantKnightmate)
8444 king += (int) WhiteUnicorn - (int) WhiteKing;
8446 /* Code added by Tord: */
8447 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8448 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8449 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8450 board[fromY][fromX] = EmptySquare;
8451 board[toY][toX] = EmptySquare;
8452 if((toX > fromX) != (piece == WhiteRook)) {
8453 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8455 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8457 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8458 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8459 board[fromY][fromX] = EmptySquare;
8460 board[toY][toX] = EmptySquare;
8461 if((toX > fromX) != (piece == BlackRook)) {
8462 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8464 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8466 /* End of code added by Tord */
8468 } else if (board[fromY][fromX] == king
8469 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8470 && toY == fromY && toX > fromX+1) {
8471 board[fromY][fromX] = EmptySquare;
8472 board[toY][toX] = king;
8473 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8474 board[fromY][BOARD_RGHT-1] = EmptySquare;
8475 } else if (board[fromY][fromX] == king
8476 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8477 && toY == fromY && toX < fromX-1) {
8478 board[fromY][fromX] = EmptySquare;
8479 board[toY][toX] = king;
8480 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8481 board[fromY][BOARD_LEFT] = EmptySquare;
8482 } else if (board[fromY][fromX] == WhitePawn
8483 && toY >= BOARD_HEIGHT-promoRank
8484 && gameInfo.variant != VariantXiangqi
8486 /* white pawn promotion */
8487 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8488 if (board[toY][toX] == EmptySquare) {
8489 board[toY][toX] = WhiteQueen;
8491 if(gameInfo.variant==VariantBughouse ||
8492 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8493 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8494 board[fromY][fromX] = EmptySquare;
8495 } else if ((fromY == BOARD_HEIGHT-4)
8497 && gameInfo.variant != VariantXiangqi
8498 && gameInfo.variant != VariantBerolina
8499 && (board[fromY][fromX] == WhitePawn)
8500 && (board[toY][toX] == EmptySquare)) {
8501 board[fromY][fromX] = EmptySquare;
8502 board[toY][toX] = WhitePawn;
8503 captured = board[toY - 1][toX];
8504 board[toY - 1][toX] = EmptySquare;
8505 } else if ((fromY == BOARD_HEIGHT-4)
8507 && gameInfo.variant == VariantBerolina
8508 && (board[fromY][fromX] == WhitePawn)
8509 && (board[toY][toX] == EmptySquare)) {
8510 board[fromY][fromX] = EmptySquare;
8511 board[toY][toX] = WhitePawn;
8512 if(oldEP & EP_BEROLIN_A) {
8513 captured = board[fromY][fromX-1];
8514 board[fromY][fromX-1] = EmptySquare;
8515 }else{ captured = board[fromY][fromX+1];
8516 board[fromY][fromX+1] = EmptySquare;
8518 } else if (board[fromY][fromX] == king
8519 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8520 && toY == fromY && toX > fromX+1) {
8521 board[fromY][fromX] = EmptySquare;
8522 board[toY][toX] = king;
8523 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8524 board[fromY][BOARD_RGHT-1] = EmptySquare;
8525 } else if (board[fromY][fromX] == king
8526 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8527 && toY == fromY && toX < fromX-1) {
8528 board[fromY][fromX] = EmptySquare;
8529 board[toY][toX] = king;
8530 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8531 board[fromY][BOARD_LEFT] = EmptySquare;
8532 } else if (fromY == 7 && fromX == 3
8533 && board[fromY][fromX] == BlackKing
8534 && toY == 7 && toX == 5) {
8535 board[fromY][fromX] = EmptySquare;
8536 board[toY][toX] = BlackKing;
8537 board[fromY][7] = EmptySquare;
8538 board[toY][4] = BlackRook;
8539 } else if (fromY == 7 && fromX == 3
8540 && board[fromY][fromX] == BlackKing
8541 && toY == 7 && toX == 1) {
8542 board[fromY][fromX] = EmptySquare;
8543 board[toY][toX] = BlackKing;
8544 board[fromY][0] = EmptySquare;
8545 board[toY][2] = BlackRook;
8546 } else if (board[fromY][fromX] == BlackPawn
8548 && gameInfo.variant != VariantXiangqi
8550 /* black pawn promotion */
8551 board[toY][toX] = CharToPiece(ToLower(promoChar));
8552 if (board[toY][toX] == EmptySquare) {
8553 board[toY][toX] = BlackQueen;
8555 if(gameInfo.variant==VariantBughouse ||
8556 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8557 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8558 board[fromY][fromX] = EmptySquare;
8559 } else if ((fromY == 3)
8561 && gameInfo.variant != VariantXiangqi
8562 && gameInfo.variant != VariantBerolina
8563 && (board[fromY][fromX] == BlackPawn)
8564 && (board[toY][toX] == EmptySquare)) {
8565 board[fromY][fromX] = EmptySquare;
8566 board[toY][toX] = BlackPawn;
8567 captured = board[toY + 1][toX];
8568 board[toY + 1][toX] = EmptySquare;
8569 } else if ((fromY == 3)
8571 && gameInfo.variant == VariantBerolina
8572 && (board[fromY][fromX] == BlackPawn)
8573 && (board[toY][toX] == EmptySquare)) {
8574 board[fromY][fromX] = EmptySquare;
8575 board[toY][toX] = BlackPawn;
8576 if(oldEP & EP_BEROLIN_A) {
8577 captured = board[fromY][fromX-1];
8578 board[fromY][fromX-1] = EmptySquare;
8579 }else{ captured = board[fromY][fromX+1];
8580 board[fromY][fromX+1] = EmptySquare;
8583 board[toY][toX] = board[fromY][fromX];
8584 board[fromY][fromX] = EmptySquare;
8588 if (gameInfo.holdingsWidth != 0) {
8590 /* !!A lot more code needs to be written to support holdings */
8591 /* [HGM] OK, so I have written it. Holdings are stored in the */
8592 /* penultimate board files, so they are automaticlly stored */
8593 /* in the game history. */
8594 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8595 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8596 /* Delete from holdings, by decreasing count */
8597 /* and erasing image if necessary */
8598 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8599 if(p < (int) BlackPawn) { /* white drop */
8600 p -= (int)WhitePawn;
8601 p = PieceToNumber((ChessSquare)p);
8602 if(p >= gameInfo.holdingsSize) p = 0;
8603 if(--board[p][BOARD_WIDTH-2] <= 0)
8604 board[p][BOARD_WIDTH-1] = EmptySquare;
8605 if((int)board[p][BOARD_WIDTH-2] < 0)
8606 board[p][BOARD_WIDTH-2] = 0;
8607 } else { /* black drop */
8608 p -= (int)BlackPawn;
8609 p = PieceToNumber((ChessSquare)p);
8610 if(p >= gameInfo.holdingsSize) p = 0;
8611 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8612 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8613 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8614 board[BOARD_HEIGHT-1-p][1] = 0;
8617 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8618 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
8619 /* [HGM] holdings: Add to holdings, if holdings exist */
8620 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8621 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8622 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8625 if (p >= (int) BlackPawn) {
8626 p -= (int)BlackPawn;
8627 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8628 /* in Shogi restore piece to its original first */
8629 captured = (ChessSquare) (DEMOTED captured);
8632 p = PieceToNumber((ChessSquare)p);
8633 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8634 board[p][BOARD_WIDTH-2]++;
8635 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8637 p -= (int)WhitePawn;
8638 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8639 captured = (ChessSquare) (DEMOTED captured);
8642 p = PieceToNumber((ChessSquare)p);
8643 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8644 board[BOARD_HEIGHT-1-p][1]++;
8645 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8648 } else if (gameInfo.variant == VariantAtomic) {
8649 if (captured != EmptySquare) {
8651 for (y = toY-1; y <= toY+1; y++) {
8652 for (x = toX-1; x <= toX+1; x++) {
8653 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8654 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8655 board[y][x] = EmptySquare;
8659 board[toY][toX] = EmptySquare;
8662 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8663 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8665 if(promoChar == '+') {
8666 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8667 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8668 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8669 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8671 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8672 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8673 // [HGM] superchess: take promotion piece out of holdings
8674 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8675 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8676 if(!--board[k][BOARD_WIDTH-2])
8677 board[k][BOARD_WIDTH-1] = EmptySquare;
8679 if(!--board[BOARD_HEIGHT-1-k][1])
8680 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8686 /* Updates forwardMostMove */
8688 MakeMove(fromX, fromY, toX, toY, promoChar)
8689 int fromX, fromY, toX, toY;
8692 // forwardMostMove++; // [HGM] bare: moved downstream
8694 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8695 int timeLeft; static int lastLoadFlag=0; int king, piece;
8696 piece = boards[forwardMostMove][fromY][fromX];
8697 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8698 if(gameInfo.variant == VariantKnightmate)
8699 king += (int) WhiteUnicorn - (int) WhiteKing;
8700 if(forwardMostMove == 0) {
8702 fprintf(serverMoves, "%s;", second.tidy);
8703 fprintf(serverMoves, "%s;", first.tidy);
8704 if(!blackPlaysFirst)
8705 fprintf(serverMoves, "%s;", second.tidy);
8706 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8707 lastLoadFlag = loadFlag;
8709 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8710 // print castling suffix
8711 if( toY == fromY && piece == king ) {
8713 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8715 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8718 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8719 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8720 boards[forwardMostMove][toY][toX] == EmptySquare
8721 && fromX != toX && fromY != toY)
8722 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8724 if(promoChar != NULLCHAR)
8725 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8727 fprintf(serverMoves, "/%d/%d",
8728 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8729 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8730 else timeLeft = blackTimeRemaining/1000;
8731 fprintf(serverMoves, "/%d", timeLeft);
8733 fflush(serverMoves);
8736 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8737 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8741 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8742 if (commentList[forwardMostMove+1] != NULL) {
8743 free(commentList[forwardMostMove+1]);
8744 commentList[forwardMostMove+1] = NULL;
8746 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8747 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8748 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8749 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8750 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8751 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8752 gameInfo.result = GameUnfinished;
8753 if (gameInfo.resultDetails != NULL) {
8754 free(gameInfo.resultDetails);
8755 gameInfo.resultDetails = NULL;
8757 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8758 moveList[forwardMostMove - 1]);
8759 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8760 PosFlags(forwardMostMove - 1),
8761 fromY, fromX, toY, toX, promoChar,
8762 parseList[forwardMostMove - 1]);
8763 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8769 if(gameInfo.variant != VariantShogi)
8770 strcat(parseList[forwardMostMove - 1], "+");
8774 strcat(parseList[forwardMostMove - 1], "#");
8777 if (appData.debugMode) {
8778 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8783 /* Updates currentMove if not pausing */
8785 ShowMove(fromX, fromY, toX, toY)
8787 int instant = (gameMode == PlayFromGameFile) ?
8788 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8789 if(appData.noGUI) return;
8790 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8792 if (forwardMostMove == currentMove + 1) {
8793 AnimateMove(boards[forwardMostMove - 1],
8794 fromX, fromY, toX, toY);
8796 if (appData.highlightLastMove) {
8797 SetHighlights(fromX, fromY, toX, toY);
8800 currentMove = forwardMostMove;
8803 if (instant) return;
8805 DisplayMove(currentMove - 1);
8806 DrawPosition(FALSE, boards[currentMove]);
8807 DisplayBothClocks();
8808 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8811 void SendEgtPath(ChessProgramState *cps)
8812 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8813 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8815 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8818 char c, *q = name+1, *r, *s;
8820 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8821 while(*p && *p != ',') *q++ = *p++;
8823 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8824 strcmp(name, ",nalimov:") == 0 ) {
8825 // take nalimov path from the menu-changeable option first, if it is defined
8826 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8827 SendToProgram(buf,cps); // send egtbpath command for nalimov
8829 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8830 (s = StrStr(appData.egtFormats, name)) != NULL) {
8831 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8832 s = r = StrStr(s, ":") + 1; // beginning of path info
8833 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8834 c = *r; *r = 0; // temporarily null-terminate path info
8835 *--q = 0; // strip of trailig ':' from name
8836 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8838 SendToProgram(buf,cps); // send egtbpath command for this format
8840 if(*p == ',') p++; // read away comma to position for next format name
8845 InitChessProgram(cps, setup)
8846 ChessProgramState *cps;
8847 int setup; /* [HGM] needed to setup FRC opening position */
8849 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8850 if (appData.noChessProgram) return;
8851 hintRequested = FALSE;
8852 bookRequested = FALSE;
8854 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8855 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8856 if(cps->memSize) { /* [HGM] memory */
8857 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8858 SendToProgram(buf, cps);
8860 SendEgtPath(cps); /* [HGM] EGT */
8861 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8862 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8863 SendToProgram(buf, cps);
8866 SendToProgram(cps->initString, cps);
8867 if (gameInfo.variant != VariantNormal &&
8868 gameInfo.variant != VariantLoadable
8869 /* [HGM] also send variant if board size non-standard */
8870 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8872 char *v = VariantName(gameInfo.variant);
8873 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8874 /* [HGM] in protocol 1 we have to assume all variants valid */
8875 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8876 DisplayFatalError(buf, 0, 1);
8880 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8881 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8882 if( gameInfo.variant == VariantXiangqi )
8883 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8884 if( gameInfo.variant == VariantShogi )
8885 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8886 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8887 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8888 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8889 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8890 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8891 if( gameInfo.variant == VariantCourier )
8892 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8893 if( gameInfo.variant == VariantSuper )
8894 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8895 if( gameInfo.variant == VariantGreat )
8896 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8897 if( gameInfo.variant == VariantSChess )
8898 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8901 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8902 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8903 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8904 if(StrStr(cps->variants, b) == NULL) {
8905 // specific sized variant not known, check if general sizing allowed
8906 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8907 if(StrStr(cps->variants, "boardsize") == NULL) {
8908 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8909 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8910 DisplayFatalError(buf, 0, 1);
8913 /* [HGM] here we really should compare with the maximum supported board size */
8916 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8917 snprintf(buf, MSG_SIZ, "variant %s\n", b);
8918 SendToProgram(buf, cps);
8920 currentlyInitializedVariant = gameInfo.variant;
8922 /* [HGM] send opening position in FRC to first engine */
8924 SendToProgram("force\n", cps);
8926 /* engine is now in force mode! Set flag to wake it up after first move. */
8927 setboardSpoiledMachineBlack = 1;
8931 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8932 SendToProgram(buf, cps);
8934 cps->maybeThinking = FALSE;
8935 cps->offeredDraw = 0;
8936 if (!appData.icsActive) {
8937 SendTimeControl(cps, movesPerSession, timeControl,
8938 timeIncrement, appData.searchDepth,
8941 if (appData.showThinking
8942 // [HGM] thinking: four options require thinking output to be sent
8943 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8945 SendToProgram("post\n", cps);
8947 SendToProgram("hard\n", cps);
8948 if (!appData.ponderNextMove) {
8949 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8950 it without being sure what state we are in first. "hard"
8951 is not a toggle, so that one is OK.
8953 SendToProgram("easy\n", cps);
8956 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8957 SendToProgram(buf, cps);
8959 cps->initDone = TRUE;
8964 StartChessProgram(cps)
8965 ChessProgramState *cps;
8970 if (appData.noChessProgram) return;
8971 cps->initDone = FALSE;
8973 if (strcmp(cps->host, "localhost") == 0) {
8974 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8975 } else if (*appData.remoteShell == NULLCHAR) {
8976 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8978 if (*appData.remoteUser == NULLCHAR) {
8979 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8982 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8983 cps->host, appData.remoteUser, cps->program);
8985 err = StartChildProcess(buf, "", &cps->pr);
8989 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8990 DisplayFatalError(buf, err, 1);
8996 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8997 if (cps->protocolVersion > 1) {
8998 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8999 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9000 cps->comboCnt = 0; // and values of combo boxes
9001 SendToProgram(buf, cps);
9003 SendToProgram("xboard\n", cps);
9009 TwoMachinesEventIfReady P((void))
9011 if (first.lastPing != first.lastPong) {
9012 DisplayMessage("", _("Waiting for first chess program"));
9013 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9016 if (second.lastPing != second.lastPong) {
9017 DisplayMessage("", _("Waiting for second chess program"));
9018 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9026 NextMatchGame P((void))
9028 int index; /* [HGM] autoinc: step load index during match */
9030 if (*appData.loadGameFile != NULLCHAR) {
9031 index = appData.loadGameIndex;
9032 if(index < 0) { // [HGM] autoinc
9033 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9034 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9036 LoadGameFromFile(appData.loadGameFile,
9038 appData.loadGameFile, FALSE);
9039 } else if (*appData.loadPositionFile != NULLCHAR) {
9040 index = appData.loadPositionIndex;
9041 if(index < 0) { // [HGM] autoinc
9042 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9043 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9045 LoadPositionFromFile(appData.loadPositionFile,
9047 appData.loadPositionFile);
9049 TwoMachinesEventIfReady();
9052 void UserAdjudicationEvent( int result )
9054 ChessMove gameResult = GameIsDrawn;
9057 gameResult = WhiteWins;
9059 else if( result < 0 ) {
9060 gameResult = BlackWins;
9063 if( gameMode == TwoMachinesPlay ) {
9064 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9069 // [HGM] save: calculate checksum of game to make games easily identifiable
9070 int StringCheckSum(char *s)
9073 if(s==NULL) return 0;
9074 while(*s) i = i*259 + *s++;
9081 for(i=backwardMostMove; i<forwardMostMove; i++) {
9082 sum += pvInfoList[i].depth;
9083 sum += StringCheckSum(parseList[i]);
9084 sum += StringCheckSum(commentList[i]);
9087 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9088 return sum + StringCheckSum(commentList[i]);
9089 } // end of save patch
9092 GameEnds(result, resultDetails, whosays)
9094 char *resultDetails;
9097 GameMode nextGameMode;
9099 char buf[MSG_SIZ], popupRequested = 0;
9101 if(endingGame) return; /* [HGM] crash: forbid recursion */
9103 if(twoBoards) { // [HGM] dual: switch back to one board
9104 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9105 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9107 if (appData.debugMode) {
9108 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9109 result, resultDetails ? resultDetails : "(null)", whosays);
9112 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9114 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9115 /* If we are playing on ICS, the server decides when the
9116 game is over, but the engine can offer to draw, claim
9120 if (appData.zippyPlay && first.initDone) {
9121 if (result == GameIsDrawn) {
9122 /* In case draw still needs to be claimed */
9123 SendToICS(ics_prefix);
9124 SendToICS("draw\n");
9125 } else if (StrCaseStr(resultDetails, "resign")) {
9126 SendToICS(ics_prefix);
9127 SendToICS("resign\n");
9131 endingGame = 0; /* [HGM] crash */
9135 /* If we're loading the game from a file, stop */
9136 if (whosays == GE_FILE) {
9137 (void) StopLoadGameTimer();
9141 /* Cancel draw offers */
9142 first.offeredDraw = second.offeredDraw = 0;
9144 /* If this is an ICS game, only ICS can really say it's done;
9145 if not, anyone can. */
9146 isIcsGame = (gameMode == IcsPlayingWhite ||
9147 gameMode == IcsPlayingBlack ||
9148 gameMode == IcsObserving ||
9149 gameMode == IcsExamining);
9151 if (!isIcsGame || whosays == GE_ICS) {
9152 /* OK -- not an ICS game, or ICS said it was done */
9154 if (!isIcsGame && !appData.noChessProgram)
9155 SetUserThinkingEnables();
9157 /* [HGM] if a machine claims the game end we verify this claim */
9158 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9159 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9161 ChessMove trueResult = (ChessMove) -1;
9163 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9164 first.twoMachinesColor[0] :
9165 second.twoMachinesColor[0] ;
9167 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9168 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9169 /* [HGM] verify: engine mate claims accepted if they were flagged */
9170 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9172 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9173 /* [HGM] verify: engine mate claims accepted if they were flagged */
9174 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9176 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9177 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9180 // now verify win claims, but not in drop games, as we don't understand those yet
9181 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9182 || gameInfo.variant == VariantGreat) &&
9183 (result == WhiteWins && claimer == 'w' ||
9184 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9185 if (appData.debugMode) {
9186 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9187 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9189 if(result != trueResult) {
9190 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9191 result = claimer == 'w' ? BlackWins : WhiteWins;
9192 resultDetails = buf;
9195 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9196 && (forwardMostMove <= backwardMostMove ||
9197 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9198 (claimer=='b')==(forwardMostMove&1))
9200 /* [HGM] verify: draws that were not flagged are false claims */
9201 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9202 result = claimer == 'w' ? BlackWins : WhiteWins;
9203 resultDetails = buf;
9205 /* (Claiming a loss is accepted no questions asked!) */
9207 /* [HGM] bare: don't allow bare King to win */
9208 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9209 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9210 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9211 && result != GameIsDrawn)
9212 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9213 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9214 int p = (signed char)boards[forwardMostMove][i][j] - color;
9215 if(p >= 0 && p <= (int)WhiteKing) k++;
9217 if (appData.debugMode) {
9218 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9219 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9222 result = GameIsDrawn;
9223 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9224 resultDetails = buf;
9230 if(serverMoves != NULL && !loadFlag) { char c = '=';
9231 if(result==WhiteWins) c = '+';
9232 if(result==BlackWins) c = '-';
9233 if(resultDetails != NULL)
9234 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9236 if (resultDetails != NULL) {
9237 gameInfo.result = result;
9238 gameInfo.resultDetails = StrSave(resultDetails);
9240 /* display last move only if game was not loaded from file */
9241 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9242 DisplayMove(currentMove - 1);
9244 if (forwardMostMove != 0) {
9245 if (gameMode != PlayFromGameFile && gameMode != EditGame
9246 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9248 if (*appData.saveGameFile != NULLCHAR) {
9249 SaveGameToFile(appData.saveGameFile, TRUE);
9250 } else if (appData.autoSaveGames) {
9253 if (*appData.savePositionFile != NULLCHAR) {
9254 SavePositionToFile(appData.savePositionFile);
9259 /* Tell program how game ended in case it is learning */
9260 /* [HGM] Moved this to after saving the PGN, just in case */
9261 /* engine died and we got here through time loss. In that */
9262 /* case we will get a fatal error writing the pipe, which */
9263 /* would otherwise lose us the PGN. */
9264 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9265 /* output during GameEnds should never be fatal anymore */
9266 if (gameMode == MachinePlaysWhite ||
9267 gameMode == MachinePlaysBlack ||
9268 gameMode == TwoMachinesPlay ||
9269 gameMode == IcsPlayingWhite ||
9270 gameMode == IcsPlayingBlack ||
9271 gameMode == BeginningOfGame) {
9273 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9275 if (first.pr != NoProc) {
9276 SendToProgram(buf, &first);
9278 if (second.pr != NoProc &&
9279 gameMode == TwoMachinesPlay) {
9280 SendToProgram(buf, &second);
9285 if (appData.icsActive) {
9286 if (appData.quietPlay &&
9287 (gameMode == IcsPlayingWhite ||
9288 gameMode == IcsPlayingBlack)) {
9289 SendToICS(ics_prefix);
9290 SendToICS("set shout 1\n");
9292 nextGameMode = IcsIdle;
9293 ics_user_moved = FALSE;
9294 /* clean up premove. It's ugly when the game has ended and the
9295 * premove highlights are still on the board.
9299 ClearPremoveHighlights();
9300 DrawPosition(FALSE, boards[currentMove]);
9302 if (whosays == GE_ICS) {
9305 if (gameMode == IcsPlayingWhite)
9307 else if(gameMode == IcsPlayingBlack)
9311 if (gameMode == IcsPlayingBlack)
9313 else if(gameMode == IcsPlayingWhite)
9320 PlayIcsUnfinishedSound();
9323 } else if (gameMode == EditGame ||
9324 gameMode == PlayFromGameFile ||
9325 gameMode == AnalyzeMode ||
9326 gameMode == AnalyzeFile) {
9327 nextGameMode = gameMode;
9329 nextGameMode = EndOfGame;
9334 nextGameMode = gameMode;
9337 if (appData.noChessProgram) {
9338 gameMode = nextGameMode;
9340 endingGame = 0; /* [HGM] crash */
9345 /* Put first chess program into idle state */
9346 if (first.pr != NoProc &&
9347 (gameMode == MachinePlaysWhite ||
9348 gameMode == MachinePlaysBlack ||
9349 gameMode == TwoMachinesPlay ||
9350 gameMode == IcsPlayingWhite ||
9351 gameMode == IcsPlayingBlack ||
9352 gameMode == BeginningOfGame)) {
9353 SendToProgram("force\n", &first);
9354 if (first.usePing) {
9356 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9357 SendToProgram(buf, &first);
9360 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9361 /* Kill off first chess program */
9362 if (first.isr != NULL)
9363 RemoveInputSource(first.isr);
9366 if (first.pr != NoProc) {
9368 DoSleep( appData.delayBeforeQuit );
9369 SendToProgram("quit\n", &first);
9370 DoSleep( appData.delayAfterQuit );
9371 DestroyChildProcess(first.pr, first.useSigterm);
9376 /* Put second chess program into idle state */
9377 if (second.pr != NoProc &&
9378 gameMode == TwoMachinesPlay) {
9379 SendToProgram("force\n", &second);
9380 if (second.usePing) {
9382 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9383 SendToProgram(buf, &second);
9386 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9387 /* Kill off second chess program */
9388 if (second.isr != NULL)
9389 RemoveInputSource(second.isr);
9392 if (second.pr != NoProc) {
9393 DoSleep( appData.delayBeforeQuit );
9394 SendToProgram("quit\n", &second);
9395 DoSleep( appData.delayAfterQuit );
9396 DestroyChildProcess(second.pr, second.useSigterm);
9401 if (matchMode && gameMode == TwoMachinesPlay) {
9404 if (first.twoMachinesColor[0] == 'w') {
9411 if (first.twoMachinesColor[0] == 'b') {
9420 if (matchGame < appData.matchGames) {
9422 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9423 tmp = first.twoMachinesColor;
9424 first.twoMachinesColor = second.twoMachinesColor;
9425 second.twoMachinesColor = tmp;
9427 gameMode = nextGameMode;
9429 if(appData.matchPause>10000 || appData.matchPause<10)
9430 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9431 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9432 endingGame = 0; /* [HGM] crash */
9435 gameMode = nextGameMode;
9436 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9437 first.tidy, second.tidy,
9438 first.matchWins, second.matchWins,
9439 appData.matchGames - (first.matchWins + second.matchWins));
9440 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9443 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9444 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9446 gameMode = nextGameMode;
9448 endingGame = 0; /* [HGM] crash */
9449 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9450 if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9451 matchMode = FALSE; appData.matchGames = matchGame = 0;
9457 /* Assumes program was just initialized (initString sent).
9458 Leaves program in force mode. */
9460 FeedMovesToProgram(cps, upto)
9461 ChessProgramState *cps;
9466 if (appData.debugMode)
9467 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9468 startedFromSetupPosition ? "position and " : "",
9469 backwardMostMove, upto, cps->which);
9470 if(currentlyInitializedVariant != gameInfo.variant) {
9472 // [HGM] variantswitch: make engine aware of new variant
9473 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9474 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9475 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9476 SendToProgram(buf, cps);
9477 currentlyInitializedVariant = gameInfo.variant;
9479 SendToProgram("force\n", cps);
9480 if (startedFromSetupPosition) {
9481 SendBoard(cps, backwardMostMove);
9482 if (appData.debugMode) {
9483 fprintf(debugFP, "feedMoves\n");
9486 for (i = backwardMostMove; i < upto; i++) {
9487 SendMoveToProgram(i, cps);
9493 ResurrectChessProgram()
9495 /* The chess program may have exited.
9496 If so, restart it and feed it all the moves made so far. */
9498 if (appData.noChessProgram || first.pr != NoProc) return;
9500 StartChessProgram(&first);
9501 InitChessProgram(&first, FALSE);
9502 FeedMovesToProgram(&first, currentMove);
9504 if (!first.sendTime) {
9505 /* can't tell gnuchess what its clock should read,
9506 so we bow to its notion. */
9508 timeRemaining[0][currentMove] = whiteTimeRemaining;
9509 timeRemaining[1][currentMove] = blackTimeRemaining;
9512 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9513 appData.icsEngineAnalyze) && first.analysisSupport) {
9514 SendToProgram("analyze\n", &first);
9515 first.analyzing = TRUE;
9528 if (appData.debugMode) {
9529 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9530 redraw, init, gameMode);
9532 CleanupTail(); // [HGM] vari: delete any stored variations
9533 pausing = pauseExamInvalid = FALSE;
9534 startedFromSetupPosition = blackPlaysFirst = FALSE;
9536 whiteFlag = blackFlag = FALSE;
9537 userOfferedDraw = FALSE;
9538 hintRequested = bookRequested = FALSE;
9539 first.maybeThinking = FALSE;
9540 second.maybeThinking = FALSE;
9541 first.bookSuspend = FALSE; // [HGM] book
9542 second.bookSuspend = FALSE;
9543 thinkOutput[0] = NULLCHAR;
9544 lastHint[0] = NULLCHAR;
9545 ClearGameInfo(&gameInfo);
9546 gameInfo.variant = StringToVariant(appData.variant);
9547 ics_user_moved = ics_clock_paused = FALSE;
9548 ics_getting_history = H_FALSE;
9550 white_holding[0] = black_holding[0] = NULLCHAR;
9551 ClearProgramStats();
9552 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9556 flipView = appData.flipView;
9557 ClearPremoveHighlights();
9559 alarmSounded = FALSE;
9561 GameEnds(EndOfFile, NULL, GE_PLAYER);
9562 if(appData.serverMovesName != NULL) {
9563 /* [HGM] prepare to make moves file for broadcasting */
9564 clock_t t = clock();
9565 if(serverMoves != NULL) fclose(serverMoves);
9566 serverMoves = fopen(appData.serverMovesName, "r");
9567 if(serverMoves != NULL) {
9568 fclose(serverMoves);
9569 /* delay 15 sec before overwriting, so all clients can see end */
9570 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9572 serverMoves = fopen(appData.serverMovesName, "w");
9576 gameMode = BeginningOfGame;
9578 if(appData.icsActive) gameInfo.variant = VariantNormal;
9579 currentMove = forwardMostMove = backwardMostMove = 0;
9580 InitPosition(redraw);
9581 for (i = 0; i < MAX_MOVES; i++) {
9582 if (commentList[i] != NULL) {
9583 free(commentList[i]);
9584 commentList[i] = NULL;
9588 timeRemaining[0][0] = whiteTimeRemaining;
9589 timeRemaining[1][0] = blackTimeRemaining;
9590 if (first.pr == NULL) {
9591 StartChessProgram(&first);
9594 InitChessProgram(&first, startedFromSetupPosition);
9597 DisplayMessage("", "");
9598 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9599 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9606 if (!AutoPlayOneMove())
9608 if (matchMode || appData.timeDelay == 0)
9610 if (appData.timeDelay < 0)
9612 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9621 int fromX, fromY, toX, toY;
9623 if (appData.debugMode) {
9624 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9627 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9630 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9631 pvInfoList[currentMove].depth = programStats.depth;
9632 pvInfoList[currentMove].score = programStats.score;
9633 pvInfoList[currentMove].time = 0;
9634 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9637 if (currentMove >= forwardMostMove) {
9638 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9639 gameMode = EditGame;
9642 /* [AS] Clear current move marker at the end of a game */
9643 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9648 toX = moveList[currentMove][2] - AAA;
9649 toY = moveList[currentMove][3] - ONE;
9651 if (moveList[currentMove][1] == '@') {
9652 if (appData.highlightLastMove) {
9653 SetHighlights(-1, -1, toX, toY);
9656 fromX = moveList[currentMove][0] - AAA;
9657 fromY = moveList[currentMove][1] - ONE;
9659 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9661 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9663 if (appData.highlightLastMove) {
9664 SetHighlights(fromX, fromY, toX, toY);
9667 DisplayMove(currentMove);
9668 SendMoveToProgram(currentMove++, &first);
9669 DisplayBothClocks();
9670 DrawPosition(FALSE, boards[currentMove]);
9671 // [HGM] PV info: always display, routine tests if empty
9672 DisplayComment(currentMove - 1, commentList[currentMove]);
9678 LoadGameOneMove(readAhead)
9679 ChessMove readAhead;
9681 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9682 char promoChar = NULLCHAR;
9687 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9688 gameMode != AnalyzeMode && gameMode != Training) {
9693 yyboardindex = forwardMostMove;
9694 if (readAhead != EndOfFile) {
9695 moveType = readAhead;
9697 if (gameFileFP == NULL)
9699 moveType = (ChessMove) Myylex();
9705 if (appData.debugMode)
9706 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9709 /* append the comment but don't display it */
9710 AppendComment(currentMove, p, FALSE);
9713 case WhiteCapturesEnPassant:
9714 case BlackCapturesEnPassant:
9715 case WhitePromotion:
9716 case BlackPromotion:
9717 case WhiteNonPromotion:
9718 case BlackNonPromotion:
9720 case WhiteKingSideCastle:
9721 case WhiteQueenSideCastle:
9722 case BlackKingSideCastle:
9723 case BlackQueenSideCastle:
9724 case WhiteKingSideCastleWild:
9725 case WhiteQueenSideCastleWild:
9726 case BlackKingSideCastleWild:
9727 case BlackQueenSideCastleWild:
9729 case WhiteHSideCastleFR:
9730 case WhiteASideCastleFR:
9731 case BlackHSideCastleFR:
9732 case BlackASideCastleFR:
9734 if (appData.debugMode)
9735 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9736 fromX = currentMoveString[0] - AAA;
9737 fromY = currentMoveString[1] - ONE;
9738 toX = currentMoveString[2] - AAA;
9739 toY = currentMoveString[3] - ONE;
9740 promoChar = currentMoveString[4];
9745 if (appData.debugMode)
9746 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9747 fromX = moveType == WhiteDrop ?
9748 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9749 (int) CharToPiece(ToLower(currentMoveString[0]));
9751 toX = currentMoveString[2] - AAA;
9752 toY = currentMoveString[3] - ONE;
9758 case GameUnfinished:
9759 if (appData.debugMode)
9760 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9761 p = strchr(yy_text, '{');
9762 if (p == NULL) p = strchr(yy_text, '(');
9765 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9767 q = strchr(p, *p == '{' ? '}' : ')');
9768 if (q != NULL) *q = NULLCHAR;
9771 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9772 GameEnds(moveType, p, GE_FILE);
9774 if (cmailMsgLoaded) {
9776 flipView = WhiteOnMove(currentMove);
9777 if (moveType == GameUnfinished) flipView = !flipView;
9778 if (appData.debugMode)
9779 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9784 if (appData.debugMode)
9785 fprintf(debugFP, "Parser hit end of file\n");
9786 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9792 if (WhiteOnMove(currentMove)) {
9793 GameEnds(BlackWins, "Black mates", GE_FILE);
9795 GameEnds(WhiteWins, "White mates", GE_FILE);
9799 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9806 if (lastLoadGameStart == GNUChessGame) {
9807 /* GNUChessGames have numbers, but they aren't move numbers */
9808 if (appData.debugMode)
9809 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9810 yy_text, (int) moveType);
9811 return LoadGameOneMove(EndOfFile); /* tail recursion */
9813 /* else fall thru */
9818 /* Reached start of next game in file */
9819 if (appData.debugMode)
9820 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9821 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9827 if (WhiteOnMove(currentMove)) {
9828 GameEnds(BlackWins, "Black mates", GE_FILE);
9830 GameEnds(WhiteWins, "White mates", GE_FILE);
9834 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9840 case PositionDiagram: /* should not happen; ignore */
9841 case ElapsedTime: /* ignore */
9842 case NAG: /* ignore */
9843 if (appData.debugMode)
9844 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9845 yy_text, (int) moveType);
9846 return LoadGameOneMove(EndOfFile); /* tail recursion */
9849 if (appData.testLegality) {
9850 if (appData.debugMode)
9851 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9852 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9853 (forwardMostMove / 2) + 1,
9854 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9855 DisplayError(move, 0);
9858 if (appData.debugMode)
9859 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9860 yy_text, currentMoveString);
9861 fromX = currentMoveString[0] - AAA;
9862 fromY = currentMoveString[1] - ONE;
9863 toX = currentMoveString[2] - AAA;
9864 toY = currentMoveString[3] - ONE;
9865 promoChar = currentMoveString[4];
9870 if (appData.debugMode)
9871 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9872 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9873 (forwardMostMove / 2) + 1,
9874 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9875 DisplayError(move, 0);
9880 case ImpossibleMove:
9881 if (appData.debugMode)
9882 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9883 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9884 (forwardMostMove / 2) + 1,
9885 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9886 DisplayError(move, 0);
9892 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9893 DrawPosition(FALSE, boards[currentMove]);
9894 DisplayBothClocks();
9895 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9896 DisplayComment(currentMove - 1, commentList[currentMove]);
9898 (void) StopLoadGameTimer();
9900 cmailOldMove = forwardMostMove;
9903 /* currentMoveString is set as a side-effect of yylex */
9904 strcat(currentMoveString, "\n");
9905 safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9907 thinkOutput[0] = NULLCHAR;
9908 MakeMove(fromX, fromY, toX, toY, promoChar);
9909 currentMove = forwardMostMove;
9914 /* Load the nth game from the given file */
9916 LoadGameFromFile(filename, n, title, useList)
9920 /*Boolean*/ int useList;
9925 if (strcmp(filename, "-") == 0) {
9929 f = fopen(filename, "rb");
9931 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9932 DisplayError(buf, errno);
9936 if (fseek(f, 0, 0) == -1) {
9937 /* f is not seekable; probably a pipe */
9940 if (useList && n == 0) {
9941 int error = GameListBuild(f);
9943 DisplayError(_("Cannot build game list"), error);
9944 } else if (!ListEmpty(&gameList) &&
9945 ((ListGame *) gameList.tailPred)->number > 1) {
9946 GameListPopUp(f, title);
9953 return LoadGame(f, n, title, FALSE);
9958 MakeRegisteredMove()
9960 int fromX, fromY, toX, toY;
9962 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9963 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9966 if (appData.debugMode)
9967 fprintf(debugFP, "Restoring %s for game %d\n",
9968 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9970 thinkOutput[0] = NULLCHAR;
9971 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9972 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9973 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9974 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9975 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9976 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9977 MakeMove(fromX, fromY, toX, toY, promoChar);
9978 ShowMove(fromX, fromY, toX, toY);
9980 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9987 if (WhiteOnMove(currentMove)) {
9988 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9990 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9995 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10002 if (WhiteOnMove(currentMove)) {
10003 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10005 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10010 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10021 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10023 CmailLoadGame(f, gameNumber, title, useList)
10031 if (gameNumber > nCmailGames) {
10032 DisplayError(_("No more games in this message"), 0);
10035 if (f == lastLoadGameFP) {
10036 int offset = gameNumber - lastLoadGameNumber;
10038 cmailMsg[0] = NULLCHAR;
10039 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10040 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10041 nCmailMovesRegistered--;
10043 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10044 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10045 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10048 if (! RegisterMove()) return FALSE;
10052 retVal = LoadGame(f, gameNumber, title, useList);
10054 /* Make move registered during previous look at this game, if any */
10055 MakeRegisteredMove();
10057 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10058 commentList[currentMove]
10059 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10060 DisplayComment(currentMove - 1, commentList[currentMove]);
10066 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10071 int gameNumber = lastLoadGameNumber + offset;
10072 if (lastLoadGameFP == NULL) {
10073 DisplayError(_("No game has been loaded yet"), 0);
10076 if (gameNumber <= 0) {
10077 DisplayError(_("Can't back up any further"), 0);
10080 if (cmailMsgLoaded) {
10081 return CmailLoadGame(lastLoadGameFP, gameNumber,
10082 lastLoadGameTitle, lastLoadGameUseList);
10084 return LoadGame(lastLoadGameFP, gameNumber,
10085 lastLoadGameTitle, lastLoadGameUseList);
10091 /* Load the nth game from open file f */
10093 LoadGame(f, gameNumber, title, useList)
10101 int gn = gameNumber;
10102 ListGame *lg = NULL;
10103 int numPGNTags = 0;
10105 GameMode oldGameMode;
10106 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10108 if (appData.debugMode)
10109 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10111 if (gameMode == Training )
10112 SetTrainingModeOff();
10114 oldGameMode = gameMode;
10115 if (gameMode != BeginningOfGame) {
10116 Reset(FALSE, TRUE);
10120 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10121 fclose(lastLoadGameFP);
10125 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10128 fseek(f, lg->offset, 0);
10129 GameListHighlight(gameNumber);
10133 DisplayError(_("Game number out of range"), 0);
10138 if (fseek(f, 0, 0) == -1) {
10139 if (f == lastLoadGameFP ?
10140 gameNumber == lastLoadGameNumber + 1 :
10144 DisplayError(_("Can't seek on game file"), 0);
10149 lastLoadGameFP = f;
10150 lastLoadGameNumber = gameNumber;
10151 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10152 lastLoadGameUseList = useList;
10156 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10157 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10158 lg->gameInfo.black);
10160 } else if (*title != NULLCHAR) {
10161 if (gameNumber > 1) {
10162 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10165 DisplayTitle(title);
10169 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10170 gameMode = PlayFromGameFile;
10174 currentMove = forwardMostMove = backwardMostMove = 0;
10175 CopyBoard(boards[0], initialPosition);
10179 * Skip the first gn-1 games in the file.
10180 * Also skip over anything that precedes an identifiable
10181 * start of game marker, to avoid being confused by
10182 * garbage at the start of the file. Currently
10183 * recognized start of game markers are the move number "1",
10184 * the pattern "gnuchess .* game", the pattern
10185 * "^[#;%] [^ ]* game file", and a PGN tag block.
10186 * A game that starts with one of the latter two patterns
10187 * will also have a move number 1, possibly
10188 * following a position diagram.
10189 * 5-4-02: Let's try being more lenient and allowing a game to
10190 * start with an unnumbered move. Does that break anything?
10192 cm = lastLoadGameStart = EndOfFile;
10194 yyboardindex = forwardMostMove;
10195 cm = (ChessMove) Myylex();
10198 if (cmailMsgLoaded) {
10199 nCmailGames = CMAIL_MAX_GAMES - gn;
10202 DisplayError(_("Game not found in file"), 0);
10209 lastLoadGameStart = cm;
10212 case MoveNumberOne:
10213 switch (lastLoadGameStart) {
10218 case MoveNumberOne:
10220 gn--; /* count this game */
10221 lastLoadGameStart = cm;
10230 switch (lastLoadGameStart) {
10233 case MoveNumberOne:
10235 gn--; /* count this game */
10236 lastLoadGameStart = cm;
10239 lastLoadGameStart = cm; /* game counted already */
10247 yyboardindex = forwardMostMove;
10248 cm = (ChessMove) Myylex();
10249 } while (cm == PGNTag || cm == Comment);
10256 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10257 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10258 != CMAIL_OLD_RESULT) {
10260 cmailResult[ CMAIL_MAX_GAMES
10261 - gn - 1] = CMAIL_OLD_RESULT;
10267 /* Only a NormalMove can be at the start of a game
10268 * without a position diagram. */
10269 if (lastLoadGameStart == EndOfFile ) {
10271 lastLoadGameStart = MoveNumberOne;
10280 if (appData.debugMode)
10281 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10283 if (cm == XBoardGame) {
10284 /* Skip any header junk before position diagram and/or move 1 */
10286 yyboardindex = forwardMostMove;
10287 cm = (ChessMove) Myylex();
10289 if (cm == EndOfFile ||
10290 cm == GNUChessGame || cm == XBoardGame) {
10291 /* Empty game; pretend end-of-file and handle later */
10296 if (cm == MoveNumberOne || cm == PositionDiagram ||
10297 cm == PGNTag || cm == Comment)
10300 } else if (cm == GNUChessGame) {
10301 if (gameInfo.event != NULL) {
10302 free(gameInfo.event);
10304 gameInfo.event = StrSave(yy_text);
10307 startedFromSetupPosition = FALSE;
10308 while (cm == PGNTag) {
10309 if (appData.debugMode)
10310 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10311 err = ParsePGNTag(yy_text, &gameInfo);
10312 if (!err) numPGNTags++;
10314 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10315 if(gameInfo.variant != oldVariant) {
10316 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10317 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10318 InitPosition(TRUE);
10319 oldVariant = gameInfo.variant;
10320 if (appData.debugMode)
10321 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10325 if (gameInfo.fen != NULL) {
10326 Board initial_position;
10327 startedFromSetupPosition = TRUE;
10328 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10330 DisplayError(_("Bad FEN position in file"), 0);
10333 CopyBoard(boards[0], initial_position);
10334 if (blackPlaysFirst) {
10335 currentMove = forwardMostMove = backwardMostMove = 1;
10336 CopyBoard(boards[1], initial_position);
10337 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10338 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10339 timeRemaining[0][1] = whiteTimeRemaining;
10340 timeRemaining[1][1] = blackTimeRemaining;
10341 if (commentList[0] != NULL) {
10342 commentList[1] = commentList[0];
10343 commentList[0] = NULL;
10346 currentMove = forwardMostMove = backwardMostMove = 0;
10348 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10350 initialRulePlies = FENrulePlies;
10351 for( i=0; i< nrCastlingRights; i++ )
10352 initialRights[i] = initial_position[CASTLING][i];
10354 yyboardindex = forwardMostMove;
10355 free(gameInfo.fen);
10356 gameInfo.fen = NULL;
10359 yyboardindex = forwardMostMove;
10360 cm = (ChessMove) Myylex();
10362 /* Handle comments interspersed among the tags */
10363 while (cm == Comment) {
10365 if (appData.debugMode)
10366 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10368 AppendComment(currentMove, p, FALSE);
10369 yyboardindex = forwardMostMove;
10370 cm = (ChessMove) Myylex();
10374 /* don't rely on existence of Event tag since if game was
10375 * pasted from clipboard the Event tag may not exist
10377 if (numPGNTags > 0){
10379 if (gameInfo.variant == VariantNormal) {
10380 VariantClass v = StringToVariant(gameInfo.event);
10381 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10382 if(v < VariantShogi) gameInfo.variant = v;
10385 if( appData.autoDisplayTags ) {
10386 tags = PGNTags(&gameInfo);
10387 TagsPopUp(tags, CmailMsg());
10392 /* Make something up, but don't display it now */
10397 if (cm == PositionDiagram) {
10400 Board initial_position;
10402 if (appData.debugMode)
10403 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10405 if (!startedFromSetupPosition) {
10407 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10408 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10418 initial_position[i][j++] = CharToPiece(*p);
10421 while (*p == ' ' || *p == '\t' ||
10422 *p == '\n' || *p == '\r') p++;
10424 if (strncmp(p, "black", strlen("black"))==0)
10425 blackPlaysFirst = TRUE;
10427 blackPlaysFirst = FALSE;
10428 startedFromSetupPosition = TRUE;
10430 CopyBoard(boards[0], initial_position);
10431 if (blackPlaysFirst) {
10432 currentMove = forwardMostMove = backwardMostMove = 1;
10433 CopyBoard(boards[1], initial_position);
10434 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10435 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10436 timeRemaining[0][1] = whiteTimeRemaining;
10437 timeRemaining[1][1] = blackTimeRemaining;
10438 if (commentList[0] != NULL) {
10439 commentList[1] = commentList[0];
10440 commentList[0] = NULL;
10443 currentMove = forwardMostMove = backwardMostMove = 0;
10446 yyboardindex = forwardMostMove;
10447 cm = (ChessMove) Myylex();
10450 if (first.pr == NoProc) {
10451 StartChessProgram(&first);
10453 InitChessProgram(&first, FALSE);
10454 SendToProgram("force\n", &first);
10455 if (startedFromSetupPosition) {
10456 SendBoard(&first, forwardMostMove);
10457 if (appData.debugMode) {
10458 fprintf(debugFP, "Load Game\n");
10460 DisplayBothClocks();
10463 /* [HGM] server: flag to write setup moves in broadcast file as one */
10464 loadFlag = appData.suppressLoadMoves;
10466 while (cm == Comment) {
10468 if (appData.debugMode)
10469 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10471 AppendComment(currentMove, p, FALSE);
10472 yyboardindex = forwardMostMove;
10473 cm = (ChessMove) Myylex();
10476 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10477 cm == WhiteWins || cm == BlackWins ||
10478 cm == GameIsDrawn || cm == GameUnfinished) {
10479 DisplayMessage("", _("No moves in game"));
10480 if (cmailMsgLoaded) {
10481 if (appData.debugMode)
10482 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10486 DrawPosition(FALSE, boards[currentMove]);
10487 DisplayBothClocks();
10488 gameMode = EditGame;
10495 // [HGM] PV info: routine tests if comment empty
10496 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10497 DisplayComment(currentMove - 1, commentList[currentMove]);
10499 if (!matchMode && appData.timeDelay != 0)
10500 DrawPosition(FALSE, boards[currentMove]);
10502 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10503 programStats.ok_to_send = 1;
10506 /* if the first token after the PGN tags is a move
10507 * and not move number 1, retrieve it from the parser
10509 if (cm != MoveNumberOne)
10510 LoadGameOneMove(cm);
10512 /* load the remaining moves from the file */
10513 while (LoadGameOneMove(EndOfFile)) {
10514 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10515 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10518 /* rewind to the start of the game */
10519 currentMove = backwardMostMove;
10521 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10523 if (oldGameMode == AnalyzeFile ||
10524 oldGameMode == AnalyzeMode) {
10525 AnalyzeFileEvent();
10528 if (matchMode || appData.timeDelay == 0) {
10530 gameMode = EditGame;
10532 } else if (appData.timeDelay > 0) {
10533 AutoPlayGameLoop();
10536 if (appData.debugMode)
10537 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10539 loadFlag = 0; /* [HGM] true game starts */
10543 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10545 ReloadPosition(offset)
10548 int positionNumber = lastLoadPositionNumber + offset;
10549 if (lastLoadPositionFP == NULL) {
10550 DisplayError(_("No position has been loaded yet"), 0);
10553 if (positionNumber <= 0) {
10554 DisplayError(_("Can't back up any further"), 0);
10557 return LoadPosition(lastLoadPositionFP, positionNumber,
10558 lastLoadPositionTitle);
10561 /* Load the nth position from the given file */
10563 LoadPositionFromFile(filename, n, title)
10571 if (strcmp(filename, "-") == 0) {
10572 return LoadPosition(stdin, n, "stdin");
10574 f = fopen(filename, "rb");
10576 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10577 DisplayError(buf, errno);
10580 return LoadPosition(f, n, title);
10585 /* Load the nth position from the given open file, and close it */
10587 LoadPosition(f, positionNumber, title)
10589 int positionNumber;
10592 char *p, line[MSG_SIZ];
10593 Board initial_position;
10594 int i, j, fenMode, pn;
10596 if (gameMode == Training )
10597 SetTrainingModeOff();
10599 if (gameMode != BeginningOfGame) {
10600 Reset(FALSE, TRUE);
10602 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10603 fclose(lastLoadPositionFP);
10605 if (positionNumber == 0) positionNumber = 1;
10606 lastLoadPositionFP = f;
10607 lastLoadPositionNumber = positionNumber;
10608 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10609 if (first.pr == NoProc) {
10610 StartChessProgram(&first);
10611 InitChessProgram(&first, FALSE);
10613 pn = positionNumber;
10614 if (positionNumber < 0) {
10615 /* Negative position number means to seek to that byte offset */
10616 if (fseek(f, -positionNumber, 0) == -1) {
10617 DisplayError(_("Can't seek on position file"), 0);
10622 if (fseek(f, 0, 0) == -1) {
10623 if (f == lastLoadPositionFP ?
10624 positionNumber == lastLoadPositionNumber + 1 :
10625 positionNumber == 1) {
10628 DisplayError(_("Can't seek on position file"), 0);
10633 /* See if this file is FEN or old-style xboard */
10634 if (fgets(line, MSG_SIZ, f) == NULL) {
10635 DisplayError(_("Position not found in file"), 0);
10638 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10639 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10642 if (fenMode || line[0] == '#') pn--;
10644 /* skip positions before number pn */
10645 if (fgets(line, MSG_SIZ, f) == NULL) {
10647 DisplayError(_("Position not found in file"), 0);
10650 if (fenMode || line[0] == '#') pn--;
10655 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10656 DisplayError(_("Bad FEN position in file"), 0);
10660 (void) fgets(line, MSG_SIZ, f);
10661 (void) fgets(line, MSG_SIZ, f);
10663 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10664 (void) fgets(line, MSG_SIZ, f);
10665 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10668 initial_position[i][j++] = CharToPiece(*p);
10672 blackPlaysFirst = FALSE;
10674 (void) fgets(line, MSG_SIZ, f);
10675 if (strncmp(line, "black", strlen("black"))==0)
10676 blackPlaysFirst = TRUE;
10679 startedFromSetupPosition = TRUE;
10681 SendToProgram("force\n", &first);
10682 CopyBoard(boards[0], initial_position);
10683 if (blackPlaysFirst) {
10684 currentMove = forwardMostMove = backwardMostMove = 1;
10685 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10686 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10687 CopyBoard(boards[1], initial_position);
10688 DisplayMessage("", _("Black to play"));
10690 currentMove = forwardMostMove = backwardMostMove = 0;
10691 DisplayMessage("", _("White to play"));
10693 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10694 SendBoard(&first, forwardMostMove);
10695 if (appData.debugMode) {
10697 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10698 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10699 fprintf(debugFP, "Load Position\n");
10702 if (positionNumber > 1) {
10703 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10704 DisplayTitle(line);
10706 DisplayTitle(title);
10708 gameMode = EditGame;
10711 timeRemaining[0][1] = whiteTimeRemaining;
10712 timeRemaining[1][1] = blackTimeRemaining;
10713 DrawPosition(FALSE, boards[currentMove]);
10720 CopyPlayerNameIntoFileName(dest, src)
10723 while (*src != NULLCHAR && *src != ',') {
10728 *(*dest)++ = *src++;
10733 char *DefaultFileName(ext)
10736 static char def[MSG_SIZ];
10739 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10741 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10743 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10745 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10752 /* Save the current game to the given file */
10754 SaveGameToFile(filename, append)
10761 if (strcmp(filename, "-") == 0) {
10762 return SaveGame(stdout, 0, NULL);
10764 f = fopen(filename, append ? "a" : "w");
10766 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10767 DisplayError(buf, errno);
10770 return SaveGame(f, 0, NULL);
10779 static char buf[MSG_SIZ];
10782 p = strchr(str, ' ');
10783 if (p == NULL) return str;
10784 strncpy(buf, str, p - str);
10785 buf[p - str] = NULLCHAR;
10789 #define PGN_MAX_LINE 75
10791 #define PGN_SIDE_WHITE 0
10792 #define PGN_SIDE_BLACK 1
10795 static int FindFirstMoveOutOfBook( int side )
10799 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10800 int index = backwardMostMove;
10801 int has_book_hit = 0;
10803 if( (index % 2) != side ) {
10807 while( index < forwardMostMove ) {
10808 /* Check to see if engine is in book */
10809 int depth = pvInfoList[index].depth;
10810 int score = pvInfoList[index].score;
10816 else if( score == 0 && depth == 63 ) {
10817 in_book = 1; /* Zappa */
10819 else if( score == 2 && depth == 99 ) {
10820 in_book = 1; /* Abrok */
10823 has_book_hit += in_book;
10839 void GetOutOfBookInfo( char * buf )
10843 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10845 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10846 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10850 if( oob[0] >= 0 || oob[1] >= 0 ) {
10851 for( i=0; i<2; i++ ) {
10855 if( i > 0 && oob[0] >= 0 ) {
10856 strcat( buf, " " );
10859 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10860 sprintf( buf+strlen(buf), "%s%.2f",
10861 pvInfoList[idx].score >= 0 ? "+" : "",
10862 pvInfoList[idx].score / 100.0 );
10868 /* Save game in PGN style and close the file */
10873 int i, offset, linelen, newblock;
10877 int movelen, numlen, blank;
10878 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10880 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10882 tm = time((time_t *) NULL);
10884 PrintPGNTags(f, &gameInfo);
10886 if (backwardMostMove > 0 || startedFromSetupPosition) {
10887 char *fen = PositionToFEN(backwardMostMove, NULL);
10888 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10889 fprintf(f, "\n{--------------\n");
10890 PrintPosition(f, backwardMostMove);
10891 fprintf(f, "--------------}\n");
10895 /* [AS] Out of book annotation */
10896 if( appData.saveOutOfBookInfo ) {
10899 GetOutOfBookInfo( buf );
10901 if( buf[0] != '\0' ) {
10902 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10909 i = backwardMostMove;
10913 while (i < forwardMostMove) {
10914 /* Print comments preceding this move */
10915 if (commentList[i] != NULL) {
10916 if (linelen > 0) fprintf(f, "\n");
10917 fprintf(f, "%s", commentList[i]);
10922 /* Format move number */
10924 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10927 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10929 numtext[0] = NULLCHAR;
10931 numlen = strlen(numtext);
10934 /* Print move number */
10935 blank = linelen > 0 && numlen > 0;
10936 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10945 fprintf(f, "%s", numtext);
10949 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10950 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10953 blank = linelen > 0 && movelen > 0;
10954 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10963 fprintf(f, "%s", move_buffer);
10964 linelen += movelen;
10966 /* [AS] Add PV info if present */
10967 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10968 /* [HGM] add time */
10969 char buf[MSG_SIZ]; int seconds;
10971 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10977 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10980 seconds = (seconds + 4)/10; // round to full seconds
10982 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10984 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10987 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10988 pvInfoList[i].score >= 0 ? "+" : "",
10989 pvInfoList[i].score / 100.0,
10990 pvInfoList[i].depth,
10993 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10995 /* Print score/depth */
10996 blank = linelen > 0 && movelen > 0;
10997 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11006 fprintf(f, "%s", move_buffer);
11007 linelen += movelen;
11013 /* Start a new line */
11014 if (linelen > 0) fprintf(f, "\n");
11016 /* Print comments after last move */
11017 if (commentList[i] != NULL) {
11018 fprintf(f, "%s\n", commentList[i]);
11022 if (gameInfo.resultDetails != NULL &&
11023 gameInfo.resultDetails[0] != NULLCHAR) {
11024 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11025 PGNResult(gameInfo.result));
11027 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11031 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11035 /* Save game in old style and close the file */
11037 SaveGameOldStyle(f)
11043 tm = time((time_t *) NULL);
11045 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11048 if (backwardMostMove > 0 || startedFromSetupPosition) {
11049 fprintf(f, "\n[--------------\n");
11050 PrintPosition(f, backwardMostMove);
11051 fprintf(f, "--------------]\n");
11056 i = backwardMostMove;
11057 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11059 while (i < forwardMostMove) {
11060 if (commentList[i] != NULL) {
11061 fprintf(f, "[%s]\n", commentList[i]);
11064 if ((i % 2) == 1) {
11065 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11068 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11070 if (commentList[i] != NULL) {
11074 if (i >= forwardMostMove) {
11078 fprintf(f, "%s\n", parseList[i]);
11083 if (commentList[i] != NULL) {
11084 fprintf(f, "[%s]\n", commentList[i]);
11087 /* This isn't really the old style, but it's close enough */
11088 if (gameInfo.resultDetails != NULL &&
11089 gameInfo.resultDetails[0] != NULLCHAR) {
11090 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11091 gameInfo.resultDetails);
11093 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11100 /* Save the current game to open file f and close the file */
11102 SaveGame(f, dummy, dummy2)
11107 if (gameMode == EditPosition) EditPositionDone(TRUE);
11108 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11109 if (appData.oldSaveStyle)
11110 return SaveGameOldStyle(f);
11112 return SaveGamePGN(f);
11115 /* Save the current position to the given file */
11117 SavePositionToFile(filename)
11123 if (strcmp(filename, "-") == 0) {
11124 return SavePosition(stdout, 0, NULL);
11126 f = fopen(filename, "a");
11128 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11129 DisplayError(buf, errno);
11132 SavePosition(f, 0, NULL);
11138 /* Save the current position to the given open file and close the file */
11140 SavePosition(f, dummy, dummy2)
11148 if (gameMode == EditPosition) EditPositionDone(TRUE);
11149 if (appData.oldSaveStyle) {
11150 tm = time((time_t *) NULL);
11152 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11154 fprintf(f, "[--------------\n");
11155 PrintPosition(f, currentMove);
11156 fprintf(f, "--------------]\n");
11158 fen = PositionToFEN(currentMove, NULL);
11159 fprintf(f, "%s\n", fen);
11167 ReloadCmailMsgEvent(unregister)
11171 static char *inFilename = NULL;
11172 static char *outFilename;
11174 struct stat inbuf, outbuf;
11177 /* Any registered moves are unregistered if unregister is set, */
11178 /* i.e. invoked by the signal handler */
11180 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11181 cmailMoveRegistered[i] = FALSE;
11182 if (cmailCommentList[i] != NULL) {
11183 free(cmailCommentList[i]);
11184 cmailCommentList[i] = NULL;
11187 nCmailMovesRegistered = 0;
11190 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11191 cmailResult[i] = CMAIL_NOT_RESULT;
11195 if (inFilename == NULL) {
11196 /* Because the filenames are static they only get malloced once */
11197 /* and they never get freed */
11198 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11199 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11201 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11202 sprintf(outFilename, "%s.out", appData.cmailGameName);
11205 status = stat(outFilename, &outbuf);
11207 cmailMailedMove = FALSE;
11209 status = stat(inFilename, &inbuf);
11210 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11213 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11214 counts the games, notes how each one terminated, etc.
11216 It would be nice to remove this kludge and instead gather all
11217 the information while building the game list. (And to keep it
11218 in the game list nodes instead of having a bunch of fixed-size
11219 parallel arrays.) Note this will require getting each game's
11220 termination from the PGN tags, as the game list builder does
11221 not process the game moves. --mann
11223 cmailMsgLoaded = TRUE;
11224 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11226 /* Load first game in the file or popup game menu */
11227 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11229 #endif /* !WIN32 */
11237 char string[MSG_SIZ];
11239 if ( cmailMailedMove
11240 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11241 return TRUE; /* Allow free viewing */
11244 /* Unregister move to ensure that we don't leave RegisterMove */
11245 /* with the move registered when the conditions for registering no */
11247 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11248 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11249 nCmailMovesRegistered --;
11251 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11253 free(cmailCommentList[lastLoadGameNumber - 1]);
11254 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11258 if (cmailOldMove == -1) {
11259 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11263 if (currentMove > cmailOldMove + 1) {
11264 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11268 if (currentMove < cmailOldMove) {
11269 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11273 if (forwardMostMove > currentMove) {
11274 /* Silently truncate extra moves */
11278 if ( (currentMove == cmailOldMove + 1)
11279 || ( (currentMove == cmailOldMove)
11280 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11281 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11282 if (gameInfo.result != GameUnfinished) {
11283 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11286 if (commentList[currentMove] != NULL) {
11287 cmailCommentList[lastLoadGameNumber - 1]
11288 = StrSave(commentList[currentMove]);
11290 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11292 if (appData.debugMode)
11293 fprintf(debugFP, "Saving %s for game %d\n",
11294 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11296 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11298 f = fopen(string, "w");
11299 if (appData.oldSaveStyle) {
11300 SaveGameOldStyle(f); /* also closes the file */
11302 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11303 f = fopen(string, "w");
11304 SavePosition(f, 0, NULL); /* also closes the file */
11306 fprintf(f, "{--------------\n");
11307 PrintPosition(f, currentMove);
11308 fprintf(f, "--------------}\n\n");
11310 SaveGame(f, 0, NULL); /* also closes the file*/
11313 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11314 nCmailMovesRegistered ++;
11315 } else if (nCmailGames == 1) {
11316 DisplayError(_("You have not made a move yet"), 0);
11327 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11328 FILE *commandOutput;
11329 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11330 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11336 if (! cmailMsgLoaded) {
11337 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11341 if (nCmailGames == nCmailResults) {
11342 DisplayError(_("No unfinished games"), 0);
11346 #if CMAIL_PROHIBIT_REMAIL
11347 if (cmailMailedMove) {
11348 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);
11349 DisplayError(msg, 0);
11354 if (! (cmailMailedMove || RegisterMove())) return;
11356 if ( cmailMailedMove
11357 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11358 snprintf(string, MSG_SIZ, partCommandString,
11359 appData.debugMode ? " -v" : "", appData.cmailGameName);
11360 commandOutput = popen(string, "r");
11362 if (commandOutput == NULL) {
11363 DisplayError(_("Failed to invoke cmail"), 0);
11365 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11366 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11368 if (nBuffers > 1) {
11369 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11370 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11371 nBytes = MSG_SIZ - 1;
11373 (void) memcpy(msg, buffer, nBytes);
11375 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11377 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11378 cmailMailedMove = TRUE; /* Prevent >1 moves */
11381 for (i = 0; i < nCmailGames; i ++) {
11382 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11387 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11389 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11391 appData.cmailGameName,
11393 LoadGameFromFile(buffer, 1, buffer, FALSE);
11394 cmailMsgLoaded = FALSE;
11398 DisplayInformation(msg);
11399 pclose(commandOutput);
11402 if ((*cmailMsg) != '\0') {
11403 DisplayInformation(cmailMsg);
11408 #endif /* !WIN32 */
11417 int prependComma = 0;
11419 char string[MSG_SIZ]; /* Space for game-list */
11422 if (!cmailMsgLoaded) return "";
11424 if (cmailMailedMove) {
11425 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11427 /* Create a list of games left */
11428 snprintf(string, MSG_SIZ, "[");
11429 for (i = 0; i < nCmailGames; i ++) {
11430 if (! ( cmailMoveRegistered[i]
11431 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11432 if (prependComma) {
11433 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11435 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11439 strcat(string, number);
11442 strcat(string, "]");
11444 if (nCmailMovesRegistered + nCmailResults == 0) {
11445 switch (nCmailGames) {
11447 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11451 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11455 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11460 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11462 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11467 if (nCmailResults == nCmailGames) {
11468 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11470 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11475 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11487 if (gameMode == Training)
11488 SetTrainingModeOff();
11491 cmailMsgLoaded = FALSE;
11492 if (appData.icsActive) {
11493 SendToICS(ics_prefix);
11494 SendToICS("refresh\n");
11504 /* Give up on clean exit */
11508 /* Keep trying for clean exit */
11512 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11514 if (telnetISR != NULL) {
11515 RemoveInputSource(telnetISR);
11517 if (icsPR != NoProc) {
11518 DestroyChildProcess(icsPR, TRUE);
11521 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11522 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11524 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11525 /* make sure this other one finishes before killing it! */
11526 if(endingGame) { int count = 0;
11527 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11528 while(endingGame && count++ < 10) DoSleep(1);
11529 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11532 /* Kill off chess programs */
11533 if (first.pr != NoProc) {
11536 DoSleep( appData.delayBeforeQuit );
11537 SendToProgram("quit\n", &first);
11538 DoSleep( appData.delayAfterQuit );
11539 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11541 if (second.pr != NoProc) {
11542 DoSleep( appData.delayBeforeQuit );
11543 SendToProgram("quit\n", &second);
11544 DoSleep( appData.delayAfterQuit );
11545 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11547 if (first.isr != NULL) {
11548 RemoveInputSource(first.isr);
11550 if (second.isr != NULL) {
11551 RemoveInputSource(second.isr);
11554 ShutDownFrontEnd();
11561 if (appData.debugMode)
11562 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11566 if (gameMode == MachinePlaysWhite ||
11567 gameMode == MachinePlaysBlack) {
11570 DisplayBothClocks();
11572 if (gameMode == PlayFromGameFile) {
11573 if (appData.timeDelay >= 0)
11574 AutoPlayGameLoop();
11575 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11576 Reset(FALSE, TRUE);
11577 SendToICS(ics_prefix);
11578 SendToICS("refresh\n");
11579 } else if (currentMove < forwardMostMove) {
11580 ForwardInner(forwardMostMove);
11582 pauseExamInvalid = FALSE;
11584 switch (gameMode) {
11588 pauseExamForwardMostMove = forwardMostMove;
11589 pauseExamInvalid = FALSE;
11592 case IcsPlayingWhite:
11593 case IcsPlayingBlack:
11597 case PlayFromGameFile:
11598 (void) StopLoadGameTimer();
11602 case BeginningOfGame:
11603 if (appData.icsActive) return;
11604 /* else fall through */
11605 case MachinePlaysWhite:
11606 case MachinePlaysBlack:
11607 case TwoMachinesPlay:
11608 if (forwardMostMove == 0)
11609 return; /* don't pause if no one has moved */
11610 if ((gameMode == MachinePlaysWhite &&
11611 !WhiteOnMove(forwardMostMove)) ||
11612 (gameMode == MachinePlaysBlack &&
11613 WhiteOnMove(forwardMostMove))) {
11626 char title[MSG_SIZ];
11628 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11629 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11631 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11632 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11633 parseList[currentMove - 1]);
11636 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11643 char *tags = PGNTags(&gameInfo);
11644 EditTagsPopUp(tags, NULL);
11651 if (appData.noChessProgram || gameMode == AnalyzeMode)
11654 if (gameMode != AnalyzeFile) {
11655 if (!appData.icsEngineAnalyze) {
11657 if (gameMode != EditGame) return;
11659 ResurrectChessProgram();
11660 SendToProgram("analyze\n", &first);
11661 first.analyzing = TRUE;
11662 /*first.maybeThinking = TRUE;*/
11663 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11664 EngineOutputPopUp();
11666 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11671 StartAnalysisClock();
11672 GetTimeMark(&lastNodeCountTime);
11679 if (appData.noChessProgram || gameMode == AnalyzeFile)
11682 if (gameMode != AnalyzeMode) {
11684 if (gameMode != EditGame) return;
11685 ResurrectChessProgram();
11686 SendToProgram("analyze\n", &first);
11687 first.analyzing = TRUE;
11688 /*first.maybeThinking = TRUE;*/
11689 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11690 EngineOutputPopUp();
11692 gameMode = AnalyzeFile;
11697 StartAnalysisClock();
11698 GetTimeMark(&lastNodeCountTime);
11703 MachineWhiteEvent()
11706 char *bookHit = NULL;
11708 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11712 if (gameMode == PlayFromGameFile ||
11713 gameMode == TwoMachinesPlay ||
11714 gameMode == Training ||
11715 gameMode == AnalyzeMode ||
11716 gameMode == EndOfGame)
11719 if (gameMode == EditPosition)
11720 EditPositionDone(TRUE);
11722 if (!WhiteOnMove(currentMove)) {
11723 DisplayError(_("It is not White's turn"), 0);
11727 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11730 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11731 gameMode == AnalyzeFile)
11734 ResurrectChessProgram(); /* in case it isn't running */
11735 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11736 gameMode = MachinePlaysWhite;
11739 gameMode = MachinePlaysWhite;
11743 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11745 if (first.sendName) {
11746 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11747 SendToProgram(buf, &first);
11749 if (first.sendTime) {
11750 if (first.useColors) {
11751 SendToProgram("black\n", &first); /*gnu kludge*/
11753 SendTimeRemaining(&first, TRUE);
11755 if (first.useColors) {
11756 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11758 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11759 SetMachineThinkingEnables();
11760 first.maybeThinking = TRUE;
11764 if (appData.autoFlipView && !flipView) {
11765 flipView = !flipView;
11766 DrawPosition(FALSE, NULL);
11767 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11770 if(bookHit) { // [HGM] book: simulate book reply
11771 static char bookMove[MSG_SIZ]; // a bit generous?
11773 programStats.nodes = programStats.depth = programStats.time =
11774 programStats.score = programStats.got_only_move = 0;
11775 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11777 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11778 strcat(bookMove, bookHit);
11779 HandleMachineMove(bookMove, &first);
11784 MachineBlackEvent()
11787 char *bookHit = NULL;
11789 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11793 if (gameMode == PlayFromGameFile ||
11794 gameMode == TwoMachinesPlay ||
11795 gameMode == Training ||
11796 gameMode == AnalyzeMode ||
11797 gameMode == EndOfGame)
11800 if (gameMode == EditPosition)
11801 EditPositionDone(TRUE);
11803 if (WhiteOnMove(currentMove)) {
11804 DisplayError(_("It is not Black's turn"), 0);
11808 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11811 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11812 gameMode == AnalyzeFile)
11815 ResurrectChessProgram(); /* in case it isn't running */
11816 gameMode = MachinePlaysBlack;
11820 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11822 if (first.sendName) {
11823 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11824 SendToProgram(buf, &first);
11826 if (first.sendTime) {
11827 if (first.useColors) {
11828 SendToProgram("white\n", &first); /*gnu kludge*/
11830 SendTimeRemaining(&first, FALSE);
11832 if (first.useColors) {
11833 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11835 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11836 SetMachineThinkingEnables();
11837 first.maybeThinking = TRUE;
11840 if (appData.autoFlipView && flipView) {
11841 flipView = !flipView;
11842 DrawPosition(FALSE, NULL);
11843 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11845 if(bookHit) { // [HGM] book: simulate book reply
11846 static char bookMove[MSG_SIZ]; // a bit generous?
11848 programStats.nodes = programStats.depth = programStats.time =
11849 programStats.score = programStats.got_only_move = 0;
11850 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11852 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11853 strcat(bookMove, bookHit);
11854 HandleMachineMove(bookMove, &first);
11860 DisplayTwoMachinesTitle()
11863 if (appData.matchGames > 0) {
11864 if (first.twoMachinesColor[0] == 'w') {
11865 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11866 gameInfo.white, gameInfo.black,
11867 first.matchWins, second.matchWins,
11868 matchGame - 1 - (first.matchWins + second.matchWins));
11870 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11871 gameInfo.white, gameInfo.black,
11872 second.matchWins, first.matchWins,
11873 matchGame - 1 - (first.matchWins + second.matchWins));
11876 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11882 SettingsMenuIfReady()
11884 if (second.lastPing != second.lastPong) {
11885 DisplayMessage("", _("Waiting for second chess program"));
11886 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11890 DisplayMessage("", "");
11891 SettingsPopUp(&second);
11895 WaitForSecond(DelayedEventCallback retry)
11897 if (second.pr == NULL) {
11898 StartChessProgram(&second);
11899 if (second.protocolVersion == 1) {
11902 /* kludge: allow timeout for initial "feature" command */
11904 DisplayMessage("", _("Starting second chess program"));
11905 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11913 TwoMachinesEvent P((void))
11917 ChessProgramState *onmove;
11918 char *bookHit = NULL;
11920 if (appData.noChessProgram) return;
11922 switch (gameMode) {
11923 case TwoMachinesPlay:
11925 case MachinePlaysWhite:
11926 case MachinePlaysBlack:
11927 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11928 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11932 case BeginningOfGame:
11933 case PlayFromGameFile:
11936 if (gameMode != EditGame) return;
11939 EditPositionDone(TRUE);
11950 // forwardMostMove = currentMove;
11951 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11952 ResurrectChessProgram(); /* in case first program isn't running */
11954 if(WaitForSecond(TwoMachinesEventIfReady)) return;
11955 DisplayMessage("", "");
11956 InitChessProgram(&second, FALSE);
11957 SendToProgram("force\n", &second);
11958 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11959 ScheduleDelayedEvent(TwoMachinesEvent, 10);
11962 if (startedFromSetupPosition) {
11963 SendBoard(&second, backwardMostMove);
11964 if (appData.debugMode) {
11965 fprintf(debugFP, "Two Machines\n");
11968 for (i = backwardMostMove; i < forwardMostMove; i++) {
11969 SendMoveToProgram(i, &second);
11972 gameMode = TwoMachinesPlay;
11976 DisplayTwoMachinesTitle();
11978 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11984 SendToProgram(first.computerString, &first);
11985 if (first.sendName) {
11986 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11987 SendToProgram(buf, &first);
11989 SendToProgram(second.computerString, &second);
11990 if (second.sendName) {
11991 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11992 SendToProgram(buf, &second);
11996 if (!first.sendTime || !second.sendTime) {
11997 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11998 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12000 if (onmove->sendTime) {
12001 if (onmove->useColors) {
12002 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12004 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12006 if (onmove->useColors) {
12007 SendToProgram(onmove->twoMachinesColor, onmove);
12009 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12010 // SendToProgram("go\n", onmove);
12011 onmove->maybeThinking = TRUE;
12012 SetMachineThinkingEnables();
12016 if(bookHit) { // [HGM] book: simulate book reply
12017 static char bookMove[MSG_SIZ]; // a bit generous?
12019 programStats.nodes = programStats.depth = programStats.time =
12020 programStats.score = programStats.got_only_move = 0;
12021 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12023 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12024 strcat(bookMove, bookHit);
12025 savedMessage = bookMove; // args for deferred call
12026 savedState = onmove;
12027 ScheduleDelayedEvent(DeferredBookMove, 1);
12034 if (gameMode == Training) {
12035 SetTrainingModeOff();
12036 gameMode = PlayFromGameFile;
12037 DisplayMessage("", _("Training mode off"));
12039 gameMode = Training;
12040 animateTraining = appData.animate;
12042 /* make sure we are not already at the end of the game */
12043 if (currentMove < forwardMostMove) {
12044 SetTrainingModeOn();
12045 DisplayMessage("", _("Training mode on"));
12047 gameMode = PlayFromGameFile;
12048 DisplayError(_("Already at end of game"), 0);
12057 if (!appData.icsActive) return;
12058 switch (gameMode) {
12059 case IcsPlayingWhite:
12060 case IcsPlayingBlack:
12063 case BeginningOfGame:
12071 EditPositionDone(TRUE);
12084 gameMode = IcsIdle;
12095 switch (gameMode) {
12097 SetTrainingModeOff();
12099 case MachinePlaysWhite:
12100 case MachinePlaysBlack:
12101 case BeginningOfGame:
12102 SendToProgram("force\n", &first);
12103 SetUserThinkingEnables();
12105 case PlayFromGameFile:
12106 (void) StopLoadGameTimer();
12107 if (gameFileFP != NULL) {
12112 EditPositionDone(TRUE);
12117 SendToProgram("force\n", &first);
12119 case TwoMachinesPlay:
12120 GameEnds(EndOfFile, NULL, GE_PLAYER);
12121 ResurrectChessProgram();
12122 SetUserThinkingEnables();
12125 ResurrectChessProgram();
12127 case IcsPlayingBlack:
12128 case IcsPlayingWhite:
12129 DisplayError(_("Warning: You are still playing a game"), 0);
12132 DisplayError(_("Warning: You are still observing a game"), 0);
12135 DisplayError(_("Warning: You are still examining a game"), 0);
12146 first.offeredDraw = second.offeredDraw = 0;
12148 if (gameMode == PlayFromGameFile) {
12149 whiteTimeRemaining = timeRemaining[0][currentMove];
12150 blackTimeRemaining = timeRemaining[1][currentMove];
12154 if (gameMode == MachinePlaysWhite ||
12155 gameMode == MachinePlaysBlack ||
12156 gameMode == TwoMachinesPlay ||
12157 gameMode == EndOfGame) {
12158 i = forwardMostMove;
12159 while (i > currentMove) {
12160 SendToProgram("undo\n", &first);
12163 whiteTimeRemaining = timeRemaining[0][currentMove];
12164 blackTimeRemaining = timeRemaining[1][currentMove];
12165 DisplayBothClocks();
12166 if (whiteFlag || blackFlag) {
12167 whiteFlag = blackFlag = 0;
12172 gameMode = EditGame;
12179 EditPositionEvent()
12181 if (gameMode == EditPosition) {
12187 if (gameMode != EditGame) return;
12189 gameMode = EditPosition;
12192 if (currentMove > 0)
12193 CopyBoard(boards[0], boards[currentMove]);
12195 blackPlaysFirst = !WhiteOnMove(currentMove);
12197 currentMove = forwardMostMove = backwardMostMove = 0;
12198 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12205 /* [DM] icsEngineAnalyze - possible call from other functions */
12206 if (appData.icsEngineAnalyze) {
12207 appData.icsEngineAnalyze = FALSE;
12209 DisplayMessage("",_("Close ICS engine analyze..."));
12211 if (first.analysisSupport && first.analyzing) {
12212 SendToProgram("exit\n", &first);
12213 first.analyzing = FALSE;
12215 thinkOutput[0] = NULLCHAR;
12219 EditPositionDone(Boolean fakeRights)
12221 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12223 startedFromSetupPosition = TRUE;
12224 InitChessProgram(&first, FALSE);
12225 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12226 boards[0][EP_STATUS] = EP_NONE;
12227 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12228 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12229 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12230 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12231 } else boards[0][CASTLING][2] = NoRights;
12232 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12233 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12234 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12235 } else boards[0][CASTLING][5] = NoRights;
12237 SendToProgram("force\n", &first);
12238 if (blackPlaysFirst) {
12239 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12240 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12241 currentMove = forwardMostMove = backwardMostMove = 1;
12242 CopyBoard(boards[1], boards[0]);
12244 currentMove = forwardMostMove = backwardMostMove = 0;
12246 SendBoard(&first, forwardMostMove);
12247 if (appData.debugMode) {
12248 fprintf(debugFP, "EditPosDone\n");
12251 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12252 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12253 gameMode = EditGame;
12255 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12256 ClearHighlights(); /* [AS] */
12259 /* Pause for `ms' milliseconds */
12260 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12270 } while (SubtractTimeMarks(&m2, &m1) < ms);
12273 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12275 SendMultiLineToICS(buf)
12278 char temp[MSG_SIZ+1], *p;
12285 strncpy(temp, buf, len);
12290 if (*p == '\n' || *p == '\r')
12295 strcat(temp, "\n");
12297 SendToPlayer(temp, strlen(temp));
12301 SetWhiteToPlayEvent()
12303 if (gameMode == EditPosition) {
12304 blackPlaysFirst = FALSE;
12305 DisplayBothClocks(); /* works because currentMove is 0 */
12306 } else if (gameMode == IcsExamining) {
12307 SendToICS(ics_prefix);
12308 SendToICS("tomove white\n");
12313 SetBlackToPlayEvent()
12315 if (gameMode == EditPosition) {
12316 blackPlaysFirst = TRUE;
12317 currentMove = 1; /* kludge */
12318 DisplayBothClocks();
12320 } else if (gameMode == IcsExamining) {
12321 SendToICS(ics_prefix);
12322 SendToICS("tomove black\n");
12327 EditPositionMenuEvent(selection, x, y)
12328 ChessSquare selection;
12332 ChessSquare piece = boards[0][y][x];
12334 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12336 switch (selection) {
12338 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12339 SendToICS(ics_prefix);
12340 SendToICS("bsetup clear\n");
12341 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12342 SendToICS(ics_prefix);
12343 SendToICS("clearboard\n");
12345 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12346 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12347 for (y = 0; y < BOARD_HEIGHT; y++) {
12348 if (gameMode == IcsExamining) {
12349 if (boards[currentMove][y][x] != EmptySquare) {
12350 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12355 boards[0][y][x] = p;
12360 if (gameMode == EditPosition) {
12361 DrawPosition(FALSE, boards[0]);
12366 SetWhiteToPlayEvent();
12370 SetBlackToPlayEvent();
12374 if (gameMode == IcsExamining) {
12375 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12376 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12379 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12380 if(x == BOARD_LEFT-2) {
12381 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12382 boards[0][y][1] = 0;
12384 if(x == BOARD_RGHT+1) {
12385 if(y >= gameInfo.holdingsSize) break;
12386 boards[0][y][BOARD_WIDTH-2] = 0;
12389 boards[0][y][x] = EmptySquare;
12390 DrawPosition(FALSE, boards[0]);
12395 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12396 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12397 selection = (ChessSquare) (PROMOTED piece);
12398 } else if(piece == EmptySquare) selection = WhiteSilver;
12399 else selection = (ChessSquare)((int)piece - 1);
12403 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12404 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12405 selection = (ChessSquare) (DEMOTED piece);
12406 } else if(piece == EmptySquare) selection = BlackSilver;
12407 else selection = (ChessSquare)((int)piece + 1);
12412 if(gameInfo.variant == VariantShatranj ||
12413 gameInfo.variant == VariantXiangqi ||
12414 gameInfo.variant == VariantCourier ||
12415 gameInfo.variant == VariantMakruk )
12416 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12421 if(gameInfo.variant == VariantXiangqi)
12422 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12423 if(gameInfo.variant == VariantKnightmate)
12424 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12427 if (gameMode == IcsExamining) {
12428 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12429 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12430 PieceToChar(selection), AAA + x, ONE + y);
12433 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12435 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12436 n = PieceToNumber(selection - BlackPawn);
12437 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12438 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12439 boards[0][BOARD_HEIGHT-1-n][1]++;
12441 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12442 n = PieceToNumber(selection);
12443 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12444 boards[0][n][BOARD_WIDTH-1] = selection;
12445 boards[0][n][BOARD_WIDTH-2]++;
12448 boards[0][y][x] = selection;
12449 DrawPosition(TRUE, boards[0]);
12457 DropMenuEvent(selection, x, y)
12458 ChessSquare selection;
12461 ChessMove moveType;
12463 switch (gameMode) {
12464 case IcsPlayingWhite:
12465 case MachinePlaysBlack:
12466 if (!WhiteOnMove(currentMove)) {
12467 DisplayMoveError(_("It is Black's turn"));
12470 moveType = WhiteDrop;
12472 case IcsPlayingBlack:
12473 case MachinePlaysWhite:
12474 if (WhiteOnMove(currentMove)) {
12475 DisplayMoveError(_("It is White's turn"));
12478 moveType = BlackDrop;
12481 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12487 if (moveType == BlackDrop && selection < BlackPawn) {
12488 selection = (ChessSquare) ((int) selection
12489 + (int) BlackPawn - (int) WhitePawn);
12491 if (boards[currentMove][y][x] != EmptySquare) {
12492 DisplayMoveError(_("That square is occupied"));
12496 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12502 /* Accept a pending offer of any kind from opponent */
12504 if (appData.icsActive) {
12505 SendToICS(ics_prefix);
12506 SendToICS("accept\n");
12507 } else if (cmailMsgLoaded) {
12508 if (currentMove == cmailOldMove &&
12509 commentList[cmailOldMove] != NULL &&
12510 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12511 "Black offers a draw" : "White offers a draw")) {
12513 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12514 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12516 DisplayError(_("There is no pending offer on this move"), 0);
12517 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12520 /* Not used for offers from chess program */
12527 /* Decline a pending offer of any kind from opponent */
12529 if (appData.icsActive) {
12530 SendToICS(ics_prefix);
12531 SendToICS("decline\n");
12532 } else if (cmailMsgLoaded) {
12533 if (currentMove == cmailOldMove &&
12534 commentList[cmailOldMove] != NULL &&
12535 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12536 "Black offers a draw" : "White offers a draw")) {
12538 AppendComment(cmailOldMove, "Draw declined", TRUE);
12539 DisplayComment(cmailOldMove - 1, "Draw declined");
12542 DisplayError(_("There is no pending offer on this move"), 0);
12545 /* Not used for offers from chess program */
12552 /* Issue ICS rematch command */
12553 if (appData.icsActive) {
12554 SendToICS(ics_prefix);
12555 SendToICS("rematch\n");
12562 /* Call your opponent's flag (claim a win on time) */
12563 if (appData.icsActive) {
12564 SendToICS(ics_prefix);
12565 SendToICS("flag\n");
12567 switch (gameMode) {
12570 case MachinePlaysWhite:
12573 GameEnds(GameIsDrawn, "Both players ran out of time",
12576 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12578 DisplayError(_("Your opponent is not out of time"), 0);
12581 case MachinePlaysBlack:
12584 GameEnds(GameIsDrawn, "Both players ran out of time",
12587 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12589 DisplayError(_("Your opponent is not out of time"), 0);
12599 /* Offer draw or accept pending draw offer from opponent */
12601 if (appData.icsActive) {
12602 /* Note: tournament rules require draw offers to be
12603 made after you make your move but before you punch
12604 your clock. Currently ICS doesn't let you do that;
12605 instead, you immediately punch your clock after making
12606 a move, but you can offer a draw at any time. */
12608 SendToICS(ics_prefix);
12609 SendToICS("draw\n");
12610 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12611 } else if (cmailMsgLoaded) {
12612 if (currentMove == cmailOldMove &&
12613 commentList[cmailOldMove] != NULL &&
12614 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12615 "Black offers a draw" : "White offers a draw")) {
12616 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12617 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12618 } else if (currentMove == cmailOldMove + 1) {
12619 char *offer = WhiteOnMove(cmailOldMove) ?
12620 "White offers a draw" : "Black offers a draw";
12621 AppendComment(currentMove, offer, TRUE);
12622 DisplayComment(currentMove - 1, offer);
12623 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12625 DisplayError(_("You must make your move before offering a draw"), 0);
12626 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12628 } else if (first.offeredDraw) {
12629 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12631 if (first.sendDrawOffers) {
12632 SendToProgram("draw\n", &first);
12633 userOfferedDraw = TRUE;
12641 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12643 if (appData.icsActive) {
12644 SendToICS(ics_prefix);
12645 SendToICS("adjourn\n");
12647 /* Currently GNU Chess doesn't offer or accept Adjourns */
12655 /* Offer Abort or accept pending Abort offer from opponent */
12657 if (appData.icsActive) {
12658 SendToICS(ics_prefix);
12659 SendToICS("abort\n");
12661 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12668 /* Resign. You can do this even if it's not your turn. */
12670 if (appData.icsActive) {
12671 SendToICS(ics_prefix);
12672 SendToICS("resign\n");
12674 switch (gameMode) {
12675 case MachinePlaysWhite:
12676 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12678 case MachinePlaysBlack:
12679 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12682 if (cmailMsgLoaded) {
12684 if (WhiteOnMove(cmailOldMove)) {
12685 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12687 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12689 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12700 StopObservingEvent()
12702 /* Stop observing current games */
12703 SendToICS(ics_prefix);
12704 SendToICS("unobserve\n");
12708 StopExaminingEvent()
12710 /* Stop observing current game */
12711 SendToICS(ics_prefix);
12712 SendToICS("unexamine\n");
12716 ForwardInner(target)
12721 if (appData.debugMode)
12722 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12723 target, currentMove, forwardMostMove);
12725 if (gameMode == EditPosition)
12728 if (gameMode == PlayFromGameFile && !pausing)
12731 if (gameMode == IcsExamining && pausing)
12732 limit = pauseExamForwardMostMove;
12734 limit = forwardMostMove;
12736 if (target > limit) target = limit;
12738 if (target > 0 && moveList[target - 1][0]) {
12739 int fromX, fromY, toX, toY;
12740 toX = moveList[target - 1][2] - AAA;
12741 toY = moveList[target - 1][3] - ONE;
12742 if (moveList[target - 1][1] == '@') {
12743 if (appData.highlightLastMove) {
12744 SetHighlights(-1, -1, toX, toY);
12747 fromX = moveList[target - 1][0] - AAA;
12748 fromY = moveList[target - 1][1] - ONE;
12749 if (target == currentMove + 1) {
12750 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12752 if (appData.highlightLastMove) {
12753 SetHighlights(fromX, fromY, toX, toY);
12757 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12758 gameMode == Training || gameMode == PlayFromGameFile ||
12759 gameMode == AnalyzeFile) {
12760 while (currentMove < target) {
12761 SendMoveToProgram(currentMove++, &first);
12764 currentMove = target;
12767 if (gameMode == EditGame || gameMode == EndOfGame) {
12768 whiteTimeRemaining = timeRemaining[0][currentMove];
12769 blackTimeRemaining = timeRemaining[1][currentMove];
12771 DisplayBothClocks();
12772 DisplayMove(currentMove - 1);
12773 DrawPosition(FALSE, boards[currentMove]);
12774 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12775 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12776 DisplayComment(currentMove - 1, commentList[currentMove]);
12784 if (gameMode == IcsExamining && !pausing) {
12785 SendToICS(ics_prefix);
12786 SendToICS("forward\n");
12788 ForwardInner(currentMove + 1);
12795 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12796 /* to optimze, we temporarily turn off analysis mode while we feed
12797 * the remaining moves to the engine. Otherwise we get analysis output
12800 if (first.analysisSupport) {
12801 SendToProgram("exit\nforce\n", &first);
12802 first.analyzing = FALSE;
12806 if (gameMode == IcsExamining && !pausing) {
12807 SendToICS(ics_prefix);
12808 SendToICS("forward 999999\n");
12810 ForwardInner(forwardMostMove);
12813 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12814 /* we have fed all the moves, so reactivate analysis mode */
12815 SendToProgram("analyze\n", &first);
12816 first.analyzing = TRUE;
12817 /*first.maybeThinking = TRUE;*/
12818 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12823 BackwardInner(target)
12826 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12828 if (appData.debugMode)
12829 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12830 target, currentMove, forwardMostMove);
12832 if (gameMode == EditPosition) return;
12833 if (currentMove <= backwardMostMove) {
12835 DrawPosition(full_redraw, boards[currentMove]);
12838 if (gameMode == PlayFromGameFile && !pausing)
12841 if (moveList[target][0]) {
12842 int fromX, fromY, toX, toY;
12843 toX = moveList[target][2] - AAA;
12844 toY = moveList[target][3] - ONE;
12845 if (moveList[target][1] == '@') {
12846 if (appData.highlightLastMove) {
12847 SetHighlights(-1, -1, toX, toY);
12850 fromX = moveList[target][0] - AAA;
12851 fromY = moveList[target][1] - ONE;
12852 if (target == currentMove - 1) {
12853 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12855 if (appData.highlightLastMove) {
12856 SetHighlights(fromX, fromY, toX, toY);
12860 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12861 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12862 while (currentMove > target) {
12863 SendToProgram("undo\n", &first);
12867 currentMove = target;
12870 if (gameMode == EditGame || gameMode == EndOfGame) {
12871 whiteTimeRemaining = timeRemaining[0][currentMove];
12872 blackTimeRemaining = timeRemaining[1][currentMove];
12874 DisplayBothClocks();
12875 DisplayMove(currentMove - 1);
12876 DrawPosition(full_redraw, boards[currentMove]);
12877 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12878 // [HGM] PV info: routine tests if comment empty
12879 DisplayComment(currentMove - 1, commentList[currentMove]);
12885 if (gameMode == IcsExamining && !pausing) {
12886 SendToICS(ics_prefix);
12887 SendToICS("backward\n");
12889 BackwardInner(currentMove - 1);
12896 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12897 /* to optimize, we temporarily turn off analysis mode while we undo
12898 * all the moves. Otherwise we get analysis output after each undo.
12900 if (first.analysisSupport) {
12901 SendToProgram("exit\nforce\n", &first);
12902 first.analyzing = FALSE;
12906 if (gameMode == IcsExamining && !pausing) {
12907 SendToICS(ics_prefix);
12908 SendToICS("backward 999999\n");
12910 BackwardInner(backwardMostMove);
12913 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12914 /* we have fed all the moves, so reactivate analysis mode */
12915 SendToProgram("analyze\n", &first);
12916 first.analyzing = TRUE;
12917 /*first.maybeThinking = TRUE;*/
12918 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12925 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12926 if (to >= forwardMostMove) to = forwardMostMove;
12927 if (to <= backwardMostMove) to = backwardMostMove;
12928 if (to < currentMove) {
12936 RevertEvent(Boolean annotate)
12938 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12941 if (gameMode != IcsExamining) {
12942 DisplayError(_("You are not examining a game"), 0);
12946 DisplayError(_("You can't revert while pausing"), 0);
12949 SendToICS(ics_prefix);
12950 SendToICS("revert\n");
12956 switch (gameMode) {
12957 case MachinePlaysWhite:
12958 case MachinePlaysBlack:
12959 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12960 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12963 if (forwardMostMove < 2) return;
12964 currentMove = forwardMostMove = forwardMostMove - 2;
12965 whiteTimeRemaining = timeRemaining[0][currentMove];
12966 blackTimeRemaining = timeRemaining[1][currentMove];
12967 DisplayBothClocks();
12968 DisplayMove(currentMove - 1);
12969 ClearHighlights();/*!! could figure this out*/
12970 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12971 SendToProgram("remove\n", &first);
12972 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12975 case BeginningOfGame:
12979 case IcsPlayingWhite:
12980 case IcsPlayingBlack:
12981 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12982 SendToICS(ics_prefix);
12983 SendToICS("takeback 2\n");
12985 SendToICS(ics_prefix);
12986 SendToICS("takeback 1\n");
12995 ChessProgramState *cps;
12997 switch (gameMode) {
12998 case MachinePlaysWhite:
12999 if (!WhiteOnMove(forwardMostMove)) {
13000 DisplayError(_("It is your turn"), 0);
13005 case MachinePlaysBlack:
13006 if (WhiteOnMove(forwardMostMove)) {
13007 DisplayError(_("It is your turn"), 0);
13012 case TwoMachinesPlay:
13013 if (WhiteOnMove(forwardMostMove) ==
13014 (first.twoMachinesColor[0] == 'w')) {
13020 case BeginningOfGame:
13024 SendToProgram("?\n", cps);
13028 TruncateGameEvent()
13031 if (gameMode != EditGame) return;
13038 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13039 if (forwardMostMove > currentMove) {
13040 if (gameInfo.resultDetails != NULL) {
13041 free(gameInfo.resultDetails);
13042 gameInfo.resultDetails = NULL;
13043 gameInfo.result = GameUnfinished;
13045 forwardMostMove = currentMove;
13046 HistorySet(parseList, backwardMostMove, forwardMostMove,
13054 if (appData.noChessProgram) return;
13055 switch (gameMode) {
13056 case MachinePlaysWhite:
13057 if (WhiteOnMove(forwardMostMove)) {
13058 DisplayError(_("Wait until your turn"), 0);
13062 case BeginningOfGame:
13063 case MachinePlaysBlack:
13064 if (!WhiteOnMove(forwardMostMove)) {
13065 DisplayError(_("Wait until your turn"), 0);
13070 DisplayError(_("No hint available"), 0);
13073 SendToProgram("hint\n", &first);
13074 hintRequested = TRUE;
13080 if (appData.noChessProgram) return;
13081 switch (gameMode) {
13082 case MachinePlaysWhite:
13083 if (WhiteOnMove(forwardMostMove)) {
13084 DisplayError(_("Wait until your turn"), 0);
13088 case BeginningOfGame:
13089 case MachinePlaysBlack:
13090 if (!WhiteOnMove(forwardMostMove)) {
13091 DisplayError(_("Wait until your turn"), 0);
13096 EditPositionDone(TRUE);
13098 case TwoMachinesPlay:
13103 SendToProgram("bk\n", &first);
13104 bookOutput[0] = NULLCHAR;
13105 bookRequested = TRUE;
13111 char *tags = PGNTags(&gameInfo);
13112 TagsPopUp(tags, CmailMsg());
13116 /* end button procedures */
13119 PrintPosition(fp, move)
13125 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13126 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13127 char c = PieceToChar(boards[move][i][j]);
13128 fputc(c == 'x' ? '.' : c, fp);
13129 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13132 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13133 fprintf(fp, "white to play\n");
13135 fprintf(fp, "black to play\n");
13142 if (gameInfo.white != NULL) {
13143 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13149 /* Find last component of program's own name, using some heuristics */
13151 TidyProgramName(prog, host, buf)
13152 char *prog, *host, buf[MSG_SIZ];
13155 int local = (strcmp(host, "localhost") == 0);
13156 while (!local && (p = strchr(prog, ';')) != NULL) {
13158 while (*p == ' ') p++;
13161 if (*prog == '"' || *prog == '\'') {
13162 q = strchr(prog + 1, *prog);
13164 q = strchr(prog, ' ');
13166 if (q == NULL) q = prog + strlen(prog);
13168 while (p >= prog && *p != '/' && *p != '\\') p--;
13170 if(p == prog && *p == '"') p++;
13171 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13172 memcpy(buf, p, q - p);
13173 buf[q - p] = NULLCHAR;
13181 TimeControlTagValue()
13184 if (!appData.clockMode) {
13185 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13186 } else if (movesPerSession > 0) {
13187 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13188 } else if (timeIncrement == 0) {
13189 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13191 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13193 return StrSave(buf);
13199 /* This routine is used only for certain modes */
13200 VariantClass v = gameInfo.variant;
13201 ChessMove r = GameUnfinished;
13204 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13205 r = gameInfo.result;
13206 p = gameInfo.resultDetails;
13207 gameInfo.resultDetails = NULL;
13209 ClearGameInfo(&gameInfo);
13210 gameInfo.variant = v;
13212 switch (gameMode) {
13213 case MachinePlaysWhite:
13214 gameInfo.event = StrSave( appData.pgnEventHeader );
13215 gameInfo.site = StrSave(HostName());
13216 gameInfo.date = PGNDate();
13217 gameInfo.round = StrSave("-");
13218 gameInfo.white = StrSave(first.tidy);
13219 gameInfo.black = StrSave(UserName());
13220 gameInfo.timeControl = TimeControlTagValue();
13223 case MachinePlaysBlack:
13224 gameInfo.event = StrSave( appData.pgnEventHeader );
13225 gameInfo.site = StrSave(HostName());
13226 gameInfo.date = PGNDate();
13227 gameInfo.round = StrSave("-");
13228 gameInfo.white = StrSave(UserName());
13229 gameInfo.black = StrSave(first.tidy);
13230 gameInfo.timeControl = TimeControlTagValue();
13233 case TwoMachinesPlay:
13234 gameInfo.event = StrSave( appData.pgnEventHeader );
13235 gameInfo.site = StrSave(HostName());
13236 gameInfo.date = PGNDate();
13237 if (matchGame > 0) {
13239 snprintf(buf, MSG_SIZ, "%d", matchGame);
13240 gameInfo.round = StrSave(buf);
13242 gameInfo.round = StrSave("-");
13244 if (first.twoMachinesColor[0] == 'w') {
13245 gameInfo.white = StrSave(first.tidy);
13246 gameInfo.black = StrSave(second.tidy);
13248 gameInfo.white = StrSave(second.tidy);
13249 gameInfo.black = StrSave(first.tidy);
13251 gameInfo.timeControl = TimeControlTagValue();
13255 gameInfo.event = StrSave("Edited game");
13256 gameInfo.site = StrSave(HostName());
13257 gameInfo.date = PGNDate();
13258 gameInfo.round = StrSave("-");
13259 gameInfo.white = StrSave("-");
13260 gameInfo.black = StrSave("-");
13261 gameInfo.result = r;
13262 gameInfo.resultDetails = p;
13266 gameInfo.event = StrSave("Edited position");
13267 gameInfo.site = StrSave(HostName());
13268 gameInfo.date = PGNDate();
13269 gameInfo.round = StrSave("-");
13270 gameInfo.white = StrSave("-");
13271 gameInfo.black = StrSave("-");
13274 case IcsPlayingWhite:
13275 case IcsPlayingBlack:
13280 case PlayFromGameFile:
13281 gameInfo.event = StrSave("Game from non-PGN file");
13282 gameInfo.site = StrSave(HostName());
13283 gameInfo.date = PGNDate();
13284 gameInfo.round = StrSave("-");
13285 gameInfo.white = StrSave("?");
13286 gameInfo.black = StrSave("?");
13295 ReplaceComment(index, text)
13303 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13304 pvInfoList[index-1].depth == len &&
13305 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13306 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13307 while (*text == '\n') text++;
13308 len = strlen(text);
13309 while (len > 0 && text[len - 1] == '\n') len--;
13311 if (commentList[index] != NULL)
13312 free(commentList[index]);
13315 commentList[index] = NULL;
13318 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13319 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13320 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13321 commentList[index] = (char *) malloc(len + 2);
13322 strncpy(commentList[index], text, len);
13323 commentList[index][len] = '\n';
13324 commentList[index][len + 1] = NULLCHAR;
13326 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13328 commentList[index] = (char *) malloc(len + 7);
13329 safeStrCpy(commentList[index], "{\n", 3);
13330 safeStrCpy(commentList[index]+2, text, len+1);
13331 commentList[index][len+2] = NULLCHAR;
13332 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13333 strcat(commentList[index], "\n}\n");
13347 if (ch == '\r') continue;
13349 } while (ch != '\0');
13353 AppendComment(index, text, addBraces)
13356 Boolean addBraces; // [HGM] braces: tells if we should add {}
13361 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13362 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13365 while (*text == '\n') text++;
13366 len = strlen(text);
13367 while (len > 0 && text[len - 1] == '\n') len--;
13369 if (len == 0) return;
13371 if (commentList[index] != NULL) {
13372 old = commentList[index];
13373 oldlen = strlen(old);
13374 while(commentList[index][oldlen-1] == '\n')
13375 commentList[index][--oldlen] = NULLCHAR;
13376 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13377 safeStrCpy(commentList[index], old, oldlen + len + 6);
13379 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13380 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13381 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13382 while (*text == '\n') { text++; len--; }
13383 commentList[index][--oldlen] = NULLCHAR;
13385 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13386 else strcat(commentList[index], "\n");
13387 strcat(commentList[index], text);
13388 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13389 else strcat(commentList[index], "\n");
13391 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13393 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13394 else commentList[index][0] = NULLCHAR;
13395 strcat(commentList[index], text);
13396 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13397 if(addBraces == TRUE) strcat(commentList[index], "}\n");
13401 static char * FindStr( char * text, char * sub_text )
13403 char * result = strstr( text, sub_text );
13405 if( result != NULL ) {
13406 result += strlen( sub_text );
13412 /* [AS] Try to extract PV info from PGN comment */
13413 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13414 char *GetInfoFromComment( int index, char * text )
13416 char * sep = text, *p;
13418 if( text != NULL && index > 0 ) {
13421 int time = -1, sec = 0, deci;
13422 char * s_eval = FindStr( text, "[%eval " );
13423 char * s_emt = FindStr( text, "[%emt " );
13425 if( s_eval != NULL || s_emt != NULL ) {
13429 if( s_eval != NULL ) {
13430 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13434 if( delim != ']' ) {
13439 if( s_emt != NULL ) {
13444 /* We expect something like: [+|-]nnn.nn/dd */
13447 if(*text != '{') return text; // [HGM] braces: must be normal comment
13449 sep = strchr( text, '/' );
13450 if( sep == NULL || sep < (text+4) ) {
13455 if(p[1] == '(') { // comment starts with PV
13456 p = strchr(p, ')'); // locate end of PV
13457 if(p == NULL || sep < p+5) return text;
13458 // at this point we have something like "{(.*) +0.23/6 ..."
13459 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13460 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13461 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13463 time = -1; sec = -1; deci = -1;
13464 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13465 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13466 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13467 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13471 if( score_lo < 0 || score_lo >= 100 ) {
13475 if(sec >= 0) time = 600*time + 10*sec; else
13476 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13478 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13480 /* [HGM] PV time: now locate end of PV info */
13481 while( *++sep >= '0' && *sep <= '9'); // strip depth
13483 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13485 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13487 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13488 while(*sep == ' ') sep++;
13499 pvInfoList[index-1].depth = depth;
13500 pvInfoList[index-1].score = score;
13501 pvInfoList[index-1].time = 10*time; // centi-sec
13502 if(*sep == '}') *sep = 0; else *--sep = '{';
13503 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13509 SendToProgram(message, cps)
13511 ChessProgramState *cps;
13513 int count, outCount, error;
13516 if (cps->pr == NULL) return;
13519 if (appData.debugMode) {
13522 fprintf(debugFP, "%ld >%-6s: %s",
13523 SubtractTimeMarks(&now, &programStartTime),
13524 cps->which, message);
13527 count = strlen(message);
13528 outCount = OutputToProcess(cps->pr, message, count, &error);
13529 if (outCount < count && !exiting
13530 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13531 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13532 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13533 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13534 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13535 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13537 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13539 gameInfo.resultDetails = StrSave(buf);
13541 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13546 ReceiveFromProgram(isr, closure, message, count, error)
13547 InputSourceRef isr;
13555 ChessProgramState *cps = (ChessProgramState *)closure;
13557 if (isr != cps->isr) return; /* Killed intentionally */
13560 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13561 cps->which, cps->program);
13562 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13563 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13564 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13565 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13567 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13569 gameInfo.resultDetails = StrSave(buf);
13571 RemoveInputSource(cps->isr);
13572 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13574 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13575 cps->which, cps->program);
13576 RemoveInputSource(cps->isr);
13578 /* [AS] Program is misbehaving badly... kill it */
13579 if( count == -2 ) {
13580 DestroyChildProcess( cps->pr, 9 );
13584 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13589 if ((end_str = strchr(message, '\r')) != NULL)
13590 *end_str = NULLCHAR;
13591 if ((end_str = strchr(message, '\n')) != NULL)
13592 *end_str = NULLCHAR;
13594 if (appData.debugMode) {
13595 TimeMark now; int print = 1;
13596 char *quote = ""; char c; int i;
13598 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13599 char start = message[0];
13600 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13601 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13602 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13603 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13604 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13605 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13606 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13607 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
13608 sscanf(message, "hint: %c", &c)!=1 &&
13609 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13610 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13611 print = (appData.engineComments >= 2);
13613 message[0] = start; // restore original message
13617 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13618 SubtractTimeMarks(&now, &programStartTime), cps->which,
13624 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13625 if (appData.icsEngineAnalyze) {
13626 if (strstr(message, "whisper") != NULL ||
13627 strstr(message, "kibitz") != NULL ||
13628 strstr(message, "tellics") != NULL) return;
13631 HandleMachineMove(message, cps);
13636 SendTimeControl(cps, mps, tc, inc, sd, st)
13637 ChessProgramState *cps;
13638 int mps, inc, sd, st;
13644 if( timeControl_2 > 0 ) {
13645 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13646 tc = timeControl_2;
13649 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13650 inc /= cps->timeOdds;
13651 st /= cps->timeOdds;
13653 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13656 /* Set exact time per move, normally using st command */
13657 if (cps->stKludge) {
13658 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13660 if (seconds == 0) {
13661 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13663 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13666 snprintf(buf, MSG_SIZ, "st %d\n", st);
13669 /* Set conventional or incremental time control, using level command */
13670 if (seconds == 0) {
13671 /* Note old gnuchess bug -- minutes:seconds used to not work.
13672 Fixed in later versions, but still avoid :seconds
13673 when seconds is 0. */
13674 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13676 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13677 seconds, inc/1000.);
13680 SendToProgram(buf, cps);
13682 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13683 /* Orthogonally, limit search to given depth */
13685 if (cps->sdKludge) {
13686 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13688 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13690 SendToProgram(buf, cps);
13693 if(cps->nps > 0) { /* [HGM] nps */
13694 if(cps->supportsNPS == FALSE)
13695 cps->nps = -1; // don't use if engine explicitly says not supported!
13697 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13698 SendToProgram(buf, cps);
13703 ChessProgramState *WhitePlayer()
13704 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13706 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13707 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13713 SendTimeRemaining(cps, machineWhite)
13714 ChessProgramState *cps;
13715 int /*boolean*/ machineWhite;
13717 char message[MSG_SIZ];
13720 /* Note: this routine must be called when the clocks are stopped
13721 or when they have *just* been set or switched; otherwise
13722 it will be off by the time since the current tick started.
13724 if (machineWhite) {
13725 time = whiteTimeRemaining / 10;
13726 otime = blackTimeRemaining / 10;
13728 time = blackTimeRemaining / 10;
13729 otime = whiteTimeRemaining / 10;
13731 /* [HGM] translate opponent's time by time-odds factor */
13732 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13733 if (appData.debugMode) {
13734 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13737 if (time <= 0) time = 1;
13738 if (otime <= 0) otime = 1;
13740 snprintf(message, MSG_SIZ, "time %ld\n", time);
13741 SendToProgram(message, cps);
13743 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13744 SendToProgram(message, cps);
13748 BoolFeature(p, name, loc, cps)
13752 ChessProgramState *cps;
13755 int len = strlen(name);
13758 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13760 sscanf(*p, "%d", &val);
13762 while (**p && **p != ' ')
13764 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13765 SendToProgram(buf, cps);
13772 IntFeature(p, name, loc, cps)
13776 ChessProgramState *cps;
13779 int len = strlen(name);
13780 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13782 sscanf(*p, "%d", loc);
13783 while (**p && **p != ' ') (*p)++;
13784 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13785 SendToProgram(buf, cps);
13792 StringFeature(p, name, loc, cps)
13796 ChessProgramState *cps;
13799 int len = strlen(name);
13800 if (strncmp((*p), name, len) == 0
13801 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13803 sscanf(*p, "%[^\"]", loc);
13804 while (**p && **p != '\"') (*p)++;
13805 if (**p == '\"') (*p)++;
13806 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13807 SendToProgram(buf, cps);
13814 ParseOption(Option *opt, ChessProgramState *cps)
13815 // [HGM] options: process the string that defines an engine option, and determine
13816 // name, type, default value, and allowed value range
13818 char *p, *q, buf[MSG_SIZ];
13819 int n, min = (-1)<<31, max = 1<<31, def;
13821 if(p = strstr(opt->name, " -spin ")) {
13822 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13823 if(max < min) max = min; // enforce consistency
13824 if(def < min) def = min;
13825 if(def > max) def = max;
13830 } else if((p = strstr(opt->name, " -slider "))) {
13831 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13832 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13833 if(max < min) max = min; // enforce consistency
13834 if(def < min) def = min;
13835 if(def > max) def = max;
13839 opt->type = Spin; // Slider;
13840 } else if((p = strstr(opt->name, " -string "))) {
13841 opt->textValue = p+9;
13842 opt->type = TextBox;
13843 } else if((p = strstr(opt->name, " -file "))) {
13844 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13845 opt->textValue = p+7;
13846 opt->type = TextBox; // FileName;
13847 } else if((p = strstr(opt->name, " -path "))) {
13848 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13849 opt->textValue = p+7;
13850 opt->type = TextBox; // PathName;
13851 } else if(p = strstr(opt->name, " -check ")) {
13852 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13853 opt->value = (def != 0);
13854 opt->type = CheckBox;
13855 } else if(p = strstr(opt->name, " -combo ")) {
13856 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13857 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13858 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13859 opt->value = n = 0;
13860 while(q = StrStr(q, " /// ")) {
13861 n++; *q = 0; // count choices, and null-terminate each of them
13863 if(*q == '*') { // remember default, which is marked with * prefix
13867 cps->comboList[cps->comboCnt++] = q;
13869 cps->comboList[cps->comboCnt++] = NULL;
13871 opt->type = ComboBox;
13872 } else if(p = strstr(opt->name, " -button")) {
13873 opt->type = Button;
13874 } else if(p = strstr(opt->name, " -save")) {
13875 opt->type = SaveButton;
13876 } else return FALSE;
13877 *p = 0; // terminate option name
13878 // now look if the command-line options define a setting for this engine option.
13879 if(cps->optionSettings && cps->optionSettings[0])
13880 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13881 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13882 snprintf(buf, MSG_SIZ, "option %s", p);
13883 if(p = strstr(buf, ",")) *p = 0;
13884 if(q = strchr(buf, '=')) switch(opt->type) {
13886 for(n=0; n<opt->max; n++)
13887 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13890 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13894 opt->value = atoi(q+1);
13899 SendToProgram(buf, cps);
13905 FeatureDone(cps, val)
13906 ChessProgramState* cps;
13909 DelayedEventCallback cb = GetDelayedEvent();
13910 if ((cb == InitBackEnd3 && cps == &first) ||
13911 (cb == SettingsMenuIfReady && cps == &second) ||
13912 (cb == TwoMachinesEventIfReady && cps == &second)) {
13913 CancelDelayedEvent();
13914 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13916 cps->initDone = val;
13919 /* Parse feature command from engine */
13921 ParseFeatures(args, cps)
13923 ChessProgramState *cps;
13931 while (*p == ' ') p++;
13932 if (*p == NULLCHAR) return;
13934 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13935 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13936 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13937 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13938 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13939 if (BoolFeature(&p, "reuse", &val, cps)) {
13940 /* Engine can disable reuse, but can't enable it if user said no */
13941 if (!val) cps->reuse = FALSE;
13944 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13945 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13946 if (gameMode == TwoMachinesPlay) {
13947 DisplayTwoMachinesTitle();
13953 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13954 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13955 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13956 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13957 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13958 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13959 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13960 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13961 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13962 if (IntFeature(&p, "done", &val, cps)) {
13963 FeatureDone(cps, val);
13966 /* Added by Tord: */
13967 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13968 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13969 /* End of additions by Tord */
13971 /* [HGM] added features: */
13972 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13973 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13974 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13975 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13976 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13977 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13978 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13979 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13980 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13981 SendToProgram(buf, cps);
13984 if(cps->nrOptions >= MAX_OPTIONS) {
13986 snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13987 DisplayError(buf, 0);
13991 /* End of additions by HGM */
13993 /* unknown feature: complain and skip */
13995 while (*q && *q != '=') q++;
13996 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13997 SendToProgram(buf, cps);
14003 while (*p && *p != '\"') p++;
14004 if (*p == '\"') p++;
14006 while (*p && *p != ' ') p++;
14014 PeriodicUpdatesEvent(newState)
14017 if (newState == appData.periodicUpdates)
14020 appData.periodicUpdates=newState;
14022 /* Display type changes, so update it now */
14023 // DisplayAnalysis();
14025 /* Get the ball rolling again... */
14027 AnalysisPeriodicEvent(1);
14028 StartAnalysisClock();
14033 PonderNextMoveEvent(newState)
14036 if (newState == appData.ponderNextMove) return;
14037 if (gameMode == EditPosition) EditPositionDone(TRUE);
14039 SendToProgram("hard\n", &first);
14040 if (gameMode == TwoMachinesPlay) {
14041 SendToProgram("hard\n", &second);
14044 SendToProgram("easy\n", &first);
14045 thinkOutput[0] = NULLCHAR;
14046 if (gameMode == TwoMachinesPlay) {
14047 SendToProgram("easy\n", &second);
14050 appData.ponderNextMove = newState;
14054 NewSettingEvent(option, feature, command, value)
14056 int option, value, *feature;
14060 if (gameMode == EditPosition) EditPositionDone(TRUE);
14061 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14062 if(feature == NULL || *feature) SendToProgram(buf, &first);
14063 if (gameMode == TwoMachinesPlay) {
14064 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14069 ShowThinkingEvent()
14070 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14072 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14073 int newState = appData.showThinking
14074 // [HGM] thinking: other features now need thinking output as well
14075 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14077 if (oldState == newState) return;
14078 oldState = newState;
14079 if (gameMode == EditPosition) EditPositionDone(TRUE);
14081 SendToProgram("post\n", &first);
14082 if (gameMode == TwoMachinesPlay) {
14083 SendToProgram("post\n", &second);
14086 SendToProgram("nopost\n", &first);
14087 thinkOutput[0] = NULLCHAR;
14088 if (gameMode == TwoMachinesPlay) {
14089 SendToProgram("nopost\n", &second);
14092 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14096 AskQuestionEvent(title, question, replyPrefix, which)
14097 char *title; char *question; char *replyPrefix; char *which;
14099 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14100 if (pr == NoProc) return;
14101 AskQuestion(title, question, replyPrefix, pr);
14105 DisplayMove(moveNumber)
14108 char message[MSG_SIZ];
14110 char cpThinkOutput[MSG_SIZ];
14112 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14114 if (moveNumber == forwardMostMove - 1 ||
14115 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14117 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14119 if (strchr(cpThinkOutput, '\n')) {
14120 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14123 *cpThinkOutput = NULLCHAR;
14126 /* [AS] Hide thinking from human user */
14127 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14128 *cpThinkOutput = NULLCHAR;
14129 if( thinkOutput[0] != NULLCHAR ) {
14132 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14133 cpThinkOutput[i] = '.';
14135 cpThinkOutput[i] = NULLCHAR;
14136 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14140 if (moveNumber == forwardMostMove - 1 &&
14141 gameInfo.resultDetails != NULL) {
14142 if (gameInfo.resultDetails[0] == NULLCHAR) {
14143 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14145 snprintf(res, MSG_SIZ, " {%s} %s",
14146 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14152 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14153 DisplayMessage(res, cpThinkOutput);
14155 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14156 WhiteOnMove(moveNumber) ? " " : ".. ",
14157 parseList[moveNumber], res);
14158 DisplayMessage(message, cpThinkOutput);
14163 DisplayComment(moveNumber, text)
14167 char title[MSG_SIZ];
14168 char buf[8000]; // comment can be long!
14171 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14172 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14174 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14175 WhiteOnMove(moveNumber) ? " " : ".. ",
14176 parseList[moveNumber]);
14178 // [HGM] PV info: display PV info together with (or as) comment
14179 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14180 if(text == NULL) text = "";
14181 score = pvInfoList[moveNumber].score;
14182 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14183 depth, (pvInfoList[moveNumber].time+50)/100, text);
14186 if (text != NULL && (appData.autoDisplayComment || commentUp))
14187 CommentPopUp(title, text);
14190 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14191 * might be busy thinking or pondering. It can be omitted if your
14192 * gnuchess is configured to stop thinking immediately on any user
14193 * input. However, that gnuchess feature depends on the FIONREAD
14194 * ioctl, which does not work properly on some flavors of Unix.
14198 ChessProgramState *cps;
14201 if (!cps->useSigint) return;
14202 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14203 switch (gameMode) {
14204 case MachinePlaysWhite:
14205 case MachinePlaysBlack:
14206 case TwoMachinesPlay:
14207 case IcsPlayingWhite:
14208 case IcsPlayingBlack:
14211 /* Skip if we know it isn't thinking */
14212 if (!cps->maybeThinking) return;
14213 if (appData.debugMode)
14214 fprintf(debugFP, "Interrupting %s\n", cps->which);
14215 InterruptChildProcess(cps->pr);
14216 cps->maybeThinking = FALSE;
14221 #endif /*ATTENTION*/
14227 if (whiteTimeRemaining <= 0) {
14230 if (appData.icsActive) {
14231 if (appData.autoCallFlag &&
14232 gameMode == IcsPlayingBlack && !blackFlag) {
14233 SendToICS(ics_prefix);
14234 SendToICS("flag\n");
14238 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14240 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14241 if (appData.autoCallFlag) {
14242 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14249 if (blackTimeRemaining <= 0) {
14252 if (appData.icsActive) {
14253 if (appData.autoCallFlag &&
14254 gameMode == IcsPlayingWhite && !whiteFlag) {
14255 SendToICS(ics_prefix);
14256 SendToICS("flag\n");
14260 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14262 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14263 if (appData.autoCallFlag) {
14264 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14277 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14278 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14281 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14283 if ( !WhiteOnMove(forwardMostMove) ) {
14284 /* White made time control */
14285 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14286 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14287 /* [HGM] time odds: correct new time quota for time odds! */
14288 / WhitePlayer()->timeOdds;
14289 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14291 lastBlack -= blackTimeRemaining;
14292 /* Black made time control */
14293 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14294 / WhitePlayer()->other->timeOdds;
14295 lastWhite = whiteTimeRemaining;
14300 DisplayBothClocks()
14302 int wom = gameMode == EditPosition ?
14303 !blackPlaysFirst : WhiteOnMove(currentMove);
14304 DisplayWhiteClock(whiteTimeRemaining, wom);
14305 DisplayBlackClock(blackTimeRemaining, !wom);
14309 /* Timekeeping seems to be a portability nightmare. I think everyone
14310 has ftime(), but I'm really not sure, so I'm including some ifdefs
14311 to use other calls if you don't. Clocks will be less accurate if
14312 you have neither ftime nor gettimeofday.
14315 /* VS 2008 requires the #include outside of the function */
14316 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14317 #include <sys/timeb.h>
14320 /* Get the current time as a TimeMark */
14325 #if HAVE_GETTIMEOFDAY
14327 struct timeval timeVal;
14328 struct timezone timeZone;
14330 gettimeofday(&timeVal, &timeZone);
14331 tm->sec = (long) timeVal.tv_sec;
14332 tm->ms = (int) (timeVal.tv_usec / 1000L);
14334 #else /*!HAVE_GETTIMEOFDAY*/
14337 // include <sys/timeb.h> / moved to just above start of function
14338 struct timeb timeB;
14341 tm->sec = (long) timeB.time;
14342 tm->ms = (int) timeB.millitm;
14344 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14345 tm->sec = (long) time(NULL);
14351 /* Return the difference in milliseconds between two
14352 time marks. We assume the difference will fit in a long!
14355 SubtractTimeMarks(tm2, tm1)
14356 TimeMark *tm2, *tm1;
14358 return 1000L*(tm2->sec - tm1->sec) +
14359 (long) (tm2->ms - tm1->ms);
14364 * Code to manage the game clocks.
14366 * In tournament play, black starts the clock and then white makes a move.
14367 * We give the human user a slight advantage if he is playing white---the
14368 * clocks don't run until he makes his first move, so it takes zero time.
14369 * Also, we don't account for network lag, so we could get out of sync
14370 * with GNU Chess's clock -- but then, referees are always right.
14373 static TimeMark tickStartTM;
14374 static long intendedTickLength;
14377 NextTickLength(timeRemaining)
14378 long timeRemaining;
14380 long nominalTickLength, nextTickLength;
14382 if (timeRemaining > 0L && timeRemaining <= 10000L)
14383 nominalTickLength = 100L;
14385 nominalTickLength = 1000L;
14386 nextTickLength = timeRemaining % nominalTickLength;
14387 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14389 return nextTickLength;
14392 /* Adjust clock one minute up or down */
14394 AdjustClock(Boolean which, int dir)
14396 if(which) blackTimeRemaining += 60000*dir;
14397 else whiteTimeRemaining += 60000*dir;
14398 DisplayBothClocks();
14401 /* Stop clocks and reset to a fresh time control */
14405 (void) StopClockTimer();
14406 if (appData.icsActive) {
14407 whiteTimeRemaining = blackTimeRemaining = 0;
14408 } else if (searchTime) {
14409 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14410 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14411 } else { /* [HGM] correct new time quote for time odds */
14412 whiteTC = blackTC = fullTimeControlString;
14413 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14414 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14416 if (whiteFlag || blackFlag) {
14418 whiteFlag = blackFlag = FALSE;
14420 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14421 DisplayBothClocks();
14424 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14426 /* Decrement running clock by amount of time that has passed */
14430 long timeRemaining;
14431 long lastTickLength, fudge;
14434 if (!appData.clockMode) return;
14435 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14439 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14441 /* Fudge if we woke up a little too soon */
14442 fudge = intendedTickLength - lastTickLength;
14443 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14445 if (WhiteOnMove(forwardMostMove)) {
14446 if(whiteNPS >= 0) lastTickLength = 0;
14447 timeRemaining = whiteTimeRemaining -= lastTickLength;
14448 if(timeRemaining < 0 && !appData.icsActive) {
14449 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14450 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14451 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14452 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14455 DisplayWhiteClock(whiteTimeRemaining - fudge,
14456 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14458 if(blackNPS >= 0) lastTickLength = 0;
14459 timeRemaining = blackTimeRemaining -= lastTickLength;
14460 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14461 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14463 blackStartMove = forwardMostMove;
14464 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14467 DisplayBlackClock(blackTimeRemaining - fudge,
14468 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14470 if (CheckFlags()) return;
14473 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14474 StartClockTimer(intendedTickLength);
14476 /* if the time remaining has fallen below the alarm threshold, sound the
14477 * alarm. if the alarm has sounded and (due to a takeback or time control
14478 * with increment) the time remaining has increased to a level above the
14479 * threshold, reset the alarm so it can sound again.
14482 if (appData.icsActive && appData.icsAlarm) {
14484 /* make sure we are dealing with the user's clock */
14485 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14486 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14489 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14490 alarmSounded = FALSE;
14491 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14493 alarmSounded = TRUE;
14499 /* A player has just moved, so stop the previously running
14500 clock and (if in clock mode) start the other one.
14501 We redisplay both clocks in case we're in ICS mode, because
14502 ICS gives us an update to both clocks after every move.
14503 Note that this routine is called *after* forwardMostMove
14504 is updated, so the last fractional tick must be subtracted
14505 from the color that is *not* on move now.
14508 SwitchClocks(int newMoveNr)
14510 long lastTickLength;
14512 int flagged = FALSE;
14516 if (StopClockTimer() && appData.clockMode) {
14517 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14518 if (!WhiteOnMove(forwardMostMove)) {
14519 if(blackNPS >= 0) lastTickLength = 0;
14520 blackTimeRemaining -= lastTickLength;
14521 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14522 // if(pvInfoList[forwardMostMove-1].time == -1)
14523 pvInfoList[forwardMostMove-1].time = // use GUI time
14524 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14526 if(whiteNPS >= 0) lastTickLength = 0;
14527 whiteTimeRemaining -= lastTickLength;
14528 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14529 // if(pvInfoList[forwardMostMove-1].time == -1)
14530 pvInfoList[forwardMostMove-1].time =
14531 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14533 flagged = CheckFlags();
14535 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14536 CheckTimeControl();
14538 if (flagged || !appData.clockMode) return;
14540 switch (gameMode) {
14541 case MachinePlaysBlack:
14542 case MachinePlaysWhite:
14543 case BeginningOfGame:
14544 if (pausing) return;
14548 case PlayFromGameFile:
14556 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14557 if(WhiteOnMove(forwardMostMove))
14558 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14559 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14563 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14564 whiteTimeRemaining : blackTimeRemaining);
14565 StartClockTimer(intendedTickLength);
14569 /* Stop both clocks */
14573 long lastTickLength;
14576 if (!StopClockTimer()) return;
14577 if (!appData.clockMode) return;
14581 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14582 if (WhiteOnMove(forwardMostMove)) {
14583 if(whiteNPS >= 0) lastTickLength = 0;
14584 whiteTimeRemaining -= lastTickLength;
14585 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14587 if(blackNPS >= 0) lastTickLength = 0;
14588 blackTimeRemaining -= lastTickLength;
14589 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14594 /* Start clock of player on move. Time may have been reset, so
14595 if clock is already running, stop and restart it. */
14599 (void) StopClockTimer(); /* in case it was running already */
14600 DisplayBothClocks();
14601 if (CheckFlags()) return;
14603 if (!appData.clockMode) return;
14604 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14606 GetTimeMark(&tickStartTM);
14607 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14608 whiteTimeRemaining : blackTimeRemaining);
14610 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14611 whiteNPS = blackNPS = -1;
14612 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14613 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14614 whiteNPS = first.nps;
14615 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14616 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14617 blackNPS = first.nps;
14618 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14619 whiteNPS = second.nps;
14620 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14621 blackNPS = second.nps;
14622 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14624 StartClockTimer(intendedTickLength);
14631 long second, minute, hour, day;
14633 static char buf[32];
14635 if (ms > 0 && ms <= 9900) {
14636 /* convert milliseconds to tenths, rounding up */
14637 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14639 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14643 /* convert milliseconds to seconds, rounding up */
14644 /* use floating point to avoid strangeness of integer division
14645 with negative dividends on many machines */
14646 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14653 day = second / (60 * 60 * 24);
14654 second = second % (60 * 60 * 24);
14655 hour = second / (60 * 60);
14656 second = second % (60 * 60);
14657 minute = second / 60;
14658 second = second % 60;
14661 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14662 sign, day, hour, minute, second);
14664 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14666 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14673 * This is necessary because some C libraries aren't ANSI C compliant yet.
14676 StrStr(string, match)
14677 char *string, *match;
14681 length = strlen(match);
14683 for (i = strlen(string) - length; i >= 0; i--, string++)
14684 if (!strncmp(match, string, length))
14691 StrCaseStr(string, match)
14692 char *string, *match;
14696 length = strlen(match);
14698 for (i = strlen(string) - length; i >= 0; i--, string++) {
14699 for (j = 0; j < length; j++) {
14700 if (ToLower(match[j]) != ToLower(string[j]))
14703 if (j == length) return string;
14717 c1 = ToLower(*s1++);
14718 c2 = ToLower(*s2++);
14719 if (c1 > c2) return 1;
14720 if (c1 < c2) return -1;
14721 if (c1 == NULLCHAR) return 0;
14730 return isupper(c) ? tolower(c) : c;
14738 return islower(c) ? toupper(c) : c;
14740 #endif /* !_amigados */
14748 if ((ret = (char *) malloc(strlen(s) + 1)))
14750 safeStrCpy(ret, s, strlen(s)+1);
14756 StrSavePtr(s, savePtr)
14757 char *s, **savePtr;
14762 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14763 safeStrCpy(*savePtr, s, strlen(s)+1);
14775 clock = time((time_t *)NULL);
14776 tm = localtime(&clock);
14777 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14778 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14779 return StrSave(buf);
14784 PositionToFEN(move, overrideCastling)
14786 char *overrideCastling;
14788 int i, j, fromX, fromY, toX, toY;
14795 whiteToPlay = (gameMode == EditPosition) ?
14796 !blackPlaysFirst : (move % 2 == 0);
14799 /* Piece placement data */
14800 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14802 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14803 if (boards[move][i][j] == EmptySquare) {
14805 } else { ChessSquare piece = boards[move][i][j];
14806 if (emptycount > 0) {
14807 if(emptycount<10) /* [HGM] can be >= 10 */
14808 *p++ = '0' + emptycount;
14809 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14812 if(PieceToChar(piece) == '+') {
14813 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14815 piece = (ChessSquare)(DEMOTED piece);
14817 *p++ = PieceToChar(piece);
14819 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14820 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14825 if (emptycount > 0) {
14826 if(emptycount<10) /* [HGM] can be >= 10 */
14827 *p++ = '0' + emptycount;
14828 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14835 /* [HGM] print Crazyhouse or Shogi holdings */
14836 if( gameInfo.holdingsWidth ) {
14837 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14839 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14840 piece = boards[move][i][BOARD_WIDTH-1];
14841 if( piece != EmptySquare )
14842 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14843 *p++ = PieceToChar(piece);
14845 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14846 piece = boards[move][BOARD_HEIGHT-i-1][0];
14847 if( piece != EmptySquare )
14848 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14849 *p++ = PieceToChar(piece);
14852 if( q == p ) *p++ = '-';
14858 *p++ = whiteToPlay ? 'w' : 'b';
14861 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14862 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14864 if(nrCastlingRights) {
14866 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14867 /* [HGM] write directly from rights */
14868 if(boards[move][CASTLING][2] != NoRights &&
14869 boards[move][CASTLING][0] != NoRights )
14870 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14871 if(boards[move][CASTLING][2] != NoRights &&
14872 boards[move][CASTLING][1] != NoRights )
14873 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14874 if(boards[move][CASTLING][5] != NoRights &&
14875 boards[move][CASTLING][3] != NoRights )
14876 *p++ = boards[move][CASTLING][3] + AAA;
14877 if(boards[move][CASTLING][5] != NoRights &&
14878 boards[move][CASTLING][4] != NoRights )
14879 *p++ = boards[move][CASTLING][4] + AAA;
14882 /* [HGM] write true castling rights */
14883 if( nrCastlingRights == 6 ) {
14884 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14885 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14886 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14887 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14888 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14889 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14890 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14891 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14894 if (q == p) *p++ = '-'; /* No castling rights */
14898 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14899 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14900 /* En passant target square */
14901 if (move > backwardMostMove) {
14902 fromX = moveList[move - 1][0] - AAA;
14903 fromY = moveList[move - 1][1] - ONE;
14904 toX = moveList[move - 1][2] - AAA;
14905 toY = moveList[move - 1][3] - ONE;
14906 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14907 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14908 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14910 /* 2-square pawn move just happened */
14912 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14916 } else if(move == backwardMostMove) {
14917 // [HGM] perhaps we should always do it like this, and forget the above?
14918 if((signed char)boards[move][EP_STATUS] >= 0) {
14919 *p++ = boards[move][EP_STATUS] + AAA;
14920 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14931 /* [HGM] find reversible plies */
14932 { int i = 0, j=move;
14934 if (appData.debugMode) { int k;
14935 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14936 for(k=backwardMostMove; k<=forwardMostMove; k++)
14937 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14941 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14942 if( j == backwardMostMove ) i += initialRulePlies;
14943 sprintf(p, "%d ", i);
14944 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14946 /* Fullmove number */
14947 sprintf(p, "%d", (move / 2) + 1);
14949 return StrSave(buf);
14953 ParseFEN(board, blackPlaysFirst, fen)
14955 int *blackPlaysFirst;
14965 /* [HGM] by default clear Crazyhouse holdings, if present */
14966 if(gameInfo.holdingsWidth) {
14967 for(i=0; i<BOARD_HEIGHT; i++) {
14968 board[i][0] = EmptySquare; /* black holdings */
14969 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14970 board[i][1] = (ChessSquare) 0; /* black counts */
14971 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14975 /* Piece placement data */
14976 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14979 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14980 if (*p == '/') p++;
14981 emptycount = gameInfo.boardWidth - j;
14982 while (emptycount--)
14983 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14985 #if(BOARD_FILES >= 10)
14986 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14987 p++; emptycount=10;
14988 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14989 while (emptycount--)
14990 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14992 } else if (isdigit(*p)) {
14993 emptycount = *p++ - '0';
14994 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14995 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14996 while (emptycount--)
14997 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14998 } else if (*p == '+' || isalpha(*p)) {
14999 if (j >= gameInfo.boardWidth) return FALSE;
15001 piece = CharToPiece(*++p);
15002 if(piece == EmptySquare) return FALSE; /* unknown piece */
15003 piece = (ChessSquare) (PROMOTED piece ); p++;
15004 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15005 } else piece = CharToPiece(*p++);
15007 if(piece==EmptySquare) return FALSE; /* unknown piece */
15008 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15009 piece = (ChessSquare) (PROMOTED piece);
15010 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15013 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15019 while (*p == '/' || *p == ' ') p++;
15021 /* [HGM] look for Crazyhouse holdings here */
15022 while(*p==' ') p++;
15023 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15025 if(*p == '-' ) p++; /* empty holdings */ else {
15026 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15027 /* if we would allow FEN reading to set board size, we would */
15028 /* have to add holdings and shift the board read so far here */
15029 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15031 if((int) piece >= (int) BlackPawn ) {
15032 i = (int)piece - (int)BlackPawn;
15033 i = PieceToNumber((ChessSquare)i);
15034 if( i >= gameInfo.holdingsSize ) return FALSE;
15035 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15036 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15038 i = (int)piece - (int)WhitePawn;
15039 i = PieceToNumber((ChessSquare)i);
15040 if( i >= gameInfo.holdingsSize ) return FALSE;
15041 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15042 board[i][BOARD_WIDTH-2]++; /* black holdings */
15049 while(*p == ' ') p++;
15053 if(appData.colorNickNames) {
15054 if( c == appData.colorNickNames[0] ) c = 'w'; else
15055 if( c == appData.colorNickNames[1] ) c = 'b';
15059 *blackPlaysFirst = FALSE;
15062 *blackPlaysFirst = TRUE;
15068 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15069 /* return the extra info in global variiables */
15071 /* set defaults in case FEN is incomplete */
15072 board[EP_STATUS] = EP_UNKNOWN;
15073 for(i=0; i<nrCastlingRights; i++ ) {
15074 board[CASTLING][i] =
15075 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15076 } /* assume possible unless obviously impossible */
15077 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15078 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15079 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15080 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15081 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15082 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15083 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15084 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15087 while(*p==' ') p++;
15088 if(nrCastlingRights) {
15089 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15090 /* castling indicator present, so default becomes no castlings */
15091 for(i=0; i<nrCastlingRights; i++ ) {
15092 board[CASTLING][i] = NoRights;
15095 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15096 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15097 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15098 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15099 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15101 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15102 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15103 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15105 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15106 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15107 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15108 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15109 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15110 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15113 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15114 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15115 board[CASTLING][2] = whiteKingFile;
15118 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15119 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15120 board[CASTLING][2] = whiteKingFile;
15123 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15124 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15125 board[CASTLING][5] = blackKingFile;
15128 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15129 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15130 board[CASTLING][5] = blackKingFile;
15133 default: /* FRC castlings */
15134 if(c >= 'a') { /* black rights */
15135 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15136 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15137 if(i == BOARD_RGHT) break;
15138 board[CASTLING][5] = i;
15140 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15141 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15143 board[CASTLING][3] = c;
15145 board[CASTLING][4] = c;
15146 } else { /* white rights */
15147 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15148 if(board[0][i] == WhiteKing) break;
15149 if(i == BOARD_RGHT) break;
15150 board[CASTLING][2] = i;
15151 c -= AAA - 'a' + 'A';
15152 if(board[0][c] >= WhiteKing) break;
15154 board[CASTLING][0] = c;
15156 board[CASTLING][1] = c;
15160 for(i=0; i<nrCastlingRights; i++)
15161 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15162 if (appData.debugMode) {
15163 fprintf(debugFP, "FEN castling rights:");
15164 for(i=0; i<nrCastlingRights; i++)
15165 fprintf(debugFP, " %d", board[CASTLING][i]);
15166 fprintf(debugFP, "\n");
15169 while(*p==' ') p++;
15172 /* read e.p. field in games that know e.p. capture */
15173 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15174 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15176 p++; board[EP_STATUS] = EP_NONE;
15178 char c = *p++ - AAA;
15180 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15181 if(*p >= '0' && *p <='9') p++;
15182 board[EP_STATUS] = c;
15187 if(sscanf(p, "%d", &i) == 1) {
15188 FENrulePlies = i; /* 50-move ply counter */
15189 /* (The move number is still ignored) */
15196 EditPositionPasteFEN(char *fen)
15199 Board initial_position;
15201 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15202 DisplayError(_("Bad FEN position in clipboard"), 0);
15205 int savedBlackPlaysFirst = blackPlaysFirst;
15206 EditPositionEvent();
15207 blackPlaysFirst = savedBlackPlaysFirst;
15208 CopyBoard(boards[0], initial_position);
15209 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15210 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15211 DisplayBothClocks();
15212 DrawPosition(FALSE, boards[currentMove]);
15217 static char cseq[12] = "\\ ";
15219 Boolean set_cont_sequence(char *new_seq)
15224 // handle bad attempts to set the sequence
15226 return 0; // acceptable error - no debug
15228 len = strlen(new_seq);
15229 ret = (len > 0) && (len < sizeof(cseq));
15231 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15232 else if (appData.debugMode)
15233 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15238 reformat a source message so words don't cross the width boundary. internal
15239 newlines are not removed. returns the wrapped size (no null character unless
15240 included in source message). If dest is NULL, only calculate the size required
15241 for the dest buffer. lp argument indicats line position upon entry, and it's
15242 passed back upon exit.
15244 int wrap(char *dest, char *src, int count, int width, int *lp)
15246 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15248 cseq_len = strlen(cseq);
15249 old_line = line = *lp;
15250 ansi = len = clen = 0;
15252 for (i=0; i < count; i++)
15254 if (src[i] == '\033')
15257 // if we hit the width, back up
15258 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15260 // store i & len in case the word is too long
15261 old_i = i, old_len = len;
15263 // find the end of the last word
15264 while (i && src[i] != ' ' && src[i] != '\n')
15270 // word too long? restore i & len before splitting it
15271 if ((old_i-i+clen) >= width)
15278 if (i && src[i-1] == ' ')
15281 if (src[i] != ' ' && src[i] != '\n')
15288 // now append the newline and continuation sequence
15293 strncpy(dest+len, cseq, cseq_len);
15301 dest[len] = src[i];
15305 if (src[i] == '\n')
15310 if (dest && appData.debugMode)
15312 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15313 count, width, line, len, *lp);
15314 show_bytes(debugFP, src, count);
15315 fprintf(debugFP, "\ndest: ");
15316 show_bytes(debugFP, dest, len);
15317 fprintf(debugFP, "\n");
15319 *lp = dest ? line : old_line;
15324 // [HGM] vari: routines for shelving variations
15327 PushTail(int firstMove, int lastMove)
15329 int i, j, nrMoves = lastMove - firstMove;
15331 if(appData.icsActive) { // only in local mode
15332 forwardMostMove = currentMove; // mimic old ICS behavior
15335 if(storedGames >= MAX_VARIATIONS-1) return;
15337 // push current tail of game on stack
15338 savedResult[storedGames] = gameInfo.result;
15339 savedDetails[storedGames] = gameInfo.resultDetails;
15340 gameInfo.resultDetails = NULL;
15341 savedFirst[storedGames] = firstMove;
15342 savedLast [storedGames] = lastMove;
15343 savedFramePtr[storedGames] = framePtr;
15344 framePtr -= nrMoves; // reserve space for the boards
15345 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15346 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15347 for(j=0; j<MOVE_LEN; j++)
15348 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15349 for(j=0; j<2*MOVE_LEN; j++)
15350 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15351 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15352 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15353 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15354 pvInfoList[firstMove+i-1].depth = 0;
15355 commentList[framePtr+i] = commentList[firstMove+i];
15356 commentList[firstMove+i] = NULL;
15360 forwardMostMove = firstMove; // truncate game so we can start variation
15361 if(storedGames == 1) GreyRevert(FALSE);
15365 PopTail(Boolean annotate)
15368 char buf[8000], moveBuf[20];
15370 if(appData.icsActive) return FALSE; // only in local mode
15371 if(!storedGames) return FALSE; // sanity
15372 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15375 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15376 nrMoves = savedLast[storedGames] - currentMove;
15379 if(!WhiteOnMove(currentMove))
15380 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15381 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15382 for(i=currentMove; i<forwardMostMove; i++) {
15384 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15385 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15386 strcat(buf, moveBuf);
15387 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15388 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15392 for(i=1; i<=nrMoves; i++) { // copy last variation back
15393 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15394 for(j=0; j<MOVE_LEN; j++)
15395 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15396 for(j=0; j<2*MOVE_LEN; j++)
15397 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15398 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15399 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15400 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15401 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15402 commentList[currentMove+i] = commentList[framePtr+i];
15403 commentList[framePtr+i] = NULL;
15405 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15406 framePtr = savedFramePtr[storedGames];
15407 gameInfo.result = savedResult[storedGames];
15408 if(gameInfo.resultDetails != NULL) {
15409 free(gameInfo.resultDetails);
15411 gameInfo.resultDetails = savedDetails[storedGames];
15412 forwardMostMove = currentMove + nrMoves;
15413 if(storedGames == 0) GreyRevert(TRUE);
15419 { // remove all shelved variations
15421 for(i=0; i<storedGames; i++) {
15422 if(savedDetails[i])
15423 free(savedDetails[i]);
15424 savedDetails[i] = NULL;
15426 for(i=framePtr; i<MAX_MOVES; i++) {
15427 if(commentList[i]) free(commentList[i]);
15428 commentList[i] = NULL;
15430 framePtr = MAX_MOVES-1;
15435 LoadVariation(int index, char *text)
15436 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15437 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15438 int level = 0, move;
15440 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15441 // first find outermost bracketing variation
15442 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15443 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15444 if(*p == '{') wait = '}'; else
15445 if(*p == '[') wait = ']'; else
15446 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15447 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15449 if(*p == wait) wait = NULLCHAR; // closing ]} found
15452 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15453 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15454 end[1] = NULLCHAR; // clip off comment beyond variation
15455 ToNrEvent(currentMove-1);
15456 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15457 // kludge: use ParsePV() to append variation to game
15458 move = currentMove;
15459 ParsePV(start, TRUE);
15460 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15461 ClearPremoveHighlights();
15463 ToNrEvent(currentMove+1);