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 )
315 /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html
317 * usage: safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]);
321 assert( dst != NULL );
322 assert( src != NULL );
325 strncpy( dst, src, count );
326 if( dst[ count-1 ] != '\0' )
328 if(appData.debugMode)
329 printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
331 dst[ count-1 ] = '\0';
336 /* Some compiler can't cast u64 to double
337 * This function do the job for us:
339 * We use the highest bit for cast, this only
340 * works if the highest bit is not
341 * in use (This should not happen)
343 * We used this for all compiler
346 u64ToDouble(u64 value)
349 u64 tmp = value & u64Const(0x7fffffffffffffff);
350 r = (double)(s64)tmp;
351 if (value & u64Const(0x8000000000000000))
352 r += 9.2233720368547758080e18; /* 2^63 */
356 /* Fake up flags for now, as we aren't keeping track of castling
357 availability yet. [HGM] Change of logic: the flag now only
358 indicates the type of castlings allowed by the rule of the game.
359 The actual rights themselves are maintained in the array
360 castlingRights, as part of the game history, and are not probed
366 int flags = F_ALL_CASTLE_OK;
367 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
368 switch (gameInfo.variant) {
370 flags &= ~F_ALL_CASTLE_OK;
371 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
372 flags |= F_IGNORE_CHECK;
374 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
377 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
379 case VariantKriegspiel:
380 flags |= F_KRIEGSPIEL_CAPTURE;
382 case VariantCapaRandom:
383 case VariantFischeRandom:
384 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
385 case VariantNoCastle:
386 case VariantShatranj:
389 flags &= ~F_ALL_CASTLE_OK;
397 FILE *gameFileFP, *debugFP;
400 [AS] Note: sometimes, the sscanf() function is used to parse the input
401 into a fixed-size buffer. Because of this, we must be prepared to
402 receive strings as long as the size of the input buffer, which is currently
403 set to 4K for Windows and 8K for the rest.
404 So, we must either allocate sufficiently large buffers here, or
405 reduce the size of the input buffer in the input reading part.
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
412 ChessProgramState first, second;
414 /* premove variables */
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int have_sent_ICS_logon = 0;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
465 /* animateTraining preserves the state of appData.animate
466 * when Training mode is activated. This allows the
467 * response to be animated when appData.animate == TRUE and
468 * appData.animateDragging == TRUE.
470 Boolean animateTraining;
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char initialRights[BOARD_FILES];
480 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int initialRulePlies, FENrulePlies;
482 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int mute; // mute all sounds
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
501 ChessSquare FIDEArray[2][BOARD_FILES] = {
502 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505 BlackKing, BlackBishop, BlackKnight, BlackRook }
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512 BlackKing, BlackKing, BlackKnight, BlackRook }
515 ChessSquare KnightmateArray[2][BOARD_FILES] = {
516 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518 { BlackRook, BlackMan, BlackBishop, BlackQueen,
519 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
522 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
523 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
526 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
529 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
530 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
531 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
532 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
533 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
536 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
538 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
539 { BlackRook, BlackKnight, BlackMan, BlackFerz,
540 BlackKing, BlackMan, BlackKnight, BlackRook }
544 #if (BOARD_FILES>=10)
545 ChessSquare ShogiArray[2][BOARD_FILES] = {
546 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
547 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
548 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
549 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
552 ChessSquare XiangqiArray[2][BOARD_FILES] = {
553 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
554 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
555 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
556 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
559 ChessSquare CapablancaArray[2][BOARD_FILES] = {
560 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
561 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
562 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
563 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
566 ChessSquare GreatArray[2][BOARD_FILES] = {
567 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
568 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
569 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
570 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
573 ChessSquare JanusArray[2][BOARD_FILES] = {
574 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
575 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
576 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
577 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
581 ChessSquare GothicArray[2][BOARD_FILES] = {
582 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
583 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
584 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
585 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
588 #define GothicArray CapablancaArray
592 ChessSquare FalconArray[2][BOARD_FILES] = {
593 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
594 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
595 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
596 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
599 #define FalconArray CapablancaArray
602 #else // !(BOARD_FILES>=10)
603 #define XiangqiPosition FIDEArray
604 #define CapablancaArray FIDEArray
605 #define GothicArray FIDEArray
606 #define GreatArray FIDEArray
607 #endif // !(BOARD_FILES>=10)
609 #if (BOARD_FILES>=12)
610 ChessSquare CourierArray[2][BOARD_FILES] = {
611 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
612 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
613 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
614 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
616 #else // !(BOARD_FILES>=12)
617 #define CourierArray CapablancaArray
618 #endif // !(BOARD_FILES>=12)
621 Board initialPosition;
624 /* Convert str to a rating. Checks for special cases of "----",
626 "++++", etc. Also strips ()'s */
628 string_to_rating(str)
631 while(*str && !isdigit(*str)) ++str;
633 return 0; /* One of the special "no rating" cases */
641 /* Init programStats */
642 programStats.movelist[0] = 0;
643 programStats.depth = 0;
644 programStats.nr_moves = 0;
645 programStats.moves_left = 0;
646 programStats.nodes = 0;
647 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
648 programStats.score = 0;
649 programStats.got_only_move = 0;
650 programStats.got_fail = 0;
651 programStats.line_is_book = 0;
657 int matched, min, sec;
659 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
660 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
662 GetTimeMark(&programStartTime);
663 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
666 programStats.ok_to_send = 1;
667 programStats.seen_stat = 0;
670 * Initialize game list
676 * Internet chess server status
678 if (appData.icsActive) {
679 appData.matchMode = FALSE;
680 appData.matchGames = 0;
682 appData.noChessProgram = !appData.zippyPlay;
684 appData.zippyPlay = FALSE;
685 appData.zippyTalk = FALSE;
686 appData.noChessProgram = TRUE;
688 if (*appData.icsHelper != NULLCHAR) {
689 appData.useTelnet = TRUE;
690 appData.telnetProgram = appData.icsHelper;
693 appData.zippyTalk = appData.zippyPlay = FALSE;
696 /* [AS] Initialize pv info list [HGM] and game state */
700 for( i=0; i<=framePtr; i++ ) {
701 pvInfoList[i].depth = -1;
702 boards[i][EP_STATUS] = EP_NONE;
703 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
708 * Parse timeControl resource
710 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
711 appData.movesPerSession)) {
713 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
714 DisplayFatalError(buf, 0, 2);
718 * Parse searchTime resource
720 if (*appData.searchTime != NULLCHAR) {
721 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
723 searchTime = min * 60;
724 } else if (matched == 2) {
725 searchTime = min * 60 + sec;
728 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
729 DisplayFatalError(buf, 0, 2);
733 /* [AS] Adjudication threshold */
734 adjudicateLossThreshold = appData.adjudicateLossThreshold;
736 first.which = _("first");
737 second.which = _("second");
738 first.maybeThinking = second.maybeThinking = FALSE;
739 first.pr = second.pr = NoProc;
740 first.isr = second.isr = NULL;
741 first.sendTime = second.sendTime = 2;
742 first.sendDrawOffers = 1;
743 if (appData.firstPlaysBlack) {
744 first.twoMachinesColor = "black\n";
745 second.twoMachinesColor = "white\n";
747 first.twoMachinesColor = "white\n";
748 second.twoMachinesColor = "black\n";
750 first.program = appData.firstChessProgram;
751 second.program = appData.secondChessProgram;
752 first.host = appData.firstHost;
753 second.host = appData.secondHost;
754 first.dir = appData.firstDirectory;
755 second.dir = appData.secondDirectory;
756 first.other = &second;
757 second.other = &first;
758 first.initString = appData.initString;
759 second.initString = appData.secondInitString;
760 first.computerString = appData.firstComputerString;
761 second.computerString = appData.secondComputerString;
762 first.useSigint = second.useSigint = TRUE;
763 first.useSigterm = second.useSigterm = TRUE;
764 first.reuse = appData.reuseFirst;
765 second.reuse = appData.reuseSecond;
766 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
767 second.nps = appData.secondNPS;
768 first.useSetboard = second.useSetboard = FALSE;
769 first.useSAN = second.useSAN = FALSE;
770 first.usePing = second.usePing = FALSE;
771 first.lastPing = second.lastPing = 0;
772 first.lastPong = second.lastPong = 0;
773 first.usePlayother = second.usePlayother = FALSE;
774 first.useColors = second.useColors = TRUE;
775 first.useUsermove = second.useUsermove = FALSE;
776 first.sendICS = second.sendICS = FALSE;
777 first.sendName = second.sendName = appData.icsActive;
778 first.sdKludge = second.sdKludge = FALSE;
779 first.stKludge = second.stKludge = FALSE;
780 TidyProgramName(first.program, first.host, first.tidy);
781 TidyProgramName(second.program, second.host, second.tidy);
782 first.matchWins = second.matchWins = 0;
783 safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
784 safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
785 first.analysisSupport = second.analysisSupport = 2; /* detect */
786 first.analyzing = second.analyzing = FALSE;
787 first.initDone = second.initDone = FALSE;
789 /* New features added by Tord: */
790 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
791 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
792 /* End of new features added by Tord. */
793 first.fenOverride = appData.fenOverride1;
794 second.fenOverride = appData.fenOverride2;
796 /* [HGM] time odds: set factor for each machine */
797 first.timeOdds = appData.firstTimeOdds;
798 second.timeOdds = appData.secondTimeOdds;
800 if(appData.timeOddsMode) {
801 norm = first.timeOdds;
802 if(norm > second.timeOdds) norm = second.timeOdds;
804 first.timeOdds /= norm;
805 second.timeOdds /= norm;
808 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
809 first.accumulateTC = appData.firstAccumulateTC;
810 second.accumulateTC = appData.secondAccumulateTC;
811 first.maxNrOfSessions = second.maxNrOfSessions = 1;
814 first.debug = second.debug = FALSE;
815 first.supportsNPS = second.supportsNPS = UNKNOWN;
818 first.optionSettings = appData.firstOptions;
819 second.optionSettings = appData.secondOptions;
821 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
822 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
823 first.isUCI = appData.firstIsUCI; /* [AS] */
824 second.isUCI = appData.secondIsUCI; /* [AS] */
825 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
826 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
828 if (appData.firstProtocolVersion > PROTOVER ||
829 appData.firstProtocolVersion < 1) {
831 sprintf(buf, _("protocol version %d not supported"),
832 appData.firstProtocolVersion);
833 DisplayFatalError(buf, 0, 2);
835 first.protocolVersion = appData.firstProtocolVersion;
838 if (appData.secondProtocolVersion > PROTOVER ||
839 appData.secondProtocolVersion < 1) {
841 sprintf(buf, _("protocol version %d not supported"),
842 appData.secondProtocolVersion);
843 DisplayFatalError(buf, 0, 2);
845 second.protocolVersion = appData.secondProtocolVersion;
848 if (appData.icsActive) {
849 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
850 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
851 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
852 appData.clockMode = FALSE;
853 first.sendTime = second.sendTime = 0;
857 /* Override some settings from environment variables, for backward
858 compatibility. Unfortunately it's not feasible to have the env
859 vars just set defaults, at least in xboard. Ugh.
861 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
866 if (appData.noChessProgram) {
867 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
868 sprintf(programVersion, "%s", PACKAGE_STRING);
870 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
871 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
872 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
875 if (!appData.icsActive) {
877 /* Check for variants that are supported only in ICS mode,
878 or not at all. Some that are accepted here nevertheless
879 have bugs; see comments below.
881 VariantClass variant = StringToVariant(appData.variant);
883 case VariantBughouse: /* need four players and two boards */
884 case VariantKriegspiel: /* need to hide pieces and move details */
885 /* case VariantFischeRandom: (Fabien: moved below) */
886 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
887 DisplayFatalError(buf, 0, 2);
891 case VariantLoadable:
901 sprintf(buf, _("Unknown variant name %s"), appData.variant);
902 DisplayFatalError(buf, 0, 2);
905 case VariantXiangqi: /* [HGM] repetition rules not implemented */
906 case VariantFairy: /* [HGM] TestLegality definitely off! */
907 case VariantGothic: /* [HGM] should work */
908 case VariantCapablanca: /* [HGM] should work */
909 case VariantCourier: /* [HGM] initial forced moves not implemented */
910 case VariantShogi: /* [HGM] could still mate with pawn drop */
911 case VariantKnightmate: /* [HGM] should work */
912 case VariantCylinder: /* [HGM] untested */
913 case VariantFalcon: /* [HGM] untested */
914 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
915 offboard interposition not understood */
916 case VariantNormal: /* definitely works! */
917 case VariantWildCastle: /* pieces not automatically shuffled */
918 case VariantNoCastle: /* pieces not automatically shuffled */
919 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
920 case VariantLosers: /* should work except for win condition,
921 and doesn't know captures are mandatory */
922 case VariantSuicide: /* should work except for win condition,
923 and doesn't know captures are mandatory */
924 case VariantGiveaway: /* should work except for win condition,
925 and doesn't know captures are mandatory */
926 case VariantTwoKings: /* should work */
927 case VariantAtomic: /* should work except for win condition */
928 case Variant3Check: /* should work except for win condition */
929 case VariantShatranj: /* should work except for all win conditions */
930 case VariantMakruk: /* should work except for daw countdown */
931 case VariantBerolina: /* might work if TestLegality is off */
932 case VariantCapaRandom: /* should work */
933 case VariantJanus: /* should work */
934 case VariantSuper: /* experimental */
935 case VariantGreat: /* experimental, requires legality testing to be off */
940 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
941 InitEngineUCI( installDir, &second );
944 int NextIntegerFromString( char ** str, long * value )
949 while( *s == ' ' || *s == '\t' ) {
955 if( *s >= '0' && *s <= '9' ) {
956 while( *s >= '0' && *s <= '9' ) {
957 *value = *value * 10 + (*s - '0');
969 int NextTimeControlFromString( char ** str, long * value )
972 int result = NextIntegerFromString( str, &temp );
975 *value = temp * 60; /* Minutes */
978 result = NextIntegerFromString( str, &temp );
979 *value += temp; /* Seconds */
986 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
987 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
988 int result = -1, type = 0; long temp, temp2;
990 if(**str != ':') return -1; // old params remain in force!
992 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
993 if( NextIntegerFromString( str, &temp ) ) return -1;
994 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
997 /* time only: incremental or sudden-death time control */
998 if(**str == '+') { /* increment follows; read it */
1000 if(**str == '!') type = *(*str)++; // Bronstein TC
1001 if(result = NextIntegerFromString( str, &temp2)) return -1;
1002 *inc = temp2 * 1000;
1004 *moves = 0; *tc = temp * 1000; *incType = type;
1008 (*str)++; /* classical time control */
1009 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1020 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1021 { /* [HGM] get time to add from the multi-session time-control string */
1022 int incType, moves=1; /* kludge to force reading of first session */
1023 long time, increment;
1026 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1027 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1029 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1030 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1031 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1032 if(movenr == -1) return time; /* last move before new session */
1033 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1034 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1035 if(!moves) return increment; /* current session is incremental */
1036 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1037 } while(movenr >= -1); /* try again for next session */
1039 return 0; // no new time quota on this move
1043 ParseTimeControl(tc, ti, mps)
1050 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1053 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1054 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1055 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1058 sprintf(buf, ":%d/%s+%d", mps, mytc, ti);
1059 else sprintf(buf, ":%s+%d", mytc, ti);
1062 sprintf(buf, ":%d/%s", mps, mytc);
1063 else sprintf(buf, ":%s", mytc);
1065 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1067 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1072 /* Parse second time control */
1075 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1083 timeControl_2 = tc2 * 1000;
1093 timeControl = tc1 * 1000;
1096 timeIncrement = ti * 1000; /* convert to ms */
1097 movesPerSession = 0;
1100 movesPerSession = mps;
1108 if (appData.debugMode) {
1109 fprintf(debugFP, "%s\n", programVersion);
1112 set_cont_sequence(appData.wrapContSeq);
1113 if (appData.matchGames > 0) {
1114 appData.matchMode = TRUE;
1115 } else if (appData.matchMode) {
1116 appData.matchGames = 1;
1118 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1119 appData.matchGames = appData.sameColorGames;
1120 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1121 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1122 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1125 if (appData.noChessProgram || first.protocolVersion == 1) {
1128 /* kludge: allow timeout for initial "feature" commands */
1130 DisplayMessage("", _("Starting chess program"));
1131 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1136 InitBackEnd3 P((void))
1138 GameMode initialMode;
1142 InitChessProgram(&first, startedFromSetupPosition);
1144 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1145 free(programVersion);
1146 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1147 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1150 if (appData.icsActive) {
1152 /* [DM] Make a console window if needed [HGM] merged ifs */
1157 if (*appData.icsCommPort != NULLCHAR) {
1158 sprintf(buf, _("Could not open comm port %s"),
1159 appData.icsCommPort);
1161 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1162 appData.icsHost, appData.icsPort);
1164 DisplayFatalError(buf, err, 1);
1169 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1171 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1172 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1173 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1174 } else if (appData.noChessProgram) {
1180 if (*appData.cmailGameName != NULLCHAR) {
1182 OpenLoopback(&cmailPR);
1184 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1188 DisplayMessage("", "");
1189 if (StrCaseCmp(appData.initialMode, "") == 0) {
1190 initialMode = BeginningOfGame;
1191 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1192 initialMode = TwoMachinesPlay;
1193 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1194 initialMode = AnalyzeFile;
1195 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1196 initialMode = AnalyzeMode;
1197 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1198 initialMode = MachinePlaysWhite;
1199 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1200 initialMode = MachinePlaysBlack;
1201 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1202 initialMode = EditGame;
1203 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1204 initialMode = EditPosition;
1205 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1206 initialMode = Training;
1208 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1209 DisplayFatalError(buf, 0, 2);
1213 if (appData.matchMode) {
1214 /* Set up machine vs. machine match */
1215 if (appData.noChessProgram) {
1216 DisplayFatalError(_("Can't have a match with no chess programs"),
1222 if (*appData.loadGameFile != NULLCHAR) {
1223 int index = appData.loadGameIndex; // [HGM] autoinc
1224 if(index<0) lastIndex = index = 1;
1225 if (!LoadGameFromFile(appData.loadGameFile,
1227 appData.loadGameFile, FALSE)) {
1228 DisplayFatalError(_("Bad game file"), 0, 1);
1231 } else if (*appData.loadPositionFile != NULLCHAR) {
1232 int index = appData.loadPositionIndex; // [HGM] autoinc
1233 if(index<0) lastIndex = index = 1;
1234 if (!LoadPositionFromFile(appData.loadPositionFile,
1236 appData.loadPositionFile)) {
1237 DisplayFatalError(_("Bad position file"), 0, 1);
1242 } else if (*appData.cmailGameName != NULLCHAR) {
1243 /* Set up cmail mode */
1244 ReloadCmailMsgEvent(TRUE);
1246 /* Set up other modes */
1247 if (initialMode == AnalyzeFile) {
1248 if (*appData.loadGameFile == NULLCHAR) {
1249 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1253 if (*appData.loadGameFile != NULLCHAR) {
1254 (void) LoadGameFromFile(appData.loadGameFile,
1255 appData.loadGameIndex,
1256 appData.loadGameFile, TRUE);
1257 } else if (*appData.loadPositionFile != NULLCHAR) {
1258 (void) LoadPositionFromFile(appData.loadPositionFile,
1259 appData.loadPositionIndex,
1260 appData.loadPositionFile);
1261 /* [HGM] try to make self-starting even after FEN load */
1262 /* to allow automatic setup of fairy variants with wtm */
1263 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1264 gameMode = BeginningOfGame;
1265 setboardSpoiledMachineBlack = 1;
1267 /* [HGM] loadPos: make that every new game uses the setup */
1268 /* from file as long as we do not switch variant */
1269 if(!blackPlaysFirst) {
1270 startedFromPositionFile = TRUE;
1271 CopyBoard(filePosition, boards[0]);
1274 if (initialMode == AnalyzeMode) {
1275 if (appData.noChessProgram) {
1276 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1279 if (appData.icsActive) {
1280 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1284 } else if (initialMode == AnalyzeFile) {
1285 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1286 ShowThinkingEvent();
1288 AnalysisPeriodicEvent(1);
1289 } else if (initialMode == MachinePlaysWhite) {
1290 if (appData.noChessProgram) {
1291 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1295 if (appData.icsActive) {
1296 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1300 MachineWhiteEvent();
1301 } else if (initialMode == MachinePlaysBlack) {
1302 if (appData.noChessProgram) {
1303 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1307 if (appData.icsActive) {
1308 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1312 MachineBlackEvent();
1313 } else if (initialMode == TwoMachinesPlay) {
1314 if (appData.noChessProgram) {
1315 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1319 if (appData.icsActive) {
1320 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1325 } else if (initialMode == EditGame) {
1327 } else if (initialMode == EditPosition) {
1328 EditPositionEvent();
1329 } else if (initialMode == Training) {
1330 if (*appData.loadGameFile == NULLCHAR) {
1331 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1340 * Establish will establish a contact to a remote host.port.
1341 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1342 * used to talk to the host.
1343 * Returns 0 if okay, error code if not.
1350 if (*appData.icsCommPort != NULLCHAR) {
1351 /* Talk to the host through a serial comm port */
1352 return OpenCommPort(appData.icsCommPort, &icsPR);
1354 } else if (*appData.gateway != NULLCHAR) {
1355 if (*appData.remoteShell == NULLCHAR) {
1356 /* Use the rcmd protocol to run telnet program on a gateway host */
1357 snprintf(buf, sizeof(buf), "%s %s %s",
1358 appData.telnetProgram, appData.icsHost, appData.icsPort);
1359 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1362 /* Use the rsh program to run telnet program on a gateway host */
1363 if (*appData.remoteUser == NULLCHAR) {
1364 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1365 appData.gateway, appData.telnetProgram,
1366 appData.icsHost, appData.icsPort);
1368 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1369 appData.remoteShell, appData.gateway,
1370 appData.remoteUser, appData.telnetProgram,
1371 appData.icsHost, appData.icsPort);
1373 return StartChildProcess(buf, "", &icsPR);
1376 } else if (appData.useTelnet) {
1377 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1380 /* TCP socket interface differs somewhat between
1381 Unix and NT; handle details in the front end.
1383 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1387 void EscapeExpand(char *p, char *q)
1388 { // [HGM] initstring: routine to shape up string arguments
1389 while(*p++ = *q++) if(p[-1] == '\\')
1391 case 'n': p[-1] = '\n'; break;
1392 case 'r': p[-1] = '\r'; break;
1393 case 't': p[-1] = '\t'; break;
1394 case '\\': p[-1] = '\\'; break;
1395 case 0: *p = 0; return;
1396 default: p[-1] = q[-1]; break;
1401 show_bytes(fp, buf, count)
1407 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1408 fprintf(fp, "\\%03o", *buf & 0xff);
1417 /* Returns an errno value */
1419 OutputMaybeTelnet(pr, message, count, outError)
1425 char buf[8192], *p, *q, *buflim;
1426 int left, newcount, outcount;
1428 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1429 *appData.gateway != NULLCHAR) {
1430 if (appData.debugMode) {
1431 fprintf(debugFP, ">ICS: ");
1432 show_bytes(debugFP, message, count);
1433 fprintf(debugFP, "\n");
1435 return OutputToProcess(pr, message, count, outError);
1438 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1445 if (appData.debugMode) {
1446 fprintf(debugFP, ">ICS: ");
1447 show_bytes(debugFP, buf, newcount);
1448 fprintf(debugFP, "\n");
1450 outcount = OutputToProcess(pr, buf, newcount, outError);
1451 if (outcount < newcount) return -1; /* to be sure */
1458 } else if (((unsigned char) *p) == TN_IAC) {
1459 *q++ = (char) TN_IAC;
1466 if (appData.debugMode) {
1467 fprintf(debugFP, ">ICS: ");
1468 show_bytes(debugFP, buf, newcount);
1469 fprintf(debugFP, "\n");
1471 outcount = OutputToProcess(pr, buf, newcount, outError);
1472 if (outcount < newcount) return -1; /* to be sure */
1477 read_from_player(isr, closure, message, count, error)
1484 int outError, outCount;
1485 static int gotEof = 0;
1487 /* Pass data read from player on to ICS */
1490 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1491 if (outCount < count) {
1492 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1494 } else if (count < 0) {
1495 RemoveInputSource(isr);
1496 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1497 } else if (gotEof++ > 0) {
1498 RemoveInputSource(isr);
1499 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1505 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1506 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1507 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1508 SendToICS("date\n");
1509 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1512 /* added routine for printf style output to ics */
1513 void ics_printf(char *format, ...)
1515 char buffer[MSG_SIZ];
1518 va_start(args, format);
1519 vsnprintf(buffer, sizeof(buffer), format, args);
1520 buffer[sizeof(buffer)-1] = '\0';
1529 int count, outCount, outError;
1531 if (icsPR == NULL) return;
1534 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1535 if (outCount < count) {
1536 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1540 /* This is used for sending logon scripts to the ICS. Sending
1541 without a delay causes problems when using timestamp on ICC
1542 (at least on my machine). */
1544 SendToICSDelayed(s,msdelay)
1548 int count, outCount, outError;
1550 if (icsPR == NULL) return;
1553 if (appData.debugMode) {
1554 fprintf(debugFP, ">ICS: ");
1555 show_bytes(debugFP, s, count);
1556 fprintf(debugFP, "\n");
1558 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1560 if (outCount < count) {
1561 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1566 /* Remove all highlighting escape sequences in s
1567 Also deletes any suffix starting with '('
1570 StripHighlightAndTitle(s)
1573 static char retbuf[MSG_SIZ];
1576 while (*s != NULLCHAR) {
1577 while (*s == '\033') {
1578 while (*s != NULLCHAR && !isalpha(*s)) s++;
1579 if (*s != NULLCHAR) s++;
1581 while (*s != NULLCHAR && *s != '\033') {
1582 if (*s == '(' || *s == '[') {
1593 /* Remove all highlighting escape sequences in s */
1598 static char retbuf[MSG_SIZ];
1601 while (*s != NULLCHAR) {
1602 while (*s == '\033') {
1603 while (*s != NULLCHAR && !isalpha(*s)) s++;
1604 if (*s != NULLCHAR) s++;
1606 while (*s != NULLCHAR && *s != '\033') {
1614 char *variantNames[] = VARIANT_NAMES;
1619 return variantNames[v];
1623 /* Identify a variant from the strings the chess servers use or the
1624 PGN Variant tag names we use. */
1631 VariantClass v = VariantNormal;
1632 int i, found = FALSE;
1637 /* [HGM] skip over optional board-size prefixes */
1638 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1639 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1640 while( *e++ != '_');
1643 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1647 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1648 if (StrCaseStr(e, variantNames[i])) {
1649 v = (VariantClass) i;
1656 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1657 || StrCaseStr(e, "wild/fr")
1658 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1659 v = VariantFischeRandom;
1660 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1661 (i = 1, p = StrCaseStr(e, "w"))) {
1663 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1670 case 0: /* FICS only, actually */
1672 /* Castling legal even if K starts on d-file */
1673 v = VariantWildCastle;
1678 /* Castling illegal even if K & R happen to start in
1679 normal positions. */
1680 v = VariantNoCastle;
1693 /* Castling legal iff K & R start in normal positions */
1699 /* Special wilds for position setup; unclear what to do here */
1700 v = VariantLoadable;
1703 /* Bizarre ICC game */
1704 v = VariantTwoKings;
1707 v = VariantKriegspiel;
1713 v = VariantFischeRandom;
1716 v = VariantCrazyhouse;
1719 v = VariantBughouse;
1725 /* Not quite the same as FICS suicide! */
1726 v = VariantGiveaway;
1732 v = VariantShatranj;
1735 /* Temporary names for future ICC types. The name *will* change in
1736 the next xboard/WinBoard release after ICC defines it. */
1774 v = VariantCapablanca;
1777 v = VariantKnightmate;
1783 v = VariantCylinder;
1789 v = VariantCapaRandom;
1792 v = VariantBerolina;
1804 /* Found "wild" or "w" in the string but no number;
1805 must assume it's normal chess. */
1809 sprintf(buf, _("Unknown wild type %d"), wnum);
1810 DisplayError(buf, 0);
1816 if (appData.debugMode) {
1817 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1818 e, wnum, VariantName(v));
1823 static int leftover_start = 0, leftover_len = 0;
1824 char star_match[STAR_MATCH_N][MSG_SIZ];
1826 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1827 advance *index beyond it, and set leftover_start to the new value of
1828 *index; else return FALSE. If pattern contains the character '*', it
1829 matches any sequence of characters not containing '\r', '\n', or the
1830 character following the '*' (if any), and the matched sequence(s) are
1831 copied into star_match.
1834 looking_at(buf, index, pattern)
1839 char *bufp = &buf[*index], *patternp = pattern;
1841 char *matchp = star_match[0];
1844 if (*patternp == NULLCHAR) {
1845 *index = leftover_start = bufp - buf;
1849 if (*bufp == NULLCHAR) return FALSE;
1850 if (*patternp == '*') {
1851 if (*bufp == *(patternp + 1)) {
1853 matchp = star_match[++star_count];
1857 } else if (*bufp == '\n' || *bufp == '\r') {
1859 if (*patternp == NULLCHAR)
1864 *matchp++ = *bufp++;
1868 if (*patternp != *bufp) return FALSE;
1875 SendToPlayer(data, length)
1879 int error, outCount;
1880 outCount = OutputToProcess(NoProc, data, length, &error);
1881 if (outCount < length) {
1882 DisplayFatalError(_("Error writing to display"), error, 1);
1887 PackHolding(packed, holding)
1899 switch (runlength) {
1910 sprintf(q, "%d", runlength);
1922 /* Telnet protocol requests from the front end */
1924 TelnetRequest(ddww, option)
1925 unsigned char ddww, option;
1927 unsigned char msg[3];
1928 int outCount, outError;
1930 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1932 if (appData.debugMode) {
1933 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1949 sprintf(buf1, "%d", ddww);
1958 sprintf(buf2, "%d", option);
1961 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1966 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1968 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1975 if (!appData.icsActive) return;
1976 TelnetRequest(TN_DO, TN_ECHO);
1982 if (!appData.icsActive) return;
1983 TelnetRequest(TN_DONT, TN_ECHO);
1987 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1989 /* put the holdings sent to us by the server on the board holdings area */
1990 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1994 if(gameInfo.holdingsWidth < 2) return;
1995 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1996 return; // prevent overwriting by pre-board holdings
1998 if( (int)lowestPiece >= BlackPawn ) {
2001 holdingsStartRow = BOARD_HEIGHT-1;
2004 holdingsColumn = BOARD_WIDTH-1;
2005 countsColumn = BOARD_WIDTH-2;
2006 holdingsStartRow = 0;
2010 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2011 board[i][holdingsColumn] = EmptySquare;
2012 board[i][countsColumn] = (ChessSquare) 0;
2014 while( (p=*holdings++) != NULLCHAR ) {
2015 piece = CharToPiece( ToUpper(p) );
2016 if(piece == EmptySquare) continue;
2017 /*j = (int) piece - (int) WhitePawn;*/
2018 j = PieceToNumber(piece);
2019 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2020 if(j < 0) continue; /* should not happen */
2021 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2022 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2023 board[holdingsStartRow+j*direction][countsColumn]++;
2029 VariantSwitch(Board board, VariantClass newVariant)
2031 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2032 static Board oldBoard;
2034 startedFromPositionFile = FALSE;
2035 if(gameInfo.variant == newVariant) return;
2037 /* [HGM] This routine is called each time an assignment is made to
2038 * gameInfo.variant during a game, to make sure the board sizes
2039 * are set to match the new variant. If that means adding or deleting
2040 * holdings, we shift the playing board accordingly
2041 * This kludge is needed because in ICS observe mode, we get boards
2042 * of an ongoing game without knowing the variant, and learn about the
2043 * latter only later. This can be because of the move list we requested,
2044 * in which case the game history is refilled from the beginning anyway,
2045 * but also when receiving holdings of a crazyhouse game. In the latter
2046 * case we want to add those holdings to the already received position.
2050 if (appData.debugMode) {
2051 fprintf(debugFP, "Switch board from %s to %s\n",
2052 VariantName(gameInfo.variant), VariantName(newVariant));
2053 setbuf(debugFP, NULL);
2055 shuffleOpenings = 0; /* [HGM] shuffle */
2056 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2060 newWidth = 9; newHeight = 9;
2061 gameInfo.holdingsSize = 7;
2062 case VariantBughouse:
2063 case VariantCrazyhouse:
2064 newHoldingsWidth = 2; break;
2068 newHoldingsWidth = 2;
2069 gameInfo.holdingsSize = 8;
2072 case VariantCapablanca:
2073 case VariantCapaRandom:
2076 newHoldingsWidth = gameInfo.holdingsSize = 0;
2079 if(newWidth != gameInfo.boardWidth ||
2080 newHeight != gameInfo.boardHeight ||
2081 newHoldingsWidth != gameInfo.holdingsWidth ) {
2083 /* shift position to new playing area, if needed */
2084 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2085 for(i=0; i<BOARD_HEIGHT; i++)
2086 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2087 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2089 for(i=0; i<newHeight; i++) {
2090 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2091 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2093 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2094 for(i=0; i<BOARD_HEIGHT; i++)
2095 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2096 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2099 gameInfo.boardWidth = newWidth;
2100 gameInfo.boardHeight = newHeight;
2101 gameInfo.holdingsWidth = newHoldingsWidth;
2102 gameInfo.variant = newVariant;
2103 InitDrawingSizes(-2, 0);
2104 } else gameInfo.variant = newVariant;
2105 CopyBoard(oldBoard, board); // remember correctly formatted board
2106 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2107 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2110 static int loggedOn = FALSE;
2112 /*-- Game start info cache: --*/
2114 char gs_kind[MSG_SIZ];
2115 static char player1Name[128] = "";
2116 static char player2Name[128] = "";
2117 static char cont_seq[] = "\n\\ ";
2118 static int player1Rating = -1;
2119 static int player2Rating = -1;
2120 /*----------------------------*/
2122 ColorClass curColor = ColorNormal;
2123 int suppressKibitz = 0;
2126 Boolean soughtPending = FALSE;
2127 Boolean seekGraphUp;
2128 #define MAX_SEEK_ADS 200
2130 char *seekAdList[MAX_SEEK_ADS];
2131 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2132 float tcList[MAX_SEEK_ADS];
2133 char colorList[MAX_SEEK_ADS];
2134 int nrOfSeekAds = 0;
2135 int minRating = 1010, maxRating = 2800;
2136 int hMargin = 10, vMargin = 20, h, w;
2137 extern int squareSize, lineGap;
2142 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2143 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2144 if(r < minRating+100 && r >=0 ) r = minRating+100;
2145 if(r > maxRating) r = maxRating;
2146 if(tc < 1.) tc = 1.;
2147 if(tc > 95.) tc = 95.;
2148 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2149 y = ((double)r - minRating)/(maxRating - minRating)
2150 * (h-vMargin-squareSize/8-1) + vMargin;
2151 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2152 if(strstr(seekAdList[i], " u ")) color = 1;
2153 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2154 !strstr(seekAdList[i], "bullet") &&
2155 !strstr(seekAdList[i], "blitz") &&
2156 !strstr(seekAdList[i], "standard") ) color = 2;
2157 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2158 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2162 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2164 char buf[MSG_SIZ], *ext = "";
2165 VariantClass v = StringToVariant(type);
2166 if(strstr(type, "wild")) {
2167 ext = type + 4; // append wild number
2168 if(v == VariantFischeRandom) type = "chess960"; else
2169 if(v == VariantLoadable) type = "setup"; else
2170 type = VariantName(v);
2172 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2173 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2174 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2175 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2176 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2177 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2178 seekNrList[nrOfSeekAds] = nr;
2179 zList[nrOfSeekAds] = 0;
2180 seekAdList[nrOfSeekAds++] = StrSave(buf);
2181 if(plot) PlotSeekAd(nrOfSeekAds-1);
2188 int x = xList[i], y = yList[i], d=squareSize/4, k;
2189 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2190 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2191 // now replot every dot that overlapped
2192 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2193 int xx = xList[k], yy = yList[k];
2194 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2195 DrawSeekDot(xx, yy, colorList[k]);
2200 RemoveSeekAd(int nr)
2203 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2205 if(seekAdList[i]) free(seekAdList[i]);
2206 seekAdList[i] = seekAdList[--nrOfSeekAds];
2207 seekNrList[i] = seekNrList[nrOfSeekAds];
2208 ratingList[i] = ratingList[nrOfSeekAds];
2209 colorList[i] = colorList[nrOfSeekAds];
2210 tcList[i] = tcList[nrOfSeekAds];
2211 xList[i] = xList[nrOfSeekAds];
2212 yList[i] = yList[nrOfSeekAds];
2213 zList[i] = zList[nrOfSeekAds];
2214 seekAdList[nrOfSeekAds] = NULL;
2220 MatchSoughtLine(char *line)
2222 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2223 int nr, base, inc, u=0; char dummy;
2225 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2226 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2228 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2229 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2230 // match: compact and save the line
2231 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2241 if(!seekGraphUp) return FALSE;
2242 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2243 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2245 DrawSeekBackground(0, 0, w, h);
2246 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2247 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2248 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2249 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2251 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2254 sprintf(buf, "%d", i);
2255 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2258 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2259 for(i=1; i<100; i+=(i<10?1:5)) {
2260 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2261 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2262 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2264 sprintf(buf, "%d", i);
2265 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2268 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2272 int SeekGraphClick(ClickType click, int x, int y, int moving)
2274 static int lastDown = 0, displayed = 0, lastSecond;
2275 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2276 if(click == Release || moving) return FALSE;
2278 soughtPending = TRUE;
2279 SendToICS(ics_prefix);
2280 SendToICS("sought\n"); // should this be "sought all"?
2281 } else { // issue challenge based on clicked ad
2282 int dist = 10000; int i, closest = 0, second = 0;
2283 for(i=0; i<nrOfSeekAds; i++) {
2284 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2285 if(d < dist) { dist = d; closest = i; }
2286 second += (d - zList[i] < 120); // count in-range ads
2287 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2291 second = (second > 1);
2292 if(displayed != closest || second != lastSecond) {
2293 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2294 lastSecond = second; displayed = closest;
2296 if(click == Press) {
2297 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2300 } // on press 'hit', only show info
2301 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2302 sprintf(buf, "play %d\n", seekNrList[closest]);
2303 SendToICS(ics_prefix);
2305 return TRUE; // let incoming board of started game pop down the graph
2306 } else if(click == Release) { // release 'miss' is ignored
2307 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2308 if(moving == 2) { // right up-click
2309 nrOfSeekAds = 0; // refresh graph
2310 soughtPending = TRUE;
2311 SendToICS(ics_prefix);
2312 SendToICS("sought\n"); // should this be "sought all"?
2315 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2316 // press miss or release hit 'pop down' seek graph
2317 seekGraphUp = FALSE;
2318 DrawPosition(TRUE, NULL);
2324 read_from_ics(isr, closure, data, count, error)
2331 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2332 #define STARTED_NONE 0
2333 #define STARTED_MOVES 1
2334 #define STARTED_BOARD 2
2335 #define STARTED_OBSERVE 3
2336 #define STARTED_HOLDINGS 4
2337 #define STARTED_CHATTER 5
2338 #define STARTED_COMMENT 6
2339 #define STARTED_MOVES_NOHIDE 7
2341 static int started = STARTED_NONE;
2342 static char parse[20000];
2343 static int parse_pos = 0;
2344 static char buf[BUF_SIZE + 1];
2345 static int firstTime = TRUE, intfSet = FALSE;
2346 static ColorClass prevColor = ColorNormal;
2347 static int savingComment = FALSE;
2348 static int cmatch = 0; // continuation sequence match
2355 int backup; /* [DM] For zippy color lines */
2357 char talker[MSG_SIZ]; // [HGM] chat
2360 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2362 if (appData.debugMode) {
2364 fprintf(debugFP, "<ICS: ");
2365 show_bytes(debugFP, data, count);
2366 fprintf(debugFP, "\n");
2370 if (appData.debugMode) { int f = forwardMostMove;
2371 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2372 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2373 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2376 /* If last read ended with a partial line that we couldn't parse,
2377 prepend it to the new read and try again. */
2378 if (leftover_len > 0) {
2379 for (i=0; i<leftover_len; i++)
2380 buf[i] = buf[leftover_start + i];
2383 /* copy new characters into the buffer */
2384 bp = buf + leftover_len;
2385 buf_len=leftover_len;
2386 for (i=0; i<count; i++)
2389 if (data[i] == '\r')
2392 // join lines split by ICS?
2393 if (!appData.noJoin)
2396 Joining just consists of finding matches against the
2397 continuation sequence, and discarding that sequence
2398 if found instead of copying it. So, until a match
2399 fails, there's nothing to do since it might be the
2400 complete sequence, and thus, something we don't want
2403 if (data[i] == cont_seq[cmatch])
2406 if (cmatch == strlen(cont_seq))
2408 cmatch = 0; // complete match. just reset the counter
2411 it's possible for the ICS to not include the space
2412 at the end of the last word, making our [correct]
2413 join operation fuse two separate words. the server
2414 does this when the space occurs at the width setting.
2416 if (!buf_len || buf[buf_len-1] != ' ')
2427 match failed, so we have to copy what matched before
2428 falling through and copying this character. In reality,
2429 this will only ever be just the newline character, but
2430 it doesn't hurt to be precise.
2432 strncpy(bp, cont_seq, cmatch);
2444 buf[buf_len] = NULLCHAR;
2445 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2450 while (i < buf_len) {
2451 /* Deal with part of the TELNET option negotiation
2452 protocol. We refuse to do anything beyond the
2453 defaults, except that we allow the WILL ECHO option,
2454 which ICS uses to turn off password echoing when we are
2455 directly connected to it. We reject this option
2456 if localLineEditing mode is on (always on in xboard)
2457 and we are talking to port 23, which might be a real
2458 telnet server that will try to keep WILL ECHO on permanently.
2460 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2461 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2462 unsigned char option;
2464 switch ((unsigned char) buf[++i]) {
2466 if (appData.debugMode)
2467 fprintf(debugFP, "\n<WILL ");
2468 switch (option = (unsigned char) buf[++i]) {
2470 if (appData.debugMode)
2471 fprintf(debugFP, "ECHO ");
2472 /* Reply only if this is a change, according
2473 to the protocol rules. */
2474 if (remoteEchoOption) break;
2475 if (appData.localLineEditing &&
2476 atoi(appData.icsPort) == TN_PORT) {
2477 TelnetRequest(TN_DONT, TN_ECHO);
2480 TelnetRequest(TN_DO, TN_ECHO);
2481 remoteEchoOption = TRUE;
2485 if (appData.debugMode)
2486 fprintf(debugFP, "%d ", option);
2487 /* Whatever this is, we don't want it. */
2488 TelnetRequest(TN_DONT, option);
2493 if (appData.debugMode)
2494 fprintf(debugFP, "\n<WONT ");
2495 switch (option = (unsigned char) buf[++i]) {
2497 if (appData.debugMode)
2498 fprintf(debugFP, "ECHO ");
2499 /* Reply only if this is a change, according
2500 to the protocol rules. */
2501 if (!remoteEchoOption) break;
2503 TelnetRequest(TN_DONT, TN_ECHO);
2504 remoteEchoOption = FALSE;
2507 if (appData.debugMode)
2508 fprintf(debugFP, "%d ", (unsigned char) option);
2509 /* Whatever this is, it must already be turned
2510 off, because we never agree to turn on
2511 anything non-default, so according to the
2512 protocol rules, we don't reply. */
2517 if (appData.debugMode)
2518 fprintf(debugFP, "\n<DO ");
2519 switch (option = (unsigned char) buf[++i]) {
2521 /* Whatever this is, we refuse to do it. */
2522 if (appData.debugMode)
2523 fprintf(debugFP, "%d ", option);
2524 TelnetRequest(TN_WONT, option);
2529 if (appData.debugMode)
2530 fprintf(debugFP, "\n<DONT ");
2531 switch (option = (unsigned char) buf[++i]) {
2533 if (appData.debugMode)
2534 fprintf(debugFP, "%d ", option);
2535 /* Whatever this is, we are already not doing
2536 it, because we never agree to do anything
2537 non-default, so according to the protocol
2538 rules, we don't reply. */
2543 if (appData.debugMode)
2544 fprintf(debugFP, "\n<IAC ");
2545 /* Doubled IAC; pass it through */
2549 if (appData.debugMode)
2550 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2551 /* Drop all other telnet commands on the floor */
2554 if (oldi > next_out)
2555 SendToPlayer(&buf[next_out], oldi - next_out);
2561 /* OK, this at least will *usually* work */
2562 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2566 if (loggedOn && !intfSet) {
2567 if (ics_type == ICS_ICC) {
2569 "/set-quietly interface %s\n/set-quietly style 12\n",
2571 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2572 strcat(str, "/set-2 51 1\n/set seek 1\n");
2573 } else if (ics_type == ICS_CHESSNET) {
2574 sprintf(str, "/style 12\n");
2576 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2577 strcat(str, programVersion);
2578 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2579 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2580 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2582 strcat(str, "$iset nohighlight 1\n");
2584 strcat(str, "$iset lock 1\n$style 12\n");
2587 NotifyFrontendLogin();
2591 if (started == STARTED_COMMENT) {
2592 /* Accumulate characters in comment */
2593 parse[parse_pos++] = buf[i];
2594 if (buf[i] == '\n') {
2595 parse[parse_pos] = NULLCHAR;
2596 if(chattingPartner>=0) {
2598 sprintf(mess, "%s%s", talker, parse);
2599 OutputChatMessage(chattingPartner, mess);
2600 chattingPartner = -1;
2601 next_out = i+1; // [HGM] suppress printing in ICS window
2603 if(!suppressKibitz) // [HGM] kibitz
2604 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2605 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2606 int nrDigit = 0, nrAlph = 0, j;
2607 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2608 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2609 parse[parse_pos] = NULLCHAR;
2610 // try to be smart: if it does not look like search info, it should go to
2611 // ICS interaction window after all, not to engine-output window.
2612 for(j=0; j<parse_pos; j++) { // count letters and digits
2613 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2614 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2615 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2617 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2618 int depth=0; float score;
2619 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2620 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2621 pvInfoList[forwardMostMove-1].depth = depth;
2622 pvInfoList[forwardMostMove-1].score = 100*score;
2624 OutputKibitz(suppressKibitz, parse);
2627 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2628 SendToPlayer(tmp, strlen(tmp));
2630 next_out = i+1; // [HGM] suppress printing in ICS window
2632 started = STARTED_NONE;
2634 /* Don't match patterns against characters in comment */
2639 if (started == STARTED_CHATTER) {
2640 if (buf[i] != '\n') {
2641 /* Don't match patterns against characters in chatter */
2645 started = STARTED_NONE;
2646 if(suppressKibitz) next_out = i+1;
2649 /* Kludge to deal with rcmd protocol */
2650 if (firstTime && looking_at(buf, &i, "\001*")) {
2651 DisplayFatalError(&buf[1], 0, 1);
2657 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2660 if (appData.debugMode)
2661 fprintf(debugFP, "ics_type %d\n", ics_type);
2664 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2665 ics_type = ICS_FICS;
2667 if (appData.debugMode)
2668 fprintf(debugFP, "ics_type %d\n", ics_type);
2671 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2672 ics_type = ICS_CHESSNET;
2674 if (appData.debugMode)
2675 fprintf(debugFP, "ics_type %d\n", ics_type);
2680 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2681 looking_at(buf, &i, "Logging you in as \"*\"") ||
2682 looking_at(buf, &i, "will be \"*\""))) {
2683 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2687 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2689 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2690 DisplayIcsInteractionTitle(buf);
2691 have_set_title = TRUE;
2694 /* skip finger notes */
2695 if (started == STARTED_NONE &&
2696 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2697 (buf[i] == '1' && buf[i+1] == '0')) &&
2698 buf[i+2] == ':' && buf[i+3] == ' ') {
2699 started = STARTED_CHATTER;
2705 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2706 if(appData.seekGraph) {
2707 if(soughtPending && MatchSoughtLine(buf+i)) {
2708 i = strstr(buf+i, "rated") - buf;
2709 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2710 next_out = leftover_start = i;
2711 started = STARTED_CHATTER;
2712 suppressKibitz = TRUE;
2715 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2716 && looking_at(buf, &i, "* ads displayed")) {
2717 soughtPending = FALSE;
2722 if(appData.autoRefresh) {
2723 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2724 int s = (ics_type == ICS_ICC); // ICC format differs
2726 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2727 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2728 looking_at(buf, &i, "*% "); // eat prompt
2729 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2730 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2731 next_out = i; // suppress
2734 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2735 char *p = star_match[0];
2737 if(seekGraphUp) RemoveSeekAd(atoi(p));
2738 while(*p && *p++ != ' '); // next
2740 looking_at(buf, &i, "*% "); // eat prompt
2741 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2748 /* skip formula vars */
2749 if (started == STARTED_NONE &&
2750 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2751 started = STARTED_CHATTER;
2756 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2757 if (appData.autoKibitz && started == STARTED_NONE &&
2758 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2759 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2760 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2761 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2762 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2763 suppressKibitz = TRUE;
2764 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2766 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2767 && (gameMode == IcsPlayingWhite)) ||
2768 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2769 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2770 started = STARTED_CHATTER; // own kibitz we simply discard
2772 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2773 parse_pos = 0; parse[0] = NULLCHAR;
2774 savingComment = TRUE;
2775 suppressKibitz = gameMode != IcsObserving ? 2 :
2776 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2780 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2781 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2782 && atoi(star_match[0])) {
2783 // suppress the acknowledgements of our own autoKibitz
2785 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2786 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2787 SendToPlayer(star_match[0], strlen(star_match[0]));
2788 if(looking_at(buf, &i, "*% ")) // eat prompt
2789 suppressKibitz = FALSE;
2793 } // [HGM] kibitz: end of patch
2795 // [HGM] chat: intercept tells by users for which we have an open chat window
2797 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2798 looking_at(buf, &i, "* whispers:") ||
2799 looking_at(buf, &i, "* kibitzes:") ||
2800 looking_at(buf, &i, "* shouts:") ||
2801 looking_at(buf, &i, "* c-shouts:") ||
2802 looking_at(buf, &i, "--> * ") ||
2803 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2804 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2805 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2806 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2808 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2809 chattingPartner = -1;
2811 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2812 for(p=0; p<MAX_CHAT; p++) {
2813 if(channel == atoi(chatPartner[p])) {
2814 talker[0] = '['; strcat(talker, "] ");
2815 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2816 chattingPartner = p; break;
2819 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2820 for(p=0; p<MAX_CHAT; p++) {
2821 if(!strcmp("kibitzes", chatPartner[p])) {
2822 talker[0] = '['; strcat(talker, "] ");
2823 chattingPartner = p; break;
2826 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2827 for(p=0; p<MAX_CHAT; p++) {
2828 if(!strcmp("whispers", chatPartner[p])) {
2829 talker[0] = '['; strcat(talker, "] ");
2830 chattingPartner = p; break;
2833 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2834 if(buf[i-8] == '-' && buf[i-3] == 't')
2835 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2836 if(!strcmp("c-shouts", chatPartner[p])) {
2837 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2838 chattingPartner = p; break;
2841 if(chattingPartner < 0)
2842 for(p=0; p<MAX_CHAT; p++) {
2843 if(!strcmp("shouts", chatPartner[p])) {
2844 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2845 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2846 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2847 chattingPartner = p; break;
2851 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2852 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2853 talker[0] = 0; Colorize(ColorTell, FALSE);
2854 chattingPartner = p; break;
2856 if(chattingPartner<0) i = oldi; else {
2857 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2858 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2859 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2860 started = STARTED_COMMENT;
2861 parse_pos = 0; parse[0] = NULLCHAR;
2862 savingComment = 3 + chattingPartner; // counts as TRUE
2863 suppressKibitz = TRUE;
2866 } // [HGM] chat: end of patch
2868 if (appData.zippyTalk || appData.zippyPlay) {
2869 /* [DM] Backup address for color zippy lines */
2872 if (loggedOn == TRUE)
2873 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2874 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2876 } // [DM] 'else { ' deleted
2878 /* Regular tells and says */
2879 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2880 looking_at(buf, &i, "* (your partner) tells you: ") ||
2881 looking_at(buf, &i, "* says: ") ||
2882 /* Don't color "message" or "messages" output */
2883 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2884 looking_at(buf, &i, "*. * at *:*: ") ||
2885 looking_at(buf, &i, "--* (*:*): ") ||
2886 /* Message notifications (same color as tells) */
2887 looking_at(buf, &i, "* has left a message ") ||
2888 looking_at(buf, &i, "* just sent you a message:\n") ||
2889 /* Whispers and kibitzes */
2890 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2891 looking_at(buf, &i, "* kibitzes: ") ||
2893 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2895 if (tkind == 1 && strchr(star_match[0], ':')) {
2896 /* Avoid "tells you:" spoofs in channels */
2899 if (star_match[0][0] == NULLCHAR ||
2900 strchr(star_match[0], ' ') ||
2901 (tkind == 3 && strchr(star_match[1], ' '))) {
2902 /* Reject bogus matches */
2905 if (appData.colorize) {
2906 if (oldi > next_out) {
2907 SendToPlayer(&buf[next_out], oldi - next_out);
2912 Colorize(ColorTell, FALSE);
2913 curColor = ColorTell;
2916 Colorize(ColorKibitz, FALSE);
2917 curColor = ColorKibitz;
2920 p = strrchr(star_match[1], '(');
2927 Colorize(ColorChannel1, FALSE);
2928 curColor = ColorChannel1;
2930 Colorize(ColorChannel, FALSE);
2931 curColor = ColorChannel;
2935 curColor = ColorNormal;
2939 if (started == STARTED_NONE && appData.autoComment &&
2940 (gameMode == IcsObserving ||
2941 gameMode == IcsPlayingWhite ||
2942 gameMode == IcsPlayingBlack)) {
2943 parse_pos = i - oldi;
2944 memcpy(parse, &buf[oldi], parse_pos);
2945 parse[parse_pos] = NULLCHAR;
2946 started = STARTED_COMMENT;
2947 savingComment = TRUE;
2949 started = STARTED_CHATTER;
2950 savingComment = FALSE;
2957 if (looking_at(buf, &i, "* s-shouts: ") ||
2958 looking_at(buf, &i, "* c-shouts: ")) {
2959 if (appData.colorize) {
2960 if (oldi > next_out) {
2961 SendToPlayer(&buf[next_out], oldi - next_out);
2964 Colorize(ColorSShout, FALSE);
2965 curColor = ColorSShout;
2968 started = STARTED_CHATTER;
2972 if (looking_at(buf, &i, "--->")) {
2977 if (looking_at(buf, &i, "* shouts: ") ||
2978 looking_at(buf, &i, "--> ")) {
2979 if (appData.colorize) {
2980 if (oldi > next_out) {
2981 SendToPlayer(&buf[next_out], oldi - next_out);
2984 Colorize(ColorShout, FALSE);
2985 curColor = ColorShout;
2988 started = STARTED_CHATTER;
2992 if (looking_at( buf, &i, "Challenge:")) {
2993 if (appData.colorize) {
2994 if (oldi > next_out) {
2995 SendToPlayer(&buf[next_out], oldi - next_out);
2998 Colorize(ColorChallenge, FALSE);
2999 curColor = ColorChallenge;
3005 if (looking_at(buf, &i, "* offers you") ||
3006 looking_at(buf, &i, "* offers to be") ||
3007 looking_at(buf, &i, "* would like to") ||
3008 looking_at(buf, &i, "* requests to") ||
3009 looking_at(buf, &i, "Your opponent offers") ||
3010 looking_at(buf, &i, "Your opponent requests")) {
3012 if (appData.colorize) {
3013 if (oldi > next_out) {
3014 SendToPlayer(&buf[next_out], oldi - next_out);
3017 Colorize(ColorRequest, FALSE);
3018 curColor = ColorRequest;
3023 if (looking_at(buf, &i, "* (*) seeking")) {
3024 if (appData.colorize) {
3025 if (oldi > next_out) {
3026 SendToPlayer(&buf[next_out], oldi - next_out);
3029 Colorize(ColorSeek, FALSE);
3030 curColor = ColorSeek;
3035 if (looking_at(buf, &i, "\\ ")) {
3036 if (prevColor != ColorNormal) {
3037 if (oldi > next_out) {
3038 SendToPlayer(&buf[next_out], oldi - next_out);
3041 Colorize(prevColor, TRUE);
3042 curColor = prevColor;
3044 if (savingComment) {
3045 parse_pos = i - oldi;
3046 memcpy(parse, &buf[oldi], parse_pos);
3047 parse[parse_pos] = NULLCHAR;
3048 started = STARTED_COMMENT;
3049 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3050 chattingPartner = savingComment - 3; // kludge to remember the box
3052 started = STARTED_CHATTER;
3057 if (looking_at(buf, &i, "Black Strength :") ||
3058 looking_at(buf, &i, "<<< style 10 board >>>") ||
3059 looking_at(buf, &i, "<10>") ||
3060 looking_at(buf, &i, "#@#")) {
3061 /* Wrong board style */
3063 SendToICS(ics_prefix);
3064 SendToICS("set style 12\n");
3065 SendToICS(ics_prefix);
3066 SendToICS("refresh\n");
3070 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3072 have_sent_ICS_logon = 1;
3076 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3077 (looking_at(buf, &i, "\n<12> ") ||
3078 looking_at(buf, &i, "<12> "))) {
3080 if (oldi > next_out) {
3081 SendToPlayer(&buf[next_out], oldi - next_out);
3084 started = STARTED_BOARD;
3089 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3090 looking_at(buf, &i, "<b1> ")) {
3091 if (oldi > next_out) {
3092 SendToPlayer(&buf[next_out], oldi - next_out);
3095 started = STARTED_HOLDINGS;
3100 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3102 /* Header for a move list -- first line */
3104 switch (ics_getting_history) {
3108 case BeginningOfGame:
3109 /* User typed "moves" or "oldmoves" while we
3110 were idle. Pretend we asked for these
3111 moves and soak them up so user can step
3112 through them and/or save them.
3115 gameMode = IcsObserving;
3118 ics_getting_history = H_GOT_UNREQ_HEADER;
3120 case EditGame: /*?*/
3121 case EditPosition: /*?*/
3122 /* Should above feature work in these modes too? */
3123 /* For now it doesn't */
3124 ics_getting_history = H_GOT_UNWANTED_HEADER;
3127 ics_getting_history = H_GOT_UNWANTED_HEADER;
3132 /* Is this the right one? */
3133 if (gameInfo.white && gameInfo.black &&
3134 strcmp(gameInfo.white, star_match[0]) == 0 &&
3135 strcmp(gameInfo.black, star_match[2]) == 0) {
3137 ics_getting_history = H_GOT_REQ_HEADER;
3140 case H_GOT_REQ_HEADER:
3141 case H_GOT_UNREQ_HEADER:
3142 case H_GOT_UNWANTED_HEADER:
3143 case H_GETTING_MOVES:
3144 /* Should not happen */
3145 DisplayError(_("Error gathering move list: two headers"), 0);
3146 ics_getting_history = H_FALSE;
3150 /* Save player ratings into gameInfo if needed */
3151 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3152 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3153 (gameInfo.whiteRating == -1 ||
3154 gameInfo.blackRating == -1)) {
3156 gameInfo.whiteRating = string_to_rating(star_match[1]);
3157 gameInfo.blackRating = string_to_rating(star_match[3]);
3158 if (appData.debugMode)
3159 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3160 gameInfo.whiteRating, gameInfo.blackRating);
3165 if (looking_at(buf, &i,
3166 "* * match, initial time: * minute*, increment: * second")) {
3167 /* Header for a move list -- second line */
3168 /* Initial board will follow if this is a wild game */
3169 if (gameInfo.event != NULL) free(gameInfo.event);
3170 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3171 gameInfo.event = StrSave(str);
3172 /* [HGM] we switched variant. Translate boards if needed. */
3173 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3177 if (looking_at(buf, &i, "Move ")) {
3178 /* Beginning of a move list */
3179 switch (ics_getting_history) {
3181 /* Normally should not happen */
3182 /* Maybe user hit reset while we were parsing */
3185 /* Happens if we are ignoring a move list that is not
3186 * the one we just requested. Common if the user
3187 * tries to observe two games without turning off
3190 case H_GETTING_MOVES:
3191 /* Should not happen */
3192 DisplayError(_("Error gathering move list: nested"), 0);
3193 ics_getting_history = H_FALSE;
3195 case H_GOT_REQ_HEADER:
3196 ics_getting_history = H_GETTING_MOVES;
3197 started = STARTED_MOVES;
3199 if (oldi > next_out) {
3200 SendToPlayer(&buf[next_out], oldi - next_out);
3203 case H_GOT_UNREQ_HEADER:
3204 ics_getting_history = H_GETTING_MOVES;
3205 started = STARTED_MOVES_NOHIDE;
3208 case H_GOT_UNWANTED_HEADER:
3209 ics_getting_history = H_FALSE;
3215 if (looking_at(buf, &i, "% ") ||
3216 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3217 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3218 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3219 soughtPending = FALSE;
3223 if(suppressKibitz) next_out = i;
3224 savingComment = FALSE;
3228 case STARTED_MOVES_NOHIDE:
3229 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3230 parse[parse_pos + i - oldi] = NULLCHAR;
3231 ParseGameHistory(parse);
3233 if (appData.zippyPlay && first.initDone) {
3234 FeedMovesToProgram(&first, forwardMostMove);
3235 if (gameMode == IcsPlayingWhite) {
3236 if (WhiteOnMove(forwardMostMove)) {
3237 if (first.sendTime) {
3238 if (first.useColors) {
3239 SendToProgram("black\n", &first);
3241 SendTimeRemaining(&first, TRUE);
3243 if (first.useColors) {
3244 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3246 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3247 first.maybeThinking = TRUE;
3249 if (first.usePlayother) {
3250 if (first.sendTime) {
3251 SendTimeRemaining(&first, TRUE);
3253 SendToProgram("playother\n", &first);
3259 } else if (gameMode == IcsPlayingBlack) {
3260 if (!WhiteOnMove(forwardMostMove)) {
3261 if (first.sendTime) {
3262 if (first.useColors) {
3263 SendToProgram("white\n", &first);
3265 SendTimeRemaining(&first, FALSE);
3267 if (first.useColors) {
3268 SendToProgram("black\n", &first);
3270 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3271 first.maybeThinking = TRUE;
3273 if (first.usePlayother) {
3274 if (first.sendTime) {
3275 SendTimeRemaining(&first, FALSE);
3277 SendToProgram("playother\n", &first);
3286 if (gameMode == IcsObserving && ics_gamenum == -1) {
3287 /* Moves came from oldmoves or moves command
3288 while we weren't doing anything else.
3290 currentMove = forwardMostMove;
3291 ClearHighlights();/*!!could figure this out*/
3292 flipView = appData.flipView;
3293 DrawPosition(TRUE, boards[currentMove]);
3294 DisplayBothClocks();
3295 sprintf(str, "%s vs. %s",
3296 gameInfo.white, gameInfo.black);
3300 /* Moves were history of an active game */
3301 if (gameInfo.resultDetails != NULL) {
3302 free(gameInfo.resultDetails);
3303 gameInfo.resultDetails = NULL;
3306 HistorySet(parseList, backwardMostMove,
3307 forwardMostMove, currentMove-1);
3308 DisplayMove(currentMove - 1);
3309 if (started == STARTED_MOVES) next_out = i;
3310 started = STARTED_NONE;
3311 ics_getting_history = H_FALSE;
3314 case STARTED_OBSERVE:
3315 started = STARTED_NONE;
3316 SendToICS(ics_prefix);
3317 SendToICS("refresh\n");
3323 if(bookHit) { // [HGM] book: simulate book reply
3324 static char bookMove[MSG_SIZ]; // a bit generous?
3326 programStats.nodes = programStats.depth = programStats.time =
3327 programStats.score = programStats.got_only_move = 0;
3328 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3330 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3331 strcat(bookMove, bookHit);
3332 HandleMachineMove(bookMove, &first);
3337 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3338 started == STARTED_HOLDINGS ||
3339 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3340 /* Accumulate characters in move list or board */
3341 parse[parse_pos++] = buf[i];
3344 /* Start of game messages. Mostly we detect start of game
3345 when the first board image arrives. On some versions
3346 of the ICS, though, we need to do a "refresh" after starting
3347 to observe in order to get the current board right away. */
3348 if (looking_at(buf, &i, "Adding game * to observation list")) {
3349 started = STARTED_OBSERVE;
3353 /* Handle auto-observe */
3354 if (appData.autoObserve &&
3355 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3356 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3358 /* Choose the player that was highlighted, if any. */
3359 if (star_match[0][0] == '\033' ||
3360 star_match[1][0] != '\033') {
3361 player = star_match[0];
3363 player = star_match[2];
3365 sprintf(str, "%sobserve %s\n",
3366 ics_prefix, StripHighlightAndTitle(player));
3369 /* Save ratings from notify string */
3370 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3371 player1Rating = string_to_rating(star_match[1]);
3372 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3373 player2Rating = string_to_rating(star_match[3]);
3375 if (appData.debugMode)
3377 "Ratings from 'Game notification:' %s %d, %s %d\n",
3378 player1Name, player1Rating,
3379 player2Name, player2Rating);
3384 /* Deal with automatic examine mode after a game,
3385 and with IcsObserving -> IcsExamining transition */
3386 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3387 looking_at(buf, &i, "has made you an examiner of game *")) {
3389 int gamenum = atoi(star_match[0]);
3390 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3391 gamenum == ics_gamenum) {
3392 /* We were already playing or observing this game;
3393 no need to refetch history */
3394 gameMode = IcsExamining;
3396 pauseExamForwardMostMove = forwardMostMove;
3397 } else if (currentMove < forwardMostMove) {
3398 ForwardInner(forwardMostMove);
3401 /* I don't think this case really can happen */
3402 SendToICS(ics_prefix);
3403 SendToICS("refresh\n");
3408 /* Error messages */
3409 // if (ics_user_moved) {
3410 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3411 if (looking_at(buf, &i, "Illegal move") ||
3412 looking_at(buf, &i, "Not a legal move") ||
3413 looking_at(buf, &i, "Your king is in check") ||
3414 looking_at(buf, &i, "It isn't your turn") ||
3415 looking_at(buf, &i, "It is not your move")) {
3417 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3418 currentMove = forwardMostMove-1;
3419 DisplayMove(currentMove - 1); /* before DMError */
3420 DrawPosition(FALSE, boards[currentMove]);
3421 SwitchClocks(forwardMostMove-1); // [HGM] race
3422 DisplayBothClocks();
3424 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3430 if (looking_at(buf, &i, "still have time") ||
3431 looking_at(buf, &i, "not out of time") ||
3432 looking_at(buf, &i, "either player is out of time") ||
3433 looking_at(buf, &i, "has timeseal; checking")) {
3434 /* We must have called his flag a little too soon */
3435 whiteFlag = blackFlag = FALSE;
3439 if (looking_at(buf, &i, "added * seconds to") ||
3440 looking_at(buf, &i, "seconds were added to")) {
3441 /* Update the clocks */
3442 SendToICS(ics_prefix);
3443 SendToICS("refresh\n");
3447 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3448 ics_clock_paused = TRUE;
3453 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3454 ics_clock_paused = FALSE;
3459 /* Grab player ratings from the Creating: message.
3460 Note we have to check for the special case when
3461 the ICS inserts things like [white] or [black]. */
3462 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3463 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3465 0 player 1 name (not necessarily white)
3467 2 empty, white, or black (IGNORED)
3468 3 player 2 name (not necessarily black)
3471 The names/ratings are sorted out when the game
3472 actually starts (below).
3474 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3475 player1Rating = string_to_rating(star_match[1]);
3476 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3477 player2Rating = string_to_rating(star_match[4]);
3479 if (appData.debugMode)
3481 "Ratings from 'Creating:' %s %d, %s %d\n",
3482 player1Name, player1Rating,
3483 player2Name, player2Rating);
3488 /* Improved generic start/end-of-game messages */
3489 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3490 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3491 /* If tkind == 0: */
3492 /* star_match[0] is the game number */
3493 /* [1] is the white player's name */
3494 /* [2] is the black player's name */
3495 /* For end-of-game: */
3496 /* [3] is the reason for the game end */
3497 /* [4] is a PGN end game-token, preceded by " " */
3498 /* For start-of-game: */
3499 /* [3] begins with "Creating" or "Continuing" */
3500 /* [4] is " *" or empty (don't care). */
3501 int gamenum = atoi(star_match[0]);
3502 char *whitename, *blackname, *why, *endtoken;
3503 ChessMove endtype = (ChessMove) 0;
3506 whitename = star_match[1];
3507 blackname = star_match[2];
3508 why = star_match[3];
3509 endtoken = star_match[4];
3511 whitename = star_match[1];
3512 blackname = star_match[3];
3513 why = star_match[5];
3514 endtoken = star_match[6];
3517 /* Game start messages */
3518 if (strncmp(why, "Creating ", 9) == 0 ||
3519 strncmp(why, "Continuing ", 11) == 0) {
3520 gs_gamenum = gamenum;
3521 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3522 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3524 if (appData.zippyPlay) {
3525 ZippyGameStart(whitename, blackname);
3528 partnerBoardValid = FALSE; // [HGM] bughouse
3532 /* Game end messages */
3533 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3534 ics_gamenum != gamenum) {
3537 while (endtoken[0] == ' ') endtoken++;
3538 switch (endtoken[0]) {
3541 endtype = GameUnfinished;
3544 endtype = BlackWins;
3547 if (endtoken[1] == '/')
3548 endtype = GameIsDrawn;
3550 endtype = WhiteWins;
3553 GameEnds(endtype, why, GE_ICS);
3555 if (appData.zippyPlay && first.initDone) {
3556 ZippyGameEnd(endtype, why);
3557 if (first.pr == NULL) {
3558 /* Start the next process early so that we'll
3559 be ready for the next challenge */
3560 StartChessProgram(&first);
3562 /* Send "new" early, in case this command takes
3563 a long time to finish, so that we'll be ready
3564 for the next challenge. */
3565 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3569 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3573 if (looking_at(buf, &i, "Removing game * from observation") ||
3574 looking_at(buf, &i, "no longer observing game *") ||
3575 looking_at(buf, &i, "Game * (*) has no examiners")) {
3576 if (gameMode == IcsObserving &&
3577 atoi(star_match[0]) == ics_gamenum)
3579 /* icsEngineAnalyze */
3580 if (appData.icsEngineAnalyze) {
3587 ics_user_moved = FALSE;
3592 if (looking_at(buf, &i, "no longer examining game *")) {
3593 if (gameMode == IcsExamining &&
3594 atoi(star_match[0]) == ics_gamenum)
3598 ics_user_moved = FALSE;
3603 /* Advance leftover_start past any newlines we find,
3604 so only partial lines can get reparsed */
3605 if (looking_at(buf, &i, "\n")) {
3606 prevColor = curColor;
3607 if (curColor != ColorNormal) {
3608 if (oldi > next_out) {
3609 SendToPlayer(&buf[next_out], oldi - next_out);
3612 Colorize(ColorNormal, FALSE);
3613 curColor = ColorNormal;
3615 if (started == STARTED_BOARD) {
3616 started = STARTED_NONE;
3617 parse[parse_pos] = NULLCHAR;
3618 ParseBoard12(parse);
3621 /* Send premove here */
3622 if (appData.premove) {
3624 if (currentMove == 0 &&
3625 gameMode == IcsPlayingWhite &&
3626 appData.premoveWhite) {
3627 sprintf(str, "%s\n", appData.premoveWhiteText);
3628 if (appData.debugMode)
3629 fprintf(debugFP, "Sending premove:\n");
3631 } else if (currentMove == 1 &&
3632 gameMode == IcsPlayingBlack &&
3633 appData.premoveBlack) {
3634 sprintf(str, "%s\n", appData.premoveBlackText);
3635 if (appData.debugMode)
3636 fprintf(debugFP, "Sending premove:\n");
3638 } else if (gotPremove) {
3640 ClearPremoveHighlights();
3641 if (appData.debugMode)
3642 fprintf(debugFP, "Sending premove:\n");
3643 UserMoveEvent(premoveFromX, premoveFromY,
3644 premoveToX, premoveToY,
3649 /* Usually suppress following prompt */
3650 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3651 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3652 if (looking_at(buf, &i, "*% ")) {
3653 savingComment = FALSE;
3658 } else if (started == STARTED_HOLDINGS) {
3660 char new_piece[MSG_SIZ];
3661 started = STARTED_NONE;
3662 parse[parse_pos] = NULLCHAR;
3663 if (appData.debugMode)
3664 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3665 parse, currentMove);
3666 if (sscanf(parse, " game %d", &gamenum) == 1) {
3667 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3668 if (gameInfo.variant == VariantNormal) {
3669 /* [HGM] We seem to switch variant during a game!
3670 * Presumably no holdings were displayed, so we have
3671 * to move the position two files to the right to
3672 * create room for them!
3674 VariantClass newVariant;
3675 switch(gameInfo.boardWidth) { // base guess on board width
3676 case 9: newVariant = VariantShogi; break;
3677 case 10: newVariant = VariantGreat; break;
3678 default: newVariant = VariantCrazyhouse; break;
3680 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3681 /* Get a move list just to see the header, which
3682 will tell us whether this is really bug or zh */
3683 if (ics_getting_history == H_FALSE) {
3684 ics_getting_history = H_REQUESTED;
3685 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3689 new_piece[0] = NULLCHAR;
3690 sscanf(parse, "game %d white [%s black [%s <- %s",
3691 &gamenum, white_holding, black_holding,
3693 white_holding[strlen(white_holding)-1] = NULLCHAR;
3694 black_holding[strlen(black_holding)-1] = NULLCHAR;
3695 /* [HGM] copy holdings to board holdings area */
3696 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3697 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3698 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3700 if (appData.zippyPlay && first.initDone) {
3701 ZippyHoldings(white_holding, black_holding,
3705 if (tinyLayout || smallLayout) {
3706 char wh[16], bh[16];
3707 PackHolding(wh, white_holding);
3708 PackHolding(bh, black_holding);
3709 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3710 gameInfo.white, gameInfo.black);
3712 sprintf(str, "%s [%s] vs. %s [%s]",
3713 gameInfo.white, white_holding,
3714 gameInfo.black, black_holding);
3716 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3717 DrawPosition(FALSE, boards[currentMove]);
3719 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3720 sscanf(parse, "game %d white [%s black [%s <- %s",
3721 &gamenum, white_holding, black_holding,
3723 white_holding[strlen(white_holding)-1] = NULLCHAR;
3724 black_holding[strlen(black_holding)-1] = NULLCHAR;
3725 /* [HGM] copy holdings to partner-board holdings area */
3726 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3727 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3728 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3729 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3730 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3733 /* Suppress following prompt */
3734 if (looking_at(buf, &i, "*% ")) {
3735 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3736 savingComment = FALSE;
3744 i++; /* skip unparsed character and loop back */
3747 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3748 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3749 // SendToPlayer(&buf[next_out], i - next_out);
3750 started != STARTED_HOLDINGS && leftover_start > next_out) {
3751 SendToPlayer(&buf[next_out], leftover_start - next_out);
3755 leftover_len = buf_len - leftover_start;
3756 /* if buffer ends with something we couldn't parse,
3757 reparse it after appending the next read */
3759 } else if (count == 0) {
3760 RemoveInputSource(isr);
3761 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3763 DisplayFatalError(_("Error reading from ICS"), error, 1);
3768 /* Board style 12 looks like this:
3770 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3772 * The "<12> " is stripped before it gets to this routine. The two
3773 * trailing 0's (flip state and clock ticking) are later addition, and
3774 * some chess servers may not have them, or may have only the first.
3775 * Additional trailing fields may be added in the future.
3778 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3780 #define RELATION_OBSERVING_PLAYED 0
3781 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3782 #define RELATION_PLAYING_MYMOVE 1
3783 #define RELATION_PLAYING_NOTMYMOVE -1
3784 #define RELATION_EXAMINING 2
3785 #define RELATION_ISOLATED_BOARD -3
3786 #define RELATION_STARTING_POSITION -4 /* FICS only */
3789 ParseBoard12(string)
3792 GameMode newGameMode;
3793 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3794 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3795 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3796 char to_play, board_chars[200];
3797 char move_str[500], str[500], elapsed_time[500];
3798 char black[32], white[32];
3800 int prevMove = currentMove;
3803 int fromX, fromY, toX, toY;
3805 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3806 char *bookHit = NULL; // [HGM] book
3807 Boolean weird = FALSE, reqFlag = FALSE;
3809 fromX = fromY = toX = toY = -1;
3813 if (appData.debugMode)
3814 fprintf(debugFP, _("Parsing board: %s\n"), string);
3816 move_str[0] = NULLCHAR;
3817 elapsed_time[0] = NULLCHAR;
3818 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3820 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3821 if(string[i] == ' ') { ranks++; files = 0; }
3823 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3826 for(j = 0; j <i; j++) board_chars[j] = string[j];
3827 board_chars[i] = '\0';
3830 n = sscanf(string, PATTERN, &to_play, &double_push,
3831 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3832 &gamenum, white, black, &relation, &basetime, &increment,
3833 &white_stren, &black_stren, &white_time, &black_time,
3834 &moveNum, str, elapsed_time, move_str, &ics_flip,
3838 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3839 DisplayError(str, 0);
3843 /* Convert the move number to internal form */
3844 moveNum = (moveNum - 1) * 2;
3845 if (to_play == 'B') moveNum++;
3846 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3847 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3853 case RELATION_OBSERVING_PLAYED:
3854 case RELATION_OBSERVING_STATIC:
3855 if (gamenum == -1) {
3856 /* Old ICC buglet */
3857 relation = RELATION_OBSERVING_STATIC;
3859 newGameMode = IcsObserving;
3861 case RELATION_PLAYING_MYMOVE:
3862 case RELATION_PLAYING_NOTMYMOVE:
3864 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3865 IcsPlayingWhite : IcsPlayingBlack;
3867 case RELATION_EXAMINING:
3868 newGameMode = IcsExamining;
3870 case RELATION_ISOLATED_BOARD:
3872 /* Just display this board. If user was doing something else,
3873 we will forget about it until the next board comes. */
3874 newGameMode = IcsIdle;
3876 case RELATION_STARTING_POSITION:
3877 newGameMode = gameMode;
3881 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3882 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3883 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3885 for (k = 0; k < ranks; k++) {
3886 for (j = 0; j < files; j++)
3887 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3888 if(gameInfo.holdingsWidth > 1) {
3889 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3890 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3893 CopyBoard(partnerBoard, board);
3894 if(toSqr = strchr(str, '/')) { // extract highlights from long move
3895 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3896 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3897 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3898 if(toSqr = strchr(str, '-')) {
3899 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3900 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3901 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3902 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3903 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3904 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3905 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3906 sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3907 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3908 DisplayMessage(partnerStatus, "");
3909 partnerBoardValid = TRUE;
3913 /* Modify behavior for initial board display on move listing
3916 switch (ics_getting_history) {
3920 case H_GOT_REQ_HEADER:
3921 case H_GOT_UNREQ_HEADER:
3922 /* This is the initial position of the current game */
3923 gamenum = ics_gamenum;
3924 moveNum = 0; /* old ICS bug workaround */
3925 if (to_play == 'B') {
3926 startedFromSetupPosition = TRUE;
3927 blackPlaysFirst = TRUE;
3929 if (forwardMostMove == 0) forwardMostMove = 1;
3930 if (backwardMostMove == 0) backwardMostMove = 1;
3931 if (currentMove == 0) currentMove = 1;
3933 newGameMode = gameMode;
3934 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3936 case H_GOT_UNWANTED_HEADER:
3937 /* This is an initial board that we don't want */
3939 case H_GETTING_MOVES:
3940 /* Should not happen */
3941 DisplayError(_("Error gathering move list: extra board"), 0);
3942 ics_getting_history = H_FALSE;
3946 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3947 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3948 /* [HGM] We seem to have switched variant unexpectedly
3949 * Try to guess new variant from board size
3951 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3952 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3953 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3954 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3955 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3956 if(!weird) newVariant = VariantNormal;
3957 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3958 /* Get a move list just to see the header, which
3959 will tell us whether this is really bug or zh */
3960 if (ics_getting_history == H_FALSE) {
3961 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3962 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3967 /* Take action if this is the first board of a new game, or of a
3968 different game than is currently being displayed. */
3969 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3970 relation == RELATION_ISOLATED_BOARD) {
3972 /* Forget the old game and get the history (if any) of the new one */
3973 if (gameMode != BeginningOfGame) {
3977 if (appData.autoRaiseBoard) BoardToTop();
3979 if (gamenum == -1) {
3980 newGameMode = IcsIdle;
3981 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3982 appData.getMoveList && !reqFlag) {
3983 /* Need to get game history */
3984 ics_getting_history = H_REQUESTED;
3985 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3989 /* Initially flip the board to have black on the bottom if playing
3990 black or if the ICS flip flag is set, but let the user change
3991 it with the Flip View button. */
3992 flipView = appData.autoFlipView ?
3993 (newGameMode == IcsPlayingBlack) || ics_flip :
3996 /* Done with values from previous mode; copy in new ones */
3997 gameMode = newGameMode;
3999 ics_gamenum = gamenum;
4000 if (gamenum == gs_gamenum) {
4001 int klen = strlen(gs_kind);
4002 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4003 sprintf(str, "ICS %s", gs_kind);
4004 gameInfo.event = StrSave(str);
4006 gameInfo.event = StrSave("ICS game");
4008 gameInfo.site = StrSave(appData.icsHost);
4009 gameInfo.date = PGNDate();
4010 gameInfo.round = StrSave("-");
4011 gameInfo.white = StrSave(white);
4012 gameInfo.black = StrSave(black);
4013 timeControl = basetime * 60 * 1000;
4015 timeIncrement = increment * 1000;
4016 movesPerSession = 0;
4017 gameInfo.timeControl = TimeControlTagValue();
4018 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4019 if (appData.debugMode) {
4020 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4021 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4022 setbuf(debugFP, NULL);
4025 gameInfo.outOfBook = NULL;
4027 /* Do we have the ratings? */
4028 if (strcmp(player1Name, white) == 0 &&
4029 strcmp(player2Name, black) == 0) {
4030 if (appData.debugMode)
4031 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4032 player1Rating, player2Rating);
4033 gameInfo.whiteRating = player1Rating;
4034 gameInfo.blackRating = player2Rating;
4035 } else if (strcmp(player2Name, white) == 0 &&
4036 strcmp(player1Name, black) == 0) {
4037 if (appData.debugMode)
4038 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4039 player2Rating, player1Rating);
4040 gameInfo.whiteRating = player2Rating;
4041 gameInfo.blackRating = player1Rating;
4043 player1Name[0] = player2Name[0] = NULLCHAR;
4045 /* Silence shouts if requested */
4046 if (appData.quietPlay &&
4047 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4048 SendToICS(ics_prefix);
4049 SendToICS("set shout 0\n");
4053 /* Deal with midgame name changes */
4055 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4056 if (gameInfo.white) free(gameInfo.white);
4057 gameInfo.white = StrSave(white);
4059 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4060 if (gameInfo.black) free(gameInfo.black);
4061 gameInfo.black = StrSave(black);
4065 /* Throw away game result if anything actually changes in examine mode */
4066 if (gameMode == IcsExamining && !newGame) {
4067 gameInfo.result = GameUnfinished;
4068 if (gameInfo.resultDetails != NULL) {
4069 free(gameInfo.resultDetails);
4070 gameInfo.resultDetails = NULL;
4074 /* In pausing && IcsExamining mode, we ignore boards coming
4075 in if they are in a different variation than we are. */
4076 if (pauseExamInvalid) return;
4077 if (pausing && gameMode == IcsExamining) {
4078 if (moveNum <= pauseExamForwardMostMove) {
4079 pauseExamInvalid = TRUE;
4080 forwardMostMove = pauseExamForwardMostMove;
4085 if (appData.debugMode) {
4086 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4088 /* Parse the board */
4089 for (k = 0; k < ranks; k++) {
4090 for (j = 0; j < files; j++)
4091 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4092 if(gameInfo.holdingsWidth > 1) {
4093 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4094 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4097 CopyBoard(boards[moveNum], board);
4098 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4100 startedFromSetupPosition =
4101 !CompareBoards(board, initialPosition);
4102 if(startedFromSetupPosition)
4103 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4106 /* [HGM] Set castling rights. Take the outermost Rooks,
4107 to make it also work for FRC opening positions. Note that board12
4108 is really defective for later FRC positions, as it has no way to
4109 indicate which Rook can castle if they are on the same side of King.
4110 For the initial position we grant rights to the outermost Rooks,
4111 and remember thos rights, and we then copy them on positions
4112 later in an FRC game. This means WB might not recognize castlings with
4113 Rooks that have moved back to their original position as illegal,
4114 but in ICS mode that is not its job anyway.
4116 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4117 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4119 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4120 if(board[0][i] == WhiteRook) j = i;
4121 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4122 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4123 if(board[0][i] == WhiteRook) j = i;
4124 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4125 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4126 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4127 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4128 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4129 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4130 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4132 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4133 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4134 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4135 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4136 if(board[BOARD_HEIGHT-1][k] == bKing)
4137 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4138 if(gameInfo.variant == VariantTwoKings) {
4139 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4140 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4141 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4144 r = boards[moveNum][CASTLING][0] = initialRights[0];
4145 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4146 r = boards[moveNum][CASTLING][1] = initialRights[1];
4147 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4148 r = boards[moveNum][CASTLING][3] = initialRights[3];
4149 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4150 r = boards[moveNum][CASTLING][4] = initialRights[4];
4151 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4152 /* wildcastle kludge: always assume King has rights */
4153 r = boards[moveNum][CASTLING][2] = initialRights[2];
4154 r = boards[moveNum][CASTLING][5] = initialRights[5];
4156 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4157 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4160 if (ics_getting_history == H_GOT_REQ_HEADER ||
4161 ics_getting_history == H_GOT_UNREQ_HEADER) {
4162 /* This was an initial position from a move list, not
4163 the current position */
4167 /* Update currentMove and known move number limits */
4168 newMove = newGame || moveNum > forwardMostMove;
4171 forwardMostMove = backwardMostMove = currentMove = moveNum;
4172 if (gameMode == IcsExamining && moveNum == 0) {
4173 /* Workaround for ICS limitation: we are not told the wild
4174 type when starting to examine a game. But if we ask for
4175 the move list, the move list header will tell us */
4176 ics_getting_history = H_REQUESTED;
4177 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4180 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4181 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4183 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4184 /* [HGM] applied this also to an engine that is silently watching */
4185 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4186 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4187 gameInfo.variant == currentlyInitializedVariant) {
4188 takeback = forwardMostMove - moveNum;
4189 for (i = 0; i < takeback; i++) {
4190 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4191 SendToProgram("undo\n", &first);
4196 forwardMostMove = moveNum;
4197 if (!pausing || currentMove > forwardMostMove)
4198 currentMove = forwardMostMove;
4200 /* New part of history that is not contiguous with old part */
4201 if (pausing && gameMode == IcsExamining) {
4202 pauseExamInvalid = TRUE;
4203 forwardMostMove = pauseExamForwardMostMove;
4206 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4208 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4209 // [HGM] when we will receive the move list we now request, it will be
4210 // fed to the engine from the first move on. So if the engine is not
4211 // in the initial position now, bring it there.
4212 InitChessProgram(&first, 0);
4215 ics_getting_history = H_REQUESTED;
4216 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4219 forwardMostMove = backwardMostMove = currentMove = moveNum;
4222 /* Update the clocks */
4223 if (strchr(elapsed_time, '.')) {
4225 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4226 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4228 /* Time is in seconds */
4229 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4230 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4235 if (appData.zippyPlay && newGame &&
4236 gameMode != IcsObserving && gameMode != IcsIdle &&
4237 gameMode != IcsExamining)
4238 ZippyFirstBoard(moveNum, basetime, increment);
4241 /* Put the move on the move list, first converting
4242 to canonical algebraic form. */
4244 if (appData.debugMode) {
4245 if (appData.debugMode) { int f = forwardMostMove;
4246 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4247 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4248 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4250 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4251 fprintf(debugFP, "moveNum = %d\n", moveNum);
4252 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4253 setbuf(debugFP, NULL);
4255 if (moveNum <= backwardMostMove) {
4256 /* We don't know what the board looked like before
4258 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4259 strcat(parseList[moveNum - 1], " ");
4260 strcat(parseList[moveNum - 1], elapsed_time);
4261 moveList[moveNum - 1][0] = NULLCHAR;
4262 } else if (strcmp(move_str, "none") == 0) {
4263 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4264 /* Again, we don't know what the board looked like;
4265 this is really the start of the game. */
4266 parseList[moveNum - 1][0] = NULLCHAR;
4267 moveList[moveNum - 1][0] = NULLCHAR;
4268 backwardMostMove = moveNum;
4269 startedFromSetupPosition = TRUE;
4270 fromX = fromY = toX = toY = -1;
4272 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4273 // So we parse the long-algebraic move string in stead of the SAN move
4274 int valid; char buf[MSG_SIZ], *prom;
4276 // str looks something like "Q/a1-a2"; kill the slash
4278 sprintf(buf, "%c%s", str[0], str+2);
4279 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4280 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4281 strcat(buf, prom); // long move lacks promo specification!
4282 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4283 if(appData.debugMode)
4284 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4285 safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
4287 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4288 &fromX, &fromY, &toX, &toY, &promoChar)
4289 || ParseOneMove(buf, moveNum - 1, &moveType,
4290 &fromX, &fromY, &toX, &toY, &promoChar);
4291 // end of long SAN patch
4293 (void) CoordsToAlgebraic(boards[moveNum - 1],
4294 PosFlags(moveNum - 1),
4295 fromY, fromX, toY, toX, promoChar,
4296 parseList[moveNum-1]);
4297 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4303 if(gameInfo.variant != VariantShogi)
4304 strcat(parseList[moveNum - 1], "+");
4307 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4308 strcat(parseList[moveNum - 1], "#");
4311 strcat(parseList[moveNum - 1], " ");
4312 strcat(parseList[moveNum - 1], elapsed_time);
4313 /* currentMoveString is set as a side-effect of ParseOneMove */
4314 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4315 strcat(moveList[moveNum - 1], "\n");
4317 /* Move from ICS was illegal!? Punt. */
4318 if (appData.debugMode) {
4319 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4320 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4322 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4323 strcat(parseList[moveNum - 1], " ");
4324 strcat(parseList[moveNum - 1], elapsed_time);
4325 moveList[moveNum - 1][0] = NULLCHAR;
4326 fromX = fromY = toX = toY = -1;
4329 if (appData.debugMode) {
4330 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4331 setbuf(debugFP, NULL);
4335 /* Send move to chess program (BEFORE animating it). */
4336 if (appData.zippyPlay && !newGame && newMove &&
4337 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4339 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4340 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4341 if (moveList[moveNum - 1][0] == NULLCHAR) {
4342 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4344 DisplayError(str, 0);
4346 if (first.sendTime) {
4347 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4349 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4350 if (firstMove && !bookHit) {
4352 if (first.useColors) {
4353 SendToProgram(gameMode == IcsPlayingWhite ?
4355 "black\ngo\n", &first);
4357 SendToProgram("go\n", &first);
4359 first.maybeThinking = TRUE;
4362 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4363 if (moveList[moveNum - 1][0] == NULLCHAR) {
4364 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4365 DisplayError(str, 0);
4367 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4368 SendMoveToProgram(moveNum - 1, &first);
4375 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4376 /* If move comes from a remote source, animate it. If it
4377 isn't remote, it will have already been animated. */
4378 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4379 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4381 if (!pausing && appData.highlightLastMove) {
4382 SetHighlights(fromX, fromY, toX, toY);
4386 /* Start the clocks */
4387 whiteFlag = blackFlag = FALSE;
4388 appData.clockMode = !(basetime == 0 && increment == 0);
4390 ics_clock_paused = TRUE;
4392 } else if (ticking == 1) {
4393 ics_clock_paused = FALSE;
4395 if (gameMode == IcsIdle ||
4396 relation == RELATION_OBSERVING_STATIC ||
4397 relation == RELATION_EXAMINING ||
4399 DisplayBothClocks();
4403 /* Display opponents and material strengths */
4404 if (gameInfo.variant != VariantBughouse &&
4405 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4406 if (tinyLayout || smallLayout) {
4407 if(gameInfo.variant == VariantNormal)
4408 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4409 gameInfo.white, white_stren, gameInfo.black, black_stren,
4410 basetime, increment);
4412 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4413 gameInfo.white, white_stren, gameInfo.black, black_stren,
4414 basetime, increment, (int) gameInfo.variant);
4416 if(gameInfo.variant == VariantNormal)
4417 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4418 gameInfo.white, white_stren, gameInfo.black, black_stren,
4419 basetime, increment);
4421 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4422 gameInfo.white, white_stren, gameInfo.black, black_stren,
4423 basetime, increment, VariantName(gameInfo.variant));
4426 if (appData.debugMode) {
4427 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4432 /* Display the board */
4433 if (!pausing && !appData.noGUI) {
4435 if (appData.premove)
4437 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4438 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4439 ClearPremoveHighlights();
4441 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4442 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4443 DrawPosition(j, boards[currentMove]);
4445 DisplayMove(moveNum - 1);
4446 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4447 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4448 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4449 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4453 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4455 if(bookHit) { // [HGM] book: simulate book reply
4456 static char bookMove[MSG_SIZ]; // a bit generous?
4458 programStats.nodes = programStats.depth = programStats.time =
4459 programStats.score = programStats.got_only_move = 0;
4460 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4462 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4463 strcat(bookMove, bookHit);
4464 HandleMachineMove(bookMove, &first);
4473 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4474 ics_getting_history = H_REQUESTED;
4475 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4481 AnalysisPeriodicEvent(force)
4484 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4485 && !force) || !appData.periodicUpdates)
4488 /* Send . command to Crafty to collect stats */
4489 SendToProgram(".\n", &first);
4491 /* Don't send another until we get a response (this makes
4492 us stop sending to old Crafty's which don't understand
4493 the "." command (sending illegal cmds resets node count & time,
4494 which looks bad)) */
4495 programStats.ok_to_send = 0;
4498 void ics_update_width(new_width)
4501 ics_printf("set width %d\n", new_width);
4505 SendMoveToProgram(moveNum, cps)
4507 ChessProgramState *cps;
4511 if (cps->useUsermove) {
4512 SendToProgram("usermove ", cps);
4516 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4517 int len = space - parseList[moveNum];
4518 memcpy(buf, parseList[moveNum], len);
4520 buf[len] = NULLCHAR;
4522 sprintf(buf, "%s\n", parseList[moveNum]);
4524 SendToProgram(buf, cps);
4526 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4527 AlphaRank(moveList[moveNum], 4);
4528 SendToProgram(moveList[moveNum], cps);
4529 AlphaRank(moveList[moveNum], 4); // and back
4531 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4532 * the engine. It would be nice to have a better way to identify castle
4534 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4535 && cps->useOOCastle) {
4536 int fromX = moveList[moveNum][0] - AAA;
4537 int fromY = moveList[moveNum][1] - ONE;
4538 int toX = moveList[moveNum][2] - AAA;
4539 int toY = moveList[moveNum][3] - ONE;
4540 if((boards[moveNum][fromY][fromX] == WhiteKing
4541 && boards[moveNum][toY][toX] == WhiteRook)
4542 || (boards[moveNum][fromY][fromX] == BlackKing
4543 && boards[moveNum][toY][toX] == BlackRook)) {
4544 if(toX > fromX) SendToProgram("O-O\n", cps);
4545 else SendToProgram("O-O-O\n", cps);
4547 else SendToProgram(moveList[moveNum], cps);
4549 else SendToProgram(moveList[moveNum], cps);
4550 /* End of additions by Tord */
4553 /* [HGM] setting up the opening has brought engine in force mode! */
4554 /* Send 'go' if we are in a mode where machine should play. */
4555 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4556 (gameMode == TwoMachinesPlay ||
4558 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4560 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4561 SendToProgram("go\n", cps);
4562 if (appData.debugMode) {
4563 fprintf(debugFP, "(extra)\n");
4566 setboardSpoiledMachineBlack = 0;
4570 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4572 int fromX, fromY, toX, toY;
4575 char user_move[MSG_SIZ];
4579 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4580 (int)moveType, fromX, fromY, toX, toY);
4581 DisplayError(user_move + strlen("say "), 0);
4583 case WhiteKingSideCastle:
4584 case BlackKingSideCastle:
4585 case WhiteQueenSideCastleWild:
4586 case BlackQueenSideCastleWild:
4588 case WhiteHSideCastleFR:
4589 case BlackHSideCastleFR:
4591 sprintf(user_move, "o-o\n");
4593 case WhiteQueenSideCastle:
4594 case BlackQueenSideCastle:
4595 case WhiteKingSideCastleWild:
4596 case BlackKingSideCastleWild:
4598 case WhiteASideCastleFR:
4599 case BlackASideCastleFR:
4601 sprintf(user_move, "o-o-o\n");
4603 case WhiteNonPromotion:
4604 case BlackNonPromotion:
4605 sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4607 case WhitePromotion:
4608 case BlackPromotion:
4609 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4610 sprintf(user_move, "%c%c%c%c=%c\n",
4611 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4612 PieceToChar(WhiteFerz));
4613 else if(gameInfo.variant == VariantGreat)
4614 sprintf(user_move, "%c%c%c%c=%c\n",
4615 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4616 PieceToChar(WhiteMan));
4618 sprintf(user_move, "%c%c%c%c=%c\n",
4619 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4624 sprintf(user_move, "%c@%c%c\n",
4625 ToUpper(PieceToChar((ChessSquare) fromX)),
4626 AAA + toX, ONE + toY);
4629 case WhiteCapturesEnPassant:
4630 case BlackCapturesEnPassant:
4631 case IllegalMove: /* could be a variant we don't quite understand */
4632 sprintf(user_move, "%c%c%c%c\n",
4633 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4636 SendToICS(user_move);
4637 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4638 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4643 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4644 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4645 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4646 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4647 DisplayError("You cannot do this while you are playing or observing", 0);
4650 if(gameMode != IcsExamining) { // is this ever not the case?
4651 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4653 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4654 sprintf(command, "match %s", ics_handle);
4655 } else { // on FICS we must first go to general examine mode
4656 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4658 if(gameInfo.variant != VariantNormal) {
4659 // try figure out wild number, as xboard names are not always valid on ICS
4660 for(i=1; i<=36; i++) {
4661 sprintf(buf, "wild/%d", i);
4662 if(StringToVariant(buf) == gameInfo.variant) break;
4664 if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4665 else if(i == 22) sprintf(buf, "%s fr\n", command);
4666 else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4667 } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4668 SendToICS(ics_prefix);
4670 if(startedFromSetupPosition || backwardMostMove != 0) {
4671 fen = PositionToFEN(backwardMostMove, NULL);
4672 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4673 sprintf(buf, "loadfen %s\n", fen);
4675 } else { // FICS: everything has to set by separate bsetup commands
4676 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4677 sprintf(buf, "bsetup fen %s\n", fen);
4679 if(!WhiteOnMove(backwardMostMove)) {
4680 SendToICS("bsetup tomove black\n");
4682 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4683 sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4685 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4686 sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4688 i = boards[backwardMostMove][EP_STATUS];
4689 if(i >= 0) { // set e.p.
4690 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4696 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4697 SendToICS("bsetup done\n"); // switch to normal examining.
4699 for(i = backwardMostMove; i<last; i++) {
4701 sprintf(buf, "%s\n", parseList[i]);
4704 SendToICS(ics_prefix);
4705 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4709 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4714 if (rf == DROP_RANK) {
4715 sprintf(move, "%c@%c%c\n",
4716 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4718 if (promoChar == 'x' || promoChar == NULLCHAR) {
4719 sprintf(move, "%c%c%c%c\n",
4720 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4722 sprintf(move, "%c%c%c%c%c\n",
4723 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4729 ProcessICSInitScript(f)
4734 while (fgets(buf, MSG_SIZ, f)) {
4735 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4742 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4744 AlphaRank(char *move, int n)
4746 // char *p = move, c; int x, y;
4748 if (appData.debugMode) {
4749 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4753 move[2]>='0' && move[2]<='9' &&
4754 move[3]>='a' && move[3]<='x' ) {
4756 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4757 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4759 if(move[0]>='0' && move[0]<='9' &&
4760 move[1]>='a' && move[1]<='x' &&
4761 move[2]>='0' && move[2]<='9' &&
4762 move[3]>='a' && move[3]<='x' ) {
4763 /* input move, Shogi -> normal */
4764 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4765 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4766 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4767 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4770 move[3]>='0' && move[3]<='9' &&
4771 move[2]>='a' && move[2]<='x' ) {
4773 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4774 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4777 move[0]>='a' && move[0]<='x' &&
4778 move[3]>='0' && move[3]<='9' &&
4779 move[2]>='a' && move[2]<='x' ) {
4780 /* output move, normal -> Shogi */
4781 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4782 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4783 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4784 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4785 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4787 if (appData.debugMode) {
4788 fprintf(debugFP, " out = '%s'\n", move);
4792 char yy_textstr[8000];
4794 /* Parser for moves from gnuchess, ICS, or user typein box */
4796 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4799 ChessMove *moveType;
4800 int *fromX, *fromY, *toX, *toY;
4803 if (appData.debugMode) {
4804 fprintf(debugFP, "move to parse: %s\n", move);
4806 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4808 switch (*moveType) {
4809 case WhitePromotion:
4810 case BlackPromotion:
4811 case WhiteNonPromotion:
4812 case BlackNonPromotion:
4814 case WhiteCapturesEnPassant:
4815 case BlackCapturesEnPassant:
4816 case WhiteKingSideCastle:
4817 case WhiteQueenSideCastle:
4818 case BlackKingSideCastle:
4819 case BlackQueenSideCastle:
4820 case WhiteKingSideCastleWild:
4821 case WhiteQueenSideCastleWild:
4822 case BlackKingSideCastleWild:
4823 case BlackQueenSideCastleWild:
4824 /* Code added by Tord: */
4825 case WhiteHSideCastleFR:
4826 case WhiteASideCastleFR:
4827 case BlackHSideCastleFR:
4828 case BlackASideCastleFR:
4829 /* End of code added by Tord */
4830 case IllegalMove: /* bug or odd chess variant */
4831 *fromX = currentMoveString[0] - AAA;
4832 *fromY = currentMoveString[1] - ONE;
4833 *toX = currentMoveString[2] - AAA;
4834 *toY = currentMoveString[3] - ONE;
4835 *promoChar = currentMoveString[4];
4836 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4837 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4838 if (appData.debugMode) {
4839 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4841 *fromX = *fromY = *toX = *toY = 0;
4844 if (appData.testLegality) {
4845 return (*moveType != IllegalMove);
4847 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4848 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4853 *fromX = *moveType == WhiteDrop ?
4854 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4855 (int) CharToPiece(ToLower(currentMoveString[0]));
4857 *toX = currentMoveString[2] - AAA;
4858 *toY = currentMoveString[3] - ONE;
4859 *promoChar = NULLCHAR;
4863 case ImpossibleMove:
4864 case (ChessMove) 0: /* end of file */
4873 if (appData.debugMode) {
4874 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4877 *fromX = *fromY = *toX = *toY = 0;
4878 *promoChar = NULLCHAR;
4885 ParsePV(char *pv, Boolean storeComments)
4886 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4887 int fromX, fromY, toX, toY; char promoChar;
4892 endPV = forwardMostMove;
4894 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4895 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4896 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4897 if(appData.debugMode){
4898 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
4900 if(!valid && nr == 0 &&
4901 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4902 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4903 // Hande case where played move is different from leading PV move
4904 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4905 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4906 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4907 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4908 endPV += 2; // if position different, keep this
4909 moveList[endPV-1][0] = fromX + AAA;
4910 moveList[endPV-1][1] = fromY + ONE;
4911 moveList[endPV-1][2] = toX + AAA;
4912 moveList[endPV-1][3] = toY + ONE;
4913 parseList[endPV-1][0] = NULLCHAR;
4914 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4917 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4918 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4919 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4920 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4921 valid++; // allow comments in PV
4925 if(endPV+1 > framePtr) break; // no space, truncate
4928 CopyBoard(boards[endPV], boards[endPV-1]);
4929 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4930 moveList[endPV-1][0] = fromX + AAA;
4931 moveList[endPV-1][1] = fromY + ONE;
4932 moveList[endPV-1][2] = toX + AAA;
4933 moveList[endPV-1][3] = toY + ONE;
4935 CoordsToAlgebraic(boards[endPV - 1],
4936 PosFlags(endPV - 1),
4937 fromY, fromX, toY, toX, promoChar,
4938 parseList[endPV - 1]);
4940 parseList[endPV-1][0] = NULLCHAR;
4942 currentMove = endPV;
4943 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4944 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4945 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4946 DrawPosition(TRUE, boards[currentMove]);
4949 static int lastX, lastY;
4952 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4957 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4958 lastX = x; lastY = y;
4959 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4961 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4962 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4964 do{ while(buf[index] && buf[index] != '\n') index++;
4965 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4967 ParsePV(buf+startPV, FALSE);
4968 *start = startPV; *end = index-1;
4973 LoadPV(int x, int y)
4974 { // called on right mouse click to load PV
4975 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4976 lastX = x; lastY = y;
4977 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4984 if(endPV < 0) return;
4986 currentMove = forwardMostMove;
4987 ClearPremoveHighlights();
4988 DrawPosition(TRUE, boards[currentMove]);
4992 MovePV(int x, int y, int h)
4993 { // step through PV based on mouse coordinates (called on mouse move)
4994 int margin = h>>3, step = 0;
4996 if(endPV < 0) return;
4997 // we must somehow check if right button is still down (might be released off board!)
4998 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4999 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5000 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5002 lastX = x; lastY = y;
5003 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5004 currentMove += step;
5005 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5006 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5007 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5008 DrawPosition(FALSE, boards[currentMove]);
5012 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5013 // All positions will have equal probability, but the current method will not provide a unique
5014 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5020 int piecesLeft[(int)BlackPawn];
5021 int seed, nrOfShuffles;
5023 void GetPositionNumber()
5024 { // sets global variable seed
5027 seed = appData.defaultFrcPosition;
5028 if(seed < 0) { // randomize based on time for negative FRC position numbers
5029 for(i=0; i<50; i++) seed += random();
5030 seed = random() ^ random() >> 8 ^ random() << 8;
5031 if(seed<0) seed = -seed;
5035 int put(Board board, int pieceType, int rank, int n, int shade)
5036 // put the piece on the (n-1)-th empty squares of the given shade
5040 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5041 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5042 board[rank][i] = (ChessSquare) pieceType;
5043 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5045 piecesLeft[pieceType]--;
5053 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5054 // calculate where the next piece goes, (any empty square), and put it there
5058 i = seed % squaresLeft[shade];
5059 nrOfShuffles *= squaresLeft[shade];
5060 seed /= squaresLeft[shade];
5061 put(board, pieceType, rank, i, shade);
5064 void AddTwoPieces(Board board, int pieceType, int rank)
5065 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5067 int i, n=squaresLeft[ANY], j=n-1, k;
5069 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5070 i = seed % k; // pick one
5073 while(i >= j) i -= j--;
5074 j = n - 1 - j; i += j;
5075 put(board, pieceType, rank, j, ANY);
5076 put(board, pieceType, rank, i, ANY);
5079 void SetUpShuffle(Board board, int number)
5083 GetPositionNumber(); nrOfShuffles = 1;
5085 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5086 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5087 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5089 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5091 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5092 p = (int) board[0][i];
5093 if(p < (int) BlackPawn) piecesLeft[p] ++;
5094 board[0][i] = EmptySquare;
5097 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5098 // shuffles restricted to allow normal castling put KRR first
5099 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5100 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5101 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5102 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5103 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5104 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5105 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5106 put(board, WhiteRook, 0, 0, ANY);
5107 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5110 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5111 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5112 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5113 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5114 while(piecesLeft[p] >= 2) {
5115 AddOnePiece(board, p, 0, LITE);
5116 AddOnePiece(board, p, 0, DARK);
5118 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5121 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5122 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5123 // but we leave King and Rooks for last, to possibly obey FRC restriction
5124 if(p == (int)WhiteRook) continue;
5125 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5126 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5129 // now everything is placed, except perhaps King (Unicorn) and Rooks
5131 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5132 // Last King gets castling rights
5133 while(piecesLeft[(int)WhiteUnicorn]) {
5134 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5135 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5138 while(piecesLeft[(int)WhiteKing]) {
5139 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5140 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5145 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5146 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5149 // Only Rooks can be left; simply place them all
5150 while(piecesLeft[(int)WhiteRook]) {
5151 i = put(board, WhiteRook, 0, 0, ANY);
5152 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5155 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5157 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5160 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5161 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5164 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5167 int SetCharTable( char *table, const char * map )
5168 /* [HGM] moved here from winboard.c because of its general usefulness */
5169 /* Basically a safe strcpy that uses the last character as King */
5171 int result = FALSE; int NrPieces;
5173 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5174 && NrPieces >= 12 && !(NrPieces&1)) {
5175 int i; /* [HGM] Accept even length from 12 to 34 */
5177 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5178 for( i=0; i<NrPieces/2-1; i++ ) {
5180 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5182 table[(int) WhiteKing] = map[NrPieces/2-1];
5183 table[(int) BlackKing] = map[NrPieces-1];
5191 void Prelude(Board board)
5192 { // [HGM] superchess: random selection of exo-pieces
5193 int i, j, k; ChessSquare p;
5194 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5196 GetPositionNumber(); // use FRC position number
5198 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5199 SetCharTable(pieceToChar, appData.pieceToCharTable);
5200 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5201 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5204 j = seed%4; seed /= 4;
5205 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5206 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5207 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5208 j = seed%3 + (seed%3 >= j); seed /= 3;
5209 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5210 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5211 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5212 j = seed%3; seed /= 3;
5213 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5214 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5215 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5216 j = seed%2 + (seed%2 >= j); seed /= 2;
5217 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5218 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5219 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5220 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5221 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5222 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5223 put(board, exoPieces[0], 0, 0, ANY);
5224 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5228 InitPosition(redraw)
5231 ChessSquare (* pieces)[BOARD_FILES];
5232 int i, j, pawnRow, overrule,
5233 oldx = gameInfo.boardWidth,
5234 oldy = gameInfo.boardHeight,
5235 oldh = gameInfo.holdingsWidth,
5236 oldv = gameInfo.variant;
5238 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5240 /* [AS] Initialize pv info list [HGM] and game status */
5242 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5243 pvInfoList[i].depth = 0;
5244 boards[i][EP_STATUS] = EP_NONE;
5245 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5248 initialRulePlies = 0; /* 50-move counter start */
5250 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5251 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5255 /* [HGM] logic here is completely changed. In stead of full positions */
5256 /* the initialized data only consist of the two backranks. The switch */
5257 /* selects which one we will use, which is than copied to the Board */
5258 /* initialPosition, which for the rest is initialized by Pawns and */
5259 /* empty squares. This initial position is then copied to boards[0], */
5260 /* possibly after shuffling, so that it remains available. */
5262 gameInfo.holdingsWidth = 0; /* default board sizes */
5263 gameInfo.boardWidth = 8;
5264 gameInfo.boardHeight = 8;
5265 gameInfo.holdingsSize = 0;
5266 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5267 for(i=0; i<BOARD_FILES-2; i++)
5268 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5269 initialPosition[EP_STATUS] = EP_NONE;
5270 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5271 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5272 SetCharTable(pieceNickName, appData.pieceNickNames);
5273 else SetCharTable(pieceNickName, "............");
5275 switch (gameInfo.variant) {
5276 case VariantFischeRandom:
5277 shuffleOpenings = TRUE;
5281 case VariantShatranj:
5282 pieces = ShatranjArray;
5283 nrCastlingRights = 0;
5284 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5287 pieces = makrukArray;
5288 nrCastlingRights = 0;
5289 startedFromSetupPosition = TRUE;
5290 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5292 case VariantTwoKings:
5293 pieces = twoKingsArray;
5295 case VariantCapaRandom:
5296 shuffleOpenings = TRUE;
5297 case VariantCapablanca:
5298 pieces = CapablancaArray;
5299 gameInfo.boardWidth = 10;
5300 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5303 pieces = GothicArray;
5304 gameInfo.boardWidth = 10;
5305 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5308 pieces = JanusArray;
5309 gameInfo.boardWidth = 10;
5310 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5311 nrCastlingRights = 6;
5312 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5313 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5314 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5315 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5316 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5317 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5320 pieces = FalconArray;
5321 gameInfo.boardWidth = 10;
5322 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5324 case VariantXiangqi:
5325 pieces = XiangqiArray;
5326 gameInfo.boardWidth = 9;
5327 gameInfo.boardHeight = 10;
5328 nrCastlingRights = 0;
5329 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5332 pieces = ShogiArray;
5333 gameInfo.boardWidth = 9;
5334 gameInfo.boardHeight = 9;
5335 gameInfo.holdingsSize = 7;
5336 nrCastlingRights = 0;
5337 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5339 case VariantCourier:
5340 pieces = CourierArray;
5341 gameInfo.boardWidth = 12;
5342 nrCastlingRights = 0;
5343 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5345 case VariantKnightmate:
5346 pieces = KnightmateArray;
5347 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5350 pieces = fairyArray;
5351 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5354 pieces = GreatArray;
5355 gameInfo.boardWidth = 10;
5356 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5357 gameInfo.holdingsSize = 8;
5361 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5362 gameInfo.holdingsSize = 8;
5363 startedFromSetupPosition = TRUE;
5365 case VariantCrazyhouse:
5366 case VariantBughouse:
5368 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5369 gameInfo.holdingsSize = 5;
5371 case VariantWildCastle:
5373 /* !!?shuffle with kings guaranteed to be on d or e file */
5374 shuffleOpenings = 1;
5376 case VariantNoCastle:
5378 nrCastlingRights = 0;
5379 /* !!?unconstrained back-rank shuffle */
5380 shuffleOpenings = 1;
5385 if(appData.NrFiles >= 0) {
5386 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5387 gameInfo.boardWidth = appData.NrFiles;
5389 if(appData.NrRanks >= 0) {
5390 gameInfo.boardHeight = appData.NrRanks;
5392 if(appData.holdingsSize >= 0) {
5393 i = appData.holdingsSize;
5394 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5395 gameInfo.holdingsSize = i;
5397 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5398 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5399 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5401 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5402 if(pawnRow < 1) pawnRow = 1;
5403 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5405 /* User pieceToChar list overrules defaults */
5406 if(appData.pieceToCharTable != NULL)
5407 SetCharTable(pieceToChar, appData.pieceToCharTable);
5409 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5411 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5412 s = (ChessSquare) 0; /* account holding counts in guard band */
5413 for( i=0; i<BOARD_HEIGHT; i++ )
5414 initialPosition[i][j] = s;
5416 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5417 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5418 initialPosition[pawnRow][j] = WhitePawn;
5419 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5420 if(gameInfo.variant == VariantXiangqi) {
5422 initialPosition[pawnRow][j] =
5423 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5424 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5425 initialPosition[2][j] = WhiteCannon;
5426 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5430 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5432 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5435 initialPosition[1][j] = WhiteBishop;
5436 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5438 initialPosition[1][j] = WhiteRook;
5439 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5442 if( nrCastlingRights == -1) {
5443 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5444 /* This sets default castling rights from none to normal corners */
5445 /* Variants with other castling rights must set them themselves above */
5446 nrCastlingRights = 6;
5448 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5449 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5450 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5451 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5452 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5453 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5456 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5457 if(gameInfo.variant == VariantGreat) { // promotion commoners
5458 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5459 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5460 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5461 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5463 if (appData.debugMode) {
5464 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5466 if(shuffleOpenings) {
5467 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5468 startedFromSetupPosition = TRUE;
5470 if(startedFromPositionFile) {
5471 /* [HGM] loadPos: use PositionFile for every new game */
5472 CopyBoard(initialPosition, filePosition);
5473 for(i=0; i<nrCastlingRights; i++)
5474 initialRights[i] = filePosition[CASTLING][i];
5475 startedFromSetupPosition = TRUE;
5478 CopyBoard(boards[0], initialPosition);
5480 if(oldx != gameInfo.boardWidth ||
5481 oldy != gameInfo.boardHeight ||
5482 oldh != gameInfo.holdingsWidth
5484 || oldv == VariantGothic || // For licensing popups
5485 gameInfo.variant == VariantGothic
5488 || oldv == VariantFalcon ||
5489 gameInfo.variant == VariantFalcon
5492 InitDrawingSizes(-2 ,0);
5495 DrawPosition(TRUE, boards[currentMove]);
5499 SendBoard(cps, moveNum)
5500 ChessProgramState *cps;
5503 char message[MSG_SIZ];
5505 if (cps->useSetboard) {
5506 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5507 sprintf(message, "setboard %s\n", fen);
5508 SendToProgram(message, cps);
5514 /* Kludge to set black to move, avoiding the troublesome and now
5515 * deprecated "black" command.
5517 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5519 SendToProgram("edit\n", cps);
5520 SendToProgram("#\n", cps);
5521 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5522 bp = &boards[moveNum][i][BOARD_LEFT];
5523 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5524 if ((int) *bp < (int) BlackPawn) {
5525 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5527 if(message[0] == '+' || message[0] == '~') {
5528 sprintf(message, "%c%c%c+\n",
5529 PieceToChar((ChessSquare)(DEMOTED *bp)),
5532 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5533 message[1] = BOARD_RGHT - 1 - j + '1';
5534 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5536 SendToProgram(message, cps);
5541 SendToProgram("c\n", cps);
5542 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5543 bp = &boards[moveNum][i][BOARD_LEFT];
5544 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5545 if (((int) *bp != (int) EmptySquare)
5546 && ((int) *bp >= (int) BlackPawn)) {
5547 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5549 if(message[0] == '+' || message[0] == '~') {
5550 sprintf(message, "%c%c%c+\n",
5551 PieceToChar((ChessSquare)(DEMOTED *bp)),
5554 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5555 message[1] = BOARD_RGHT - 1 - j + '1';
5556 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5558 SendToProgram(message, cps);
5563 SendToProgram(".\n", cps);
5565 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5568 static int autoQueen; // [HGM] oneclick
5571 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5573 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5574 /* [HGM] add Shogi promotions */
5575 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5580 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5581 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5583 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5584 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5587 piece = boards[currentMove][fromY][fromX];
5588 if(gameInfo.variant == VariantShogi) {
5589 promotionZoneSize = BOARD_HEIGHT/3;
5590 highestPromotingPiece = (int)WhiteFerz;
5591 } else if(gameInfo.variant == VariantMakruk) {
5592 promotionZoneSize = 3;
5595 // next weed out all moves that do not touch the promotion zone at all
5596 if((int)piece >= BlackPawn) {
5597 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5599 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5601 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5602 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5605 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5607 // weed out mandatory Shogi promotions
5608 if(gameInfo.variant == VariantShogi) {
5609 if(piece >= BlackPawn) {
5610 if(toY == 0 && piece == BlackPawn ||
5611 toY == 0 && piece == BlackQueen ||
5612 toY <= 1 && piece == BlackKnight) {
5617 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5618 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5619 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5626 // weed out obviously illegal Pawn moves
5627 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5628 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5629 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5630 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5631 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5632 // note we are not allowed to test for valid (non-)capture, due to premove
5635 // we either have a choice what to promote to, or (in Shogi) whether to promote
5636 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5637 *promoChoice = PieceToChar(BlackFerz); // no choice
5640 if(autoQueen) { // predetermined
5641 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5642 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5643 else *promoChoice = PieceToChar(BlackQueen);
5647 // suppress promotion popup on illegal moves that are not premoves
5648 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5649 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5650 if(appData.testLegality && !premove) {
5651 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5652 fromY, fromX, toY, toX, NULLCHAR);
5653 if(moveType != WhitePromotion && moveType != BlackPromotion)
5661 InPalace(row, column)
5663 { /* [HGM] for Xiangqi */
5664 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5665 column < (BOARD_WIDTH + 4)/2 &&
5666 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5671 PieceForSquare (x, y)
5675 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5678 return boards[currentMove][y][x];
5682 OKToStartUserMove(x, y)
5685 ChessSquare from_piece;
5688 if (matchMode) return FALSE;
5689 if (gameMode == EditPosition) return TRUE;
5691 if (x >= 0 && y >= 0)
5692 from_piece = boards[currentMove][y][x];
5694 from_piece = EmptySquare;
5696 if (from_piece == EmptySquare) return FALSE;
5698 white_piece = (int)from_piece >= (int)WhitePawn &&
5699 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5702 case PlayFromGameFile:
5704 case TwoMachinesPlay:
5712 case MachinePlaysWhite:
5713 case IcsPlayingBlack:
5714 if (appData.zippyPlay) return FALSE;
5716 DisplayMoveError(_("You are playing Black"));
5721 case MachinePlaysBlack:
5722 case IcsPlayingWhite:
5723 if (appData.zippyPlay) return FALSE;
5725 DisplayMoveError(_("You are playing White"));
5731 if (!white_piece && WhiteOnMove(currentMove)) {
5732 DisplayMoveError(_("It is White's turn"));
5735 if (white_piece && !WhiteOnMove(currentMove)) {
5736 DisplayMoveError(_("It is Black's turn"));
5739 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5740 /* Editing correspondence game history */
5741 /* Could disallow this or prompt for confirmation */
5746 case BeginningOfGame:
5747 if (appData.icsActive) return FALSE;
5748 if (!appData.noChessProgram) {
5750 DisplayMoveError(_("You are playing White"));
5757 if (!white_piece && WhiteOnMove(currentMove)) {
5758 DisplayMoveError(_("It is White's turn"));
5761 if (white_piece && !WhiteOnMove(currentMove)) {
5762 DisplayMoveError(_("It is Black's turn"));
5771 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5772 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5773 && gameMode != AnalyzeFile && gameMode != Training) {
5774 DisplayMoveError(_("Displayed position is not current"));
5781 OnlyMove(int *x, int *y, Boolean captures) {
5782 DisambiguateClosure cl;
5783 if (appData.zippyPlay) return FALSE;
5785 case MachinePlaysBlack:
5786 case IcsPlayingWhite:
5787 case BeginningOfGame:
5788 if(!WhiteOnMove(currentMove)) return FALSE;
5790 case MachinePlaysWhite:
5791 case IcsPlayingBlack:
5792 if(WhiteOnMove(currentMove)) return FALSE;
5797 cl.pieceIn = EmptySquare;
5802 cl.promoCharIn = NULLCHAR;
5803 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5804 if( cl.kind == NormalMove ||
5805 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5806 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5807 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5814 if(cl.kind != ImpossibleMove) return FALSE;
5815 cl.pieceIn = EmptySquare;
5820 cl.promoCharIn = NULLCHAR;
5821 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5822 if( cl.kind == NormalMove ||
5823 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5824 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5825 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5830 autoQueen = TRUE; // act as if autoQueen on when we click to-square
5836 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5837 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5838 int lastLoadGameUseList = FALSE;
5839 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5840 ChessMove lastLoadGameStart = (ChessMove) 0;
5843 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5844 int fromX, fromY, toX, toY;
5848 ChessSquare pdown, pup;
5850 /* Check if the user is playing in turn. This is complicated because we
5851 let the user "pick up" a piece before it is his turn. So the piece he
5852 tried to pick up may have been captured by the time he puts it down!
5853 Therefore we use the color the user is supposed to be playing in this
5854 test, not the color of the piece that is currently on the starting
5855 square---except in EditGame mode, where the user is playing both
5856 sides; fortunately there the capture race can't happen. (It can
5857 now happen in IcsExamining mode, but that's just too bad. The user
5858 will get a somewhat confusing message in that case.)
5862 case PlayFromGameFile:
5864 case TwoMachinesPlay:
5868 /* We switched into a game mode where moves are not accepted,
5869 perhaps while the mouse button was down. */
5872 case MachinePlaysWhite:
5873 /* User is moving for Black */
5874 if (WhiteOnMove(currentMove)) {
5875 DisplayMoveError(_("It is White's turn"));
5880 case MachinePlaysBlack:
5881 /* User is moving for White */
5882 if (!WhiteOnMove(currentMove)) {
5883 DisplayMoveError(_("It is Black's turn"));
5890 case BeginningOfGame:
5893 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5894 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5895 /* User is moving for Black */
5896 if (WhiteOnMove(currentMove)) {
5897 DisplayMoveError(_("It is White's turn"));
5901 /* User is moving for White */
5902 if (!WhiteOnMove(currentMove)) {
5903 DisplayMoveError(_("It is Black's turn"));
5909 case IcsPlayingBlack:
5910 /* User is moving for Black */
5911 if (WhiteOnMove(currentMove)) {
5912 if (!appData.premove) {
5913 DisplayMoveError(_("It is White's turn"));
5914 } else if (toX >= 0 && toY >= 0) {
5917 premoveFromX = fromX;
5918 premoveFromY = fromY;
5919 premovePromoChar = promoChar;
5921 if (appData.debugMode)
5922 fprintf(debugFP, "Got premove: fromX %d,"
5923 "fromY %d, toX %d, toY %d\n",
5924 fromX, fromY, toX, toY);
5930 case IcsPlayingWhite:
5931 /* User is moving for White */
5932 if (!WhiteOnMove(currentMove)) {
5933 if (!appData.premove) {
5934 DisplayMoveError(_("It is Black's turn"));
5935 } else if (toX >= 0 && toY >= 0) {
5938 premoveFromX = fromX;
5939 premoveFromY = fromY;
5940 premovePromoChar = promoChar;
5942 if (appData.debugMode)
5943 fprintf(debugFP, "Got premove: fromX %d,"
5944 "fromY %d, toX %d, toY %d\n",
5945 fromX, fromY, toX, toY);
5955 /* EditPosition, empty square, or different color piece;
5956 click-click move is possible */
5957 if (toX == -2 || toY == -2) {
5958 boards[0][fromY][fromX] = EmptySquare;
5959 DrawPosition(FALSE, boards[currentMove]);
5961 } else if (toX >= 0 && toY >= 0) {
5962 boards[0][toY][toX] = boards[0][fromY][fromX];
5963 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5964 if(boards[0][fromY][0] != EmptySquare) {
5965 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5966 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5969 if(fromX == BOARD_RGHT+1) {
5970 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5971 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5972 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5975 boards[0][fromY][fromX] = EmptySquare;
5976 DrawPosition(FALSE, boards[currentMove]);
5982 if(toX < 0 || toY < 0) return;
5983 pdown = boards[currentMove][fromY][fromX];
5984 pup = boards[currentMove][toY][toX];
5986 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
5987 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5988 if( pup != EmptySquare ) return;
5989 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5990 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5991 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5992 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5993 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5994 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5995 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5999 /* [HGM] always test for legality, to get promotion info */
6000 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6001 fromY, fromX, toY, toX, promoChar);
6002 /* [HGM] but possibly ignore an IllegalMove result */
6003 if (appData.testLegality) {
6004 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6005 DisplayMoveError(_("Illegal move"));
6010 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6013 /* Common tail of UserMoveEvent and DropMenuEvent */
6015 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6017 int fromX, fromY, toX, toY;
6018 /*char*/int promoChar;
6022 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6023 // [HGM] superchess: suppress promotions to non-available piece
6024 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6025 if(WhiteOnMove(currentMove)) {
6026 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6028 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6032 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6033 move type in caller when we know the move is a legal promotion */
6034 if(moveType == NormalMove && promoChar)
6035 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6037 /* [HGM] <popupFix> The following if has been moved here from
6038 UserMoveEvent(). Because it seemed to belong here (why not allow
6039 piece drops in training games?), and because it can only be
6040 performed after it is known to what we promote. */
6041 if (gameMode == Training) {
6042 /* compare the move played on the board to the next move in the
6043 * game. If they match, display the move and the opponent's response.
6044 * If they don't match, display an error message.
6048 CopyBoard(testBoard, boards[currentMove]);
6049 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6051 if (CompareBoards(testBoard, boards[currentMove+1])) {
6052 ForwardInner(currentMove+1);
6054 /* Autoplay the opponent's response.
6055 * if appData.animate was TRUE when Training mode was entered,
6056 * the response will be animated.
6058 saveAnimate = appData.animate;
6059 appData.animate = animateTraining;
6060 ForwardInner(currentMove+1);
6061 appData.animate = saveAnimate;
6063 /* check for the end of the game */
6064 if (currentMove >= forwardMostMove) {
6065 gameMode = PlayFromGameFile;
6067 SetTrainingModeOff();
6068 DisplayInformation(_("End of game"));
6071 DisplayError(_("Incorrect move"), 0);
6076 /* Ok, now we know that the move is good, so we can kill
6077 the previous line in Analysis Mode */
6078 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6079 && currentMove < forwardMostMove) {
6080 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6083 /* If we need the chess program but it's dead, restart it */
6084 ResurrectChessProgram();
6086 /* A user move restarts a paused game*/
6090 thinkOutput[0] = NULLCHAR;
6092 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6094 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6096 if (gameMode == BeginningOfGame) {
6097 if (appData.noChessProgram) {
6098 gameMode = EditGame;
6102 gameMode = MachinePlaysBlack;
6105 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6107 if (first.sendName) {
6108 sprintf(buf, "name %s\n", gameInfo.white);
6109 SendToProgram(buf, &first);
6116 /* Relay move to ICS or chess engine */
6117 if (appData.icsActive) {
6118 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6119 gameMode == IcsExamining) {
6120 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6121 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6123 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6125 // also send plain move, in case ICS does not understand atomic claims
6126 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6130 if (first.sendTime && (gameMode == BeginningOfGame ||
6131 gameMode == MachinePlaysWhite ||
6132 gameMode == MachinePlaysBlack)) {
6133 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6135 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6136 // [HGM] book: if program might be playing, let it use book
6137 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6138 first.maybeThinking = TRUE;
6139 } else SendMoveToProgram(forwardMostMove-1, &first);
6140 if (currentMove == cmailOldMove + 1) {
6141 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6145 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6149 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6155 if (WhiteOnMove(currentMove)) {
6156 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6158 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6162 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6167 case MachinePlaysBlack:
6168 case MachinePlaysWhite:
6169 /* disable certain menu options while machine is thinking */
6170 SetMachineThinkingEnables();
6177 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6179 if(bookHit) { // [HGM] book: simulate book reply
6180 static char bookMove[MSG_SIZ]; // a bit generous?
6182 programStats.nodes = programStats.depth = programStats.time =
6183 programStats.score = programStats.got_only_move = 0;
6184 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6186 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6187 strcat(bookMove, bookHit);
6188 HandleMachineMove(bookMove, &first);
6194 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6201 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6202 Markers *m = (Markers *) closure;
6203 if(rf == fromY && ff == fromX)
6204 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6205 || kind == WhiteCapturesEnPassant
6206 || kind == BlackCapturesEnPassant);
6207 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6211 MarkTargetSquares(int clear)
6214 if(!appData.markers || !appData.highlightDragging ||
6215 !appData.testLegality || gameMode == EditPosition) return;
6217 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6220 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6221 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6222 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6224 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6227 DrawPosition(TRUE, NULL);
6230 void LeftClick(ClickType clickType, int xPix, int yPix)
6233 Boolean saveAnimate;
6234 static int second = 0, promotionChoice = 0, dragging = 0;
6235 char promoChoice = NULLCHAR;
6237 if(appData.seekGraph && appData.icsActive && loggedOn &&
6238 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6239 SeekGraphClick(clickType, xPix, yPix, 0);
6243 if (clickType == Press) ErrorPopDown();
6244 MarkTargetSquares(1);
6246 x = EventToSquare(xPix, BOARD_WIDTH);
6247 y = EventToSquare(yPix, BOARD_HEIGHT);
6248 if (!flipView && y >= 0) {
6249 y = BOARD_HEIGHT - 1 - y;
6251 if (flipView && x >= 0) {
6252 x = BOARD_WIDTH - 1 - x;
6255 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6256 if(clickType == Release) return; // ignore upclick of click-click destination
6257 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6258 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6259 if(gameInfo.holdingsWidth &&
6260 (WhiteOnMove(currentMove)
6261 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6262 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6263 // click in right holdings, for determining promotion piece
6264 ChessSquare p = boards[currentMove][y][x];
6265 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6266 if(p != EmptySquare) {
6267 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6272 DrawPosition(FALSE, boards[currentMove]);
6276 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6277 if(clickType == Press
6278 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6279 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6280 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6283 autoQueen = appData.alwaysPromoteToQueen;
6286 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6287 if (clickType == Press) {
6289 if (OKToStartUserMove(x, y)) {
6293 MarkTargetSquares(0);
6294 DragPieceBegin(xPix, yPix); dragging = 1;
6295 if (appData.highlightDragging) {
6296 SetHighlights(x, y, -1, -1);
6299 } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6300 DragPieceEnd(xPix, yPix); dragging = 0;
6301 DrawPosition(FALSE, NULL);
6308 if (clickType == Press && gameMode != EditPosition) {
6313 // ignore off-board to clicks
6314 if(y < 0 || x < 0) return;
6316 /* Check if clicking again on the same color piece */
6317 fromP = boards[currentMove][fromY][fromX];
6318 toP = boards[currentMove][y][x];
6319 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6320 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6321 WhitePawn <= toP && toP <= WhiteKing &&
6322 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6323 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6324 (BlackPawn <= fromP && fromP <= BlackKing &&
6325 BlackPawn <= toP && toP <= BlackKing &&
6326 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6327 !(fromP == BlackKing && toP == BlackRook && frc))) {
6328 /* Clicked again on same color piece -- changed his mind */
6329 second = (x == fromX && y == fromY);
6330 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6331 if (appData.highlightDragging) {
6332 SetHighlights(x, y, -1, -1);
6336 if (OKToStartUserMove(x, y)) {
6338 fromY = y; dragging = 1;
6339 MarkTargetSquares(0);
6340 DragPieceBegin(xPix, yPix);
6345 // ignore clicks on holdings
6346 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6349 if (clickType == Release && x == fromX && y == fromY) {
6350 DragPieceEnd(xPix, yPix); dragging = 0;
6351 if (appData.animateDragging) {
6352 /* Undo animation damage if any */
6353 DrawPosition(FALSE, NULL);
6356 /* Second up/down in same square; just abort move */
6361 ClearPremoveHighlights();
6363 /* First upclick in same square; start click-click mode */
6364 SetHighlights(x, y, -1, -1);
6369 /* we now have a different from- and (possibly off-board) to-square */
6370 /* Completed move */
6373 saveAnimate = appData.animate;
6374 if (clickType == Press) {
6375 /* Finish clickclick move */
6376 if (appData.animate || appData.highlightLastMove) {
6377 SetHighlights(fromX, fromY, toX, toY);
6382 /* Finish drag move */
6383 if (appData.highlightLastMove) {
6384 SetHighlights(fromX, fromY, toX, toY);
6388 DragPieceEnd(xPix, yPix); dragging = 0;
6389 /* Don't animate move and drag both */
6390 appData.animate = FALSE;
6393 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6394 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6395 ChessSquare piece = boards[currentMove][fromY][fromX];
6396 if(gameMode == EditPosition && piece != EmptySquare &&
6397 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6400 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6401 n = PieceToNumber(piece - (int)BlackPawn);
6402 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6403 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6404 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6406 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6407 n = PieceToNumber(piece);
6408 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6409 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6410 boards[currentMove][n][BOARD_WIDTH-2]++;
6412 boards[currentMove][fromY][fromX] = EmptySquare;
6416 DrawPosition(TRUE, boards[currentMove]);
6420 // off-board moves should not be highlighted
6421 if(x < 0 || x < 0) ClearHighlights();
6423 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6424 SetHighlights(fromX, fromY, toX, toY);
6425 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6426 // [HGM] super: promotion to captured piece selected from holdings
6427 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6428 promotionChoice = TRUE;
6429 // kludge follows to temporarily execute move on display, without promoting yet
6430 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6431 boards[currentMove][toY][toX] = p;
6432 DrawPosition(FALSE, boards[currentMove]);
6433 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6434 boards[currentMove][toY][toX] = q;
6435 DisplayMessage("Click in holdings to choose piece", "");
6440 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6441 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6442 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6445 appData.animate = saveAnimate;
6446 if (appData.animate || appData.animateDragging) {
6447 /* Undo animation damage if needed */
6448 DrawPosition(FALSE, NULL);
6452 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6453 { // front-end-free part taken out of PieceMenuPopup
6454 int whichMenu; int xSqr, ySqr;
6456 if(seekGraphUp) { // [HGM] seekgraph
6457 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6458 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6462 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6463 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6464 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6465 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6466 if(action == Press) {
6467 originalFlip = flipView;
6468 flipView = !flipView; // temporarily flip board to see game from partners perspective
6469 DrawPosition(TRUE, partnerBoard);
6470 DisplayMessage(partnerStatus, "");
6472 } else if(action == Release) {
6473 flipView = originalFlip;
6474 DrawPosition(TRUE, boards[currentMove]);
6480 xSqr = EventToSquare(x, BOARD_WIDTH);
6481 ySqr = EventToSquare(y, BOARD_HEIGHT);
6482 if (action == Release) UnLoadPV(); // [HGM] pv
6483 if (action != Press) return -2; // return code to be ignored
6486 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6488 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6489 if (xSqr < 0 || ySqr < 0) return -1;
\r
6490 whichMenu = 0; // edit-position menu
6493 if(!appData.icsEngineAnalyze) return -1;
6494 case IcsPlayingWhite:
6495 case IcsPlayingBlack:
6496 if(!appData.zippyPlay) goto noZip;
6499 case MachinePlaysWhite:
6500 case MachinePlaysBlack:
6501 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6502 if (!appData.dropMenu) {
6504 return 2; // flag front-end to grab mouse events
6506 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6507 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6510 if (xSqr < 0 || ySqr < 0) return -1;
6511 if (!appData.dropMenu || appData.testLegality &&
6512 gameInfo.variant != VariantBughouse &&
6513 gameInfo.variant != VariantCrazyhouse) return -1;
6514 whichMenu = 1; // drop menu
6520 if (((*fromX = xSqr) < 0) ||
6521 ((*fromY = ySqr) < 0)) {
6522 *fromX = *fromY = -1;
6526 *fromX = BOARD_WIDTH - 1 - *fromX;
6528 *fromY = BOARD_HEIGHT - 1 - *fromY;
6533 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6535 // char * hint = lastHint;
6536 FrontEndProgramStats stats;
6538 stats.which = cps == &first ? 0 : 1;
6539 stats.depth = cpstats->depth;
6540 stats.nodes = cpstats->nodes;
6541 stats.score = cpstats->score;
6542 stats.time = cpstats->time;
6543 stats.pv = cpstats->movelist;
6544 stats.hint = lastHint;
6545 stats.an_move_index = 0;
6546 stats.an_move_count = 0;
6548 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6549 stats.hint = cpstats->move_name;
6550 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6551 stats.an_move_count = cpstats->nr_moves;
6554 if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
6556 SetProgramStats( &stats );
6560 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6561 { // count all piece types
6563 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6564 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6565 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6568 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6569 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6570 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6571 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6572 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
6573 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6578 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6580 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6581 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6583 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6584 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6585 if(myPawns == 2 && nMine == 3) // KPP
6586 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6587 if(myPawns == 1 && nMine == 2) // KP
6588 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6589 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6590 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6591 if(myPawns) return FALSE;
6592 if(pCnt[WhiteRook+side])
6593 return pCnt[BlackRook-side] ||
6594 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6595 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6596 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6597 if(pCnt[WhiteCannon+side]) {
6598 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6599 return majorDefense || pCnt[BlackAlfil-side] >= 2;
6601 if(pCnt[WhiteKnight+side])
6602 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6607 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6609 VariantClass v = gameInfo.variant;
6611 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6612 if(v == VariantShatranj) return TRUE; // always winnable through baring
6613 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6614 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6616 if(v == VariantXiangqi) {
6617 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6619 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6620 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6621 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6622 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6623 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6624 if(stale) // we have at least one last-rank P plus perhaps C
6625 return majors // KPKX
6626 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6628 return pCnt[WhiteFerz+side] // KCAK
6629 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6630 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6631 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6633 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6634 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6636 if(nMine == 1) return FALSE; // bare King
6637 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
6638 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6639 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6640 // by now we have King + 1 piece (or multiple Bishops on the same color)
6641 if(pCnt[WhiteKnight+side])
6642 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6643 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6644 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6646 return (pCnt[BlackKnight-side]); // KBKN, KFKN
6647 if(pCnt[WhiteAlfil+side])
6648 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6649 if(pCnt[WhiteWazir+side])
6650 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6657 Adjudicate(ChessProgramState *cps)
6658 { // [HGM] some adjudications useful with buggy engines
6659 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6660 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6661 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6662 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6663 int k, count = 0; static int bare = 1;
6664 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6665 Boolean canAdjudicate = !appData.icsActive;
6667 // most tests only when we understand the game, i.e. legality-checking on
6668 if( appData.testLegality )
6669 { /* [HGM] Some more adjudications for obstinate engines */
6670 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6671 static int moveCount = 6;
6673 char *reason = NULL;
6675 /* Count what is on board. */
6676 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6678 /* Some material-based adjudications that have to be made before stalemate test */
6679 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6680 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6681 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6682 if(canAdjudicate && appData.checkMates) {
6684 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6685 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6686 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6687 "Xboard adjudication: King destroyed", GE_XBOARD );
6692 /* Bare King in Shatranj (loses) or Losers (wins) */
6693 if( nrW == 1 || nrB == 1) {
6694 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6695 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6696 if(canAdjudicate && appData.checkMates) {
6698 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6699 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6700 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6701 "Xboard adjudication: Bare king", GE_XBOARD );
6705 if( gameInfo.variant == VariantShatranj && --bare < 0)
6707 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6708 if(canAdjudicate && appData.checkMates) {
6709 /* but only adjudicate if adjudication enabled */
6711 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6712 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6713 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6714 "Xboard adjudication: Bare king", GE_XBOARD );
6721 // don't wait for engine to announce game end if we can judge ourselves
6722 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6724 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6725 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6726 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6727 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6730 reason = "Xboard adjudication: 3rd check";
6731 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6741 reason = "Xboard adjudication: Stalemate";
6742 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6743 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6744 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6745 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6746 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6747 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6748 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6749 EP_CHECKMATE : EP_WINS);
6750 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6751 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6755 reason = "Xboard adjudication: Checkmate";
6756 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6760 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6762 result = GameIsDrawn; break;
6764 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6766 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6768 result = (ChessMove) 0;
6770 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6772 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6773 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6774 GameEnds( result, reason, GE_XBOARD );
6778 /* Next absolutely insufficient mating material. */
6779 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6780 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6781 { /* includes KBK, KNK, KK of KBKB with like Bishops */
6783 /* always flag draws, for judging claims */
6784 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6786 if(canAdjudicate && appData.materialDraws) {
6787 /* but only adjudicate them if adjudication enabled */
6788 if(engineOpponent) {
6789 SendToProgram("force\n", engineOpponent); // suppress reply
6790 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6792 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6793 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6798 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6799 if(gameInfo.variant == VariantXiangqi ?
6800 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6802 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6803 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
6804 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
6805 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6807 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6808 { /* if the first 3 moves do not show a tactical win, declare draw */
6809 if(engineOpponent) {
6810 SendToProgram("force\n", engineOpponent); // suppress reply
6811 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6813 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6814 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6817 } else moveCount = 6;
6819 if (appData.debugMode) { int i;
6820 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6821 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6822 appData.drawRepeats);
6823 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6824 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6828 // Repetition draws and 50-move rule can be applied independently of legality testing
6830 /* Check for rep-draws */
6832 for(k = forwardMostMove-2;
6833 k>=backwardMostMove && k>=forwardMostMove-100 &&
6834 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6835 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6838 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6839 /* compare castling rights */
6840 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6841 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6842 rights++; /* King lost rights, while rook still had them */
6843 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6844 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6845 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6846 rights++; /* but at least one rook lost them */
6848 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6849 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6851 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6852 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6853 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6856 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6857 && appData.drawRepeats > 1) {
6858 /* adjudicate after user-specified nr of repeats */
6859 int result = GameIsDrawn;
6860 char *details = "XBoard adjudication: repetition draw";
6861 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6862 // [HGM] xiangqi: check for forbidden perpetuals
6863 int m, ourPerpetual = 1, hisPerpetual = 1;
6864 for(m=forwardMostMove; m>k; m-=2) {
6865 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6866 ourPerpetual = 0; // the current mover did not always check
6867 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6868 hisPerpetual = 0; // the opponent did not always check
6870 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6871 ourPerpetual, hisPerpetual);
6872 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6873 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6874 details = "Xboard adjudication: perpetual checking";
6876 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6877 break; // (or we would have caught him before). Abort repetition-checking loop.
6879 // Now check for perpetual chases
6880 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6881 hisPerpetual = PerpetualChase(k, forwardMostMove);
6882 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6883 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6884 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6885 details = "Xboard adjudication: perpetual chasing";
6887 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6888 break; // Abort repetition-checking loop.
6890 // if neither of us is checking or chasing all the time, or both are, it is draw
6892 if(engineOpponent) {
6893 SendToProgram("force\n", engineOpponent); // suppress reply
6894 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6896 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6897 GameEnds( result, details, GE_XBOARD );
6900 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6901 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6905 /* Now we test for 50-move draws. Determine ply count */
6906 count = forwardMostMove;
6907 /* look for last irreversble move */
6908 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6910 /* if we hit starting position, add initial plies */
6911 if( count == backwardMostMove )
6912 count -= initialRulePlies;
6913 count = forwardMostMove - count;
6914 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6915 // adjust reversible move counter for checks in Xiangqi
6916 int i = forwardMostMove - count, inCheck = 0, lastCheck;
6917 if(i < backwardMostMove) i = backwardMostMove;
6918 while(i <= forwardMostMove) {
6919 lastCheck = inCheck; // check evasion does not count
6920 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6921 if(inCheck || lastCheck) count--; // check does not count
6926 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6927 /* this is used to judge if draw claims are legal */
6928 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6929 if(engineOpponent) {
6930 SendToProgram("force\n", engineOpponent); // suppress reply
6931 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6933 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6934 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6938 /* if draw offer is pending, treat it as a draw claim
6939 * when draw condition present, to allow engines a way to
6940 * claim draws before making their move to avoid a race
6941 * condition occurring after their move
6943 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6945 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6946 p = "Draw claim: 50-move rule";
6947 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6948 p = "Draw claim: 3-fold repetition";
6949 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6950 p = "Draw claim: insufficient mating material";
6951 if( p != NULL && canAdjudicate) {
6952 if(engineOpponent) {
6953 SendToProgram("force\n", engineOpponent); // suppress reply
6954 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6956 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6957 GameEnds( GameIsDrawn, p, GE_XBOARD );
6962 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6963 if(engineOpponent) {
6964 SendToProgram("force\n", engineOpponent); // suppress reply
6965 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6967 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6968 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6974 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6975 { // [HGM] book: this routine intercepts moves to simulate book replies
6976 char *bookHit = NULL;
6978 //first determine if the incoming move brings opponent into his book
6979 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6980 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6981 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6982 if(bookHit != NULL && !cps->bookSuspend) {
6983 // make sure opponent is not going to reply after receiving move to book position
6984 SendToProgram("force\n", cps);
6985 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6987 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6988 // now arrange restart after book miss
6990 // after a book hit we never send 'go', and the code after the call to this routine
6991 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6993 sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
6994 SendToProgram(buf, cps);
6995 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6996 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6997 SendToProgram("go\n", cps);
6998 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6999 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7000 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7001 SendToProgram("go\n", cps);
7002 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7004 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7008 ChessProgramState *savedState;
7009 void DeferredBookMove(void)
7011 if(savedState->lastPing != savedState->lastPong)
7012 ScheduleDelayedEvent(DeferredBookMove, 10);
7014 HandleMachineMove(savedMessage, savedState);
7018 HandleMachineMove(message, cps)
7020 ChessProgramState *cps;
7022 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7023 char realname[MSG_SIZ];
7024 int fromX, fromY, toX, toY;
7033 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7035 * Kludge to ignore BEL characters
7037 while (*message == '\007') message++;
7040 * [HGM] engine debug message: ignore lines starting with '#' character
7042 if(cps->debug && *message == '#') return;
7045 * Look for book output
7047 if (cps == &first && bookRequested) {
7048 if (message[0] == '\t' || message[0] == ' ') {
7049 /* Part of the book output is here; append it */
7050 strcat(bookOutput, message);
7051 strcat(bookOutput, " \n");
7053 } else if (bookOutput[0] != NULLCHAR) {
7054 /* All of book output has arrived; display it */
7055 char *p = bookOutput;
7056 while (*p != NULLCHAR) {
7057 if (*p == '\t') *p = ' ';
7060 DisplayInformation(bookOutput);
7061 bookRequested = FALSE;
7062 /* Fall through to parse the current output */
7067 * Look for machine move.
7069 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7070 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7072 /* This method is only useful on engines that support ping */
7073 if (cps->lastPing != cps->lastPong) {
7074 if (gameMode == BeginningOfGame) {
7075 /* Extra move from before last new; ignore */
7076 if (appData.debugMode) {
7077 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7080 if (appData.debugMode) {
7081 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7082 cps->which, gameMode);
7085 SendToProgram("undo\n", cps);
7091 case BeginningOfGame:
7092 /* Extra move from before last reset; ignore */
7093 if (appData.debugMode) {
7094 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7101 /* Extra move after we tried to stop. The mode test is
7102 not a reliable way of detecting this problem, but it's
7103 the best we can do on engines that don't support ping.
7105 if (appData.debugMode) {
7106 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7107 cps->which, gameMode);
7109 SendToProgram("undo\n", cps);
7112 case MachinePlaysWhite:
7113 case IcsPlayingWhite:
7114 machineWhite = TRUE;
7117 case MachinePlaysBlack:
7118 case IcsPlayingBlack:
7119 machineWhite = FALSE;
7122 case TwoMachinesPlay:
7123 machineWhite = (cps->twoMachinesColor[0] == 'w');
7126 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7127 if (appData.debugMode) {
7129 "Ignoring move out of turn by %s, gameMode %d"
7130 ", forwardMost %d\n",
7131 cps->which, gameMode, forwardMostMove);
7136 if (appData.debugMode) { int f = forwardMostMove;
7137 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7138 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7139 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7141 if(cps->alphaRank) AlphaRank(machineMove, 4);
7142 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7143 &fromX, &fromY, &toX, &toY, &promoChar)) {
7144 /* Machine move could not be parsed; ignore it. */
7145 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7146 machineMove, cps->which);
7147 DisplayError(buf1, 0);
7148 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7149 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7150 if (gameMode == TwoMachinesPlay) {
7151 GameEnds(machineWhite ? BlackWins : WhiteWins,
7157 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7158 /* So we have to redo legality test with true e.p. status here, */
7159 /* to make sure an illegal e.p. capture does not slip through, */
7160 /* to cause a forfeit on a justified illegal-move complaint */
7161 /* of the opponent. */
7162 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7164 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7165 fromY, fromX, toY, toX, promoChar);
7166 if (appData.debugMode) {
7168 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7169 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7170 fprintf(debugFP, "castling rights\n");
7172 if(moveType == IllegalMove) {
7173 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7174 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7175 GameEnds(machineWhite ? BlackWins : WhiteWins,
7178 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7179 /* [HGM] Kludge to handle engines that send FRC-style castling
7180 when they shouldn't (like TSCP-Gothic) */
7182 case WhiteASideCastleFR:
7183 case BlackASideCastleFR:
7185 currentMoveString[2]++;
7187 case WhiteHSideCastleFR:
7188 case BlackHSideCastleFR:
7190 currentMoveString[2]--;
7192 default: ; // nothing to do, but suppresses warning of pedantic compilers
7195 hintRequested = FALSE;
7196 lastHint[0] = NULLCHAR;
7197 bookRequested = FALSE;
7198 /* Program may be pondering now */
7199 cps->maybeThinking = TRUE;
7200 if (cps->sendTime == 2) cps->sendTime = 1;
7201 if (cps->offeredDraw) cps->offeredDraw--;
7203 /* currentMoveString is set as a side-effect of ParseOneMove */
7204 safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7205 strcat(machineMove, "\n");
7206 safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7208 /* [AS] Save move info*/
7209 pvInfoList[ forwardMostMove ].score = programStats.score;
7210 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7211 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7213 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7215 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7216 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7219 while( count < adjudicateLossPlies ) {
7220 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7223 score = -score; /* Flip score for winning side */
7226 if( score > adjudicateLossThreshold ) {
7233 if( count >= adjudicateLossPlies ) {
7234 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7236 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7237 "Xboard adjudication",
7244 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7247 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7249 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7250 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7252 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7254 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7256 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7257 char buf[3*MSG_SIZ];
7259 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7260 programStats.score / 100.,
7262 programStats.time / 100.,
7263 (unsigned int)programStats.nodes,
7264 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7265 programStats.movelist);
7267 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7272 /* [AS] Clear stats for next move */
7273 ClearProgramStats();
7274 thinkOutput[0] = NULLCHAR;
7275 hiddenThinkOutputState = 0;
7278 if (gameMode == TwoMachinesPlay) {
7279 /* [HGM] relaying draw offers moved to after reception of move */
7280 /* and interpreting offer as claim if it brings draw condition */
7281 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7282 SendToProgram("draw\n", cps->other);
7284 if (cps->other->sendTime) {
7285 SendTimeRemaining(cps->other,
7286 cps->other->twoMachinesColor[0] == 'w');
7288 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7289 if (firstMove && !bookHit) {
7291 if (cps->other->useColors) {
7292 SendToProgram(cps->other->twoMachinesColor, cps->other);
7294 SendToProgram("go\n", cps->other);
7296 cps->other->maybeThinking = TRUE;
7299 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7301 if (!pausing && appData.ringBellAfterMoves) {
7306 * Reenable menu items that were disabled while
7307 * machine was thinking
7309 if (gameMode != TwoMachinesPlay)
7310 SetUserThinkingEnables();
7312 // [HGM] book: after book hit opponent has received move and is now in force mode
7313 // force the book reply into it, and then fake that it outputted this move by jumping
7314 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7316 static char bookMove[MSG_SIZ]; // a bit generous?
7318 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7319 strcat(bookMove, bookHit);
7322 programStats.nodes = programStats.depth = programStats.time =
7323 programStats.score = programStats.got_only_move = 0;
7324 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7326 if(cps->lastPing != cps->lastPong) {
7327 savedMessage = message; // args for deferred call
7329 ScheduleDelayedEvent(DeferredBookMove, 10);
7338 /* Set special modes for chess engines. Later something general
7339 * could be added here; for now there is just one kludge feature,
7340 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7341 * when "xboard" is given as an interactive command.
7343 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7344 cps->useSigint = FALSE;
7345 cps->useSigterm = FALSE;
7347 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7348 ParseFeatures(message+8, cps);
7349 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7352 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7353 * want this, I was asked to put it in, and obliged.
7355 if (!strncmp(message, "setboard ", 9)) {
7356 Board initial_position;
7358 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7360 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7361 DisplayError(_("Bad FEN received from engine"), 0);
7365 CopyBoard(boards[0], initial_position);
7366 initialRulePlies = FENrulePlies;
7367 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7368 else gameMode = MachinePlaysBlack;
7369 DrawPosition(FALSE, boards[currentMove]);
7375 * Look for communication commands
7377 if (!strncmp(message, "telluser ", 9)) {
7378 EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7379 DisplayNote(message + 9);
7382 if (!strncmp(message, "tellusererror ", 14)) {
7384 EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7385 DisplayError(message + 14, 0);
7388 if (!strncmp(message, "tellopponent ", 13)) {
7389 if (appData.icsActive) {
7391 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7395 DisplayNote(message + 13);
7399 if (!strncmp(message, "tellothers ", 11)) {
7400 if (appData.icsActive) {
7402 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7408 if (!strncmp(message, "tellall ", 8)) {
7409 if (appData.icsActive) {
7411 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7415 DisplayNote(message + 8);
7419 if (strncmp(message, "warning", 7) == 0) {
7420 /* Undocumented feature, use tellusererror in new code */
7421 DisplayError(message, 0);
7424 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7425 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7426 strcat(realname, " query");
7427 AskQuestion(realname, buf2, buf1, cps->pr);
7430 /* Commands from the engine directly to ICS. We don't allow these to be
7431 * sent until we are logged on. Crafty kibitzes have been known to
7432 * interfere with the login process.
7435 if (!strncmp(message, "tellics ", 8)) {
7436 SendToICS(message + 8);
7440 if (!strncmp(message, "tellicsnoalias ", 15)) {
7441 SendToICS(ics_prefix);
7442 SendToICS(message + 15);
7446 /* The following are for backward compatibility only */
7447 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7448 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7449 SendToICS(ics_prefix);
7455 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7459 * If the move is illegal, cancel it and redraw the board.
7460 * Also deal with other error cases. Matching is rather loose
7461 * here to accommodate engines written before the spec.
7463 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7464 strncmp(message, "Error", 5) == 0) {
7465 if (StrStr(message, "name") ||
7466 StrStr(message, "rating") || StrStr(message, "?") ||
7467 StrStr(message, "result") || StrStr(message, "board") ||
7468 StrStr(message, "bk") || StrStr(message, "computer") ||
7469 StrStr(message, "variant") || StrStr(message, "hint") ||
7470 StrStr(message, "random") || StrStr(message, "depth") ||
7471 StrStr(message, "accepted")) {
7474 if (StrStr(message, "protover")) {
7475 /* Program is responding to input, so it's apparently done
7476 initializing, and this error message indicates it is
7477 protocol version 1. So we don't need to wait any longer
7478 for it to initialize and send feature commands. */
7479 FeatureDone(cps, 1);
7480 cps->protocolVersion = 1;
7483 cps->maybeThinking = FALSE;
7485 if (StrStr(message, "draw")) {
7486 /* Program doesn't have "draw" command */
7487 cps->sendDrawOffers = 0;
7490 if (cps->sendTime != 1 &&
7491 (StrStr(message, "time") || StrStr(message, "otim"))) {
7492 /* Program apparently doesn't have "time" or "otim" command */
7496 if (StrStr(message, "analyze")) {
7497 cps->analysisSupport = FALSE;
7498 cps->analyzing = FALSE;
7500 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7501 DisplayError(buf2, 0);
7504 if (StrStr(message, "(no matching move)st")) {
7505 /* Special kludge for GNU Chess 4 only */
7506 cps->stKludge = TRUE;
7507 SendTimeControl(cps, movesPerSession, timeControl,
7508 timeIncrement, appData.searchDepth,
7512 if (StrStr(message, "(no matching move)sd")) {
7513 /* Special kludge for GNU Chess 4 only */
7514 cps->sdKludge = TRUE;
7515 SendTimeControl(cps, movesPerSession, timeControl,
7516 timeIncrement, appData.searchDepth,
7520 if (!StrStr(message, "llegal")) {
7523 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7524 gameMode == IcsIdle) return;
7525 if (forwardMostMove <= backwardMostMove) return;
7526 if (pausing) PauseEvent();
7527 if(appData.forceIllegal) {
7528 // [HGM] illegal: machine refused move; force position after move into it
7529 SendToProgram("force\n", cps);
7530 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7531 // we have a real problem now, as SendBoard will use the a2a3 kludge
7532 // when black is to move, while there might be nothing on a2 or black
7533 // might already have the move. So send the board as if white has the move.
7534 // But first we must change the stm of the engine, as it refused the last move
7535 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7536 if(WhiteOnMove(forwardMostMove)) {
7537 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7538 SendBoard(cps, forwardMostMove); // kludgeless board
7540 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7541 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7542 SendBoard(cps, forwardMostMove+1); // kludgeless board
7544 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7545 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7546 gameMode == TwoMachinesPlay)
7547 SendToProgram("go\n", cps);
7550 if (gameMode == PlayFromGameFile) {
7551 /* Stop reading this game file */
7552 gameMode = EditGame;
7555 currentMove = forwardMostMove-1;
7556 DisplayMove(currentMove-1); /* before DisplayMoveError */
7557 SwitchClocks(forwardMostMove-1); // [HGM] race
7558 DisplayBothClocks();
7559 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7560 parseList[currentMove], cps->which);
7561 DisplayMoveError(buf1);
7562 DrawPosition(FALSE, boards[currentMove]);
7564 /* [HGM] illegal-move claim should forfeit game when Xboard */
7565 /* only passes fully legal moves */
7566 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7567 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7568 "False illegal-move claim", GE_XBOARD );
7572 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7573 /* Program has a broken "time" command that
7574 outputs a string not ending in newline.
7580 * If chess program startup fails, exit with an error message.
7581 * Attempts to recover here are futile.
7583 if ((StrStr(message, "unknown host") != NULL)
7584 || (StrStr(message, "No remote directory") != NULL)
7585 || (StrStr(message, "not found") != NULL)
7586 || (StrStr(message, "No such file") != NULL)
7587 || (StrStr(message, "can't alloc") != NULL)
7588 || (StrStr(message, "Permission denied") != NULL)) {
7590 cps->maybeThinking = FALSE;
7591 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7592 cps->which, cps->program, cps->host, message);
7593 RemoveInputSource(cps->isr);
7594 DisplayFatalError(buf1, 0, 1);
7599 * Look for hint output
7601 if (sscanf(message, "Hint: %s", buf1) == 1) {
7602 if (cps == &first && hintRequested) {
7603 hintRequested = FALSE;
7604 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7605 &fromX, &fromY, &toX, &toY, &promoChar)) {
7606 (void) CoordsToAlgebraic(boards[forwardMostMove],
7607 PosFlags(forwardMostMove),
7608 fromY, fromX, toY, toX, promoChar, buf1);
7609 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7610 DisplayInformation(buf2);
7612 /* Hint move could not be parsed!? */
7613 snprintf(buf2, sizeof(buf2),
7614 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7616 DisplayError(buf2, 0);
7619 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7625 * Ignore other messages if game is not in progress
7627 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7628 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7631 * look for win, lose, draw, or draw offer
7633 if (strncmp(message, "1-0", 3) == 0) {
7634 char *p, *q, *r = "";
7635 p = strchr(message, '{');
7643 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7645 } else if (strncmp(message, "0-1", 3) == 0) {
7646 char *p, *q, *r = "";
7647 p = strchr(message, '{');
7655 /* Kludge for Arasan 4.1 bug */
7656 if (strcmp(r, "Black resigns") == 0) {
7657 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7660 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7662 } else if (strncmp(message, "1/2", 3) == 0) {
7663 char *p, *q, *r = "";
7664 p = strchr(message, '{');
7673 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7676 } else if (strncmp(message, "White resign", 12) == 0) {
7677 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7679 } else if (strncmp(message, "Black resign", 12) == 0) {
7680 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7682 } else if (strncmp(message, "White matches", 13) == 0 ||
7683 strncmp(message, "Black matches", 13) == 0 ) {
7684 /* [HGM] ignore GNUShogi noises */
7686 } else if (strncmp(message, "White", 5) == 0 &&
7687 message[5] != '(' &&
7688 StrStr(message, "Black") == NULL) {
7689 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7691 } else if (strncmp(message, "Black", 5) == 0 &&
7692 message[5] != '(') {
7693 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7695 } else if (strcmp(message, "resign") == 0 ||
7696 strcmp(message, "computer resigns") == 0) {
7698 case MachinePlaysBlack:
7699 case IcsPlayingBlack:
7700 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7702 case MachinePlaysWhite:
7703 case IcsPlayingWhite:
7704 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7706 case TwoMachinesPlay:
7707 if (cps->twoMachinesColor[0] == 'w')
7708 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7710 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7717 } else if (strncmp(message, "opponent mates", 14) == 0) {
7719 case MachinePlaysBlack:
7720 case IcsPlayingBlack:
7721 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7723 case MachinePlaysWhite:
7724 case IcsPlayingWhite:
7725 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7727 case TwoMachinesPlay:
7728 if (cps->twoMachinesColor[0] == 'w')
7729 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7731 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7738 } else if (strncmp(message, "computer mates", 14) == 0) {
7740 case MachinePlaysBlack:
7741 case IcsPlayingBlack:
7742 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7744 case MachinePlaysWhite:
7745 case IcsPlayingWhite:
7746 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7748 case TwoMachinesPlay:
7749 if (cps->twoMachinesColor[0] == 'w')
7750 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7752 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7759 } else if (strncmp(message, "checkmate", 9) == 0) {
7760 if (WhiteOnMove(forwardMostMove)) {
7761 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7763 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7766 } else if (strstr(message, "Draw") != NULL ||
7767 strstr(message, "game is a draw") != NULL) {
7768 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7770 } else if (strstr(message, "offer") != NULL &&
7771 strstr(message, "draw") != NULL) {
7773 if (appData.zippyPlay && first.initDone) {
7774 /* Relay offer to ICS */
7775 SendToICS(ics_prefix);
7776 SendToICS("draw\n");
7779 cps->offeredDraw = 2; /* valid until this engine moves twice */
7780 if (gameMode == TwoMachinesPlay) {
7781 if (cps->other->offeredDraw) {
7782 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7783 /* [HGM] in two-machine mode we delay relaying draw offer */
7784 /* until after we also have move, to see if it is really claim */
7786 } else if (gameMode == MachinePlaysWhite ||
7787 gameMode == MachinePlaysBlack) {
7788 if (userOfferedDraw) {
7789 DisplayInformation(_("Machine accepts your draw offer"));
7790 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7792 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7799 * Look for thinking output
7801 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7802 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7804 int plylev, mvleft, mvtot, curscore, time;
7805 char mvname[MOVE_LEN];
7809 int prefixHint = FALSE;
7810 mvname[0] = NULLCHAR;
7813 case MachinePlaysBlack:
7814 case IcsPlayingBlack:
7815 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7817 case MachinePlaysWhite:
7818 case IcsPlayingWhite:
7819 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7824 case IcsObserving: /* [DM] icsEngineAnalyze */
7825 if (!appData.icsEngineAnalyze) ignore = TRUE;
7827 case TwoMachinesPlay:
7828 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7838 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7840 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7841 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7843 if (plyext != ' ' && plyext != '\t') {
7847 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7848 if( cps->scoreIsAbsolute &&
7849 ( gameMode == MachinePlaysBlack ||
7850 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7851 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7852 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7853 !WhiteOnMove(currentMove)
7856 curscore = -curscore;
7860 tempStats.depth = plylev;
7861 tempStats.nodes = nodes;
7862 tempStats.time = time;
7863 tempStats.score = curscore;
7864 tempStats.got_only_move = 0;
7866 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7869 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7870 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7871 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7872 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7873 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7874 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7875 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7876 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7879 /* Buffer overflow protection */
7880 if (buf1[0] != NULLCHAR) {
7881 if (strlen(buf1) >= sizeof(tempStats.movelist)
7882 && appData.debugMode) {
7884 "PV is too long; using the first %u bytes.\n",
7885 (unsigned) sizeof(tempStats.movelist) - 1);
7888 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
7890 sprintf(tempStats.movelist, " no PV\n");
7893 if (tempStats.seen_stat) {
7894 tempStats.ok_to_send = 1;
7897 if (strchr(tempStats.movelist, '(') != NULL) {
7898 tempStats.line_is_book = 1;
7899 tempStats.nr_moves = 0;
7900 tempStats.moves_left = 0;
7902 tempStats.line_is_book = 0;
7905 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7906 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7908 SendProgramStatsToFrontend( cps, &tempStats );
7911 [AS] Protect the thinkOutput buffer from overflow... this
7912 is only useful if buf1 hasn't overflowed first!
7914 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7916 (gameMode == TwoMachinesPlay ?
7917 ToUpper(cps->twoMachinesColor[0]) : ' '),
7918 ((double) curscore) / 100.0,
7919 prefixHint ? lastHint : "",
7920 prefixHint ? " " : "" );
7922 if( buf1[0] != NULLCHAR ) {
7923 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7925 if( strlen(buf1) > max_len ) {
7926 if( appData.debugMode) {
7927 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7929 buf1[max_len+1] = '\0';
7932 strcat( thinkOutput, buf1 );
7935 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7936 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7937 DisplayMove(currentMove - 1);
7941 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7942 /* crafty (9.25+) says "(only move) <move>"
7943 * if there is only 1 legal move
7945 sscanf(p, "(only move) %s", buf1);
7946 sprintf(thinkOutput, "%s (only move)", buf1);
7947 sprintf(programStats.movelist, "%s (only move)", buf1);
7948 programStats.depth = 1;
7949 programStats.nr_moves = 1;
7950 programStats.moves_left = 1;
7951 programStats.nodes = 1;
7952 programStats.time = 1;
7953 programStats.got_only_move = 1;
7955 /* Not really, but we also use this member to
7956 mean "line isn't going to change" (Crafty
7957 isn't searching, so stats won't change) */
7958 programStats.line_is_book = 1;
7960 SendProgramStatsToFrontend( cps, &programStats );
7962 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7963 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7964 DisplayMove(currentMove - 1);
7967 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7968 &time, &nodes, &plylev, &mvleft,
7969 &mvtot, mvname) >= 5) {
7970 /* The stat01: line is from Crafty (9.29+) in response
7971 to the "." command */
7972 programStats.seen_stat = 1;
7973 cps->maybeThinking = TRUE;
7975 if (programStats.got_only_move || !appData.periodicUpdates)
7978 programStats.depth = plylev;
7979 programStats.time = time;
7980 programStats.nodes = nodes;
7981 programStats.moves_left = mvleft;
7982 programStats.nr_moves = mvtot;
7983 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
7984 programStats.ok_to_send = 1;
7985 programStats.movelist[0] = '\0';
7987 SendProgramStatsToFrontend( cps, &programStats );
7991 } else if (strncmp(message,"++",2) == 0) {
7992 /* Crafty 9.29+ outputs this */
7993 programStats.got_fail = 2;
7996 } else if (strncmp(message,"--",2) == 0) {
7997 /* Crafty 9.29+ outputs this */
7998 programStats.got_fail = 1;
8001 } else if (thinkOutput[0] != NULLCHAR &&
8002 strncmp(message, " ", 4) == 0) {
8003 unsigned message_len;
8006 while (*p && *p == ' ') p++;
8008 message_len = strlen( p );
8010 /* [AS] Avoid buffer overflow */
8011 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8012 strcat(thinkOutput, " ");
8013 strcat(thinkOutput, p);
8016 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8017 strcat(programStats.movelist, " ");
8018 strcat(programStats.movelist, p);
8021 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8022 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8023 DisplayMove(currentMove - 1);
8031 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8032 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8034 ChessProgramStats cpstats;
8036 if (plyext != ' ' && plyext != '\t') {
8040 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8041 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8042 curscore = -curscore;
8045 cpstats.depth = plylev;
8046 cpstats.nodes = nodes;
8047 cpstats.time = time;
8048 cpstats.score = curscore;
8049 cpstats.got_only_move = 0;
8050 cpstats.movelist[0] = '\0';
8052 if (buf1[0] != NULLCHAR) {
8053 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8056 cpstats.ok_to_send = 0;
8057 cpstats.line_is_book = 0;
8058 cpstats.nr_moves = 0;
8059 cpstats.moves_left = 0;
8061 SendProgramStatsToFrontend( cps, &cpstats );
8068 /* Parse a game score from the character string "game", and
8069 record it as the history of the current game. The game
8070 score is NOT assumed to start from the standard position.
8071 The display is not updated in any way.
8074 ParseGameHistory(game)
8078 int fromX, fromY, toX, toY, boardIndex;
8083 if (appData.debugMode)
8084 fprintf(debugFP, "Parsing game history: %s\n", game);
8086 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8087 gameInfo.site = StrSave(appData.icsHost);
8088 gameInfo.date = PGNDate();
8089 gameInfo.round = StrSave("-");
8091 /* Parse out names of players */
8092 while (*game == ' ') game++;
8094 while (*game != ' ') *p++ = *game++;
8096 gameInfo.white = StrSave(buf);
8097 while (*game == ' ') game++;
8099 while (*game != ' ' && *game != '\n') *p++ = *game++;
8101 gameInfo.black = StrSave(buf);
8104 boardIndex = blackPlaysFirst ? 1 : 0;
8107 yyboardindex = boardIndex;
8108 moveType = (ChessMove) yylex();
8110 case IllegalMove: /* maybe suicide chess, etc. */
8111 if (appData.debugMode) {
8112 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8113 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8114 setbuf(debugFP, NULL);
8116 case WhitePromotion:
8117 case BlackPromotion:
8118 case WhiteNonPromotion:
8119 case BlackNonPromotion:
8121 case WhiteCapturesEnPassant:
8122 case BlackCapturesEnPassant:
8123 case WhiteKingSideCastle:
8124 case WhiteQueenSideCastle:
8125 case BlackKingSideCastle:
8126 case BlackQueenSideCastle:
8127 case WhiteKingSideCastleWild:
8128 case WhiteQueenSideCastleWild:
8129 case BlackKingSideCastleWild:
8130 case BlackQueenSideCastleWild:
8132 case WhiteHSideCastleFR:
8133 case WhiteASideCastleFR:
8134 case BlackHSideCastleFR:
8135 case BlackASideCastleFR:
8137 fromX = currentMoveString[0] - AAA;
8138 fromY = currentMoveString[1] - ONE;
8139 toX = currentMoveString[2] - AAA;
8140 toY = currentMoveString[3] - ONE;
8141 promoChar = currentMoveString[4];
8145 fromX = moveType == WhiteDrop ?
8146 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8147 (int) CharToPiece(ToLower(currentMoveString[0]));
8149 toX = currentMoveString[2] - AAA;
8150 toY = currentMoveString[3] - ONE;
8151 promoChar = NULLCHAR;
8155 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8156 if (appData.debugMode) {
8157 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8158 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8159 setbuf(debugFP, NULL);
8161 DisplayError(buf, 0);
8163 case ImpossibleMove:
8165 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8166 if (appData.debugMode) {
8167 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8168 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8169 setbuf(debugFP, NULL);
8171 DisplayError(buf, 0);
8173 case (ChessMove) 0: /* end of file */
8174 if (boardIndex < backwardMostMove) {
8175 /* Oops, gap. How did that happen? */
8176 DisplayError(_("Gap in move list"), 0);
8179 backwardMostMove = blackPlaysFirst ? 1 : 0;
8180 if (boardIndex > forwardMostMove) {
8181 forwardMostMove = boardIndex;
8185 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8186 strcat(parseList[boardIndex-1], " ");
8187 strcat(parseList[boardIndex-1], yy_text);
8199 case GameUnfinished:
8200 if (gameMode == IcsExamining) {
8201 if (boardIndex < backwardMostMove) {
8202 /* Oops, gap. How did that happen? */
8205 backwardMostMove = blackPlaysFirst ? 1 : 0;
8208 gameInfo.result = moveType;
8209 p = strchr(yy_text, '{');
8210 if (p == NULL) p = strchr(yy_text, '(');
8213 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8215 q = strchr(p, *p == '{' ? '}' : ')');
8216 if (q != NULL) *q = NULLCHAR;
8219 gameInfo.resultDetails = StrSave(p);
8222 if (boardIndex >= forwardMostMove &&
8223 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8224 backwardMostMove = blackPlaysFirst ? 1 : 0;
8227 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8228 fromY, fromX, toY, toX, promoChar,
8229 parseList[boardIndex]);
8230 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8231 /* currentMoveString is set as a side-effect of yylex */
8232 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8233 strcat(moveList[boardIndex], "\n");
8235 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8236 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8242 if(gameInfo.variant != VariantShogi)
8243 strcat(parseList[boardIndex - 1], "+");
8247 strcat(parseList[boardIndex - 1], "#");
8254 /* Apply a move to the given board */
8256 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8257 int fromX, fromY, toX, toY;
8261 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8262 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8264 /* [HGM] compute & store e.p. status and castling rights for new position */
8265 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8267 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8268 oldEP = (signed char)board[EP_STATUS];
8269 board[EP_STATUS] = EP_NONE;
8271 if( board[toY][toX] != EmptySquare )
8272 board[EP_STATUS] = EP_CAPTURE;
8274 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8275 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8276 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8278 if (fromY == DROP_RANK) {
8280 piece = board[toY][toX] = (ChessSquare) fromX;
8284 if( board[fromY][fromX] == WhitePawn ) {
8285 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8286 board[EP_STATUS] = EP_PAWN_MOVE;
8288 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8289 gameInfo.variant != VariantBerolina || toX < fromX)
8290 board[EP_STATUS] = toX | berolina;
8291 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8292 gameInfo.variant != VariantBerolina || toX > fromX)
8293 board[EP_STATUS] = toX;
8296 if( board[fromY][fromX] == BlackPawn ) {
8297 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8298 board[EP_STATUS] = EP_PAWN_MOVE;
8299 if( toY-fromY== -2) {
8300 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8301 gameInfo.variant != VariantBerolina || toX < fromX)
8302 board[EP_STATUS] = toX | berolina;
8303 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8304 gameInfo.variant != VariantBerolina || toX > fromX)
8305 board[EP_STATUS] = toX;
8309 for(i=0; i<nrCastlingRights; i++) {
8310 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8311 board[CASTLING][i] == toX && castlingRank[i] == toY
8312 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8315 if (fromX == toX && fromY == toY) return;
8317 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8318 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8319 if(gameInfo.variant == VariantKnightmate)
8320 king += (int) WhiteUnicorn - (int) WhiteKing;
8322 /* Code added by Tord: */
8323 /* FRC castling assumed when king captures friendly rook. */
8324 if (board[fromY][fromX] == WhiteKing &&
8325 board[toY][toX] == WhiteRook) {
8326 board[fromY][fromX] = EmptySquare;
8327 board[toY][toX] = EmptySquare;
8329 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8331 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8333 } else if (board[fromY][fromX] == BlackKing &&
8334 board[toY][toX] == BlackRook) {
8335 board[fromY][fromX] = EmptySquare;
8336 board[toY][toX] = EmptySquare;
8338 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8340 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8342 /* End of code added by Tord */
8344 } else if (board[fromY][fromX] == king
8345 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8346 && toY == fromY && toX > fromX+1) {
8347 board[fromY][fromX] = EmptySquare;
8348 board[toY][toX] = king;
8349 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8350 board[fromY][BOARD_RGHT-1] = EmptySquare;
8351 } else if (board[fromY][fromX] == king
8352 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8353 && toY == fromY && toX < fromX-1) {
8354 board[fromY][fromX] = EmptySquare;
8355 board[toY][toX] = king;
8356 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8357 board[fromY][BOARD_LEFT] = EmptySquare;
8358 } else if (board[fromY][fromX] == WhitePawn
8359 && toY >= BOARD_HEIGHT-promoRank
8360 && gameInfo.variant != VariantXiangqi
8362 /* white pawn promotion */
8363 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8364 if (board[toY][toX] == EmptySquare) {
8365 board[toY][toX] = WhiteQueen;
8367 if(gameInfo.variant==VariantBughouse ||
8368 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8369 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8370 board[fromY][fromX] = EmptySquare;
8371 } else if ((fromY == BOARD_HEIGHT-4)
8373 && gameInfo.variant != VariantXiangqi
8374 && gameInfo.variant != VariantBerolina
8375 && (board[fromY][fromX] == WhitePawn)
8376 && (board[toY][toX] == EmptySquare)) {
8377 board[fromY][fromX] = EmptySquare;
8378 board[toY][toX] = WhitePawn;
8379 captured = board[toY - 1][toX];
8380 board[toY - 1][toX] = EmptySquare;
8381 } else if ((fromY == BOARD_HEIGHT-4)
8383 && gameInfo.variant == VariantBerolina
8384 && (board[fromY][fromX] == WhitePawn)
8385 && (board[toY][toX] == EmptySquare)) {
8386 board[fromY][fromX] = EmptySquare;
8387 board[toY][toX] = WhitePawn;
8388 if(oldEP & EP_BEROLIN_A) {
8389 captured = board[fromY][fromX-1];
8390 board[fromY][fromX-1] = EmptySquare;
8391 }else{ captured = board[fromY][fromX+1];
8392 board[fromY][fromX+1] = EmptySquare;
8394 } else if (board[fromY][fromX] == king
8395 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8396 && toY == fromY && toX > fromX+1) {
8397 board[fromY][fromX] = EmptySquare;
8398 board[toY][toX] = king;
8399 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8400 board[fromY][BOARD_RGHT-1] = EmptySquare;
8401 } else if (board[fromY][fromX] == king
8402 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8403 && toY == fromY && toX < fromX-1) {
8404 board[fromY][fromX] = EmptySquare;
8405 board[toY][toX] = king;
8406 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8407 board[fromY][BOARD_LEFT] = EmptySquare;
8408 } else if (fromY == 7 && fromX == 3
8409 && board[fromY][fromX] == BlackKing
8410 && toY == 7 && toX == 5) {
8411 board[fromY][fromX] = EmptySquare;
8412 board[toY][toX] = BlackKing;
8413 board[fromY][7] = EmptySquare;
8414 board[toY][4] = BlackRook;
8415 } else if (fromY == 7 && fromX == 3
8416 && board[fromY][fromX] == BlackKing
8417 && toY == 7 && toX == 1) {
8418 board[fromY][fromX] = EmptySquare;
8419 board[toY][toX] = BlackKing;
8420 board[fromY][0] = EmptySquare;
8421 board[toY][2] = BlackRook;
8422 } else if (board[fromY][fromX] == BlackPawn
8424 && gameInfo.variant != VariantXiangqi
8426 /* black pawn promotion */
8427 board[toY][toX] = CharToPiece(ToLower(promoChar));
8428 if (board[toY][toX] == EmptySquare) {
8429 board[toY][toX] = BlackQueen;
8431 if(gameInfo.variant==VariantBughouse ||
8432 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8433 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8434 board[fromY][fromX] = EmptySquare;
8435 } else if ((fromY == 3)
8437 && gameInfo.variant != VariantXiangqi
8438 && gameInfo.variant != VariantBerolina
8439 && (board[fromY][fromX] == BlackPawn)
8440 && (board[toY][toX] == EmptySquare)) {
8441 board[fromY][fromX] = EmptySquare;
8442 board[toY][toX] = BlackPawn;
8443 captured = board[toY + 1][toX];
8444 board[toY + 1][toX] = EmptySquare;
8445 } else if ((fromY == 3)
8447 && gameInfo.variant == VariantBerolina
8448 && (board[fromY][fromX] == BlackPawn)
8449 && (board[toY][toX] == EmptySquare)) {
8450 board[fromY][fromX] = EmptySquare;
8451 board[toY][toX] = BlackPawn;
8452 if(oldEP & EP_BEROLIN_A) {
8453 captured = board[fromY][fromX-1];
8454 board[fromY][fromX-1] = EmptySquare;
8455 }else{ captured = board[fromY][fromX+1];
8456 board[fromY][fromX+1] = EmptySquare;
8459 board[toY][toX] = board[fromY][fromX];
8460 board[fromY][fromX] = EmptySquare;
8464 if (gameInfo.holdingsWidth != 0) {
8466 /* !!A lot more code needs to be written to support holdings */
8467 /* [HGM] OK, so I have written it. Holdings are stored in the */
8468 /* penultimate board files, so they are automaticlly stored */
8469 /* in the game history. */
8470 if (fromY == DROP_RANK) {
8471 /* Delete from holdings, by decreasing count */
8472 /* and erasing image if necessary */
8474 if(p < (int) BlackPawn) { /* white drop */
8475 p -= (int)WhitePawn;
8476 p = PieceToNumber((ChessSquare)p);
8477 if(p >= gameInfo.holdingsSize) p = 0;
8478 if(--board[p][BOARD_WIDTH-2] <= 0)
8479 board[p][BOARD_WIDTH-1] = EmptySquare;
8480 if((int)board[p][BOARD_WIDTH-2] < 0)
8481 board[p][BOARD_WIDTH-2] = 0;
8482 } else { /* black drop */
8483 p -= (int)BlackPawn;
8484 p = PieceToNumber((ChessSquare)p);
8485 if(p >= gameInfo.holdingsSize) p = 0;
8486 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8487 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8488 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8489 board[BOARD_HEIGHT-1-p][1] = 0;
8492 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8493 && gameInfo.variant != VariantBughouse ) {
8494 /* [HGM] holdings: Add to holdings, if holdings exist */
8495 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8496 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8497 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8500 if (p >= (int) BlackPawn) {
8501 p -= (int)BlackPawn;
8502 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8503 /* in Shogi restore piece to its original first */
8504 captured = (ChessSquare) (DEMOTED captured);
8507 p = PieceToNumber((ChessSquare)p);
8508 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8509 board[p][BOARD_WIDTH-2]++;
8510 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8512 p -= (int)WhitePawn;
8513 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8514 captured = (ChessSquare) (DEMOTED captured);
8517 p = PieceToNumber((ChessSquare)p);
8518 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8519 board[BOARD_HEIGHT-1-p][1]++;
8520 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8523 } else if (gameInfo.variant == VariantAtomic) {
8524 if (captured != EmptySquare) {
8526 for (y = toY-1; y <= toY+1; y++) {
8527 for (x = toX-1; x <= toX+1; x++) {
8528 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8529 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8530 board[y][x] = EmptySquare;
8534 board[toY][toX] = EmptySquare;
8537 if(promoChar == '+') {
8538 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8539 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8540 } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
8541 board[toY][toX] = CharToPiece(promoChar);
8543 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8544 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8545 // [HGM] superchess: take promotion piece out of holdings
8546 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8547 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8548 if(!--board[k][BOARD_WIDTH-2])
8549 board[k][BOARD_WIDTH-1] = EmptySquare;
8551 if(!--board[BOARD_HEIGHT-1-k][1])
8552 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8558 /* Updates forwardMostMove */
8560 MakeMove(fromX, fromY, toX, toY, promoChar)
8561 int fromX, fromY, toX, toY;
8564 // forwardMostMove++; // [HGM] bare: moved downstream
8566 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8567 int timeLeft; static int lastLoadFlag=0; int king, piece;
8568 piece = boards[forwardMostMove][fromY][fromX];
8569 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8570 if(gameInfo.variant == VariantKnightmate)
8571 king += (int) WhiteUnicorn - (int) WhiteKing;
8572 if(forwardMostMove == 0) {
8574 fprintf(serverMoves, "%s;", second.tidy);
8575 fprintf(serverMoves, "%s;", first.tidy);
8576 if(!blackPlaysFirst)
8577 fprintf(serverMoves, "%s;", second.tidy);
8578 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8579 lastLoadFlag = loadFlag;
8581 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8582 // print castling suffix
8583 if( toY == fromY && piece == king ) {
8585 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8587 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8590 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8591 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8592 boards[forwardMostMove][toY][toX] == EmptySquare
8593 && fromX != toX && fromY != toY)
8594 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8596 if(promoChar != NULLCHAR)
8597 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8599 fprintf(serverMoves, "/%d/%d",
8600 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8601 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8602 else timeLeft = blackTimeRemaining/1000;
8603 fprintf(serverMoves, "/%d", timeLeft);
8605 fflush(serverMoves);
8608 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8609 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8613 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8614 if (commentList[forwardMostMove+1] != NULL) {
8615 free(commentList[forwardMostMove+1]);
8616 commentList[forwardMostMove+1] = NULL;
8618 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8619 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8620 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8621 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8622 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8623 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8624 gameInfo.result = GameUnfinished;
8625 if (gameInfo.resultDetails != NULL) {
8626 free(gameInfo.resultDetails);
8627 gameInfo.resultDetails = NULL;
8629 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8630 moveList[forwardMostMove - 1]);
8631 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8632 PosFlags(forwardMostMove - 1),
8633 fromY, fromX, toY, toX, promoChar,
8634 parseList[forwardMostMove - 1]);
8635 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8641 if(gameInfo.variant != VariantShogi)
8642 strcat(parseList[forwardMostMove - 1], "+");
8646 strcat(parseList[forwardMostMove - 1], "#");
8649 if (appData.debugMode) {
8650 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8655 /* Updates currentMove if not pausing */
8657 ShowMove(fromX, fromY, toX, toY)
8659 int instant = (gameMode == PlayFromGameFile) ?
8660 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8661 if(appData.noGUI) return;
8662 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8664 if (forwardMostMove == currentMove + 1) {
8665 AnimateMove(boards[forwardMostMove - 1],
8666 fromX, fromY, toX, toY);
8668 if (appData.highlightLastMove) {
8669 SetHighlights(fromX, fromY, toX, toY);
8672 currentMove = forwardMostMove;
8675 if (instant) return;
8677 DisplayMove(currentMove - 1);
8678 DrawPosition(FALSE, boards[currentMove]);
8679 DisplayBothClocks();
8680 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8683 void SendEgtPath(ChessProgramState *cps)
8684 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8685 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8687 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8690 char c, *q = name+1, *r, *s;
8692 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8693 while(*p && *p != ',') *q++ = *p++;
8695 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8696 strcmp(name, ",nalimov:") == 0 ) {
8697 // take nalimov path from the menu-changeable option first, if it is defined
8698 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8699 SendToProgram(buf,cps); // send egtbpath command for nalimov
8701 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8702 (s = StrStr(appData.egtFormats, name)) != NULL) {
8703 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8704 s = r = StrStr(s, ":") + 1; // beginning of path info
8705 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8706 c = *r; *r = 0; // temporarily null-terminate path info
8707 *--q = 0; // strip of trailig ':' from name
8708 sprintf(buf, "egtpath %s %s\n", name+1, s);
8710 SendToProgram(buf,cps); // send egtbpath command for this format
8712 if(*p == ',') p++; // read away comma to position for next format name
8717 InitChessProgram(cps, setup)
8718 ChessProgramState *cps;
8719 int setup; /* [HGM] needed to setup FRC opening position */
8721 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8722 if (appData.noChessProgram) return;
8723 hintRequested = FALSE;
8724 bookRequested = FALSE;
8726 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8727 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8728 if(cps->memSize) { /* [HGM] memory */
8729 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8730 SendToProgram(buf, cps);
8732 SendEgtPath(cps); /* [HGM] EGT */
8733 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8734 sprintf(buf, "cores %d\n", appData.smpCores);
8735 SendToProgram(buf, cps);
8738 SendToProgram(cps->initString, cps);
8739 if (gameInfo.variant != VariantNormal &&
8740 gameInfo.variant != VariantLoadable
8741 /* [HGM] also send variant if board size non-standard */
8742 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8744 char *v = VariantName(gameInfo.variant);
8745 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8746 /* [HGM] in protocol 1 we have to assume all variants valid */
8747 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8748 DisplayFatalError(buf, 0, 1);
8752 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8753 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8754 if( gameInfo.variant == VariantXiangqi )
8755 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8756 if( gameInfo.variant == VariantShogi )
8757 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8758 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8759 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8760 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8761 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8762 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8763 if( gameInfo.variant == VariantCourier )
8764 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8765 if( gameInfo.variant == VariantSuper )
8766 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8767 if( gameInfo.variant == VariantGreat )
8768 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8771 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8772 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8773 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8774 if(StrStr(cps->variants, b) == NULL) {
8775 // specific sized variant not known, check if general sizing allowed
8776 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8777 if(StrStr(cps->variants, "boardsize") == NULL) {
8778 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8779 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8780 DisplayFatalError(buf, 0, 1);
8783 /* [HGM] here we really should compare with the maximum supported board size */
8786 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8787 sprintf(buf, "variant %s\n", b);
8788 SendToProgram(buf, cps);
8790 currentlyInitializedVariant = gameInfo.variant;
8792 /* [HGM] send opening position in FRC to first engine */
8794 SendToProgram("force\n", cps);
8796 /* engine is now in force mode! Set flag to wake it up after first move. */
8797 setboardSpoiledMachineBlack = 1;
8801 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8802 SendToProgram(buf, cps);
8804 cps->maybeThinking = FALSE;
8805 cps->offeredDraw = 0;
8806 if (!appData.icsActive) {
8807 SendTimeControl(cps, movesPerSession, timeControl,
8808 timeIncrement, appData.searchDepth,
8811 if (appData.showThinking
8812 // [HGM] thinking: four options require thinking output to be sent
8813 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8815 SendToProgram("post\n", cps);
8817 SendToProgram("hard\n", cps);
8818 if (!appData.ponderNextMove) {
8819 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8820 it without being sure what state we are in first. "hard"
8821 is not a toggle, so that one is OK.
8823 SendToProgram("easy\n", cps);
8826 sprintf(buf, "ping %d\n", ++cps->lastPing);
8827 SendToProgram(buf, cps);
8829 cps->initDone = TRUE;
8834 StartChessProgram(cps)
8835 ChessProgramState *cps;
8840 if (appData.noChessProgram) return;
8841 cps->initDone = FALSE;
8843 if (strcmp(cps->host, "localhost") == 0) {
8844 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8845 } else if (*appData.remoteShell == NULLCHAR) {
8846 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8848 if (*appData.remoteUser == NULLCHAR) {
8849 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8852 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8853 cps->host, appData.remoteUser, cps->program);
8855 err = StartChildProcess(buf, "", &cps->pr);
8859 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8860 DisplayFatalError(buf, err, 1);
8866 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8867 if (cps->protocolVersion > 1) {
8868 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8869 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8870 cps->comboCnt = 0; // and values of combo boxes
8871 SendToProgram(buf, cps);
8873 SendToProgram("xboard\n", cps);
8879 TwoMachinesEventIfReady P((void))
8881 if (first.lastPing != first.lastPong) {
8882 DisplayMessage("", _("Waiting for first chess program"));
8883 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8886 if (second.lastPing != second.lastPong) {
8887 DisplayMessage("", _("Waiting for second chess program"));
8888 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8896 NextMatchGame P((void))
8898 int index; /* [HGM] autoinc: step load index during match */
8900 if (*appData.loadGameFile != NULLCHAR) {
8901 index = appData.loadGameIndex;
8902 if(index < 0) { // [HGM] autoinc
8903 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8904 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8906 LoadGameFromFile(appData.loadGameFile,
8908 appData.loadGameFile, FALSE);
8909 } else if (*appData.loadPositionFile != NULLCHAR) {
8910 index = appData.loadPositionIndex;
8911 if(index < 0) { // [HGM] autoinc
8912 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8913 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8915 LoadPositionFromFile(appData.loadPositionFile,
8917 appData.loadPositionFile);
8919 TwoMachinesEventIfReady();
8922 void UserAdjudicationEvent( int result )
8924 ChessMove gameResult = GameIsDrawn;
8927 gameResult = WhiteWins;
8929 else if( result < 0 ) {
8930 gameResult = BlackWins;
8933 if( gameMode == TwoMachinesPlay ) {
8934 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8939 // [HGM] save: calculate checksum of game to make games easily identifiable
8940 int StringCheckSum(char *s)
8943 if(s==NULL) return 0;
8944 while(*s) i = i*259 + *s++;
8951 for(i=backwardMostMove; i<forwardMostMove; i++) {
8952 sum += pvInfoList[i].depth;
8953 sum += StringCheckSum(parseList[i]);
8954 sum += StringCheckSum(commentList[i]);
8957 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8958 return sum + StringCheckSum(commentList[i]);
8959 } // end of save patch
8962 GameEnds(result, resultDetails, whosays)
8964 char *resultDetails;
8967 GameMode nextGameMode;
8969 char buf[MSG_SIZ], popupRequested = 0;
8971 if(endingGame) return; /* [HGM] crash: forbid recursion */
8973 if(twoBoards) { // [HGM] dual: switch back to one board
8974 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8975 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8977 if (appData.debugMode) {
8978 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8979 result, resultDetails ? resultDetails : "(null)", whosays);
8982 fromX = fromY = -1; // [HGM] abort any move the user is entering.
8984 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8985 /* If we are playing on ICS, the server decides when the
8986 game is over, but the engine can offer to draw, claim
8990 if (appData.zippyPlay && first.initDone) {
8991 if (result == GameIsDrawn) {
8992 /* In case draw still needs to be claimed */
8993 SendToICS(ics_prefix);
8994 SendToICS("draw\n");
8995 } else if (StrCaseStr(resultDetails, "resign")) {
8996 SendToICS(ics_prefix);
8997 SendToICS("resign\n");
9001 endingGame = 0; /* [HGM] crash */
9005 /* If we're loading the game from a file, stop */
9006 if (whosays == GE_FILE) {
9007 (void) StopLoadGameTimer();
9011 /* Cancel draw offers */
9012 first.offeredDraw = second.offeredDraw = 0;
9014 /* If this is an ICS game, only ICS can really say it's done;
9015 if not, anyone can. */
9016 isIcsGame = (gameMode == IcsPlayingWhite ||
9017 gameMode == IcsPlayingBlack ||
9018 gameMode == IcsObserving ||
9019 gameMode == IcsExamining);
9021 if (!isIcsGame || whosays == GE_ICS) {
9022 /* OK -- not an ICS game, or ICS said it was done */
9024 if (!isIcsGame && !appData.noChessProgram)
9025 SetUserThinkingEnables();
9027 /* [HGM] if a machine claims the game end we verify this claim */
9028 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9029 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9031 ChessMove trueResult = (ChessMove) -1;
9033 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9034 first.twoMachinesColor[0] :
9035 second.twoMachinesColor[0] ;
9037 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9038 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9039 /* [HGM] verify: engine mate claims accepted if they were flagged */
9040 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9042 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9043 /* [HGM] verify: engine mate claims accepted if they were flagged */
9044 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9046 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9047 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9050 // now verify win claims, but not in drop games, as we don't understand those yet
9051 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9052 || gameInfo.variant == VariantGreat) &&
9053 (result == WhiteWins && claimer == 'w' ||
9054 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9055 if (appData.debugMode) {
9056 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9057 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9059 if(result != trueResult) {
9060 sprintf(buf, "False win claim: '%s'", resultDetails);
9061 result = claimer == 'w' ? BlackWins : WhiteWins;
9062 resultDetails = buf;
9065 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9066 && (forwardMostMove <= backwardMostMove ||
9067 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9068 (claimer=='b')==(forwardMostMove&1))
9070 /* [HGM] verify: draws that were not flagged are false claims */
9071 sprintf(buf, "False draw claim: '%s'", resultDetails);
9072 result = claimer == 'w' ? BlackWins : WhiteWins;
9073 resultDetails = buf;
9075 /* (Claiming a loss is accepted no questions asked!) */
9077 /* [HGM] bare: don't allow bare King to win */
9078 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9079 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9080 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9081 && result != GameIsDrawn)
9082 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9083 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9084 int p = (signed char)boards[forwardMostMove][i][j] - color;
9085 if(p >= 0 && p <= (int)WhiteKing) k++;
9087 if (appData.debugMode) {
9088 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9089 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9092 result = GameIsDrawn;
9093 sprintf(buf, "%s but bare king", resultDetails);
9094 resultDetails = buf;
9100 if(serverMoves != NULL && !loadFlag) { char c = '=';
9101 if(result==WhiteWins) c = '+';
9102 if(result==BlackWins) c = '-';
9103 if(resultDetails != NULL)
9104 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9106 if (resultDetails != NULL) {
9107 gameInfo.result = result;
9108 gameInfo.resultDetails = StrSave(resultDetails);
9110 /* display last move only if game was not loaded from file */
9111 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9112 DisplayMove(currentMove - 1);
9114 if (forwardMostMove != 0) {
9115 if (gameMode != PlayFromGameFile && gameMode != EditGame
9116 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9118 if (*appData.saveGameFile != NULLCHAR) {
9119 SaveGameToFile(appData.saveGameFile, TRUE);
9120 } else if (appData.autoSaveGames) {
9123 if (*appData.savePositionFile != NULLCHAR) {
9124 SavePositionToFile(appData.savePositionFile);
9129 /* Tell program how game ended in case it is learning */
9130 /* [HGM] Moved this to after saving the PGN, just in case */
9131 /* engine died and we got here through time loss. In that */
9132 /* case we will get a fatal error writing the pipe, which */
9133 /* would otherwise lose us the PGN. */
9134 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9135 /* output during GameEnds should never be fatal anymore */
9136 if (gameMode == MachinePlaysWhite ||
9137 gameMode == MachinePlaysBlack ||
9138 gameMode == TwoMachinesPlay ||
9139 gameMode == IcsPlayingWhite ||
9140 gameMode == IcsPlayingBlack ||
9141 gameMode == BeginningOfGame) {
9143 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9145 if (first.pr != NoProc) {
9146 SendToProgram(buf, &first);
9148 if (second.pr != NoProc &&
9149 gameMode == TwoMachinesPlay) {
9150 SendToProgram(buf, &second);
9155 if (appData.icsActive) {
9156 if (appData.quietPlay &&
9157 (gameMode == IcsPlayingWhite ||
9158 gameMode == IcsPlayingBlack)) {
9159 SendToICS(ics_prefix);
9160 SendToICS("set shout 1\n");
9162 nextGameMode = IcsIdle;
9163 ics_user_moved = FALSE;
9164 /* clean up premove. It's ugly when the game has ended and the
9165 * premove highlights are still on the board.
9169 ClearPremoveHighlights();
9170 DrawPosition(FALSE, boards[currentMove]);
9172 if (whosays == GE_ICS) {
9175 if (gameMode == IcsPlayingWhite)
9177 else if(gameMode == IcsPlayingBlack)
9181 if (gameMode == IcsPlayingBlack)
9183 else if(gameMode == IcsPlayingWhite)
9190 PlayIcsUnfinishedSound();
9193 } else if (gameMode == EditGame ||
9194 gameMode == PlayFromGameFile ||
9195 gameMode == AnalyzeMode ||
9196 gameMode == AnalyzeFile) {
9197 nextGameMode = gameMode;
9199 nextGameMode = EndOfGame;
9204 nextGameMode = gameMode;
9207 if (appData.noChessProgram) {
9208 gameMode = nextGameMode;
9210 endingGame = 0; /* [HGM] crash */
9215 /* Put first chess program into idle state */
9216 if (first.pr != NoProc &&
9217 (gameMode == MachinePlaysWhite ||
9218 gameMode == MachinePlaysBlack ||
9219 gameMode == TwoMachinesPlay ||
9220 gameMode == IcsPlayingWhite ||
9221 gameMode == IcsPlayingBlack ||
9222 gameMode == BeginningOfGame)) {
9223 SendToProgram("force\n", &first);
9224 if (first.usePing) {
9226 sprintf(buf, "ping %d\n", ++first.lastPing);
9227 SendToProgram(buf, &first);
9230 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9231 /* Kill off first chess program */
9232 if (first.isr != NULL)
9233 RemoveInputSource(first.isr);
9236 if (first.pr != NoProc) {
9238 DoSleep( appData.delayBeforeQuit );
9239 SendToProgram("quit\n", &first);
9240 DoSleep( appData.delayAfterQuit );
9241 DestroyChildProcess(first.pr, first.useSigterm);
9246 /* Put second chess program into idle state */
9247 if (second.pr != NoProc &&
9248 gameMode == TwoMachinesPlay) {
9249 SendToProgram("force\n", &second);
9250 if (second.usePing) {
9252 sprintf(buf, "ping %d\n", ++second.lastPing);
9253 SendToProgram(buf, &second);
9256 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9257 /* Kill off second chess program */
9258 if (second.isr != NULL)
9259 RemoveInputSource(second.isr);
9262 if (second.pr != NoProc) {
9263 DoSleep( appData.delayBeforeQuit );
9264 SendToProgram("quit\n", &second);
9265 DoSleep( appData.delayAfterQuit );
9266 DestroyChildProcess(second.pr, second.useSigterm);
9271 if (matchMode && gameMode == TwoMachinesPlay) {
9274 if (first.twoMachinesColor[0] == 'w') {
9281 if (first.twoMachinesColor[0] == 'b') {
9290 if (matchGame < appData.matchGames) {
9292 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9293 tmp = first.twoMachinesColor;
9294 first.twoMachinesColor = second.twoMachinesColor;
9295 second.twoMachinesColor = tmp;
9297 gameMode = nextGameMode;
9299 if(appData.matchPause>10000 || appData.matchPause<10)
9300 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9301 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9302 endingGame = 0; /* [HGM] crash */
9305 gameMode = nextGameMode;
9306 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9307 first.tidy, second.tidy,
9308 first.matchWins, second.matchWins,
9309 appData.matchGames - (first.matchWins + second.matchWins));
9310 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9313 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9314 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9316 gameMode = nextGameMode;
9318 endingGame = 0; /* [HGM] crash */
9319 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9320 if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9321 matchMode = FALSE; appData.matchGames = matchGame = 0;
9327 /* Assumes program was just initialized (initString sent).
9328 Leaves program in force mode. */
9330 FeedMovesToProgram(cps, upto)
9331 ChessProgramState *cps;
9336 if (appData.debugMode)
9337 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9338 startedFromSetupPosition ? "position and " : "",
9339 backwardMostMove, upto, cps->which);
9340 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9341 // [HGM] variantswitch: make engine aware of new variant
9342 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9343 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9344 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9345 SendToProgram(buf, cps);
9346 currentlyInitializedVariant = gameInfo.variant;
9348 SendToProgram("force\n", cps);
9349 if (startedFromSetupPosition) {
9350 SendBoard(cps, backwardMostMove);
9351 if (appData.debugMode) {
9352 fprintf(debugFP, "feedMoves\n");
9355 for (i = backwardMostMove; i < upto; i++) {
9356 SendMoveToProgram(i, cps);
9362 ResurrectChessProgram()
9364 /* The chess program may have exited.
9365 If so, restart it and feed it all the moves made so far. */
9367 if (appData.noChessProgram || first.pr != NoProc) return;
9369 StartChessProgram(&first);
9370 InitChessProgram(&first, FALSE);
9371 FeedMovesToProgram(&first, currentMove);
9373 if (!first.sendTime) {
9374 /* can't tell gnuchess what its clock should read,
9375 so we bow to its notion. */
9377 timeRemaining[0][currentMove] = whiteTimeRemaining;
9378 timeRemaining[1][currentMove] = blackTimeRemaining;
9381 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9382 appData.icsEngineAnalyze) && first.analysisSupport) {
9383 SendToProgram("analyze\n", &first);
9384 first.analyzing = TRUE;
9397 if (appData.debugMode) {
9398 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9399 redraw, init, gameMode);
9401 CleanupTail(); // [HGM] vari: delete any stored variations
9402 pausing = pauseExamInvalid = FALSE;
9403 startedFromSetupPosition = blackPlaysFirst = FALSE;
9405 whiteFlag = blackFlag = FALSE;
9406 userOfferedDraw = FALSE;
9407 hintRequested = bookRequested = FALSE;
9408 first.maybeThinking = FALSE;
9409 second.maybeThinking = FALSE;
9410 first.bookSuspend = FALSE; // [HGM] book
9411 second.bookSuspend = FALSE;
9412 thinkOutput[0] = NULLCHAR;
9413 lastHint[0] = NULLCHAR;
9414 ClearGameInfo(&gameInfo);
9415 gameInfo.variant = StringToVariant(appData.variant);
9416 ics_user_moved = ics_clock_paused = FALSE;
9417 ics_getting_history = H_FALSE;
9419 white_holding[0] = black_holding[0] = NULLCHAR;
9420 ClearProgramStats();
9421 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9425 flipView = appData.flipView;
9426 ClearPremoveHighlights();
9428 alarmSounded = FALSE;
9430 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9431 if(appData.serverMovesName != NULL) {
9432 /* [HGM] prepare to make moves file for broadcasting */
9433 clock_t t = clock();
9434 if(serverMoves != NULL) fclose(serverMoves);
9435 serverMoves = fopen(appData.serverMovesName, "r");
9436 if(serverMoves != NULL) {
9437 fclose(serverMoves);
9438 /* delay 15 sec before overwriting, so all clients can see end */
9439 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9441 serverMoves = fopen(appData.serverMovesName, "w");
9445 gameMode = BeginningOfGame;
9447 if(appData.icsActive) gameInfo.variant = VariantNormal;
9448 currentMove = forwardMostMove = backwardMostMove = 0;
9449 InitPosition(redraw);
9450 for (i = 0; i < MAX_MOVES; i++) {
9451 if (commentList[i] != NULL) {
9452 free(commentList[i]);
9453 commentList[i] = NULL;
9457 timeRemaining[0][0] = whiteTimeRemaining;
9458 timeRemaining[1][0] = blackTimeRemaining;
9459 if (first.pr == NULL) {
9460 StartChessProgram(&first);
9463 InitChessProgram(&first, startedFromSetupPosition);
9466 DisplayMessage("", "");
9467 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9468 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9475 if (!AutoPlayOneMove())
9477 if (matchMode || appData.timeDelay == 0)
9479 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9481 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9490 int fromX, fromY, toX, toY;
9492 if (appData.debugMode) {
9493 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9496 if (gameMode != PlayFromGameFile)
9499 if (currentMove >= forwardMostMove) {
9500 gameMode = EditGame;
9503 /* [AS] Clear current move marker at the end of a game */
9504 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9509 toX = moveList[currentMove][2] - AAA;
9510 toY = moveList[currentMove][3] - ONE;
9512 if (moveList[currentMove][1] == '@') {
9513 if (appData.highlightLastMove) {
9514 SetHighlights(-1, -1, toX, toY);
9517 fromX = moveList[currentMove][0] - AAA;
9518 fromY = moveList[currentMove][1] - ONE;
9520 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9522 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9524 if (appData.highlightLastMove) {
9525 SetHighlights(fromX, fromY, toX, toY);
9528 DisplayMove(currentMove);
9529 SendMoveToProgram(currentMove++, &first);
9530 DisplayBothClocks();
9531 DrawPosition(FALSE, boards[currentMove]);
9532 // [HGM] PV info: always display, routine tests if empty
9533 DisplayComment(currentMove - 1, commentList[currentMove]);
9539 LoadGameOneMove(readAhead)
9540 ChessMove readAhead;
9542 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9543 char promoChar = NULLCHAR;
9548 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9549 gameMode != AnalyzeMode && gameMode != Training) {
9554 yyboardindex = forwardMostMove;
9555 if (readAhead != (ChessMove)0) {
9556 moveType = readAhead;
9558 if (gameFileFP == NULL)
9560 moveType = (ChessMove) yylex();
9566 if (appData.debugMode)
9567 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9570 /* append the comment but don't display it */
9571 AppendComment(currentMove, p, FALSE);
9574 case WhiteCapturesEnPassant:
9575 case BlackCapturesEnPassant:
9576 case WhitePromotion:
9577 case BlackPromotion:
9578 case WhiteNonPromotion:
9579 case BlackNonPromotion:
9581 case WhiteKingSideCastle:
9582 case WhiteQueenSideCastle:
9583 case BlackKingSideCastle:
9584 case BlackQueenSideCastle:
9585 case WhiteKingSideCastleWild:
9586 case WhiteQueenSideCastleWild:
9587 case BlackKingSideCastleWild:
9588 case BlackQueenSideCastleWild:
9590 case WhiteHSideCastleFR:
9591 case WhiteASideCastleFR:
9592 case BlackHSideCastleFR:
9593 case BlackASideCastleFR:
9595 if (appData.debugMode)
9596 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9597 fromX = currentMoveString[0] - AAA;
9598 fromY = currentMoveString[1] - ONE;
9599 toX = currentMoveString[2] - AAA;
9600 toY = currentMoveString[3] - ONE;
9601 promoChar = currentMoveString[4];
9606 if (appData.debugMode)
9607 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9608 fromX = moveType == WhiteDrop ?
9609 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9610 (int) CharToPiece(ToLower(currentMoveString[0]));
9612 toX = currentMoveString[2] - AAA;
9613 toY = currentMoveString[3] - ONE;
9619 case GameUnfinished:
9620 if (appData.debugMode)
9621 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9622 p = strchr(yy_text, '{');
9623 if (p == NULL) p = strchr(yy_text, '(');
9626 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9628 q = strchr(p, *p == '{' ? '}' : ')');
9629 if (q != NULL) *q = NULLCHAR;
9632 GameEnds(moveType, p, GE_FILE);
9634 if (cmailMsgLoaded) {
9636 flipView = WhiteOnMove(currentMove);
9637 if (moveType == GameUnfinished) flipView = !flipView;
9638 if (appData.debugMode)
9639 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9643 case (ChessMove) 0: /* end of file */
9644 if (appData.debugMode)
9645 fprintf(debugFP, "Parser hit end of file\n");
9646 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9652 if (WhiteOnMove(currentMove)) {
9653 GameEnds(BlackWins, "Black mates", GE_FILE);
9655 GameEnds(WhiteWins, "White mates", GE_FILE);
9659 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9666 if (lastLoadGameStart == GNUChessGame) {
9667 /* GNUChessGames have numbers, but they aren't move numbers */
9668 if (appData.debugMode)
9669 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9670 yy_text, (int) moveType);
9671 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9673 /* else fall thru */
9678 /* Reached start of next game in file */
9679 if (appData.debugMode)
9680 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9681 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9687 if (WhiteOnMove(currentMove)) {
9688 GameEnds(BlackWins, "Black mates", GE_FILE);
9690 GameEnds(WhiteWins, "White mates", GE_FILE);
9694 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9700 case PositionDiagram: /* should not happen; ignore */
9701 case ElapsedTime: /* ignore */
9702 case NAG: /* ignore */
9703 if (appData.debugMode)
9704 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9705 yy_text, (int) moveType);
9706 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9709 if (appData.testLegality) {
9710 if (appData.debugMode)
9711 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9712 sprintf(move, _("Illegal move: %d.%s%s"),
9713 (forwardMostMove / 2) + 1,
9714 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9715 DisplayError(move, 0);
9718 if (appData.debugMode)
9719 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9720 yy_text, currentMoveString);
9721 fromX = currentMoveString[0] - AAA;
9722 fromY = currentMoveString[1] - ONE;
9723 toX = currentMoveString[2] - AAA;
9724 toY = currentMoveString[3] - ONE;
9725 promoChar = currentMoveString[4];
9730 if (appData.debugMode)
9731 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9732 sprintf(move, _("Ambiguous move: %d.%s%s"),
9733 (forwardMostMove / 2) + 1,
9734 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9735 DisplayError(move, 0);
9740 case ImpossibleMove:
9741 if (appData.debugMode)
9742 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9743 sprintf(move, _("Illegal move: %d.%s%s"),
9744 (forwardMostMove / 2) + 1,
9745 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9746 DisplayError(move, 0);
9752 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9753 DrawPosition(FALSE, boards[currentMove]);
9754 DisplayBothClocks();
9755 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9756 DisplayComment(currentMove - 1, commentList[currentMove]);
9758 (void) StopLoadGameTimer();
9760 cmailOldMove = forwardMostMove;
9763 /* currentMoveString is set as a side-effect of yylex */
9764 strcat(currentMoveString, "\n");
9765 safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9767 thinkOutput[0] = NULLCHAR;
9768 MakeMove(fromX, fromY, toX, toY, promoChar);
9769 currentMove = forwardMostMove;
9774 /* Load the nth game from the given file */
9776 LoadGameFromFile(filename, n, title, useList)
9780 /*Boolean*/ int useList;
9785 if (strcmp(filename, "-") == 0) {
9789 f = fopen(filename, "rb");
9791 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9792 DisplayError(buf, errno);
9796 if (fseek(f, 0, 0) == -1) {
9797 /* f is not seekable; probably a pipe */
9800 if (useList && n == 0) {
9801 int error = GameListBuild(f);
9803 DisplayError(_("Cannot build game list"), error);
9804 } else if (!ListEmpty(&gameList) &&
9805 ((ListGame *) gameList.tailPred)->number > 1) {
9806 GameListPopUp(f, title);
9813 return LoadGame(f, n, title, FALSE);
9818 MakeRegisteredMove()
9820 int fromX, fromY, toX, toY;
9822 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9823 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9826 if (appData.debugMode)
9827 fprintf(debugFP, "Restoring %s for game %d\n",
9828 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9830 thinkOutput[0] = NULLCHAR;
9831 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9832 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9833 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9834 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9835 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9836 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9837 MakeMove(fromX, fromY, toX, toY, promoChar);
9838 ShowMove(fromX, fromY, toX, toY);
9840 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9847 if (WhiteOnMove(currentMove)) {
9848 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9850 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9855 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9862 if (WhiteOnMove(currentMove)) {
9863 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9865 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9870 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9881 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9883 CmailLoadGame(f, gameNumber, title, useList)
9891 if (gameNumber > nCmailGames) {
9892 DisplayError(_("No more games in this message"), 0);
9895 if (f == lastLoadGameFP) {
9896 int offset = gameNumber - lastLoadGameNumber;
9898 cmailMsg[0] = NULLCHAR;
9899 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9900 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9901 nCmailMovesRegistered--;
9903 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9904 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9905 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9908 if (! RegisterMove()) return FALSE;
9912 retVal = LoadGame(f, gameNumber, title, useList);
9914 /* Make move registered during previous look at this game, if any */
9915 MakeRegisteredMove();
9917 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9918 commentList[currentMove]
9919 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9920 DisplayComment(currentMove - 1, commentList[currentMove]);
9926 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9931 int gameNumber = lastLoadGameNumber + offset;
9932 if (lastLoadGameFP == NULL) {
9933 DisplayError(_("No game has been loaded yet"), 0);
9936 if (gameNumber <= 0) {
9937 DisplayError(_("Can't back up any further"), 0);
9940 if (cmailMsgLoaded) {
9941 return CmailLoadGame(lastLoadGameFP, gameNumber,
9942 lastLoadGameTitle, lastLoadGameUseList);
9944 return LoadGame(lastLoadGameFP, gameNumber,
9945 lastLoadGameTitle, lastLoadGameUseList);
9951 /* Load the nth game from open file f */
9953 LoadGame(f, gameNumber, title, useList)
9961 int gn = gameNumber;
9962 ListGame *lg = NULL;
9965 GameMode oldGameMode;
9966 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9968 if (appData.debugMode)
9969 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9971 if (gameMode == Training )
9972 SetTrainingModeOff();
9974 oldGameMode = gameMode;
9975 if (gameMode != BeginningOfGame) {
9980 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9981 fclose(lastLoadGameFP);
9985 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9988 fseek(f, lg->offset, 0);
9989 GameListHighlight(gameNumber);
9993 DisplayError(_("Game number out of range"), 0);
9998 if (fseek(f, 0, 0) == -1) {
9999 if (f == lastLoadGameFP ?
10000 gameNumber == lastLoadGameNumber + 1 :
10004 DisplayError(_("Can't seek on game file"), 0);
10009 lastLoadGameFP = f;
10010 lastLoadGameNumber = gameNumber;
10011 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10012 lastLoadGameUseList = useList;
10016 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10017 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10018 lg->gameInfo.black);
10020 } else if (*title != NULLCHAR) {
10021 if (gameNumber > 1) {
10022 sprintf(buf, "%s %d", title, gameNumber);
10025 DisplayTitle(title);
10029 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10030 gameMode = PlayFromGameFile;
10034 currentMove = forwardMostMove = backwardMostMove = 0;
10035 CopyBoard(boards[0], initialPosition);
10039 * Skip the first gn-1 games in the file.
10040 * Also skip over anything that precedes an identifiable
10041 * start of game marker, to avoid being confused by
10042 * garbage at the start of the file. Currently
10043 * recognized start of game markers are the move number "1",
10044 * the pattern "gnuchess .* game", the pattern
10045 * "^[#;%] [^ ]* game file", and a PGN tag block.
10046 * A game that starts with one of the latter two patterns
10047 * will also have a move number 1, possibly
10048 * following a position diagram.
10049 * 5-4-02: Let's try being more lenient and allowing a game to
10050 * start with an unnumbered move. Does that break anything?
10052 cm = lastLoadGameStart = (ChessMove) 0;
10054 yyboardindex = forwardMostMove;
10055 cm = (ChessMove) yylex();
10057 case (ChessMove) 0:
10058 if (cmailMsgLoaded) {
10059 nCmailGames = CMAIL_MAX_GAMES - gn;
10062 DisplayError(_("Game not found in file"), 0);
10069 lastLoadGameStart = cm;
10072 case MoveNumberOne:
10073 switch (lastLoadGameStart) {
10078 case MoveNumberOne:
10079 case (ChessMove) 0:
10080 gn--; /* count this game */
10081 lastLoadGameStart = cm;
10090 switch (lastLoadGameStart) {
10093 case MoveNumberOne:
10094 case (ChessMove) 0:
10095 gn--; /* count this game */
10096 lastLoadGameStart = cm;
10099 lastLoadGameStart = cm; /* game counted already */
10107 yyboardindex = forwardMostMove;
10108 cm = (ChessMove) yylex();
10109 } while (cm == PGNTag || cm == Comment);
10116 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10117 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10118 != CMAIL_OLD_RESULT) {
10120 cmailResult[ CMAIL_MAX_GAMES
10121 - gn - 1] = CMAIL_OLD_RESULT;
10127 /* Only a NormalMove can be at the start of a game
10128 * without a position diagram. */
10129 if (lastLoadGameStart == (ChessMove) 0) {
10131 lastLoadGameStart = MoveNumberOne;
10140 if (appData.debugMode)
10141 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10143 if (cm == XBoardGame) {
10144 /* Skip any header junk before position diagram and/or move 1 */
10146 yyboardindex = forwardMostMove;
10147 cm = (ChessMove) yylex();
10149 if (cm == (ChessMove) 0 ||
10150 cm == GNUChessGame || cm == XBoardGame) {
10151 /* Empty game; pretend end-of-file and handle later */
10152 cm = (ChessMove) 0;
10156 if (cm == MoveNumberOne || cm == PositionDiagram ||
10157 cm == PGNTag || cm == Comment)
10160 } else if (cm == GNUChessGame) {
10161 if (gameInfo.event != NULL) {
10162 free(gameInfo.event);
10164 gameInfo.event = StrSave(yy_text);
10167 startedFromSetupPosition = FALSE;
10168 while (cm == PGNTag) {
10169 if (appData.debugMode)
10170 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10171 err = ParsePGNTag(yy_text, &gameInfo);
10172 if (!err) numPGNTags++;
10174 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10175 if(gameInfo.variant != oldVariant) {
10176 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10177 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10178 InitPosition(TRUE);
10179 oldVariant = gameInfo.variant;
10180 if (appData.debugMode)
10181 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10185 if (gameInfo.fen != NULL) {
10186 Board initial_position;
10187 startedFromSetupPosition = TRUE;
10188 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10190 DisplayError(_("Bad FEN position in file"), 0);
10193 CopyBoard(boards[0], initial_position);
10194 if (blackPlaysFirst) {
10195 currentMove = forwardMostMove = backwardMostMove = 1;
10196 CopyBoard(boards[1], initial_position);
10197 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10198 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10199 timeRemaining[0][1] = whiteTimeRemaining;
10200 timeRemaining[1][1] = blackTimeRemaining;
10201 if (commentList[0] != NULL) {
10202 commentList[1] = commentList[0];
10203 commentList[0] = NULL;
10206 currentMove = forwardMostMove = backwardMostMove = 0;
10208 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10210 initialRulePlies = FENrulePlies;
10211 for( i=0; i< nrCastlingRights; i++ )
10212 initialRights[i] = initial_position[CASTLING][i];
10214 yyboardindex = forwardMostMove;
10215 free(gameInfo.fen);
10216 gameInfo.fen = NULL;
10219 yyboardindex = forwardMostMove;
10220 cm = (ChessMove) yylex();
10222 /* Handle comments interspersed among the tags */
10223 while (cm == Comment) {
10225 if (appData.debugMode)
10226 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10228 AppendComment(currentMove, p, FALSE);
10229 yyboardindex = forwardMostMove;
10230 cm = (ChessMove) yylex();
10234 /* don't rely on existence of Event tag since if game was
10235 * pasted from clipboard the Event tag may not exist
10237 if (numPGNTags > 0){
10239 if (gameInfo.variant == VariantNormal) {
10240 VariantClass v = StringToVariant(gameInfo.event);
10241 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10242 if(v < VariantShogi) gameInfo.variant = v;
10245 if( appData.autoDisplayTags ) {
10246 tags = PGNTags(&gameInfo);
10247 TagsPopUp(tags, CmailMsg());
10252 /* Make something up, but don't display it now */
10257 if (cm == PositionDiagram) {
10260 Board initial_position;
10262 if (appData.debugMode)
10263 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10265 if (!startedFromSetupPosition) {
10267 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10268 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10278 initial_position[i][j++] = CharToPiece(*p);
10281 while (*p == ' ' || *p == '\t' ||
10282 *p == '\n' || *p == '\r') p++;
10284 if (strncmp(p, "black", strlen("black"))==0)
10285 blackPlaysFirst = TRUE;
10287 blackPlaysFirst = FALSE;
10288 startedFromSetupPosition = TRUE;
10290 CopyBoard(boards[0], initial_position);
10291 if (blackPlaysFirst) {
10292 currentMove = forwardMostMove = backwardMostMove = 1;
10293 CopyBoard(boards[1], initial_position);
10294 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10295 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10296 timeRemaining[0][1] = whiteTimeRemaining;
10297 timeRemaining[1][1] = blackTimeRemaining;
10298 if (commentList[0] != NULL) {
10299 commentList[1] = commentList[0];
10300 commentList[0] = NULL;
10303 currentMove = forwardMostMove = backwardMostMove = 0;
10306 yyboardindex = forwardMostMove;
10307 cm = (ChessMove) yylex();
10310 if (first.pr == NoProc) {
10311 StartChessProgram(&first);
10313 InitChessProgram(&first, FALSE);
10314 SendToProgram("force\n", &first);
10315 if (startedFromSetupPosition) {
10316 SendBoard(&first, forwardMostMove);
10317 if (appData.debugMode) {
10318 fprintf(debugFP, "Load Game\n");
10320 DisplayBothClocks();
10323 /* [HGM] server: flag to write setup moves in broadcast file as one */
10324 loadFlag = appData.suppressLoadMoves;
10326 while (cm == Comment) {
10328 if (appData.debugMode)
10329 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10331 AppendComment(currentMove, p, FALSE);
10332 yyboardindex = forwardMostMove;
10333 cm = (ChessMove) yylex();
10336 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10337 cm == WhiteWins || cm == BlackWins ||
10338 cm == GameIsDrawn || cm == GameUnfinished) {
10339 DisplayMessage("", _("No moves in game"));
10340 if (cmailMsgLoaded) {
10341 if (appData.debugMode)
10342 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10346 DrawPosition(FALSE, boards[currentMove]);
10347 DisplayBothClocks();
10348 gameMode = EditGame;
10355 // [HGM] PV info: routine tests if comment empty
10356 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10357 DisplayComment(currentMove - 1, commentList[currentMove]);
10359 if (!matchMode && appData.timeDelay != 0)
10360 DrawPosition(FALSE, boards[currentMove]);
10362 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10363 programStats.ok_to_send = 1;
10366 /* if the first token after the PGN tags is a move
10367 * and not move number 1, retrieve it from the parser
10369 if (cm != MoveNumberOne)
10370 LoadGameOneMove(cm);
10372 /* load the remaining moves from the file */
10373 while (LoadGameOneMove((ChessMove)0)) {
10374 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10375 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10378 /* rewind to the start of the game */
10379 currentMove = backwardMostMove;
10381 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10383 if (oldGameMode == AnalyzeFile ||
10384 oldGameMode == AnalyzeMode) {
10385 AnalyzeFileEvent();
10388 if (matchMode || appData.timeDelay == 0) {
10390 gameMode = EditGame;
10392 } else if (appData.timeDelay > 0) {
10393 AutoPlayGameLoop();
10396 if (appData.debugMode)
10397 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10399 loadFlag = 0; /* [HGM] true game starts */
10403 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10405 ReloadPosition(offset)
10408 int positionNumber = lastLoadPositionNumber + offset;
10409 if (lastLoadPositionFP == NULL) {
10410 DisplayError(_("No position has been loaded yet"), 0);
10413 if (positionNumber <= 0) {
10414 DisplayError(_("Can't back up any further"), 0);
10417 return LoadPosition(lastLoadPositionFP, positionNumber,
10418 lastLoadPositionTitle);
10421 /* Load the nth position from the given file */
10423 LoadPositionFromFile(filename, n, title)
10431 if (strcmp(filename, "-") == 0) {
10432 return LoadPosition(stdin, n, "stdin");
10434 f = fopen(filename, "rb");
10436 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10437 DisplayError(buf, errno);
10440 return LoadPosition(f, n, title);
10445 /* Load the nth position from the given open file, and close it */
10447 LoadPosition(f, positionNumber, title)
10449 int positionNumber;
10452 char *p, line[MSG_SIZ];
10453 Board initial_position;
10454 int i, j, fenMode, pn;
10456 if (gameMode == Training )
10457 SetTrainingModeOff();
10459 if (gameMode != BeginningOfGame) {
10460 Reset(FALSE, TRUE);
10462 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10463 fclose(lastLoadPositionFP);
10465 if (positionNumber == 0) positionNumber = 1;
10466 lastLoadPositionFP = f;
10467 lastLoadPositionNumber = positionNumber;
10468 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10469 if (first.pr == NoProc) {
10470 StartChessProgram(&first);
10471 InitChessProgram(&first, FALSE);
10473 pn = positionNumber;
10474 if (positionNumber < 0) {
10475 /* Negative position number means to seek to that byte offset */
10476 if (fseek(f, -positionNumber, 0) == -1) {
10477 DisplayError(_("Can't seek on position file"), 0);
10482 if (fseek(f, 0, 0) == -1) {
10483 if (f == lastLoadPositionFP ?
10484 positionNumber == lastLoadPositionNumber + 1 :
10485 positionNumber == 1) {
10488 DisplayError(_("Can't seek on position file"), 0);
10493 /* See if this file is FEN or old-style xboard */
10494 if (fgets(line, MSG_SIZ, f) == NULL) {
10495 DisplayError(_("Position not found in file"), 0);
10498 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10499 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10502 if (fenMode || line[0] == '#') pn--;
10504 /* skip positions before number pn */
10505 if (fgets(line, MSG_SIZ, f) == NULL) {
10507 DisplayError(_("Position not found in file"), 0);
10510 if (fenMode || line[0] == '#') pn--;
10515 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10516 DisplayError(_("Bad FEN position in file"), 0);
10520 (void) fgets(line, MSG_SIZ, f);
10521 (void) fgets(line, MSG_SIZ, f);
10523 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10524 (void) fgets(line, MSG_SIZ, f);
10525 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10528 initial_position[i][j++] = CharToPiece(*p);
10532 blackPlaysFirst = FALSE;
10534 (void) fgets(line, MSG_SIZ, f);
10535 if (strncmp(line, "black", strlen("black"))==0)
10536 blackPlaysFirst = TRUE;
10539 startedFromSetupPosition = TRUE;
10541 SendToProgram("force\n", &first);
10542 CopyBoard(boards[0], initial_position);
10543 if (blackPlaysFirst) {
10544 currentMove = forwardMostMove = backwardMostMove = 1;
10545 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10546 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10547 CopyBoard(boards[1], initial_position);
10548 DisplayMessage("", _("Black to play"));
10550 currentMove = forwardMostMove = backwardMostMove = 0;
10551 DisplayMessage("", _("White to play"));
10553 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10554 SendBoard(&first, forwardMostMove);
10555 if (appData.debugMode) {
10557 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10558 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10559 fprintf(debugFP, "Load Position\n");
10562 if (positionNumber > 1) {
10563 sprintf(line, "%s %d", title, positionNumber);
10564 DisplayTitle(line);
10566 DisplayTitle(title);
10568 gameMode = EditGame;
10571 timeRemaining[0][1] = whiteTimeRemaining;
10572 timeRemaining[1][1] = blackTimeRemaining;
10573 DrawPosition(FALSE, boards[currentMove]);
10580 CopyPlayerNameIntoFileName(dest, src)
10583 while (*src != NULLCHAR && *src != ',') {
10588 *(*dest)++ = *src++;
10593 char *DefaultFileName(ext)
10596 static char def[MSG_SIZ];
10599 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10601 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10603 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10605 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10612 /* Save the current game to the given file */
10614 SaveGameToFile(filename, append)
10621 if (strcmp(filename, "-") == 0) {
10622 return SaveGame(stdout, 0, NULL);
10624 f = fopen(filename, append ? "a" : "w");
10626 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10627 DisplayError(buf, errno);
10630 return SaveGame(f, 0, NULL);
10639 static char buf[MSG_SIZ];
10642 p = strchr(str, ' ');
10643 if (p == NULL) return str;
10644 strncpy(buf, str, p - str);
10645 buf[p - str] = NULLCHAR;
10649 #define PGN_MAX_LINE 75
10651 #define PGN_SIDE_WHITE 0
10652 #define PGN_SIDE_BLACK 1
10655 static int FindFirstMoveOutOfBook( int side )
10659 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10660 int index = backwardMostMove;
10661 int has_book_hit = 0;
10663 if( (index % 2) != side ) {
10667 while( index < forwardMostMove ) {
10668 /* Check to see if engine is in book */
10669 int depth = pvInfoList[index].depth;
10670 int score = pvInfoList[index].score;
10676 else if( score == 0 && depth == 63 ) {
10677 in_book = 1; /* Zappa */
10679 else if( score == 2 && depth == 99 ) {
10680 in_book = 1; /* Abrok */
10683 has_book_hit += in_book;
10699 void GetOutOfBookInfo( char * buf )
10703 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10705 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10706 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10710 if( oob[0] >= 0 || oob[1] >= 0 ) {
10711 for( i=0; i<2; i++ ) {
10715 if( i > 0 && oob[0] >= 0 ) {
10716 strcat( buf, " " );
10719 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10720 sprintf( buf+strlen(buf), "%s%.2f",
10721 pvInfoList[idx].score >= 0 ? "+" : "",
10722 pvInfoList[idx].score / 100.0 );
10728 /* Save game in PGN style and close the file */
10733 int i, offset, linelen, newblock;
10737 int movelen, numlen, blank;
10738 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10740 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10742 tm = time((time_t *) NULL);
10744 PrintPGNTags(f, &gameInfo);
10746 if (backwardMostMove > 0 || startedFromSetupPosition) {
10747 char *fen = PositionToFEN(backwardMostMove, NULL);
10748 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10749 fprintf(f, "\n{--------------\n");
10750 PrintPosition(f, backwardMostMove);
10751 fprintf(f, "--------------}\n");
10755 /* [AS] Out of book annotation */
10756 if( appData.saveOutOfBookInfo ) {
10759 GetOutOfBookInfo( buf );
10761 if( buf[0] != '\0' ) {
10762 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10769 i = backwardMostMove;
10773 while (i < forwardMostMove) {
10774 /* Print comments preceding this move */
10775 if (commentList[i] != NULL) {
10776 if (linelen > 0) fprintf(f, "\n");
10777 fprintf(f, "%s", commentList[i]);
10782 /* Format move number */
10783 if ((i % 2) == 0) {
10784 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10787 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10789 numtext[0] = NULLCHAR;
10792 numlen = strlen(numtext);
10795 /* Print move number */
10796 blank = linelen > 0 && numlen > 0;
10797 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10806 fprintf(f, "%s", numtext);
10810 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10811 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10814 blank = linelen > 0 && movelen > 0;
10815 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10824 fprintf(f, "%s", move_buffer);
10825 linelen += movelen;
10827 /* [AS] Add PV info if present */
10828 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10829 /* [HGM] add time */
10830 char buf[MSG_SIZ]; int seconds;
10832 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10834 if( seconds <= 0) buf[0] = 0; else
10835 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10836 seconds = (seconds + 4)/10; // round to full seconds
10837 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10838 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10841 sprintf( move_buffer, "{%s%.2f/%d%s}",
10842 pvInfoList[i].score >= 0 ? "+" : "",
10843 pvInfoList[i].score / 100.0,
10844 pvInfoList[i].depth,
10847 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10849 /* Print score/depth */
10850 blank = linelen > 0 && movelen > 0;
10851 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10860 fprintf(f, "%s", move_buffer);
10861 linelen += movelen;
10867 /* Start a new line */
10868 if (linelen > 0) fprintf(f, "\n");
10870 /* Print comments after last move */
10871 if (commentList[i] != NULL) {
10872 fprintf(f, "%s\n", commentList[i]);
10876 if (gameInfo.resultDetails != NULL &&
10877 gameInfo.resultDetails[0] != NULLCHAR) {
10878 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10879 PGNResult(gameInfo.result));
10881 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10885 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10889 /* Save game in old style and close the file */
10891 SaveGameOldStyle(f)
10897 tm = time((time_t *) NULL);
10899 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10902 if (backwardMostMove > 0 || startedFromSetupPosition) {
10903 fprintf(f, "\n[--------------\n");
10904 PrintPosition(f, backwardMostMove);
10905 fprintf(f, "--------------]\n");
10910 i = backwardMostMove;
10911 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10913 while (i < forwardMostMove) {
10914 if (commentList[i] != NULL) {
10915 fprintf(f, "[%s]\n", commentList[i]);
10918 if ((i % 2) == 1) {
10919 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10922 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10924 if (commentList[i] != NULL) {
10928 if (i >= forwardMostMove) {
10932 fprintf(f, "%s\n", parseList[i]);
10937 if (commentList[i] != NULL) {
10938 fprintf(f, "[%s]\n", commentList[i]);
10941 /* This isn't really the old style, but it's close enough */
10942 if (gameInfo.resultDetails != NULL &&
10943 gameInfo.resultDetails[0] != NULLCHAR) {
10944 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10945 gameInfo.resultDetails);
10947 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10954 /* Save the current game to open file f and close the file */
10956 SaveGame(f, dummy, dummy2)
10961 if (gameMode == EditPosition) EditPositionDone(TRUE);
10962 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10963 if (appData.oldSaveStyle)
10964 return SaveGameOldStyle(f);
10966 return SaveGamePGN(f);
10969 /* Save the current position to the given file */
10971 SavePositionToFile(filename)
10977 if (strcmp(filename, "-") == 0) {
10978 return SavePosition(stdout, 0, NULL);
10980 f = fopen(filename, "a");
10982 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10983 DisplayError(buf, errno);
10986 SavePosition(f, 0, NULL);
10992 /* Save the current position to the given open file and close the file */
10994 SavePosition(f, dummy, dummy2)
11002 if (gameMode == EditPosition) EditPositionDone(TRUE);
11003 if (appData.oldSaveStyle) {
11004 tm = time((time_t *) NULL);
11006 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11008 fprintf(f, "[--------------\n");
11009 PrintPosition(f, currentMove);
11010 fprintf(f, "--------------]\n");
11012 fen = PositionToFEN(currentMove, NULL);
11013 fprintf(f, "%s\n", fen);
11021 ReloadCmailMsgEvent(unregister)
11025 static char *inFilename = NULL;
11026 static char *outFilename;
11028 struct stat inbuf, outbuf;
11031 /* Any registered moves are unregistered if unregister is set, */
11032 /* i.e. invoked by the signal handler */
11034 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11035 cmailMoveRegistered[i] = FALSE;
11036 if (cmailCommentList[i] != NULL) {
11037 free(cmailCommentList[i]);
11038 cmailCommentList[i] = NULL;
11041 nCmailMovesRegistered = 0;
11044 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11045 cmailResult[i] = CMAIL_NOT_RESULT;
11049 if (inFilename == NULL) {
11050 /* Because the filenames are static they only get malloced once */
11051 /* and they never get freed */
11052 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11053 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11055 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11056 sprintf(outFilename, "%s.out", appData.cmailGameName);
11059 status = stat(outFilename, &outbuf);
11061 cmailMailedMove = FALSE;
11063 status = stat(inFilename, &inbuf);
11064 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11067 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11068 counts the games, notes how each one terminated, etc.
11070 It would be nice to remove this kludge and instead gather all
11071 the information while building the game list. (And to keep it
11072 in the game list nodes instead of having a bunch of fixed-size
11073 parallel arrays.) Note this will require getting each game's
11074 termination from the PGN tags, as the game list builder does
11075 not process the game moves. --mann
11077 cmailMsgLoaded = TRUE;
11078 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11080 /* Load first game in the file or popup game menu */
11081 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11083 #endif /* !WIN32 */
11091 char string[MSG_SIZ];
11093 if ( cmailMailedMove
11094 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11095 return TRUE; /* Allow free viewing */
11098 /* Unregister move to ensure that we don't leave RegisterMove */
11099 /* with the move registered when the conditions for registering no */
11101 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11102 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11103 nCmailMovesRegistered --;
11105 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11107 free(cmailCommentList[lastLoadGameNumber - 1]);
11108 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11112 if (cmailOldMove == -1) {
11113 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11117 if (currentMove > cmailOldMove + 1) {
11118 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11122 if (currentMove < cmailOldMove) {
11123 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11127 if (forwardMostMove > currentMove) {
11128 /* Silently truncate extra moves */
11132 if ( (currentMove == cmailOldMove + 1)
11133 || ( (currentMove == cmailOldMove)
11134 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11135 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11136 if (gameInfo.result != GameUnfinished) {
11137 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11140 if (commentList[currentMove] != NULL) {
11141 cmailCommentList[lastLoadGameNumber - 1]
11142 = StrSave(commentList[currentMove]);
11144 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11146 if (appData.debugMode)
11147 fprintf(debugFP, "Saving %s for game %d\n",
11148 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11151 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11153 f = fopen(string, "w");
11154 if (appData.oldSaveStyle) {
11155 SaveGameOldStyle(f); /* also closes the file */
11157 sprintf(string, "%s.pos.out", appData.cmailGameName);
11158 f = fopen(string, "w");
11159 SavePosition(f, 0, NULL); /* also closes the file */
11161 fprintf(f, "{--------------\n");
11162 PrintPosition(f, currentMove);
11163 fprintf(f, "--------------}\n\n");
11165 SaveGame(f, 0, NULL); /* also closes the file*/
11168 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11169 nCmailMovesRegistered ++;
11170 } else if (nCmailGames == 1) {
11171 DisplayError(_("You have not made a move yet"), 0);
11182 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11183 FILE *commandOutput;
11184 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11185 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11191 if (! cmailMsgLoaded) {
11192 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11196 if (nCmailGames == nCmailResults) {
11197 DisplayError(_("No unfinished games"), 0);
11201 #if CMAIL_PROHIBIT_REMAIL
11202 if (cmailMailedMove) {
11203 sprintf(msg, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
11204 DisplayError(msg, 0);
11209 if (! (cmailMailedMove || RegisterMove())) return;
11211 if ( cmailMailedMove
11212 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11213 sprintf(string, partCommandString,
11214 appData.debugMode ? " -v" : "", appData.cmailGameName);
11215 commandOutput = popen(string, "r");
11217 if (commandOutput == NULL) {
11218 DisplayError(_("Failed to invoke cmail"), 0);
11220 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11221 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11223 if (nBuffers > 1) {
11224 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11225 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11226 nBytes = MSG_SIZ - 1;
11228 (void) memcpy(msg, buffer, nBytes);
11230 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11232 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11233 cmailMailedMove = TRUE; /* Prevent >1 moves */
11236 for (i = 0; i < nCmailGames; i ++) {
11237 if (cmailResult[i] == CMAIL_NOT_RESULT) {
11242 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11244 sprintf(buffer, "%s/%s.%s.archive",
11246 appData.cmailGameName,
11248 LoadGameFromFile(buffer, 1, buffer, FALSE);
11249 cmailMsgLoaded = FALSE;
11253 DisplayInformation(msg);
11254 pclose(commandOutput);
11257 if ((*cmailMsg) != '\0') {
11258 DisplayInformation(cmailMsg);
11263 #endif /* !WIN32 */
11272 int prependComma = 0;
11274 char string[MSG_SIZ]; /* Space for game-list */
11277 if (!cmailMsgLoaded) return "";
11279 if (cmailMailedMove) {
11280 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11282 /* Create a list of games left */
11283 sprintf(string, "[");
11284 for (i = 0; i < nCmailGames; i ++) {
11285 if (! ( cmailMoveRegistered[i]
11286 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11287 if (prependComma) {
11288 sprintf(number, ",%d", i + 1);
11290 sprintf(number, "%d", i + 1);
11294 strcat(string, number);
11297 strcat(string, "]");
11299 if (nCmailMovesRegistered + nCmailResults == 0) {
11300 switch (nCmailGames) {
11303 _("Still need to make move for game\n"));
11308 _("Still need to make moves for both games\n"));
11313 _("Still need to make moves for all %d games\n"),
11318 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11321 _("Still need to make a move for game %s\n"),
11326 if (nCmailResults == nCmailGames) {
11327 sprintf(cmailMsg, _("No unfinished games\n"));
11329 sprintf(cmailMsg, _("Ready to send mail\n"));
11335 _("Still need to make moves for games %s\n"),
11347 if (gameMode == Training)
11348 SetTrainingModeOff();
11351 cmailMsgLoaded = FALSE;
11352 if (appData.icsActive) {
11353 SendToICS(ics_prefix);
11354 SendToICS("refresh\n");
11364 /* Give up on clean exit */
11368 /* Keep trying for clean exit */
11372 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11374 if (telnetISR != NULL) {
11375 RemoveInputSource(telnetISR);
11377 if (icsPR != NoProc) {
11378 DestroyChildProcess(icsPR, TRUE);
11381 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11382 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11384 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11385 /* make sure this other one finishes before killing it! */
11386 if(endingGame) { int count = 0;
11387 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11388 while(endingGame && count++ < 10) DoSleep(1);
11389 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11392 /* Kill off chess programs */
11393 if (first.pr != NoProc) {
11396 DoSleep( appData.delayBeforeQuit );
11397 SendToProgram("quit\n", &first);
11398 DoSleep( appData.delayAfterQuit );
11399 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11401 if (second.pr != NoProc) {
11402 DoSleep( appData.delayBeforeQuit );
11403 SendToProgram("quit\n", &second);
11404 DoSleep( appData.delayAfterQuit );
11405 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11407 if (first.isr != NULL) {
11408 RemoveInputSource(first.isr);
11410 if (second.isr != NULL) {
11411 RemoveInputSource(second.isr);
11414 ShutDownFrontEnd();
11421 if (appData.debugMode)
11422 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11426 if (gameMode == MachinePlaysWhite ||
11427 gameMode == MachinePlaysBlack) {
11430 DisplayBothClocks();
11432 if (gameMode == PlayFromGameFile) {
11433 if (appData.timeDelay >= 0)
11434 AutoPlayGameLoop();
11435 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11436 Reset(FALSE, TRUE);
11437 SendToICS(ics_prefix);
11438 SendToICS("refresh\n");
11439 } else if (currentMove < forwardMostMove) {
11440 ForwardInner(forwardMostMove);
11442 pauseExamInvalid = FALSE;
11444 switch (gameMode) {
11448 pauseExamForwardMostMove = forwardMostMove;
11449 pauseExamInvalid = FALSE;
11452 case IcsPlayingWhite:
11453 case IcsPlayingBlack:
11457 case PlayFromGameFile:
11458 (void) StopLoadGameTimer();
11462 case BeginningOfGame:
11463 if (appData.icsActive) return;
11464 /* else fall through */
11465 case MachinePlaysWhite:
11466 case MachinePlaysBlack:
11467 case TwoMachinesPlay:
11468 if (forwardMostMove == 0)
11469 return; /* don't pause if no one has moved */
11470 if ((gameMode == MachinePlaysWhite &&
11471 !WhiteOnMove(forwardMostMove)) ||
11472 (gameMode == MachinePlaysBlack &&
11473 WhiteOnMove(forwardMostMove))) {
11486 char title[MSG_SIZ];
11488 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11489 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11491 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11492 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11493 parseList[currentMove - 1]);
11496 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11503 char *tags = PGNTags(&gameInfo);
11504 EditTagsPopUp(tags);
11511 if (appData.noChessProgram || gameMode == AnalyzeMode)
11514 if (gameMode != AnalyzeFile) {
11515 if (!appData.icsEngineAnalyze) {
11517 if (gameMode != EditGame) return;
11519 ResurrectChessProgram();
11520 SendToProgram("analyze\n", &first);
11521 first.analyzing = TRUE;
11522 /*first.maybeThinking = TRUE;*/
11523 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11524 EngineOutputPopUp();
11526 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11531 StartAnalysisClock();
11532 GetTimeMark(&lastNodeCountTime);
11539 if (appData.noChessProgram || gameMode == AnalyzeFile)
11542 if (gameMode != AnalyzeMode) {
11544 if (gameMode != EditGame) return;
11545 ResurrectChessProgram();
11546 SendToProgram("analyze\n", &first);
11547 first.analyzing = TRUE;
11548 /*first.maybeThinking = TRUE;*/
11549 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11550 EngineOutputPopUp();
11552 gameMode = AnalyzeFile;
11557 StartAnalysisClock();
11558 GetTimeMark(&lastNodeCountTime);
11563 MachineWhiteEvent()
11566 char *bookHit = NULL;
11568 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11572 if (gameMode == PlayFromGameFile ||
11573 gameMode == TwoMachinesPlay ||
11574 gameMode == Training ||
11575 gameMode == AnalyzeMode ||
11576 gameMode == EndOfGame)
11579 if (gameMode == EditPosition)
11580 EditPositionDone(TRUE);
11582 if (!WhiteOnMove(currentMove)) {
11583 DisplayError(_("It is not White's turn"), 0);
11587 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11590 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11591 gameMode == AnalyzeFile)
11594 ResurrectChessProgram(); /* in case it isn't running */
11595 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11596 gameMode = MachinePlaysWhite;
11599 gameMode = MachinePlaysWhite;
11603 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11605 if (first.sendName) {
11606 sprintf(buf, "name %s\n", gameInfo.black);
11607 SendToProgram(buf, &first);
11609 if (first.sendTime) {
11610 if (first.useColors) {
11611 SendToProgram("black\n", &first); /*gnu kludge*/
11613 SendTimeRemaining(&first, TRUE);
11615 if (first.useColors) {
11616 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11618 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11619 SetMachineThinkingEnables();
11620 first.maybeThinking = TRUE;
11624 if (appData.autoFlipView && !flipView) {
11625 flipView = !flipView;
11626 DrawPosition(FALSE, NULL);
11627 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11630 if(bookHit) { // [HGM] book: simulate book reply
11631 static char bookMove[MSG_SIZ]; // a bit generous?
11633 programStats.nodes = programStats.depth = programStats.time =
11634 programStats.score = programStats.got_only_move = 0;
11635 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11637 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11638 strcat(bookMove, bookHit);
11639 HandleMachineMove(bookMove, &first);
11644 MachineBlackEvent()
11647 char *bookHit = NULL;
11649 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11653 if (gameMode == PlayFromGameFile ||
11654 gameMode == TwoMachinesPlay ||
11655 gameMode == Training ||
11656 gameMode == AnalyzeMode ||
11657 gameMode == EndOfGame)
11660 if (gameMode == EditPosition)
11661 EditPositionDone(TRUE);
11663 if (WhiteOnMove(currentMove)) {
11664 DisplayError(_("It is not Black's turn"), 0);
11668 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11671 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11672 gameMode == AnalyzeFile)
11675 ResurrectChessProgram(); /* in case it isn't running */
11676 gameMode = MachinePlaysBlack;
11680 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11682 if (first.sendName) {
11683 sprintf(buf, "name %s\n", gameInfo.white);
11684 SendToProgram(buf, &first);
11686 if (first.sendTime) {
11687 if (first.useColors) {
11688 SendToProgram("white\n", &first); /*gnu kludge*/
11690 SendTimeRemaining(&first, FALSE);
11692 if (first.useColors) {
11693 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11695 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11696 SetMachineThinkingEnables();
11697 first.maybeThinking = TRUE;
11700 if (appData.autoFlipView && flipView) {
11701 flipView = !flipView;
11702 DrawPosition(FALSE, NULL);
11703 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11705 if(bookHit) { // [HGM] book: simulate book reply
11706 static char bookMove[MSG_SIZ]; // a bit generous?
11708 programStats.nodes = programStats.depth = programStats.time =
11709 programStats.score = programStats.got_only_move = 0;
11710 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11712 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11713 strcat(bookMove, bookHit);
11714 HandleMachineMove(bookMove, &first);
11720 DisplayTwoMachinesTitle()
11723 if (appData.matchGames > 0) {
11724 if (first.twoMachinesColor[0] == 'w') {
11725 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11726 gameInfo.white, gameInfo.black,
11727 first.matchWins, second.matchWins,
11728 matchGame - 1 - (first.matchWins + second.matchWins));
11730 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11731 gameInfo.white, gameInfo.black,
11732 second.matchWins, first.matchWins,
11733 matchGame - 1 - (first.matchWins + second.matchWins));
11736 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11742 TwoMachinesEvent P((void))
11746 ChessProgramState *onmove;
11747 char *bookHit = NULL;
11749 if (appData.noChessProgram) return;
11751 switch (gameMode) {
11752 case TwoMachinesPlay:
11754 case MachinePlaysWhite:
11755 case MachinePlaysBlack:
11756 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11757 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11761 case BeginningOfGame:
11762 case PlayFromGameFile:
11765 if (gameMode != EditGame) return;
11768 EditPositionDone(TRUE);
11779 // forwardMostMove = currentMove;
11780 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11781 ResurrectChessProgram(); /* in case first program isn't running */
11783 if (second.pr == NULL) {
11784 StartChessProgram(&second);
11785 if (second.protocolVersion == 1) {
11786 TwoMachinesEventIfReady();
11788 /* kludge: allow timeout for initial "feature" command */
11790 DisplayMessage("", _("Starting second chess program"));
11791 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11795 DisplayMessage("", "");
11796 InitChessProgram(&second, FALSE);
11797 SendToProgram("force\n", &second);
11798 if (startedFromSetupPosition) {
11799 SendBoard(&second, backwardMostMove);
11800 if (appData.debugMode) {
11801 fprintf(debugFP, "Two Machines\n");
11804 for (i = backwardMostMove; i < forwardMostMove; i++) {
11805 SendMoveToProgram(i, &second);
11808 gameMode = TwoMachinesPlay;
11812 DisplayTwoMachinesTitle();
11814 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11820 SendToProgram(first.computerString, &first);
11821 if (first.sendName) {
11822 sprintf(buf, "name %s\n", second.tidy);
11823 SendToProgram(buf, &first);
11825 SendToProgram(second.computerString, &second);
11826 if (second.sendName) {
11827 sprintf(buf, "name %s\n", first.tidy);
11828 SendToProgram(buf, &second);
11832 if (!first.sendTime || !second.sendTime) {
11833 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11834 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11836 if (onmove->sendTime) {
11837 if (onmove->useColors) {
11838 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11840 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11842 if (onmove->useColors) {
11843 SendToProgram(onmove->twoMachinesColor, onmove);
11845 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11846 // SendToProgram("go\n", onmove);
11847 onmove->maybeThinking = TRUE;
11848 SetMachineThinkingEnables();
11852 if(bookHit) { // [HGM] book: simulate book reply
11853 static char bookMove[MSG_SIZ]; // a bit generous?
11855 programStats.nodes = programStats.depth = programStats.time =
11856 programStats.score = programStats.got_only_move = 0;
11857 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11859 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11860 strcat(bookMove, bookHit);
11861 savedMessage = bookMove; // args for deferred call
11862 savedState = onmove;
11863 ScheduleDelayedEvent(DeferredBookMove, 1);
11870 if (gameMode == Training) {
11871 SetTrainingModeOff();
11872 gameMode = PlayFromGameFile;
11873 DisplayMessage("", _("Training mode off"));
11875 gameMode = Training;
11876 animateTraining = appData.animate;
11878 /* make sure we are not already at the end of the game */
11879 if (currentMove < forwardMostMove) {
11880 SetTrainingModeOn();
11881 DisplayMessage("", _("Training mode on"));
11883 gameMode = PlayFromGameFile;
11884 DisplayError(_("Already at end of game"), 0);
11893 if (!appData.icsActive) return;
11894 switch (gameMode) {
11895 case IcsPlayingWhite:
11896 case IcsPlayingBlack:
11899 case BeginningOfGame:
11907 EditPositionDone(TRUE);
11920 gameMode = IcsIdle;
11931 switch (gameMode) {
11933 SetTrainingModeOff();
11935 case MachinePlaysWhite:
11936 case MachinePlaysBlack:
11937 case BeginningOfGame:
11938 SendToProgram("force\n", &first);
11939 SetUserThinkingEnables();
11941 case PlayFromGameFile:
11942 (void) StopLoadGameTimer();
11943 if (gameFileFP != NULL) {
11948 EditPositionDone(TRUE);
11953 SendToProgram("force\n", &first);
11955 case TwoMachinesPlay:
11956 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11957 ResurrectChessProgram();
11958 SetUserThinkingEnables();
11961 ResurrectChessProgram();
11963 case IcsPlayingBlack:
11964 case IcsPlayingWhite:
11965 DisplayError(_("Warning: You are still playing a game"), 0);
11968 DisplayError(_("Warning: You are still observing a game"), 0);
11971 DisplayError(_("Warning: You are still examining a game"), 0);
11982 first.offeredDraw = second.offeredDraw = 0;
11984 if (gameMode == PlayFromGameFile) {
11985 whiteTimeRemaining = timeRemaining[0][currentMove];
11986 blackTimeRemaining = timeRemaining[1][currentMove];
11990 if (gameMode == MachinePlaysWhite ||
11991 gameMode == MachinePlaysBlack ||
11992 gameMode == TwoMachinesPlay ||
11993 gameMode == EndOfGame) {
11994 i = forwardMostMove;
11995 while (i > currentMove) {
11996 SendToProgram("undo\n", &first);
11999 whiteTimeRemaining = timeRemaining[0][currentMove];
12000 blackTimeRemaining = timeRemaining[1][currentMove];
12001 DisplayBothClocks();
12002 if (whiteFlag || blackFlag) {
12003 whiteFlag = blackFlag = 0;
12008 gameMode = EditGame;
12015 EditPositionEvent()
12017 if (gameMode == EditPosition) {
12023 if (gameMode != EditGame) return;
12025 gameMode = EditPosition;
12028 if (currentMove > 0)
12029 CopyBoard(boards[0], boards[currentMove]);
12031 blackPlaysFirst = !WhiteOnMove(currentMove);
12033 currentMove = forwardMostMove = backwardMostMove = 0;
12034 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12041 /* [DM] icsEngineAnalyze - possible call from other functions */
12042 if (appData.icsEngineAnalyze) {
12043 appData.icsEngineAnalyze = FALSE;
12045 DisplayMessage("",_("Close ICS engine analyze..."));
12047 if (first.analysisSupport && first.analyzing) {
12048 SendToProgram("exit\n", &first);
12049 first.analyzing = FALSE;
12051 thinkOutput[0] = NULLCHAR;
12055 EditPositionDone(Boolean fakeRights)
12057 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12059 startedFromSetupPosition = TRUE;
12060 InitChessProgram(&first, FALSE);
12061 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12062 boards[0][EP_STATUS] = EP_NONE;
12063 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12064 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12065 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12066 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12067 } else boards[0][CASTLING][2] = NoRights;
12068 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12069 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12070 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12071 } else boards[0][CASTLING][5] = NoRights;
12073 SendToProgram("force\n", &first);
12074 if (blackPlaysFirst) {
12075 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12076 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12077 currentMove = forwardMostMove = backwardMostMove = 1;
12078 CopyBoard(boards[1], boards[0]);
12080 currentMove = forwardMostMove = backwardMostMove = 0;
12082 SendBoard(&first, forwardMostMove);
12083 if (appData.debugMode) {
12084 fprintf(debugFP, "EditPosDone\n");
12087 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12088 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12089 gameMode = EditGame;
12091 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12092 ClearHighlights(); /* [AS] */
12095 /* Pause for `ms' milliseconds */
12096 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12106 } while (SubtractTimeMarks(&m2, &m1) < ms);
12109 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12111 SendMultiLineToICS(buf)
12114 char temp[MSG_SIZ+1], *p;
12121 strncpy(temp, buf, len);
12126 if (*p == '\n' || *p == '\r')
12131 strcat(temp, "\n");
12133 SendToPlayer(temp, strlen(temp));
12137 SetWhiteToPlayEvent()
12139 if (gameMode == EditPosition) {
12140 blackPlaysFirst = FALSE;
12141 DisplayBothClocks(); /* works because currentMove is 0 */
12142 } else if (gameMode == IcsExamining) {
12143 SendToICS(ics_prefix);
12144 SendToICS("tomove white\n");
12149 SetBlackToPlayEvent()
12151 if (gameMode == EditPosition) {
12152 blackPlaysFirst = TRUE;
12153 currentMove = 1; /* kludge */
12154 DisplayBothClocks();
12156 } else if (gameMode == IcsExamining) {
12157 SendToICS(ics_prefix);
12158 SendToICS("tomove black\n");
12163 EditPositionMenuEvent(selection, x, y)
12164 ChessSquare selection;
12168 ChessSquare piece = boards[0][y][x];
12170 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12172 switch (selection) {
12174 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12175 SendToICS(ics_prefix);
12176 SendToICS("bsetup clear\n");
12177 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12178 SendToICS(ics_prefix);
12179 SendToICS("clearboard\n");
12181 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12182 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12183 for (y = 0; y < BOARD_HEIGHT; y++) {
12184 if (gameMode == IcsExamining) {
12185 if (boards[currentMove][y][x] != EmptySquare) {
12186 sprintf(buf, "%sx@%c%c\n", ics_prefix,
12191 boards[0][y][x] = p;
12196 if (gameMode == EditPosition) {
12197 DrawPosition(FALSE, boards[0]);
12202 SetWhiteToPlayEvent();
12206 SetBlackToPlayEvent();
12210 if (gameMode == IcsExamining) {
12211 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12212 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12215 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12216 if(x == BOARD_LEFT-2) {
12217 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12218 boards[0][y][1] = 0;
12220 if(x == BOARD_RGHT+1) {
12221 if(y >= gameInfo.holdingsSize) break;
12222 boards[0][y][BOARD_WIDTH-2] = 0;
12225 boards[0][y][x] = EmptySquare;
12226 DrawPosition(FALSE, boards[0]);
12231 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12232 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
12233 selection = (ChessSquare) (PROMOTED piece);
12234 } else if(piece == EmptySquare) selection = WhiteSilver;
12235 else selection = (ChessSquare)((int)piece - 1);
12239 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12240 piece > (int)BlackMan && piece <= (int)BlackKing ) {
12241 selection = (ChessSquare) (DEMOTED piece);
12242 } else if(piece == EmptySquare) selection = BlackSilver;
12243 else selection = (ChessSquare)((int)piece + 1);
12248 if(gameInfo.variant == VariantShatranj ||
12249 gameInfo.variant == VariantXiangqi ||
12250 gameInfo.variant == VariantCourier ||
12251 gameInfo.variant == VariantMakruk )
12252 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12257 if(gameInfo.variant == VariantXiangqi)
12258 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12259 if(gameInfo.variant == VariantKnightmate)
12260 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12263 if (gameMode == IcsExamining) {
12264 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12265 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12266 PieceToChar(selection), AAA + x, ONE + y);
12269 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12271 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12272 n = PieceToNumber(selection - BlackPawn);
12273 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12274 boards[0][BOARD_HEIGHT-1-n][0] = selection;
12275 boards[0][BOARD_HEIGHT-1-n][1]++;
12277 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12278 n = PieceToNumber(selection);
12279 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12280 boards[0][n][BOARD_WIDTH-1] = selection;
12281 boards[0][n][BOARD_WIDTH-2]++;
12284 boards[0][y][x] = selection;
12285 DrawPosition(TRUE, boards[0]);
12293 DropMenuEvent(selection, x, y)
12294 ChessSquare selection;
12297 ChessMove moveType;
12299 switch (gameMode) {
12300 case IcsPlayingWhite:
12301 case MachinePlaysBlack:
12302 if (!WhiteOnMove(currentMove)) {
12303 DisplayMoveError(_("It is Black's turn"));
12306 moveType = WhiteDrop;
12308 case IcsPlayingBlack:
12309 case MachinePlaysWhite:
12310 if (WhiteOnMove(currentMove)) {
12311 DisplayMoveError(_("It is White's turn"));
12314 moveType = BlackDrop;
12317 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12323 if (moveType == BlackDrop && selection < BlackPawn) {
12324 selection = (ChessSquare) ((int) selection
12325 + (int) BlackPawn - (int) WhitePawn);
12327 if (boards[currentMove][y][x] != EmptySquare) {
12328 DisplayMoveError(_("That square is occupied"));
12332 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12338 /* Accept a pending offer of any kind from opponent */
12340 if (appData.icsActive) {
12341 SendToICS(ics_prefix);
12342 SendToICS("accept\n");
12343 } else if (cmailMsgLoaded) {
12344 if (currentMove == cmailOldMove &&
12345 commentList[cmailOldMove] != NULL &&
12346 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12347 "Black offers a draw" : "White offers a draw")) {
12349 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12350 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12352 DisplayError(_("There is no pending offer on this move"), 0);
12353 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12356 /* Not used for offers from chess program */
12363 /* Decline a pending offer of any kind from opponent */
12365 if (appData.icsActive) {
12366 SendToICS(ics_prefix);
12367 SendToICS("decline\n");
12368 } else if (cmailMsgLoaded) {
12369 if (currentMove == cmailOldMove &&
12370 commentList[cmailOldMove] != NULL &&
12371 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12372 "Black offers a draw" : "White offers a draw")) {
12374 AppendComment(cmailOldMove, "Draw declined", TRUE);
12375 DisplayComment(cmailOldMove - 1, "Draw declined");
12378 DisplayError(_("There is no pending offer on this move"), 0);
12381 /* Not used for offers from chess program */
12388 /* Issue ICS rematch command */
12389 if (appData.icsActive) {
12390 SendToICS(ics_prefix);
12391 SendToICS("rematch\n");
12398 /* Call your opponent's flag (claim a win on time) */
12399 if (appData.icsActive) {
12400 SendToICS(ics_prefix);
12401 SendToICS("flag\n");
12403 switch (gameMode) {
12406 case MachinePlaysWhite:
12409 GameEnds(GameIsDrawn, "Both players ran out of time",
12412 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12414 DisplayError(_("Your opponent is not out of time"), 0);
12417 case MachinePlaysBlack:
12420 GameEnds(GameIsDrawn, "Both players ran out of time",
12423 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12425 DisplayError(_("Your opponent is not out of time"), 0);
12435 /* Offer draw or accept pending draw offer from opponent */
12437 if (appData.icsActive) {
12438 /* Note: tournament rules require draw offers to be
12439 made after you make your move but before you punch
12440 your clock. Currently ICS doesn't let you do that;
12441 instead, you immediately punch your clock after making
12442 a move, but you can offer a draw at any time. */
12444 SendToICS(ics_prefix);
12445 SendToICS("draw\n");
12446 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12447 } else if (cmailMsgLoaded) {
12448 if (currentMove == cmailOldMove &&
12449 commentList[cmailOldMove] != NULL &&
12450 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12451 "Black offers a draw" : "White offers a draw")) {
12452 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12453 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12454 } else if (currentMove == cmailOldMove + 1) {
12455 char *offer = WhiteOnMove(cmailOldMove) ?
12456 "White offers a draw" : "Black offers a draw";
12457 AppendComment(currentMove, offer, TRUE);
12458 DisplayComment(currentMove - 1, offer);
12459 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12461 DisplayError(_("You must make your move before offering a draw"), 0);
12462 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12464 } else if (first.offeredDraw) {
12465 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12467 if (first.sendDrawOffers) {
12468 SendToProgram("draw\n", &first);
12469 userOfferedDraw = TRUE;
12477 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12479 if (appData.icsActive) {
12480 SendToICS(ics_prefix);
12481 SendToICS("adjourn\n");
12483 /* Currently GNU Chess doesn't offer or accept Adjourns */
12491 /* Offer Abort or accept pending Abort offer from opponent */
12493 if (appData.icsActive) {
12494 SendToICS(ics_prefix);
12495 SendToICS("abort\n");
12497 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12504 /* Resign. You can do this even if it's not your turn. */
12506 if (appData.icsActive) {
12507 SendToICS(ics_prefix);
12508 SendToICS("resign\n");
12510 switch (gameMode) {
12511 case MachinePlaysWhite:
12512 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12514 case MachinePlaysBlack:
12515 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12518 if (cmailMsgLoaded) {
12520 if (WhiteOnMove(cmailOldMove)) {
12521 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12523 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12525 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12536 StopObservingEvent()
12538 /* Stop observing current games */
12539 SendToICS(ics_prefix);
12540 SendToICS("unobserve\n");
12544 StopExaminingEvent()
12546 /* Stop observing current game */
12547 SendToICS(ics_prefix);
12548 SendToICS("unexamine\n");
12552 ForwardInner(target)
12557 if (appData.debugMode)
12558 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12559 target, currentMove, forwardMostMove);
12561 if (gameMode == EditPosition)
12564 if (gameMode == PlayFromGameFile && !pausing)
12567 if (gameMode == IcsExamining && pausing)
12568 limit = pauseExamForwardMostMove;
12570 limit = forwardMostMove;
12572 if (target > limit) target = limit;
12574 if (target > 0 && moveList[target - 1][0]) {
12575 int fromX, fromY, toX, toY;
12576 toX = moveList[target - 1][2] - AAA;
12577 toY = moveList[target - 1][3] - ONE;
12578 if (moveList[target - 1][1] == '@') {
12579 if (appData.highlightLastMove) {
12580 SetHighlights(-1, -1, toX, toY);
12583 fromX = moveList[target - 1][0] - AAA;
12584 fromY = moveList[target - 1][1] - ONE;
12585 if (target == currentMove + 1) {
12586 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12588 if (appData.highlightLastMove) {
12589 SetHighlights(fromX, fromY, toX, toY);
12593 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12594 gameMode == Training || gameMode == PlayFromGameFile ||
12595 gameMode == AnalyzeFile) {
12596 while (currentMove < target) {
12597 SendMoveToProgram(currentMove++, &first);
12600 currentMove = target;
12603 if (gameMode == EditGame || gameMode == EndOfGame) {
12604 whiteTimeRemaining = timeRemaining[0][currentMove];
12605 blackTimeRemaining = timeRemaining[1][currentMove];
12607 DisplayBothClocks();
12608 DisplayMove(currentMove - 1);
12609 DrawPosition(FALSE, boards[currentMove]);
12610 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12611 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12612 DisplayComment(currentMove - 1, commentList[currentMove]);
12620 if (gameMode == IcsExamining && !pausing) {
12621 SendToICS(ics_prefix);
12622 SendToICS("forward\n");
12624 ForwardInner(currentMove + 1);
12631 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12632 /* to optimze, we temporarily turn off analysis mode while we feed
12633 * the remaining moves to the engine. Otherwise we get analysis output
12636 if (first.analysisSupport) {
12637 SendToProgram("exit\nforce\n", &first);
12638 first.analyzing = FALSE;
12642 if (gameMode == IcsExamining && !pausing) {
12643 SendToICS(ics_prefix);
12644 SendToICS("forward 999999\n");
12646 ForwardInner(forwardMostMove);
12649 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12650 /* we have fed all the moves, so reactivate analysis mode */
12651 SendToProgram("analyze\n", &first);
12652 first.analyzing = TRUE;
12653 /*first.maybeThinking = TRUE;*/
12654 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12659 BackwardInner(target)
12662 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12664 if (appData.debugMode)
12665 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12666 target, currentMove, forwardMostMove);
12668 if (gameMode == EditPosition) return;
12669 if (currentMove <= backwardMostMove) {
12671 DrawPosition(full_redraw, boards[currentMove]);
12674 if (gameMode == PlayFromGameFile && !pausing)
12677 if (moveList[target][0]) {
12678 int fromX, fromY, toX, toY;
12679 toX = moveList[target][2] - AAA;
12680 toY = moveList[target][3] - ONE;
12681 if (moveList[target][1] == '@') {
12682 if (appData.highlightLastMove) {
12683 SetHighlights(-1, -1, toX, toY);
12686 fromX = moveList[target][0] - AAA;
12687 fromY = moveList[target][1] - ONE;
12688 if (target == currentMove - 1) {
12689 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12691 if (appData.highlightLastMove) {
12692 SetHighlights(fromX, fromY, toX, toY);
12696 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12697 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12698 while (currentMove > target) {
12699 SendToProgram("undo\n", &first);
12703 currentMove = target;
12706 if (gameMode == EditGame || gameMode == EndOfGame) {
12707 whiteTimeRemaining = timeRemaining[0][currentMove];
12708 blackTimeRemaining = timeRemaining[1][currentMove];
12710 DisplayBothClocks();
12711 DisplayMove(currentMove - 1);
12712 DrawPosition(full_redraw, boards[currentMove]);
12713 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12714 // [HGM] PV info: routine tests if comment empty
12715 DisplayComment(currentMove - 1, commentList[currentMove]);
12721 if (gameMode == IcsExamining && !pausing) {
12722 SendToICS(ics_prefix);
12723 SendToICS("backward\n");
12725 BackwardInner(currentMove - 1);
12732 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12733 /* to optimize, we temporarily turn off analysis mode while we undo
12734 * all the moves. Otherwise we get analysis output after each undo.
12736 if (first.analysisSupport) {
12737 SendToProgram("exit\nforce\n", &first);
12738 first.analyzing = FALSE;
12742 if (gameMode == IcsExamining && !pausing) {
12743 SendToICS(ics_prefix);
12744 SendToICS("backward 999999\n");
12746 BackwardInner(backwardMostMove);
12749 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12750 /* we have fed all the moves, so reactivate analysis mode */
12751 SendToProgram("analyze\n", &first);
12752 first.analyzing = TRUE;
12753 /*first.maybeThinking = TRUE;*/
12754 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12761 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12762 if (to >= forwardMostMove) to = forwardMostMove;
12763 if (to <= backwardMostMove) to = backwardMostMove;
12764 if (to < currentMove) {
12772 RevertEvent(Boolean annotate)
12774 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12777 if (gameMode != IcsExamining) {
12778 DisplayError(_("You are not examining a game"), 0);
12782 DisplayError(_("You can't revert while pausing"), 0);
12785 SendToICS(ics_prefix);
12786 SendToICS("revert\n");
12792 switch (gameMode) {
12793 case MachinePlaysWhite:
12794 case MachinePlaysBlack:
12795 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12796 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12799 if (forwardMostMove < 2) return;
12800 currentMove = forwardMostMove = forwardMostMove - 2;
12801 whiteTimeRemaining = timeRemaining[0][currentMove];
12802 blackTimeRemaining = timeRemaining[1][currentMove];
12803 DisplayBothClocks();
12804 DisplayMove(currentMove - 1);
12805 ClearHighlights();/*!! could figure this out*/
12806 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12807 SendToProgram("remove\n", &first);
12808 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12811 case BeginningOfGame:
12815 case IcsPlayingWhite:
12816 case IcsPlayingBlack:
12817 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12818 SendToICS(ics_prefix);
12819 SendToICS("takeback 2\n");
12821 SendToICS(ics_prefix);
12822 SendToICS("takeback 1\n");
12831 ChessProgramState *cps;
12833 switch (gameMode) {
12834 case MachinePlaysWhite:
12835 if (!WhiteOnMove(forwardMostMove)) {
12836 DisplayError(_("It is your turn"), 0);
12841 case MachinePlaysBlack:
12842 if (WhiteOnMove(forwardMostMove)) {
12843 DisplayError(_("It is your turn"), 0);
12848 case TwoMachinesPlay:
12849 if (WhiteOnMove(forwardMostMove) ==
12850 (first.twoMachinesColor[0] == 'w')) {
12856 case BeginningOfGame:
12860 SendToProgram("?\n", cps);
12864 TruncateGameEvent()
12867 if (gameMode != EditGame) return;
12874 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12875 if (forwardMostMove > currentMove) {
12876 if (gameInfo.resultDetails != NULL) {
12877 free(gameInfo.resultDetails);
12878 gameInfo.resultDetails = NULL;
12879 gameInfo.result = GameUnfinished;
12881 forwardMostMove = currentMove;
12882 HistorySet(parseList, backwardMostMove, forwardMostMove,
12890 if (appData.noChessProgram) return;
12891 switch (gameMode) {
12892 case MachinePlaysWhite:
12893 if (WhiteOnMove(forwardMostMove)) {
12894 DisplayError(_("Wait until your turn"), 0);
12898 case BeginningOfGame:
12899 case MachinePlaysBlack:
12900 if (!WhiteOnMove(forwardMostMove)) {
12901 DisplayError(_("Wait until your turn"), 0);
12906 DisplayError(_("No hint available"), 0);
12909 SendToProgram("hint\n", &first);
12910 hintRequested = TRUE;
12916 if (appData.noChessProgram) return;
12917 switch (gameMode) {
12918 case MachinePlaysWhite:
12919 if (WhiteOnMove(forwardMostMove)) {
12920 DisplayError(_("Wait until your turn"), 0);
12924 case BeginningOfGame:
12925 case MachinePlaysBlack:
12926 if (!WhiteOnMove(forwardMostMove)) {
12927 DisplayError(_("Wait until your turn"), 0);
12932 EditPositionDone(TRUE);
12934 case TwoMachinesPlay:
12939 SendToProgram("bk\n", &first);
12940 bookOutput[0] = NULLCHAR;
12941 bookRequested = TRUE;
12947 char *tags = PGNTags(&gameInfo);
12948 TagsPopUp(tags, CmailMsg());
12952 /* end button procedures */
12955 PrintPosition(fp, move)
12961 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12962 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12963 char c = PieceToChar(boards[move][i][j]);
12964 fputc(c == 'x' ? '.' : c, fp);
12965 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12968 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12969 fprintf(fp, "white to play\n");
12971 fprintf(fp, "black to play\n");
12978 if (gameInfo.white != NULL) {
12979 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12985 /* Find last component of program's own name, using some heuristics */
12987 TidyProgramName(prog, host, buf)
12988 char *prog, *host, buf[MSG_SIZ];
12991 int local = (strcmp(host, "localhost") == 0);
12992 while (!local && (p = strchr(prog, ';')) != NULL) {
12994 while (*p == ' ') p++;
12997 if (*prog == '"' || *prog == '\'') {
12998 q = strchr(prog + 1, *prog);
13000 q = strchr(prog, ' ');
13002 if (q == NULL) q = prog + strlen(prog);
13004 while (p >= prog && *p != '/' && *p != '\\') p--;
13006 if(p == prog && *p == '"') p++;
13007 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13008 memcpy(buf, p, q - p);
13009 buf[q - p] = NULLCHAR;
13017 TimeControlTagValue()
13020 if (!appData.clockMode) {
13021 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13022 } else if (movesPerSession > 0) {
13023 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13024 } else if (timeIncrement == 0) {
13025 sprintf(buf, "%ld", timeControl/1000);
13027 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13029 return StrSave(buf);
13035 /* This routine is used only for certain modes */
13036 VariantClass v = gameInfo.variant;
13037 ChessMove r = GameUnfinished;
13040 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13041 r = gameInfo.result;
13042 p = gameInfo.resultDetails;
13043 gameInfo.resultDetails = NULL;
13045 ClearGameInfo(&gameInfo);
13046 gameInfo.variant = v;
13048 switch (gameMode) {
13049 case MachinePlaysWhite:
13050 gameInfo.event = StrSave( appData.pgnEventHeader );
13051 gameInfo.site = StrSave(HostName());
13052 gameInfo.date = PGNDate();
13053 gameInfo.round = StrSave("-");
13054 gameInfo.white = StrSave(first.tidy);
13055 gameInfo.black = StrSave(UserName());
13056 gameInfo.timeControl = TimeControlTagValue();
13059 case MachinePlaysBlack:
13060 gameInfo.event = StrSave( appData.pgnEventHeader );
13061 gameInfo.site = StrSave(HostName());
13062 gameInfo.date = PGNDate();
13063 gameInfo.round = StrSave("-");
13064 gameInfo.white = StrSave(UserName());
13065 gameInfo.black = StrSave(first.tidy);
13066 gameInfo.timeControl = TimeControlTagValue();
13069 case TwoMachinesPlay:
13070 gameInfo.event = StrSave( appData.pgnEventHeader );
13071 gameInfo.site = StrSave(HostName());
13072 gameInfo.date = PGNDate();
13073 if (matchGame > 0) {
13075 sprintf(buf, "%d", matchGame);
13076 gameInfo.round = StrSave(buf);
13078 gameInfo.round = StrSave("-");
13080 if (first.twoMachinesColor[0] == 'w') {
13081 gameInfo.white = StrSave(first.tidy);
13082 gameInfo.black = StrSave(second.tidy);
13084 gameInfo.white = StrSave(second.tidy);
13085 gameInfo.black = StrSave(first.tidy);
13087 gameInfo.timeControl = TimeControlTagValue();
13091 gameInfo.event = StrSave("Edited game");
13092 gameInfo.site = StrSave(HostName());
13093 gameInfo.date = PGNDate();
13094 gameInfo.round = StrSave("-");
13095 gameInfo.white = StrSave("-");
13096 gameInfo.black = StrSave("-");
13097 gameInfo.result = r;
13098 gameInfo.resultDetails = p;
13102 gameInfo.event = StrSave("Edited position");
13103 gameInfo.site = StrSave(HostName());
13104 gameInfo.date = PGNDate();
13105 gameInfo.round = StrSave("-");
13106 gameInfo.white = StrSave("-");
13107 gameInfo.black = StrSave("-");
13110 case IcsPlayingWhite:
13111 case IcsPlayingBlack:
13116 case PlayFromGameFile:
13117 gameInfo.event = StrSave("Game from non-PGN file");
13118 gameInfo.site = StrSave(HostName());
13119 gameInfo.date = PGNDate();
13120 gameInfo.round = StrSave("-");
13121 gameInfo.white = StrSave("?");
13122 gameInfo.black = StrSave("?");
13131 ReplaceComment(index, text)
13137 while (*text == '\n') text++;
13138 len = strlen(text);
13139 while (len > 0 && text[len - 1] == '\n') len--;
13141 if (commentList[index] != NULL)
13142 free(commentList[index]);
13145 commentList[index] = NULL;
13148 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13149 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13150 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13151 commentList[index] = (char *) malloc(len + 2);
13152 strncpy(commentList[index], text, len);
13153 commentList[index][len] = '\n';
13154 commentList[index][len + 1] = NULLCHAR;
13156 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13158 commentList[index] = (char *) malloc(len + 7);
13159 safeStrCpy(commentList[index], "{\n", 3);
13160 safeStrCpy(commentList[index]+2, text, len+1);
13161 commentList[index][len+2] = NULLCHAR;
13162 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13163 strcat(commentList[index], "\n}\n");
13177 if (ch == '\r') continue;
13179 } while (ch != '\0');
13183 AppendComment(index, text, addBraces)
13186 Boolean addBraces; // [HGM] braces: tells if we should add {}
13191 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13192 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13195 while (*text == '\n') text++;
13196 len = strlen(text);
13197 while (len > 0 && text[len - 1] == '\n') len--;
13199 if (len == 0) return;
13201 if (commentList[index] != NULL) {
13202 old = commentList[index];
13203 oldlen = strlen(old);
13204 while(commentList[index][oldlen-1] == '\n')
13205 commentList[index][--oldlen] = NULLCHAR;
13206 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13207 safeStrCpy(commentList[index], old, oldlen);
13209 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13210 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13211 if(addBraces) addBraces = FALSE; else { text++; len--; }
13212 while (*text == '\n') { text++; len--; }
13213 commentList[index][--oldlen] = NULLCHAR;
13215 if(addBraces) strcat(commentList[index], "\n{\n");
13216 else strcat(commentList[index], "\n");
13217 strcat(commentList[index], text);
13218 if(addBraces) strcat(commentList[index], "\n}\n");
13219 else strcat(commentList[index], "\n");
13221 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13223 safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0]));
13224 else commentList[index][0] = NULLCHAR;
13225 strcat(commentList[index], text);
13226 strcat(commentList[index], "\n");
13227 if(addBraces) strcat(commentList[index], "}\n");
13231 static char * FindStr( char * text, char * sub_text )
13233 char * result = strstr( text, sub_text );
13235 if( result != NULL ) {
13236 result += strlen( sub_text );
13242 /* [AS] Try to extract PV info from PGN comment */
13243 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13244 char *GetInfoFromComment( int index, char * text )
13248 if( text != NULL && index > 0 ) {
13251 int time = -1, sec = 0, deci;
13252 char * s_eval = FindStr( text, "[%eval " );
13253 char * s_emt = FindStr( text, "[%emt " );
13255 if( s_eval != NULL || s_emt != NULL ) {
13259 if( s_eval != NULL ) {
13260 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13264 if( delim != ']' ) {
13269 if( s_emt != NULL ) {
13274 /* We expect something like: [+|-]nnn.nn/dd */
13277 if(*text != '{') return text; // [HGM] braces: must be normal comment
13279 sep = strchr( text, '/' );
13280 if( sep == NULL || sep < (text+4) ) {
13284 time = -1; sec = -1; deci = -1;
13285 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13286 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13287 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13288 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
13292 if( score_lo < 0 || score_lo >= 100 ) {
13296 if(sec >= 0) time = 600*time + 10*sec; else
13297 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13299 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13301 /* [HGM] PV time: now locate end of PV info */
13302 while( *++sep >= '0' && *sep <= '9'); // strip depth
13304 while( *++sep >= '0' && *sep <= '9'); // strip time
13306 while( *++sep >= '0' && *sep <= '9'); // strip seconds
13308 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13309 while(*sep == ' ') sep++;
13320 pvInfoList[index-1].depth = depth;
13321 pvInfoList[index-1].score = score;
13322 pvInfoList[index-1].time = 10*time; // centi-sec
13323 if(*sep == '}') *sep = 0; else *--sep = '{';
13329 SendToProgram(message, cps)
13331 ChessProgramState *cps;
13333 int count, outCount, error;
13336 if (cps->pr == NULL) return;
13339 if (appData.debugMode) {
13342 fprintf(debugFP, "%ld >%-6s: %s",
13343 SubtractTimeMarks(&now, &programStartTime),
13344 cps->which, message);
13347 count = strlen(message);
13348 outCount = OutputToProcess(cps->pr, message, count, &error);
13349 if (outCount < count && !exiting
13350 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13351 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13352 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13353 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13354 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13355 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13357 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13359 gameInfo.resultDetails = StrSave(buf);
13361 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13366 ReceiveFromProgram(isr, closure, message, count, error)
13367 InputSourceRef isr;
13375 ChessProgramState *cps = (ChessProgramState *)closure;
13377 if (isr != cps->isr) return; /* Killed intentionally */
13381 _("Error: %s chess program (%s) exited unexpectedly"),
13382 cps->which, cps->program);
13383 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13384 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13385 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13386 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13388 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13390 gameInfo.resultDetails = StrSave(buf);
13392 RemoveInputSource(cps->isr);
13393 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13396 _("Error reading from %s chess program (%s)"),
13397 cps->which, cps->program);
13398 RemoveInputSource(cps->isr);
13400 /* [AS] Program is misbehaving badly... kill it */
13401 if( count == -2 ) {
13402 DestroyChildProcess( cps->pr, 9 );
13406 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13411 if ((end_str = strchr(message, '\r')) != NULL)
13412 *end_str = NULLCHAR;
13413 if ((end_str = strchr(message, '\n')) != NULL)
13414 *end_str = NULLCHAR;
13416 if (appData.debugMode) {
13417 TimeMark now; int print = 1;
13418 char *quote = ""; char c; int i;
13420 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13421 char start = message[0];
13422 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13423 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13424 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13425 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13426 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13427 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13428 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13429 sscanf(message, "pong %c", &c)!=1 && start != '#') {
13430 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13431 print = (appData.engineComments >= 2);
13433 message[0] = start; // restore original message
13437 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13438 SubtractTimeMarks(&now, &programStartTime), cps->which,
13444 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13445 if (appData.icsEngineAnalyze) {
13446 if (strstr(message, "whisper") != NULL ||
13447 strstr(message, "kibitz") != NULL ||
13448 strstr(message, "tellics") != NULL) return;
13451 HandleMachineMove(message, cps);
13456 SendTimeControl(cps, mps, tc, inc, sd, st)
13457 ChessProgramState *cps;
13458 int mps, inc, sd, st;
13464 if( timeControl_2 > 0 ) {
13465 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13466 tc = timeControl_2;
13469 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13470 inc /= cps->timeOdds;
13471 st /= cps->timeOdds;
13473 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13476 /* Set exact time per move, normally using st command */
13477 if (cps->stKludge) {
13478 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13480 if (seconds == 0) {
13481 sprintf(buf, "level 1 %d\n", st/60);
13483 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13486 sprintf(buf, "st %d\n", st);
13489 /* Set conventional or incremental time control, using level command */
13490 if (seconds == 0) {
13491 /* Note old gnuchess bug -- minutes:seconds used to not work.
13492 Fixed in later versions, but still avoid :seconds
13493 when seconds is 0. */
13494 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13496 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13497 seconds, inc/1000);
13500 SendToProgram(buf, cps);
13502 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13503 /* Orthogonally, limit search to given depth */
13505 if (cps->sdKludge) {
13506 sprintf(buf, "depth\n%d\n", sd);
13508 sprintf(buf, "sd %d\n", sd);
13510 SendToProgram(buf, cps);
13513 if(cps->nps > 0) { /* [HGM] nps */
13514 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13516 sprintf(buf, "nps %d\n", cps->nps);
13517 SendToProgram(buf, cps);
13522 ChessProgramState *WhitePlayer()
13523 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13525 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13526 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13532 SendTimeRemaining(cps, machineWhite)
13533 ChessProgramState *cps;
13534 int /*boolean*/ machineWhite;
13536 char message[MSG_SIZ];
13539 /* Note: this routine must be called when the clocks are stopped
13540 or when they have *just* been set or switched; otherwise
13541 it will be off by the time since the current tick started.
13543 if (machineWhite) {
13544 time = whiteTimeRemaining / 10;
13545 otime = blackTimeRemaining / 10;
13547 time = blackTimeRemaining / 10;
13548 otime = whiteTimeRemaining / 10;
13550 /* [HGM] translate opponent's time by time-odds factor */
13551 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13552 if (appData.debugMode) {
13553 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13556 if (time <= 0) time = 1;
13557 if (otime <= 0) otime = 1;
13559 sprintf(message, "time %ld\n", time);
13560 SendToProgram(message, cps);
13562 sprintf(message, "otim %ld\n", otime);
13563 SendToProgram(message, cps);
13567 BoolFeature(p, name, loc, cps)
13571 ChessProgramState *cps;
13574 int len = strlen(name);
13576 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13578 sscanf(*p, "%d", &val);
13580 while (**p && **p != ' ') (*p)++;
13581 sprintf(buf, "accepted %s\n", name);
13582 SendToProgram(buf, cps);
13589 IntFeature(p, name, loc, cps)
13593 ChessProgramState *cps;
13596 int len = strlen(name);
13597 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13599 sscanf(*p, "%d", loc);
13600 while (**p && **p != ' ') (*p)++;
13601 sprintf(buf, "accepted %s\n", name);
13602 SendToProgram(buf, cps);
13609 StringFeature(p, name, loc, cps)
13613 ChessProgramState *cps;
13616 int len = strlen(name);
13617 if (strncmp((*p), name, len) == 0
13618 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13620 sscanf(*p, "%[^\"]", loc);
13621 while (**p && **p != '\"') (*p)++;
13622 if (**p == '\"') (*p)++;
13623 sprintf(buf, "accepted %s\n", name);
13624 SendToProgram(buf, cps);
13631 ParseOption(Option *opt, ChessProgramState *cps)
13632 // [HGM] options: process the string that defines an engine option, and determine
13633 // name, type, default value, and allowed value range
13635 char *p, *q, buf[MSG_SIZ];
13636 int n, min = (-1)<<31, max = 1<<31, def;
13638 if(p = strstr(opt->name, " -spin ")) {
13639 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13640 if(max < min) max = min; // enforce consistency
13641 if(def < min) def = min;
13642 if(def > max) def = max;
13647 } else if((p = strstr(opt->name, " -slider "))) {
13648 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13649 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13650 if(max < min) max = min; // enforce consistency
13651 if(def < min) def = min;
13652 if(def > max) def = max;
13656 opt->type = Spin; // Slider;
13657 } else if((p = strstr(opt->name, " -string "))) {
13658 opt->textValue = p+9;
13659 opt->type = TextBox;
13660 } else if((p = strstr(opt->name, " -file "))) {
13661 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13662 opt->textValue = p+7;
13663 opt->type = TextBox; // FileName;
13664 } else if((p = strstr(opt->name, " -path "))) {
13665 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13666 opt->textValue = p+7;
13667 opt->type = TextBox; // PathName;
13668 } else if(p = strstr(opt->name, " -check ")) {
13669 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13670 opt->value = (def != 0);
13671 opt->type = CheckBox;
13672 } else if(p = strstr(opt->name, " -combo ")) {
13673 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13674 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13675 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13676 opt->value = n = 0;
13677 while(q = StrStr(q, " /// ")) {
13678 n++; *q = 0; // count choices, and null-terminate each of them
13680 if(*q == '*') { // remember default, which is marked with * prefix
13684 cps->comboList[cps->comboCnt++] = q;
13686 cps->comboList[cps->comboCnt++] = NULL;
13688 opt->type = ComboBox;
13689 } else if(p = strstr(opt->name, " -button")) {
13690 opt->type = Button;
13691 } else if(p = strstr(opt->name, " -save")) {
13692 opt->type = SaveButton;
13693 } else return FALSE;
13694 *p = 0; // terminate option name
13695 // now look if the command-line options define a setting for this engine option.
13696 if(cps->optionSettings && cps->optionSettings[0])
13697 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13698 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13699 sprintf(buf, "option %s", p);
13700 if(p = strstr(buf, ",")) *p = 0;
13702 SendToProgram(buf, cps);
13708 FeatureDone(cps, val)
13709 ChessProgramState* cps;
13712 DelayedEventCallback cb = GetDelayedEvent();
13713 if ((cb == InitBackEnd3 && cps == &first) ||
13714 (cb == TwoMachinesEventIfReady && cps == &second)) {
13715 CancelDelayedEvent();
13716 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13718 cps->initDone = val;
13721 /* Parse feature command from engine */
13723 ParseFeatures(args, cps)
13725 ChessProgramState *cps;
13733 while (*p == ' ') p++;
13734 if (*p == NULLCHAR) return;
13736 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13737 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13738 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13739 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13740 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13741 if (BoolFeature(&p, "reuse", &val, cps)) {
13742 /* Engine can disable reuse, but can't enable it if user said no */
13743 if (!val) cps->reuse = FALSE;
13746 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13747 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13748 if (gameMode == TwoMachinesPlay) {
13749 DisplayTwoMachinesTitle();
13755 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13756 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13757 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13758 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13759 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13760 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13761 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13762 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13763 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13764 if (IntFeature(&p, "done", &val, cps)) {
13765 FeatureDone(cps, val);
13768 /* Added by Tord: */
13769 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13770 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13771 /* End of additions by Tord */
13773 /* [HGM] added features: */
13774 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13775 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13776 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13777 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13778 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13779 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13780 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13781 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13782 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13783 SendToProgram(buf, cps);
13786 if(cps->nrOptions >= MAX_OPTIONS) {
13788 sprintf(buf, "%s engine has too many options\n", cps->which);
13789 DisplayError(buf, 0);
13793 /* End of additions by HGM */
13795 /* unknown feature: complain and skip */
13797 while (*q && *q != '=') q++;
13798 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13799 SendToProgram(buf, cps);
13805 while (*p && *p != '\"') p++;
13806 if (*p == '\"') p++;
13808 while (*p && *p != ' ') p++;
13816 PeriodicUpdatesEvent(newState)
13819 if (newState == appData.periodicUpdates)
13822 appData.periodicUpdates=newState;
13824 /* Display type changes, so update it now */
13825 // DisplayAnalysis();
13827 /* Get the ball rolling again... */
13829 AnalysisPeriodicEvent(1);
13830 StartAnalysisClock();
13835 PonderNextMoveEvent(newState)
13838 if (newState == appData.ponderNextMove) return;
13839 if (gameMode == EditPosition) EditPositionDone(TRUE);
13841 SendToProgram("hard\n", &first);
13842 if (gameMode == TwoMachinesPlay) {
13843 SendToProgram("hard\n", &second);
13846 SendToProgram("easy\n", &first);
13847 thinkOutput[0] = NULLCHAR;
13848 if (gameMode == TwoMachinesPlay) {
13849 SendToProgram("easy\n", &second);
13852 appData.ponderNextMove = newState;
13856 NewSettingEvent(option, feature, command, value)
13858 int option, value, *feature;
13862 if (gameMode == EditPosition) EditPositionDone(TRUE);
13863 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13864 if(feature == NULL || *feature) SendToProgram(buf, &first);
13865 if (gameMode == TwoMachinesPlay) {
13866 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13871 ShowThinkingEvent()
13872 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13874 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13875 int newState = appData.showThinking
13876 // [HGM] thinking: other features now need thinking output as well
13877 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13879 if (oldState == newState) return;
13880 oldState = newState;
13881 if (gameMode == EditPosition) EditPositionDone(TRUE);
13883 SendToProgram("post\n", &first);
13884 if (gameMode == TwoMachinesPlay) {
13885 SendToProgram("post\n", &second);
13888 SendToProgram("nopost\n", &first);
13889 thinkOutput[0] = NULLCHAR;
13890 if (gameMode == TwoMachinesPlay) {
13891 SendToProgram("nopost\n", &second);
13894 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13898 AskQuestionEvent(title, question, replyPrefix, which)
13899 char *title; char *question; char *replyPrefix; char *which;
13901 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13902 if (pr == NoProc) return;
13903 AskQuestion(title, question, replyPrefix, pr);
13907 DisplayMove(moveNumber)
13910 char message[MSG_SIZ];
13912 char cpThinkOutput[MSG_SIZ];
13914 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13916 if (moveNumber == forwardMostMove - 1 ||
13917 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13919 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
13921 if (strchr(cpThinkOutput, '\n')) {
13922 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13925 *cpThinkOutput = NULLCHAR;
13928 /* [AS] Hide thinking from human user */
13929 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13930 *cpThinkOutput = NULLCHAR;
13931 if( thinkOutput[0] != NULLCHAR ) {
13934 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13935 cpThinkOutput[i] = '.';
13937 cpThinkOutput[i] = NULLCHAR;
13938 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13942 if (moveNumber == forwardMostMove - 1 &&
13943 gameInfo.resultDetails != NULL) {
13944 if (gameInfo.resultDetails[0] == NULLCHAR) {
13945 sprintf(res, " %s", PGNResult(gameInfo.result));
13947 sprintf(res, " {%s} %s",
13948 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
13954 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13955 DisplayMessage(res, cpThinkOutput);
13957 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13958 WhiteOnMove(moveNumber) ? " " : ".. ",
13959 parseList[moveNumber], res);
13960 DisplayMessage(message, cpThinkOutput);
13965 DisplayComment(moveNumber, text)
13969 char title[MSG_SIZ];
13970 char buf[8000]; // comment can be long!
13973 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13974 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
13976 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13977 WhiteOnMove(moveNumber) ? " " : ".. ",
13978 parseList[moveNumber]);
13980 // [HGM] PV info: display PV info together with (or as) comment
13981 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13982 if(text == NULL) text = "";
13983 score = pvInfoList[moveNumber].score;
13984 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13985 depth, (pvInfoList[moveNumber].time+50)/100, text);
13988 if (text != NULL && (appData.autoDisplayComment || commentUp))
13989 CommentPopUp(title, text);
13992 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13993 * might be busy thinking or pondering. It can be omitted if your
13994 * gnuchess is configured to stop thinking immediately on any user
13995 * input. However, that gnuchess feature depends on the FIONREAD
13996 * ioctl, which does not work properly on some flavors of Unix.
14000 ChessProgramState *cps;
14003 if (!cps->useSigint) return;
14004 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14005 switch (gameMode) {
14006 case MachinePlaysWhite:
14007 case MachinePlaysBlack:
14008 case TwoMachinesPlay:
14009 case IcsPlayingWhite:
14010 case IcsPlayingBlack:
14013 /* Skip if we know it isn't thinking */
14014 if (!cps->maybeThinking) return;
14015 if (appData.debugMode)
14016 fprintf(debugFP, "Interrupting %s\n", cps->which);
14017 InterruptChildProcess(cps->pr);
14018 cps->maybeThinking = FALSE;
14023 #endif /*ATTENTION*/
14029 if (whiteTimeRemaining <= 0) {
14032 if (appData.icsActive) {
14033 if (appData.autoCallFlag &&
14034 gameMode == IcsPlayingBlack && !blackFlag) {
14035 SendToICS(ics_prefix);
14036 SendToICS("flag\n");
14040 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14042 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14043 if (appData.autoCallFlag) {
14044 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14051 if (blackTimeRemaining <= 0) {
14054 if (appData.icsActive) {
14055 if (appData.autoCallFlag &&
14056 gameMode == IcsPlayingWhite && !whiteFlag) {
14057 SendToICS(ics_prefix);
14058 SendToICS("flag\n");
14062 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14064 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14065 if (appData.autoCallFlag) {
14066 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14079 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14080 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14083 * add time to clocks when time control is achieved ([HGM] now also used for increment)
14085 if ( !WhiteOnMove(forwardMostMove) ) {
14086 /* White made time control */
14087 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14088 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14089 /* [HGM] time odds: correct new time quota for time odds! */
14090 / WhitePlayer()->timeOdds;
14091 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14093 lastBlack -= blackTimeRemaining;
14094 /* Black made time control */
14095 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14096 / WhitePlayer()->other->timeOdds;
14097 lastWhite = whiteTimeRemaining;
14102 DisplayBothClocks()
14104 int wom = gameMode == EditPosition ?
14105 !blackPlaysFirst : WhiteOnMove(currentMove);
14106 DisplayWhiteClock(whiteTimeRemaining, wom);
14107 DisplayBlackClock(blackTimeRemaining, !wom);
14111 /* Timekeeping seems to be a portability nightmare. I think everyone
14112 has ftime(), but I'm really not sure, so I'm including some ifdefs
14113 to use other calls if you don't. Clocks will be less accurate if
14114 you have neither ftime nor gettimeofday.
14117 /* VS 2008 requires the #include outside of the function */
14118 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14119 #include <sys/timeb.h>
14122 /* Get the current time as a TimeMark */
14127 #if HAVE_GETTIMEOFDAY
14129 struct timeval timeVal;
14130 struct timezone timeZone;
14132 gettimeofday(&timeVal, &timeZone);
14133 tm->sec = (long) timeVal.tv_sec;
14134 tm->ms = (int) (timeVal.tv_usec / 1000L);
14136 #else /*!HAVE_GETTIMEOFDAY*/
14139 // include <sys/timeb.h> / moved to just above start of function
14140 struct timeb timeB;
14143 tm->sec = (long) timeB.time;
14144 tm->ms = (int) timeB.millitm;
14146 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14147 tm->sec = (long) time(NULL);
14153 /* Return the difference in milliseconds between two
14154 time marks. We assume the difference will fit in a long!
14157 SubtractTimeMarks(tm2, tm1)
14158 TimeMark *tm2, *tm1;
14160 return 1000L*(tm2->sec - tm1->sec) +
14161 (long) (tm2->ms - tm1->ms);
14166 * Code to manage the game clocks.
14168 * In tournament play, black starts the clock and then white makes a move.
14169 * We give the human user a slight advantage if he is playing white---the
14170 * clocks don't run until he makes his first move, so it takes zero time.
14171 * Also, we don't account for network lag, so we could get out of sync
14172 * with GNU Chess's clock -- but then, referees are always right.
14175 static TimeMark tickStartTM;
14176 static long intendedTickLength;
14179 NextTickLength(timeRemaining)
14180 long timeRemaining;
14182 long nominalTickLength, nextTickLength;
14184 if (timeRemaining > 0L && timeRemaining <= 10000L)
14185 nominalTickLength = 100L;
14187 nominalTickLength = 1000L;
14188 nextTickLength = timeRemaining % nominalTickLength;
14189 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14191 return nextTickLength;
14194 /* Adjust clock one minute up or down */
14196 AdjustClock(Boolean which, int dir)
14198 if(which) blackTimeRemaining += 60000*dir;
14199 else whiteTimeRemaining += 60000*dir;
14200 DisplayBothClocks();
14203 /* Stop clocks and reset to a fresh time control */
14207 (void) StopClockTimer();
14208 if (appData.icsActive) {
14209 whiteTimeRemaining = blackTimeRemaining = 0;
14210 } else if (searchTime) {
14211 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14212 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14213 } else { /* [HGM] correct new time quote for time odds */
14214 whiteTC = blackTC = fullTimeControlString;
14215 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14216 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14218 if (whiteFlag || blackFlag) {
14220 whiteFlag = blackFlag = FALSE;
14222 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14223 DisplayBothClocks();
14226 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14228 /* Decrement running clock by amount of time that has passed */
14232 long timeRemaining;
14233 long lastTickLength, fudge;
14236 if (!appData.clockMode) return;
14237 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14241 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14243 /* Fudge if we woke up a little too soon */
14244 fudge = intendedTickLength - lastTickLength;
14245 if (fudge < 0 || fudge > FUDGE) fudge = 0;
14247 if (WhiteOnMove(forwardMostMove)) {
14248 if(whiteNPS >= 0) lastTickLength = 0;
14249 timeRemaining = whiteTimeRemaining -= lastTickLength;
14250 if(timeRemaining < 0) {
14251 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14252 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14253 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14254 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14257 DisplayWhiteClock(whiteTimeRemaining - fudge,
14258 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14260 if(blackNPS >= 0) lastTickLength = 0;
14261 timeRemaining = blackTimeRemaining -= lastTickLength;
14262 if(timeRemaining < 0) { // [HGM] if we run out of a non-last incremental session, go to the next
14263 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14265 blackStartMove = forwardMostMove;
14266 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14269 DisplayBlackClock(blackTimeRemaining - fudge,
14270 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14272 if (CheckFlags()) return;
14275 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14276 StartClockTimer(intendedTickLength);
14278 /* if the time remaining has fallen below the alarm threshold, sound the
14279 * alarm. if the alarm has sounded and (due to a takeback or time control
14280 * with increment) the time remaining has increased to a level above the
14281 * threshold, reset the alarm so it can sound again.
14284 if (appData.icsActive && appData.icsAlarm) {
14286 /* make sure we are dealing with the user's clock */
14287 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14288 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14291 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14292 alarmSounded = FALSE;
14293 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14295 alarmSounded = TRUE;
14301 /* A player has just moved, so stop the previously running
14302 clock and (if in clock mode) start the other one.
14303 We redisplay both clocks in case we're in ICS mode, because
14304 ICS gives us an update to both clocks after every move.
14305 Note that this routine is called *after* forwardMostMove
14306 is updated, so the last fractional tick must be subtracted
14307 from the color that is *not* on move now.
14310 SwitchClocks(int newMoveNr)
14312 long lastTickLength;
14314 int flagged = FALSE;
14318 if (StopClockTimer() && appData.clockMode) {
14319 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14320 if (!WhiteOnMove(forwardMostMove)) {
14321 if(blackNPS >= 0) lastTickLength = 0;
14322 blackTimeRemaining -= lastTickLength;
14323 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14324 // if(pvInfoList[forwardMostMove-1].time == -1)
14325 pvInfoList[forwardMostMove-1].time = // use GUI time
14326 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14328 if(whiteNPS >= 0) lastTickLength = 0;
14329 whiteTimeRemaining -= lastTickLength;
14330 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14331 // if(pvInfoList[forwardMostMove-1].time == -1)
14332 pvInfoList[forwardMostMove-1].time =
14333 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14335 flagged = CheckFlags();
14337 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14338 CheckTimeControl();
14340 if (flagged || !appData.clockMode) return;
14342 switch (gameMode) {
14343 case MachinePlaysBlack:
14344 case MachinePlaysWhite:
14345 case BeginningOfGame:
14346 if (pausing) return;
14350 case PlayFromGameFile:
14358 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14359 if(WhiteOnMove(forwardMostMove))
14360 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14361 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14365 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14366 whiteTimeRemaining : blackTimeRemaining);
14367 StartClockTimer(intendedTickLength);
14371 /* Stop both clocks */
14375 long lastTickLength;
14378 if (!StopClockTimer()) return;
14379 if (!appData.clockMode) return;
14383 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14384 if (WhiteOnMove(forwardMostMove)) {
14385 if(whiteNPS >= 0) lastTickLength = 0;
14386 whiteTimeRemaining -= lastTickLength;
14387 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14389 if(blackNPS >= 0) lastTickLength = 0;
14390 blackTimeRemaining -= lastTickLength;
14391 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14396 /* Start clock of player on move. Time may have been reset, so
14397 if clock is already running, stop and restart it. */
14401 (void) StopClockTimer(); /* in case it was running already */
14402 DisplayBothClocks();
14403 if (CheckFlags()) return;
14405 if (!appData.clockMode) return;
14406 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14408 GetTimeMark(&tickStartTM);
14409 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14410 whiteTimeRemaining : blackTimeRemaining);
14412 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14413 whiteNPS = blackNPS = -1;
14414 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14415 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14416 whiteNPS = first.nps;
14417 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14418 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14419 blackNPS = first.nps;
14420 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14421 whiteNPS = second.nps;
14422 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14423 blackNPS = second.nps;
14424 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14426 StartClockTimer(intendedTickLength);
14433 long second, minute, hour, day;
14435 static char buf[32];
14437 if (ms > 0 && ms <= 9900) {
14438 /* convert milliseconds to tenths, rounding up */
14439 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14441 sprintf(buf, " %03.1f ", tenths/10.0);
14445 /* convert milliseconds to seconds, rounding up */
14446 /* use floating point to avoid strangeness of integer division
14447 with negative dividends on many machines */
14448 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14455 day = second / (60 * 60 * 24);
14456 second = second % (60 * 60 * 24);
14457 hour = second / (60 * 60);
14458 second = second % (60 * 60);
14459 minute = second / 60;
14460 second = second % 60;
14463 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14464 sign, day, hour, minute, second);
14466 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14468 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14475 * This is necessary because some C libraries aren't ANSI C compliant yet.
14478 StrStr(string, match)
14479 char *string, *match;
14483 length = strlen(match);
14485 for (i = strlen(string) - length; i >= 0; i--, string++)
14486 if (!strncmp(match, string, length))
14493 StrCaseStr(string, match)
14494 char *string, *match;
14498 length = strlen(match);
14500 for (i = strlen(string) - length; i >= 0; i--, string++) {
14501 for (j = 0; j < length; j++) {
14502 if (ToLower(match[j]) != ToLower(string[j]))
14505 if (j == length) return string;
14519 c1 = ToLower(*s1++);
14520 c2 = ToLower(*s2++);
14521 if (c1 > c2) return 1;
14522 if (c1 < c2) return -1;
14523 if (c1 == NULLCHAR) return 0;
14532 return isupper(c) ? tolower(c) : c;
14540 return islower(c) ? toupper(c) : c;
14542 #endif /* !_amigados */
14550 if ((ret = (char *) malloc(strlen(s) + 1)))
14552 safeStrCpy(ret, s, strlen(s)+1);
14558 StrSavePtr(s, savePtr)
14559 char *s, **savePtr;
14564 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14565 safeStrCpy(*savePtr, s, strlen(s)+1);
14577 clock = time((time_t *)NULL);
14578 tm = localtime(&clock);
14579 sprintf(buf, "%04d.%02d.%02d",
14580 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14581 return StrSave(buf);
14586 PositionToFEN(move, overrideCastling)
14588 char *overrideCastling;
14590 int i, j, fromX, fromY, toX, toY;
14597 whiteToPlay = (gameMode == EditPosition) ?
14598 !blackPlaysFirst : (move % 2 == 0);
14601 /* Piece placement data */
14602 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14604 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14605 if (boards[move][i][j] == EmptySquare) {
14607 } else { ChessSquare piece = boards[move][i][j];
14608 if (emptycount > 0) {
14609 if(emptycount<10) /* [HGM] can be >= 10 */
14610 *p++ = '0' + emptycount;
14611 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14614 if(PieceToChar(piece) == '+') {
14615 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14617 piece = (ChessSquare)(DEMOTED piece);
14619 *p++ = PieceToChar(piece);
14621 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14622 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14627 if (emptycount > 0) {
14628 if(emptycount<10) /* [HGM] can be >= 10 */
14629 *p++ = '0' + emptycount;
14630 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14637 /* [HGM] print Crazyhouse or Shogi holdings */
14638 if( gameInfo.holdingsWidth ) {
14639 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14641 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14642 piece = boards[move][i][BOARD_WIDTH-1];
14643 if( piece != EmptySquare )
14644 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14645 *p++ = PieceToChar(piece);
14647 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14648 piece = boards[move][BOARD_HEIGHT-i-1][0];
14649 if( piece != EmptySquare )
14650 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14651 *p++ = PieceToChar(piece);
14654 if( q == p ) *p++ = '-';
14660 *p++ = whiteToPlay ? 'w' : 'b';
14663 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14664 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14666 if(nrCastlingRights) {
14668 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14669 /* [HGM] write directly from rights */
14670 if(boards[move][CASTLING][2] != NoRights &&
14671 boards[move][CASTLING][0] != NoRights )
14672 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14673 if(boards[move][CASTLING][2] != NoRights &&
14674 boards[move][CASTLING][1] != NoRights )
14675 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14676 if(boards[move][CASTLING][5] != NoRights &&
14677 boards[move][CASTLING][3] != NoRights )
14678 *p++ = boards[move][CASTLING][3] + AAA;
14679 if(boards[move][CASTLING][5] != NoRights &&
14680 boards[move][CASTLING][4] != NoRights )
14681 *p++ = boards[move][CASTLING][4] + AAA;
14684 /* [HGM] write true castling rights */
14685 if( nrCastlingRights == 6 ) {
14686 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14687 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14688 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14689 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14690 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14691 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14692 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14693 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14696 if (q == p) *p++ = '-'; /* No castling rights */
14700 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14701 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14702 /* En passant target square */
14703 if (move > backwardMostMove) {
14704 fromX = moveList[move - 1][0] - AAA;
14705 fromY = moveList[move - 1][1] - ONE;
14706 toX = moveList[move - 1][2] - AAA;
14707 toY = moveList[move - 1][3] - ONE;
14708 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14709 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14710 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14712 /* 2-square pawn move just happened */
14714 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14718 } else if(move == backwardMostMove) {
14719 // [HGM] perhaps we should always do it like this, and forget the above?
14720 if((signed char)boards[move][EP_STATUS] >= 0) {
14721 *p++ = boards[move][EP_STATUS] + AAA;
14722 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14733 /* [HGM] find reversible plies */
14734 { int i = 0, j=move;
14736 if (appData.debugMode) { int k;
14737 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14738 for(k=backwardMostMove; k<=forwardMostMove; k++)
14739 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14743 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14744 if( j == backwardMostMove ) i += initialRulePlies;
14745 sprintf(p, "%d ", i);
14746 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14748 /* Fullmove number */
14749 sprintf(p, "%d", (move / 2) + 1);
14751 return StrSave(buf);
14755 ParseFEN(board, blackPlaysFirst, fen)
14757 int *blackPlaysFirst;
14767 /* [HGM] by default clear Crazyhouse holdings, if present */
14768 if(gameInfo.holdingsWidth) {
14769 for(i=0; i<BOARD_HEIGHT; i++) {
14770 board[i][0] = EmptySquare; /* black holdings */
14771 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14772 board[i][1] = (ChessSquare) 0; /* black counts */
14773 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14777 /* Piece placement data */
14778 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14781 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14782 if (*p == '/') p++;
14783 emptycount = gameInfo.boardWidth - j;
14784 while (emptycount--)
14785 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14787 #if(BOARD_FILES >= 10)
14788 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14789 p++; emptycount=10;
14790 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14791 while (emptycount--)
14792 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14794 } else if (isdigit(*p)) {
14795 emptycount = *p++ - '0';
14796 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14797 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14798 while (emptycount--)
14799 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14800 } else if (*p == '+' || isalpha(*p)) {
14801 if (j >= gameInfo.boardWidth) return FALSE;
14803 piece = CharToPiece(*++p);
14804 if(piece == EmptySquare) return FALSE; /* unknown piece */
14805 piece = (ChessSquare) (PROMOTED piece ); p++;
14806 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14807 } else piece = CharToPiece(*p++);
14809 if(piece==EmptySquare) return FALSE; /* unknown piece */
14810 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14811 piece = (ChessSquare) (PROMOTED piece);
14812 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14815 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14821 while (*p == '/' || *p == ' ') p++;
14823 /* [HGM] look for Crazyhouse holdings here */
14824 while(*p==' ') p++;
14825 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14827 if(*p == '-' ) *p++; /* empty holdings */ else {
14828 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14829 /* if we would allow FEN reading to set board size, we would */
14830 /* have to add holdings and shift the board read so far here */
14831 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14833 if((int) piece >= (int) BlackPawn ) {
14834 i = (int)piece - (int)BlackPawn;
14835 i = PieceToNumber((ChessSquare)i);
14836 if( i >= gameInfo.holdingsSize ) return FALSE;
14837 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14838 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14840 i = (int)piece - (int)WhitePawn;
14841 i = PieceToNumber((ChessSquare)i);
14842 if( i >= gameInfo.holdingsSize ) return FALSE;
14843 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14844 board[i][BOARD_WIDTH-2]++; /* black holdings */
14848 if(*p == ']') *p++;
14851 while(*p == ' ') p++;
14855 if(appData.colorNickNames) {
14856 if( c == appData.colorNickNames[0] ) c = 'w'; else
14857 if( c == appData.colorNickNames[1] ) c = 'b';
14861 *blackPlaysFirst = FALSE;
14864 *blackPlaysFirst = TRUE;
14870 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14871 /* return the extra info in global variiables */
14873 /* set defaults in case FEN is incomplete */
14874 board[EP_STATUS] = EP_UNKNOWN;
14875 for(i=0; i<nrCastlingRights; i++ ) {
14876 board[CASTLING][i] =
14877 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14878 } /* assume possible unless obviously impossible */
14879 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14880 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14881 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14882 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14883 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14884 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14885 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14886 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14889 while(*p==' ') p++;
14890 if(nrCastlingRights) {
14891 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14892 /* castling indicator present, so default becomes no castlings */
14893 for(i=0; i<nrCastlingRights; i++ ) {
14894 board[CASTLING][i] = NoRights;
14897 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14898 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14899 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14900 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14901 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14903 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14904 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14905 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14907 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14908 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14909 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14910 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14911 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14912 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14915 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14916 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14917 board[CASTLING][2] = whiteKingFile;
14920 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14921 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14922 board[CASTLING][2] = whiteKingFile;
14925 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14926 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14927 board[CASTLING][5] = blackKingFile;
14930 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14931 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14932 board[CASTLING][5] = blackKingFile;
14935 default: /* FRC castlings */
14936 if(c >= 'a') { /* black rights */
14937 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14938 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14939 if(i == BOARD_RGHT) break;
14940 board[CASTLING][5] = i;
14942 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14943 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14945 board[CASTLING][3] = c;
14947 board[CASTLING][4] = c;
14948 } else { /* white rights */
14949 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14950 if(board[0][i] == WhiteKing) break;
14951 if(i == BOARD_RGHT) break;
14952 board[CASTLING][2] = i;
14953 c -= AAA - 'a' + 'A';
14954 if(board[0][c] >= WhiteKing) break;
14956 board[CASTLING][0] = c;
14958 board[CASTLING][1] = c;
14962 for(i=0; i<nrCastlingRights; i++)
14963 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14964 if (appData.debugMode) {
14965 fprintf(debugFP, "FEN castling rights:");
14966 for(i=0; i<nrCastlingRights; i++)
14967 fprintf(debugFP, " %d", board[CASTLING][i]);
14968 fprintf(debugFP, "\n");
14971 while(*p==' ') p++;
14974 /* read e.p. field in games that know e.p. capture */
14975 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14976 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14978 p++; board[EP_STATUS] = EP_NONE;
14980 char c = *p++ - AAA;
14982 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14983 if(*p >= '0' && *p <='9') *p++;
14984 board[EP_STATUS] = c;
14989 if(sscanf(p, "%d", &i) == 1) {
14990 FENrulePlies = i; /* 50-move ply counter */
14991 /* (The move number is still ignored) */
14998 EditPositionPasteFEN(char *fen)
15001 Board initial_position;
15003 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15004 DisplayError(_("Bad FEN position in clipboard"), 0);
15007 int savedBlackPlaysFirst = blackPlaysFirst;
15008 EditPositionEvent();
15009 blackPlaysFirst = savedBlackPlaysFirst;
15010 CopyBoard(boards[0], initial_position);
15011 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15012 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15013 DisplayBothClocks();
15014 DrawPosition(FALSE, boards[currentMove]);
15019 static char cseq[12] = "\\ ";
15021 Boolean set_cont_sequence(char *new_seq)
15026 // handle bad attempts to set the sequence
15028 return 0; // acceptable error - no debug
15030 len = strlen(new_seq);
15031 ret = (len > 0) && (len < sizeof(cseq));
15033 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15034 else if (appData.debugMode)
15035 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15040 reformat a source message so words don't cross the width boundary. internal
15041 newlines are not removed. returns the wrapped size (no null character unless
15042 included in source message). If dest is NULL, only calculate the size required
15043 for the dest buffer. lp argument indicats line position upon entry, and it's
15044 passed back upon exit.
15046 int wrap(char *dest, char *src, int count, int width, int *lp)
15048 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15050 cseq_len = strlen(cseq);
15051 old_line = line = *lp;
15052 ansi = len = clen = 0;
15054 for (i=0; i < count; i++)
15056 if (src[i] == '\033')
15059 // if we hit the width, back up
15060 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15062 // store i & len in case the word is too long
15063 old_i = i, old_len = len;
15065 // find the end of the last word
15066 while (i && src[i] != ' ' && src[i] != '\n')
15072 // word too long? restore i & len before splitting it
15073 if ((old_i-i+clen) >= width)
15080 if (i && src[i-1] == ' ')
15083 if (src[i] != ' ' && src[i] != '\n')
15090 // now append the newline and continuation sequence
15095 strncpy(dest+len, cseq, cseq_len);
15103 dest[len] = src[i];
15107 if (src[i] == '\n')
15112 if (dest && appData.debugMode)
15114 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15115 count, width, line, len, *lp);
15116 show_bytes(debugFP, src, count);
15117 fprintf(debugFP, "\ndest: ");
15118 show_bytes(debugFP, dest, len);
15119 fprintf(debugFP, "\n");
15121 *lp = dest ? line : old_line;
15126 // [HGM] vari: routines for shelving variations
15129 PushTail(int firstMove, int lastMove)
15131 int i, j, nrMoves = lastMove - firstMove;
15133 if(appData.icsActive) { // only in local mode
15134 forwardMostMove = currentMove; // mimic old ICS behavior
15137 if(storedGames >= MAX_VARIATIONS-1) return;
15139 // push current tail of game on stack
15140 savedResult[storedGames] = gameInfo.result;
15141 savedDetails[storedGames] = gameInfo.resultDetails;
15142 gameInfo.resultDetails = NULL;
15143 savedFirst[storedGames] = firstMove;
15144 savedLast [storedGames] = lastMove;
15145 savedFramePtr[storedGames] = framePtr;
15146 framePtr -= nrMoves; // reserve space for the boards
15147 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15148 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15149 for(j=0; j<MOVE_LEN; j++)
15150 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15151 for(j=0; j<2*MOVE_LEN; j++)
15152 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15153 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15154 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15155 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15156 pvInfoList[firstMove+i-1].depth = 0;
15157 commentList[framePtr+i] = commentList[firstMove+i];
15158 commentList[firstMove+i] = NULL;
15162 forwardMostMove = firstMove; // truncate game so we can start variation
15163 if(storedGames == 1) GreyRevert(FALSE);
15167 PopTail(Boolean annotate)
15170 char buf[8000], moveBuf[20];
15172 if(appData.icsActive) return FALSE; // only in local mode
15173 if(!storedGames) return FALSE; // sanity
15174 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15177 ToNrEvent(savedFirst[storedGames]); // sets currentMove
15178 nrMoves = savedLast[storedGames] - currentMove;
15181 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15182 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15183 for(i=currentMove; i<forwardMostMove; i++) {
15185 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15186 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15187 strcat(buf, moveBuf);
15188 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15189 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15193 for(i=1; i<=nrMoves; i++) { // copy last variation back
15194 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15195 for(j=0; j<MOVE_LEN; j++)
15196 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15197 for(j=0; j<2*MOVE_LEN; j++)
15198 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15199 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15200 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15201 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15202 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15203 commentList[currentMove+i] = commentList[framePtr+i];
15204 commentList[framePtr+i] = NULL;
15206 if(annotate) AppendComment(currentMove+1, buf, FALSE);
15207 framePtr = savedFramePtr[storedGames];
15208 gameInfo.result = savedResult[storedGames];
15209 if(gameInfo.resultDetails != NULL) {
15210 free(gameInfo.resultDetails);
15212 gameInfo.resultDetails = savedDetails[storedGames];
15213 forwardMostMove = currentMove + nrMoves;
15214 if(storedGames == 0) GreyRevert(TRUE);
15220 { // remove all shelved variations
15222 for(i=0; i<storedGames; i++) {
15223 if(savedDetails[i])
15224 free(savedDetails[i]);
15225 savedDetails[i] = NULL;
15227 for(i=framePtr; i<MAX_MOVES; i++) {
15228 if(commentList[i]) free(commentList[i]);
15229 commentList[i] = NULL;
15231 framePtr = MAX_MOVES-1;
15236 LoadVariation(int index, char *text)
15237 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15238 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15239 int level = 0, move;
15241 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15242 // first find outermost bracketing variation
15243 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15244 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15245 if(*p == '{') wait = '}'; else
15246 if(*p == '[') wait = ']'; else
15247 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15248 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15250 if(*p == wait) wait = NULLCHAR; // closing ]} found
15253 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15254 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15255 end[1] = NULLCHAR; // clip off comment beyond variation
15256 ToNrEvent(currentMove-1);
15257 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15258 // kludge: use ParsePV() to append variation to game
15259 move = currentMove;
15260 ParsePV(start, TRUE);
15261 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15262 ClearPremoveHighlights();
15264 ToNrEvent(currentMove+1);