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 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)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
240 static int exiting = 0; /* [HGM] moved to top */
241 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
242 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
243 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
244 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
245 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
246 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
252 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
254 /* States for ics_getting_history */
256 #define H_REQUESTED 1
257 #define H_GOT_REQ_HEADER 2
258 #define H_GOT_UNREQ_HEADER 3
259 #define H_GETTING_MOVES 4
260 #define H_GOT_UNWANTED_HEADER 5
262 /* whosays values for GameEnds */
271 /* Maximum number of games in a cmail message */
272 #define CMAIL_MAX_GAMES 20
274 /* Different types of move when calling RegisterMove */
276 #define CMAIL_RESIGN 1
278 #define CMAIL_ACCEPT 3
280 /* Different types of result to remember for each game */
281 #define CMAIL_NOT_RESULT 0
282 #define CMAIL_OLD_RESULT 1
283 #define CMAIL_NEW_RESULT 2
285 /* Telnet protocol constants */
296 static char * safeStrCpy( char * dst, const char * src, size_t count )
298 assert( dst != NULL );
299 assert( src != NULL );
302 strncpy( dst, src, count );
303 dst[ count-1 ] = '\0';
307 /* Some compiler can't cast u64 to double
308 * This function do the job for us:
310 * We use the highest bit for cast, this only
311 * works if the highest bit is not
312 * in use (This should not happen)
314 * We used this for all compiler
317 u64ToDouble(u64 value)
320 u64 tmp = value & u64Const(0x7fffffffffffffff);
321 r = (double)(s64)tmp;
322 if (value & u64Const(0x8000000000000000))
323 r += 9.2233720368547758080e18; /* 2^63 */
327 /* Fake up flags for now, as we aren't keeping track of castling
328 availability yet. [HGM] Change of logic: the flag now only
329 indicates the type of castlings allowed by the rule of the game.
330 The actual rights themselves are maintained in the array
331 castlingRights, as part of the game history, and are not probed
337 int flags = F_ALL_CASTLE_OK;
338 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
339 switch (gameInfo.variant) {
341 flags &= ~F_ALL_CASTLE_OK;
342 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
343 flags |= F_IGNORE_CHECK;
345 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
348 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
350 case VariantKriegspiel:
351 flags |= F_KRIEGSPIEL_CAPTURE;
353 case VariantCapaRandom:
354 case VariantFischeRandom:
355 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
356 case VariantNoCastle:
357 case VariantShatranj:
359 flags &= ~F_ALL_CASTLE_OK;
367 FILE *gameFileFP, *debugFP;
370 [AS] Note: sometimes, the sscanf() function is used to parse the input
371 into a fixed-size buffer. Because of this, we must be prepared to
372 receive strings as long as the size of the input buffer, which is currently
373 set to 4K for Windows and 8K for the rest.
374 So, we must either allocate sufficiently large buffers here, or
375 reduce the size of the input buffer in the input reading part.
378 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
379 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
380 char thinkOutput1[MSG_SIZ*10];
382 ChessProgramState first, second;
384 /* premove variables */
387 int premoveFromX = 0;
388 int premoveFromY = 0;
389 int premovePromoChar = 0;
391 Boolean alarmSounded;
392 /* end premove variables */
394 char *ics_prefix = "$";
395 int ics_type = ICS_GENERIC;
397 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
398 int pauseExamForwardMostMove = 0;
399 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
400 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
401 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
402 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
403 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
404 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
405 int whiteFlag = FALSE, blackFlag = FALSE;
406 int userOfferedDraw = FALSE;
407 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
408 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
409 int cmailMoveType[CMAIL_MAX_GAMES];
410 long ics_clock_paused = 0;
411 ProcRef icsPR = NoProc, cmailPR = NoProc;
412 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
413 GameMode gameMode = BeginningOfGame;
414 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
415 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
416 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
417 int hiddenThinkOutputState = 0; /* [AS] */
418 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
419 int adjudicateLossPlies = 6;
420 char white_holding[64], black_holding[64];
421 TimeMark lastNodeCountTime;
422 long lastNodeCount=0;
423 int have_sent_ICS_logon = 0;
425 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
426 long timeControl_2; /* [AS] Allow separate time controls */
427 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
428 long timeRemaining[2][MAX_MOVES];
430 TimeMark programStartTime;
431 char ics_handle[MSG_SIZ];
432 int have_set_title = 0;
434 /* animateTraining preserves the state of appData.animate
435 * when Training mode is activated. This allows the
436 * response to be animated when appData.animate == TRUE and
437 * appData.animateDragging == TRUE.
439 Boolean animateTraining;
445 Board boards[MAX_MOVES];
446 /* [HGM] Following 7 needed for accurate legality tests: */
447 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
448 signed char initialRights[BOARD_FILES];
449 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
450 int initialRulePlies, FENrulePlies;
451 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
454 int mute; // mute all sounds
456 // [HGM] vari: next 12 to save and restore variations
457 #define MAX_VARIATIONS 10
458 int framePtr = MAX_MOVES-1; // points to free stack entry
460 int savedFirst[MAX_VARIATIONS];
461 int savedLast[MAX_VARIATIONS];
462 int savedFramePtr[MAX_VARIATIONS];
463 char *savedDetails[MAX_VARIATIONS];
464 ChessMove savedResult[MAX_VARIATIONS];
466 void PushTail P((int firstMove, int lastMove));
467 Boolean PopTail P((Boolean annotate));
468 void CleanupTail P((void));
470 ChessSquare FIDEArray[2][BOARD_FILES] = {
471 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474 BlackKing, BlackBishop, BlackKnight, BlackRook }
477 ChessSquare twoKingsArray[2][BOARD_FILES] = {
478 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481 BlackKing, BlackKing, BlackKnight, BlackRook }
484 ChessSquare KnightmateArray[2][BOARD_FILES] = {
485 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487 { BlackRook, BlackMan, BlackBishop, BlackQueen,
488 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
491 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
492 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495 BlackKing, BlackBishop, BlackKnight, BlackRook }
498 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
506 #if (BOARD_FILES>=10)
507 ChessSquare ShogiArray[2][BOARD_FILES] = {
508 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
514 ChessSquare XiangqiArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
521 ChessSquare CapablancaArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
525 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
528 ChessSquare GreatArray[2][BOARD_FILES] = {
529 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
530 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
532 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
535 ChessSquare JanusArray[2][BOARD_FILES] = {
536 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
537 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
539 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
543 ChessSquare GothicArray[2][BOARD_FILES] = {
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
545 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
547 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
550 #define GothicArray CapablancaArray
554 ChessSquare FalconArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
556 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
558 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
561 #define FalconArray CapablancaArray
564 #else // !(BOARD_FILES>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_FILES>=10)
571 #if (BOARD_FILES>=12)
572 ChessSquare CourierArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
578 #else // !(BOARD_FILES>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_FILES>=12)
583 Board initialPosition;
586 /* Convert str to a rating. Checks for special cases of "----",
588 "++++", etc. Also strips ()'s */
590 string_to_rating(str)
593 while(*str && !isdigit(*str)) ++str;
595 return 0; /* One of the special "no rating" cases */
603 /* Init programStats */
604 programStats.movelist[0] = 0;
605 programStats.depth = 0;
606 programStats.nr_moves = 0;
607 programStats.moves_left = 0;
608 programStats.nodes = 0;
609 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
610 programStats.score = 0;
611 programStats.got_only_move = 0;
612 programStats.got_fail = 0;
613 programStats.line_is_book = 0;
619 int matched, min, sec;
621 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
623 GetTimeMark(&programStartTime);
624 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
627 programStats.ok_to_send = 1;
628 programStats.seen_stat = 0;
631 * Initialize game list
637 * Internet chess server status
639 if (appData.icsActive) {
640 appData.matchMode = FALSE;
641 appData.matchGames = 0;
643 appData.noChessProgram = !appData.zippyPlay;
645 appData.zippyPlay = FALSE;
646 appData.zippyTalk = FALSE;
647 appData.noChessProgram = TRUE;
649 if (*appData.icsHelper != NULLCHAR) {
650 appData.useTelnet = TRUE;
651 appData.telnetProgram = appData.icsHelper;
654 appData.zippyTalk = appData.zippyPlay = FALSE;
657 /* [AS] Initialize pv info list [HGM] and game state */
661 for( i=0; i<=framePtr; i++ ) {
662 pvInfoList[i].depth = -1;
663 boards[i][EP_STATUS] = EP_NONE;
664 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
669 * Parse timeControl resource
671 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672 appData.movesPerSession)) {
674 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675 DisplayFatalError(buf, 0, 2);
679 * Parse searchTime resource
681 if (*appData.searchTime != NULLCHAR) {
682 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
684 searchTime = min * 60;
685 } else if (matched == 2) {
686 searchTime = min * 60 + sec;
689 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690 DisplayFatalError(buf, 0, 2);
694 /* [AS] Adjudication threshold */
695 adjudicateLossThreshold = appData.adjudicateLossThreshold;
697 first.which = "first";
698 second.which = "second";
699 first.maybeThinking = second.maybeThinking = FALSE;
700 first.pr = second.pr = NoProc;
701 first.isr = second.isr = NULL;
702 first.sendTime = second.sendTime = 2;
703 first.sendDrawOffers = 1;
704 if (appData.firstPlaysBlack) {
705 first.twoMachinesColor = "black\n";
706 second.twoMachinesColor = "white\n";
708 first.twoMachinesColor = "white\n";
709 second.twoMachinesColor = "black\n";
711 first.program = appData.firstChessProgram;
712 second.program = appData.secondChessProgram;
713 first.host = appData.firstHost;
714 second.host = appData.secondHost;
715 first.dir = appData.firstDirectory;
716 second.dir = appData.secondDirectory;
717 first.other = &second;
718 second.other = &first;
719 first.initString = appData.initString;
720 second.initString = appData.secondInitString;
721 first.computerString = appData.firstComputerString;
722 second.computerString = appData.secondComputerString;
723 first.useSigint = second.useSigint = TRUE;
724 first.useSigterm = second.useSigterm = TRUE;
725 first.reuse = appData.reuseFirst;
726 second.reuse = appData.reuseSecond;
727 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
728 second.nps = appData.secondNPS;
729 first.useSetboard = second.useSetboard = FALSE;
730 first.useSAN = second.useSAN = FALSE;
731 first.usePing = second.usePing = FALSE;
732 first.lastPing = second.lastPing = 0;
733 first.lastPong = second.lastPong = 0;
734 first.usePlayother = second.usePlayother = FALSE;
735 first.useColors = second.useColors = TRUE;
736 first.useUsermove = second.useUsermove = FALSE;
737 first.sendICS = second.sendICS = FALSE;
738 first.sendName = second.sendName = appData.icsActive;
739 first.sdKludge = second.sdKludge = FALSE;
740 first.stKludge = second.stKludge = FALSE;
741 TidyProgramName(first.program, first.host, first.tidy);
742 TidyProgramName(second.program, second.host, second.tidy);
743 first.matchWins = second.matchWins = 0;
744 strcpy(first.variants, appData.variant);
745 strcpy(second.variants, appData.variant);
746 first.analysisSupport = second.analysisSupport = 2; /* detect */
747 first.analyzing = second.analyzing = FALSE;
748 first.initDone = second.initDone = FALSE;
750 /* New features added by Tord: */
751 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753 /* End of new features added by Tord. */
754 first.fenOverride = appData.fenOverride1;
755 second.fenOverride = appData.fenOverride2;
757 /* [HGM] time odds: set factor for each machine */
758 first.timeOdds = appData.firstTimeOdds;
759 second.timeOdds = appData.secondTimeOdds;
761 if(appData.timeOddsMode) {
762 norm = first.timeOdds;
763 if(norm > second.timeOdds) norm = second.timeOdds;
765 first.timeOdds /= norm;
766 second.timeOdds /= norm;
769 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770 first.accumulateTC = appData.firstAccumulateTC;
771 second.accumulateTC = appData.secondAccumulateTC;
772 first.maxNrOfSessions = second.maxNrOfSessions = 1;
775 first.debug = second.debug = FALSE;
776 first.supportsNPS = second.supportsNPS = UNKNOWN;
779 first.optionSettings = appData.firstOptions;
780 second.optionSettings = appData.secondOptions;
782 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784 first.isUCI = appData.firstIsUCI; /* [AS] */
785 second.isUCI = appData.secondIsUCI; /* [AS] */
786 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
789 if (appData.firstProtocolVersion > PROTOVER ||
790 appData.firstProtocolVersion < 1) {
792 sprintf(buf, _("protocol version %d not supported"),
793 appData.firstProtocolVersion);
794 DisplayFatalError(buf, 0, 2);
796 first.protocolVersion = appData.firstProtocolVersion;
799 if (appData.secondProtocolVersion > PROTOVER ||
800 appData.secondProtocolVersion < 1) {
802 sprintf(buf, _("protocol version %d not supported"),
803 appData.secondProtocolVersion);
804 DisplayFatalError(buf, 0, 2);
806 second.protocolVersion = appData.secondProtocolVersion;
809 if (appData.icsActive) {
810 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
811 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
813 appData.clockMode = FALSE;
814 first.sendTime = second.sendTime = 0;
818 /* Override some settings from environment variables, for backward
819 compatibility. Unfortunately it's not feasible to have the env
820 vars just set defaults, at least in xboard. Ugh.
822 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
827 if (appData.noChessProgram) {
828 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
829 sprintf(programVersion, "%s", PACKAGE_STRING);
831 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
832 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
833 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
836 if (!appData.icsActive) {
838 /* Check for variants that are supported only in ICS mode,
839 or not at all. Some that are accepted here nevertheless
840 have bugs; see comments below.
842 VariantClass variant = StringToVariant(appData.variant);
844 case VariantBughouse: /* need four players and two boards */
845 case VariantKriegspiel: /* need to hide pieces and move details */
846 /* case VariantFischeRandom: (Fabien: moved below) */
847 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
848 DisplayFatalError(buf, 0, 2);
852 case VariantLoadable:
862 sprintf(buf, _("Unknown variant name %s"), appData.variant);
863 DisplayFatalError(buf, 0, 2);
866 case VariantXiangqi: /* [HGM] repetition rules not implemented */
867 case VariantFairy: /* [HGM] TestLegality definitely off! */
868 case VariantGothic: /* [HGM] should work */
869 case VariantCapablanca: /* [HGM] should work */
870 case VariantCourier: /* [HGM] initial forced moves not implemented */
871 case VariantShogi: /* [HGM] drops not tested for legality */
872 case VariantKnightmate: /* [HGM] should work */
873 case VariantCylinder: /* [HGM] untested */
874 case VariantFalcon: /* [HGM] untested */
875 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
876 offboard interposition not understood */
877 case VariantNormal: /* definitely works! */
878 case VariantWildCastle: /* pieces not automatically shuffled */
879 case VariantNoCastle: /* pieces not automatically shuffled */
880 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
881 case VariantLosers: /* should work except for win condition,
882 and doesn't know captures are mandatory */
883 case VariantSuicide: /* should work except for win condition,
884 and doesn't know captures are mandatory */
885 case VariantGiveaway: /* should work except for win condition,
886 and doesn't know captures are mandatory */
887 case VariantTwoKings: /* should work */
888 case VariantAtomic: /* should work except for win condition */
889 case Variant3Check: /* should work except for win condition */
890 case VariantShatranj: /* should work except for all win conditions */
891 case VariantBerolina: /* might work if TestLegality is off */
892 case VariantCapaRandom: /* should work */
893 case VariantJanus: /* should work */
894 case VariantSuper: /* experimental */
895 case VariantGreat: /* experimental, requires legality testing to be off */
900 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
901 InitEngineUCI( installDir, &second );
904 int NextIntegerFromString( char ** str, long * value )
909 while( *s == ' ' || *s == '\t' ) {
915 if( *s >= '0' && *s <= '9' ) {
916 while( *s >= '0' && *s <= '9' ) {
917 *value = *value * 10 + (*s - '0');
929 int NextTimeControlFromString( char ** str, long * value )
932 int result = NextIntegerFromString( str, &temp );
935 *value = temp * 60; /* Minutes */
938 result = NextIntegerFromString( str, &temp );
939 *value += temp; /* Seconds */
946 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
947 { /* [HGM] routine added to read '+moves/time' for secondary time control */
948 int result = -1; long temp, temp2;
950 if(**str != '+') return -1; // old params remain in force!
952 if( NextTimeControlFromString( str, &temp ) ) return -1;
955 /* time only: incremental or sudden-death time control */
956 if(**str == '+') { /* increment follows; read it */
958 if(result = NextIntegerFromString( str, &temp2)) return -1;
961 *moves = 0; *tc = temp * 1000;
963 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
965 (*str)++; /* classical time control */
966 result = NextTimeControlFromString( str, &temp2);
975 int GetTimeQuota(int movenr)
976 { /* [HGM] get time to add from the multi-session time-control string */
977 int moves=1; /* kludge to force reading of first session */
978 long time, increment;
979 char *s = fullTimeControlString;
981 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
983 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
984 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
985 if(movenr == -1) return time; /* last move before new session */
986 if(!moves) return increment; /* current session is incremental */
987 if(movenr >= 0) movenr -= moves; /* we already finished this session */
988 } while(movenr >= -1); /* try again for next session */
990 return 0; // no new time quota on this move
994 ParseTimeControl(tc, ti, mps)
1003 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1006 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1007 else sprintf(buf, "+%s+%d", tc, ti);
1010 sprintf(buf, "+%d/%s", mps, tc);
1011 else sprintf(buf, "+%s", tc);
1013 fullTimeControlString = StrSave(buf);
1015 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1020 /* Parse second time control */
1023 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1031 timeControl_2 = tc2 * 1000;
1041 timeControl = tc1 * 1000;
1044 timeIncrement = ti * 1000; /* convert to ms */
1045 movesPerSession = 0;
1048 movesPerSession = mps;
1056 if (appData.debugMode) {
1057 fprintf(debugFP, "%s\n", programVersion);
1060 set_cont_sequence(appData.wrapContSeq);
1061 if (appData.matchGames > 0) {
1062 appData.matchMode = TRUE;
1063 } else if (appData.matchMode) {
1064 appData.matchGames = 1;
1066 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1067 appData.matchGames = appData.sameColorGames;
1068 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1069 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1070 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1073 if (appData.noChessProgram || first.protocolVersion == 1) {
1076 /* kludge: allow timeout for initial "feature" commands */
1078 DisplayMessage("", _("Starting chess program"));
1079 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1084 InitBackEnd3 P((void))
1086 GameMode initialMode;
1090 InitChessProgram(&first, startedFromSetupPosition);
1093 if (appData.icsActive) {
1095 /* [DM] Make a console window if needed [HGM] merged ifs */
1100 if (*appData.icsCommPort != NULLCHAR) {
1101 sprintf(buf, _("Could not open comm port %s"),
1102 appData.icsCommPort);
1104 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1105 appData.icsHost, appData.icsPort);
1107 DisplayFatalError(buf, err, 1);
1112 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1114 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1115 } else if (appData.noChessProgram) {
1121 if (*appData.cmailGameName != NULLCHAR) {
1123 OpenLoopback(&cmailPR);
1125 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1129 DisplayMessage("", "");
1130 if (StrCaseCmp(appData.initialMode, "") == 0) {
1131 initialMode = BeginningOfGame;
1132 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1133 initialMode = TwoMachinesPlay;
1134 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1135 initialMode = AnalyzeFile;
1136 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1137 initialMode = AnalyzeMode;
1138 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1139 initialMode = MachinePlaysWhite;
1140 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1141 initialMode = MachinePlaysBlack;
1142 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1143 initialMode = EditGame;
1144 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1145 initialMode = EditPosition;
1146 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1147 initialMode = Training;
1149 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1150 DisplayFatalError(buf, 0, 2);
1154 if (appData.matchMode) {
1155 /* Set up machine vs. machine match */
1156 if (appData.noChessProgram) {
1157 DisplayFatalError(_("Can't have a match with no chess programs"),
1163 if (*appData.loadGameFile != NULLCHAR) {
1164 int index = appData.loadGameIndex; // [HGM] autoinc
1165 if(index<0) lastIndex = index = 1;
1166 if (!LoadGameFromFile(appData.loadGameFile,
1168 appData.loadGameFile, FALSE)) {
1169 DisplayFatalError(_("Bad game file"), 0, 1);
1172 } else if (*appData.loadPositionFile != NULLCHAR) {
1173 int index = appData.loadPositionIndex; // [HGM] autoinc
1174 if(index<0) lastIndex = index = 1;
1175 if (!LoadPositionFromFile(appData.loadPositionFile,
1177 appData.loadPositionFile)) {
1178 DisplayFatalError(_("Bad position file"), 0, 1);
1183 } else if (*appData.cmailGameName != NULLCHAR) {
1184 /* Set up cmail mode */
1185 ReloadCmailMsgEvent(TRUE);
1187 /* Set up other modes */
1188 if (initialMode == AnalyzeFile) {
1189 if (*appData.loadGameFile == NULLCHAR) {
1190 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1194 if (*appData.loadGameFile != NULLCHAR) {
1195 (void) LoadGameFromFile(appData.loadGameFile,
1196 appData.loadGameIndex,
1197 appData.loadGameFile, TRUE);
1198 } else if (*appData.loadPositionFile != NULLCHAR) {
1199 (void) LoadPositionFromFile(appData.loadPositionFile,
1200 appData.loadPositionIndex,
1201 appData.loadPositionFile);
1202 /* [HGM] try to make self-starting even after FEN load */
1203 /* to allow automatic setup of fairy variants with wtm */
1204 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1205 gameMode = BeginningOfGame;
1206 setboardSpoiledMachineBlack = 1;
1208 /* [HGM] loadPos: make that every new game uses the setup */
1209 /* from file as long as we do not switch variant */
1210 if(!blackPlaysFirst) {
1211 startedFromPositionFile = TRUE;
1212 CopyBoard(filePosition, boards[0]);
1215 if (initialMode == AnalyzeMode) {
1216 if (appData.noChessProgram) {
1217 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1220 if (appData.icsActive) {
1221 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1225 } else if (initialMode == AnalyzeFile) {
1226 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1227 ShowThinkingEvent();
1229 AnalysisPeriodicEvent(1);
1230 } else if (initialMode == MachinePlaysWhite) {
1231 if (appData.noChessProgram) {
1232 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1236 if (appData.icsActive) {
1237 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1241 MachineWhiteEvent();
1242 } else if (initialMode == MachinePlaysBlack) {
1243 if (appData.noChessProgram) {
1244 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1248 if (appData.icsActive) {
1249 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1253 MachineBlackEvent();
1254 } else if (initialMode == TwoMachinesPlay) {
1255 if (appData.noChessProgram) {
1256 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1260 if (appData.icsActive) {
1261 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1266 } else if (initialMode == EditGame) {
1268 } else if (initialMode == EditPosition) {
1269 EditPositionEvent();
1270 } else if (initialMode == Training) {
1271 if (*appData.loadGameFile == NULLCHAR) {
1272 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1281 * Establish will establish a contact to a remote host.port.
1282 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1283 * used to talk to the host.
1284 * Returns 0 if okay, error code if not.
1291 if (*appData.icsCommPort != NULLCHAR) {
1292 /* Talk to the host through a serial comm port */
1293 return OpenCommPort(appData.icsCommPort, &icsPR);
1295 } else if (*appData.gateway != NULLCHAR) {
1296 if (*appData.remoteShell == NULLCHAR) {
1297 /* Use the rcmd protocol to run telnet program on a gateway host */
1298 snprintf(buf, sizeof(buf), "%s %s %s",
1299 appData.telnetProgram, appData.icsHost, appData.icsPort);
1300 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1303 /* Use the rsh program to run telnet program on a gateway host */
1304 if (*appData.remoteUser == NULLCHAR) {
1305 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1306 appData.gateway, appData.telnetProgram,
1307 appData.icsHost, appData.icsPort);
1309 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1310 appData.remoteShell, appData.gateway,
1311 appData.remoteUser, appData.telnetProgram,
1312 appData.icsHost, appData.icsPort);
1314 return StartChildProcess(buf, "", &icsPR);
1317 } else if (appData.useTelnet) {
1318 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1321 /* TCP socket interface differs somewhat between
1322 Unix and NT; handle details in the front end.
1324 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1329 show_bytes(fp, buf, count)
1335 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1336 fprintf(fp, "\\%03o", *buf & 0xff);
1345 /* Returns an errno value */
1347 OutputMaybeTelnet(pr, message, count, outError)
1353 char buf[8192], *p, *q, *buflim;
1354 int left, newcount, outcount;
1356 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1357 *appData.gateway != NULLCHAR) {
1358 if (appData.debugMode) {
1359 fprintf(debugFP, ">ICS: ");
1360 show_bytes(debugFP, message, count);
1361 fprintf(debugFP, "\n");
1363 return OutputToProcess(pr, message, count, outError);
1366 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1373 if (appData.debugMode) {
1374 fprintf(debugFP, ">ICS: ");
1375 show_bytes(debugFP, buf, newcount);
1376 fprintf(debugFP, "\n");
1378 outcount = OutputToProcess(pr, buf, newcount, outError);
1379 if (outcount < newcount) return -1; /* to be sure */
1386 } else if (((unsigned char) *p) == TN_IAC) {
1387 *q++ = (char) TN_IAC;
1394 if (appData.debugMode) {
1395 fprintf(debugFP, ">ICS: ");
1396 show_bytes(debugFP, buf, newcount);
1397 fprintf(debugFP, "\n");
1399 outcount = OutputToProcess(pr, buf, newcount, outError);
1400 if (outcount < newcount) return -1; /* to be sure */
1405 read_from_player(isr, closure, message, count, error)
1412 int outError, outCount;
1413 static int gotEof = 0;
1415 /* Pass data read from player on to ICS */
1418 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1419 if (outCount < count) {
1420 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1422 } else if (count < 0) {
1423 RemoveInputSource(isr);
1424 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1425 } else if (gotEof++ > 0) {
1426 RemoveInputSource(isr);
1427 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1433 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1434 SendToICS("date\n");
1435 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1438 /* added routine for printf style output to ics */
1439 void ics_printf(char *format, ...)
1441 char buffer[MSG_SIZ];
1444 va_start(args, format);
1445 vsnprintf(buffer, sizeof(buffer), format, args);
1446 buffer[sizeof(buffer)-1] = '\0';
1455 int count, outCount, outError;
1457 if (icsPR == NULL) return;
1460 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1461 if (outCount < count) {
1462 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1466 /* This is used for sending logon scripts to the ICS. Sending
1467 without a delay causes problems when using timestamp on ICC
1468 (at least on my machine). */
1470 SendToICSDelayed(s,msdelay)
1474 int count, outCount, outError;
1476 if (icsPR == NULL) return;
1479 if (appData.debugMode) {
1480 fprintf(debugFP, ">ICS: ");
1481 show_bytes(debugFP, s, count);
1482 fprintf(debugFP, "\n");
1484 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1486 if (outCount < count) {
1487 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1492 /* Remove all highlighting escape sequences in s
1493 Also deletes any suffix starting with '('
1496 StripHighlightAndTitle(s)
1499 static char retbuf[MSG_SIZ];
1502 while (*s != NULLCHAR) {
1503 while (*s == '\033') {
1504 while (*s != NULLCHAR && !isalpha(*s)) s++;
1505 if (*s != NULLCHAR) s++;
1507 while (*s != NULLCHAR && *s != '\033') {
1508 if (*s == '(' || *s == '[') {
1519 /* Remove all highlighting escape sequences in s */
1524 static char retbuf[MSG_SIZ];
1527 while (*s != NULLCHAR) {
1528 while (*s == '\033') {
1529 while (*s != NULLCHAR && !isalpha(*s)) s++;
1530 if (*s != NULLCHAR) s++;
1532 while (*s != NULLCHAR && *s != '\033') {
1540 char *variantNames[] = VARIANT_NAMES;
1545 return variantNames[v];
1549 /* Identify a variant from the strings the chess servers use or the
1550 PGN Variant tag names we use. */
1557 VariantClass v = VariantNormal;
1558 int i, found = FALSE;
1563 /* [HGM] skip over optional board-size prefixes */
1564 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1565 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1566 while( *e++ != '_');
1569 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1573 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1574 if (StrCaseStr(e, variantNames[i])) {
1575 v = (VariantClass) i;
1582 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1583 || StrCaseStr(e, "wild/fr")
1584 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1585 v = VariantFischeRandom;
1586 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1587 (i = 1, p = StrCaseStr(e, "w"))) {
1589 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1596 case 0: /* FICS only, actually */
1598 /* Castling legal even if K starts on d-file */
1599 v = VariantWildCastle;
1604 /* Castling illegal even if K & R happen to start in
1605 normal positions. */
1606 v = VariantNoCastle;
1619 /* Castling legal iff K & R start in normal positions */
1625 /* Special wilds for position setup; unclear what to do here */
1626 v = VariantLoadable;
1629 /* Bizarre ICC game */
1630 v = VariantTwoKings;
1633 v = VariantKriegspiel;
1639 v = VariantFischeRandom;
1642 v = VariantCrazyhouse;
1645 v = VariantBughouse;
1651 /* Not quite the same as FICS suicide! */
1652 v = VariantGiveaway;
1658 v = VariantShatranj;
1661 /* Temporary names for future ICC types. The name *will* change in
1662 the next xboard/WinBoard release after ICC defines it. */
1700 v = VariantCapablanca;
1703 v = VariantKnightmate;
1709 v = VariantCylinder;
1715 v = VariantCapaRandom;
1718 v = VariantBerolina;
1730 /* Found "wild" or "w" in the string but no number;
1731 must assume it's normal chess. */
1735 sprintf(buf, _("Unknown wild type %d"), wnum);
1736 DisplayError(buf, 0);
1742 if (appData.debugMode) {
1743 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1744 e, wnum, VariantName(v));
1749 static int leftover_start = 0, leftover_len = 0;
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1753 advance *index beyond it, and set leftover_start to the new value of
1754 *index; else return FALSE. If pattern contains the character '*', it
1755 matches any sequence of characters not containing '\r', '\n', or the
1756 character following the '*' (if any), and the matched sequence(s) are
1757 copied into star_match.
1760 looking_at(buf, index, pattern)
1765 char *bufp = &buf[*index], *patternp = pattern;
1767 char *matchp = star_match[0];
1770 if (*patternp == NULLCHAR) {
1771 *index = leftover_start = bufp - buf;
1775 if (*bufp == NULLCHAR) return FALSE;
1776 if (*patternp == '*') {
1777 if (*bufp == *(patternp + 1)) {
1779 matchp = star_match[++star_count];
1783 } else if (*bufp == '\n' || *bufp == '\r') {
1785 if (*patternp == NULLCHAR)
1790 *matchp++ = *bufp++;
1794 if (*patternp != *bufp) return FALSE;
1801 SendToPlayer(data, length)
1805 int error, outCount;
1806 outCount = OutputToProcess(NoProc, data, length, &error);
1807 if (outCount < length) {
1808 DisplayFatalError(_("Error writing to display"), error, 1);
1813 PackHolding(packed, holding)
1825 switch (runlength) {
1836 sprintf(q, "%d", runlength);
1848 /* Telnet protocol requests from the front end */
1850 TelnetRequest(ddww, option)
1851 unsigned char ddww, option;
1853 unsigned char msg[3];
1854 int outCount, outError;
1856 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1858 if (appData.debugMode) {
1859 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1875 sprintf(buf1, "%d", ddww);
1884 sprintf(buf2, "%d", option);
1887 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1892 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1894 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1901 if (!appData.icsActive) return;
1902 TelnetRequest(TN_DO, TN_ECHO);
1908 if (!appData.icsActive) return;
1909 TelnetRequest(TN_DONT, TN_ECHO);
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1915 /* put the holdings sent to us by the server on the board holdings area */
1916 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1920 if(gameInfo.holdingsWidth < 2) return;
1921 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1922 return; // prevent overwriting by pre-board holdings
1924 if( (int)lowestPiece >= BlackPawn ) {
1927 holdingsStartRow = BOARD_HEIGHT-1;
1930 holdingsColumn = BOARD_WIDTH-1;
1931 countsColumn = BOARD_WIDTH-2;
1932 holdingsStartRow = 0;
1936 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1937 board[i][holdingsColumn] = EmptySquare;
1938 board[i][countsColumn] = (ChessSquare) 0;
1940 while( (p=*holdings++) != NULLCHAR ) {
1941 piece = CharToPiece( ToUpper(p) );
1942 if(piece == EmptySquare) continue;
1943 /*j = (int) piece - (int) WhitePawn;*/
1944 j = PieceToNumber(piece);
1945 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1946 if(j < 0) continue; /* should not happen */
1947 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1948 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1949 board[holdingsStartRow+j*direction][countsColumn]++;
1955 VariantSwitch(Board board, VariantClass newVariant)
1957 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1960 startedFromPositionFile = FALSE;
1961 if(gameInfo.variant == newVariant) return;
1963 /* [HGM] This routine is called each time an assignment is made to
1964 * gameInfo.variant during a game, to make sure the board sizes
1965 * are set to match the new variant. If that means adding or deleting
1966 * holdings, we shift the playing board accordingly
1967 * This kludge is needed because in ICS observe mode, we get boards
1968 * of an ongoing game without knowing the variant, and learn about the
1969 * latter only later. This can be because of the move list we requested,
1970 * in which case the game history is refilled from the beginning anyway,
1971 * but also when receiving holdings of a crazyhouse game. In the latter
1972 * case we want to add those holdings to the already received position.
1976 if (appData.debugMode) {
1977 fprintf(debugFP, "Switch board from %s to %s\n",
1978 VariantName(gameInfo.variant), VariantName(newVariant));
1979 setbuf(debugFP, NULL);
1981 shuffleOpenings = 0; /* [HGM] shuffle */
1982 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1986 newWidth = 9; newHeight = 9;
1987 gameInfo.holdingsSize = 7;
1988 case VariantBughouse:
1989 case VariantCrazyhouse:
1990 newHoldingsWidth = 2; break;
1994 newHoldingsWidth = 2;
1995 gameInfo.holdingsSize = 8;
1998 case VariantCapablanca:
1999 case VariantCapaRandom:
2002 newHoldingsWidth = gameInfo.holdingsSize = 0;
2005 if(newWidth != gameInfo.boardWidth ||
2006 newHeight != gameInfo.boardHeight ||
2007 newHoldingsWidth != gameInfo.holdingsWidth ) {
2009 /* shift position to new playing area, if needed */
2010 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2011 for(i=0; i<BOARD_HEIGHT; i++)
2012 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2013 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2015 for(i=0; i<newHeight; i++) {
2016 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2017 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2019 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2020 for(i=0; i<BOARD_HEIGHT; i++)
2021 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2022 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2025 gameInfo.boardWidth = newWidth;
2026 gameInfo.boardHeight = newHeight;
2027 gameInfo.holdingsWidth = newHoldingsWidth;
2028 gameInfo.variant = newVariant;
2029 InitDrawingSizes(-2, 0);
2030 } else gameInfo.variant = newVariant;
2031 CopyBoard(oldBoard, board); // remember correctly formatted board
2032 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2033 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2036 static int loggedOn = FALSE;
2038 /*-- Game start info cache: --*/
2040 char gs_kind[MSG_SIZ];
2041 static char player1Name[128] = "";
2042 static char player2Name[128] = "";
2043 static char cont_seq[] = "\n\\ ";
2044 static int player1Rating = -1;
2045 static int player2Rating = -1;
2046 /*----------------------------*/
2048 ColorClass curColor = ColorNormal;
2049 int suppressKibitz = 0;
2052 read_from_ics(isr, closure, data, count, error)
2059 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2060 #define STARTED_NONE 0
2061 #define STARTED_MOVES 1
2062 #define STARTED_BOARD 2
2063 #define STARTED_OBSERVE 3
2064 #define STARTED_HOLDINGS 4
2065 #define STARTED_CHATTER 5
2066 #define STARTED_COMMENT 6
2067 #define STARTED_MOVES_NOHIDE 7
2069 static int started = STARTED_NONE;
2070 static char parse[20000];
2071 static int parse_pos = 0;
2072 static char buf[BUF_SIZE + 1];
2073 static int firstTime = TRUE, intfSet = FALSE;
2074 static ColorClass prevColor = ColorNormal;
2075 static int savingComment = FALSE;
2076 static int cmatch = 0; // continuation sequence match
2083 int backup; /* [DM] For zippy color lines */
2085 char talker[MSG_SIZ]; // [HGM] chat
2088 if (appData.debugMode) {
2090 fprintf(debugFP, "<ICS: ");
2091 show_bytes(debugFP, data, count);
2092 fprintf(debugFP, "\n");
2096 if (appData.debugMode) { int f = forwardMostMove;
2097 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2098 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2099 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2102 /* If last read ended with a partial line that we couldn't parse,
2103 prepend it to the new read and try again. */
2104 if (leftover_len > 0) {
2105 for (i=0; i<leftover_len; i++)
2106 buf[i] = buf[leftover_start + i];
2109 /* copy new characters into the buffer */
2110 bp = buf + leftover_len;
2111 buf_len=leftover_len;
2112 for (i=0; i<count; i++)
2115 if (data[i] == '\r')
2118 // join lines split by ICS?
2119 if (!appData.noJoin)
2122 Joining just consists of finding matches against the
2123 continuation sequence, and discarding that sequence
2124 if found instead of copying it. So, until a match
2125 fails, there's nothing to do since it might be the
2126 complete sequence, and thus, something we don't want
2129 if (data[i] == cont_seq[cmatch])
2132 if (cmatch == strlen(cont_seq))
2134 cmatch = 0; // complete match. just reset the counter
2137 it's possible for the ICS to not include the space
2138 at the end of the last word, making our [correct]
2139 join operation fuse two separate words. the server
2140 does this when the space occurs at the width setting.
2142 if (!buf_len || buf[buf_len-1] != ' ')
2153 match failed, so we have to copy what matched before
2154 falling through and copying this character. In reality,
2155 this will only ever be just the newline character, but
2156 it doesn't hurt to be precise.
2158 strncpy(bp, cont_seq, cmatch);
2170 buf[buf_len] = NULLCHAR;
2171 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2176 while (i < buf_len) {
2177 /* Deal with part of the TELNET option negotiation
2178 protocol. We refuse to do anything beyond the
2179 defaults, except that we allow the WILL ECHO option,
2180 which ICS uses to turn off password echoing when we are
2181 directly connected to it. We reject this option
2182 if localLineEditing mode is on (always on in xboard)
2183 and we are talking to port 23, which might be a real
2184 telnet server that will try to keep WILL ECHO on permanently.
2186 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2187 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2188 unsigned char option;
2190 switch ((unsigned char) buf[++i]) {
2192 if (appData.debugMode)
2193 fprintf(debugFP, "\n<WILL ");
2194 switch (option = (unsigned char) buf[++i]) {
2196 if (appData.debugMode)
2197 fprintf(debugFP, "ECHO ");
2198 /* Reply only if this is a change, according
2199 to the protocol rules. */
2200 if (remoteEchoOption) break;
2201 if (appData.localLineEditing &&
2202 atoi(appData.icsPort) == TN_PORT) {
2203 TelnetRequest(TN_DONT, TN_ECHO);
2206 TelnetRequest(TN_DO, TN_ECHO);
2207 remoteEchoOption = TRUE;
2211 if (appData.debugMode)
2212 fprintf(debugFP, "%d ", option);
2213 /* Whatever this is, we don't want it. */
2214 TelnetRequest(TN_DONT, option);
2219 if (appData.debugMode)
2220 fprintf(debugFP, "\n<WONT ");
2221 switch (option = (unsigned char) buf[++i]) {
2223 if (appData.debugMode)
2224 fprintf(debugFP, "ECHO ");
2225 /* Reply only if this is a change, according
2226 to the protocol rules. */
2227 if (!remoteEchoOption) break;
2229 TelnetRequest(TN_DONT, TN_ECHO);
2230 remoteEchoOption = FALSE;
2233 if (appData.debugMode)
2234 fprintf(debugFP, "%d ", (unsigned char) option);
2235 /* Whatever this is, it must already be turned
2236 off, because we never agree to turn on
2237 anything non-default, so according to the
2238 protocol rules, we don't reply. */
2243 if (appData.debugMode)
2244 fprintf(debugFP, "\n<DO ");
2245 switch (option = (unsigned char) buf[++i]) {
2247 /* Whatever this is, we refuse to do it. */
2248 if (appData.debugMode)
2249 fprintf(debugFP, "%d ", option);
2250 TelnetRequest(TN_WONT, option);
2255 if (appData.debugMode)
2256 fprintf(debugFP, "\n<DONT ");
2257 switch (option = (unsigned char) buf[++i]) {
2259 if (appData.debugMode)
2260 fprintf(debugFP, "%d ", option);
2261 /* Whatever this is, we are already not doing
2262 it, because we never agree to do anything
2263 non-default, so according to the protocol
2264 rules, we don't reply. */
2269 if (appData.debugMode)
2270 fprintf(debugFP, "\n<IAC ");
2271 /* Doubled IAC; pass it through */
2275 if (appData.debugMode)
2276 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2277 /* Drop all other telnet commands on the floor */
2280 if (oldi > next_out)
2281 SendToPlayer(&buf[next_out], oldi - next_out);
2287 /* OK, this at least will *usually* work */
2288 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2292 if (loggedOn && !intfSet) {
2293 if (ics_type == ICS_ICC) {
2295 "/set-quietly interface %s\n/set-quietly style 12\n",
2297 } else if (ics_type == ICS_CHESSNET) {
2298 sprintf(str, "/style 12\n");
2300 strcpy(str, "alias $ @\n$set interface ");
2301 strcat(str, programVersion);
2302 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2304 strcat(str, "$iset nohighlight 1\n");
2306 strcat(str, "$iset lock 1\n$style 12\n");
2309 NotifyFrontendLogin();
2313 if (started == STARTED_COMMENT) {
2314 /* Accumulate characters in comment */
2315 parse[parse_pos++] = buf[i];
2316 if (buf[i] == '\n') {
2317 parse[parse_pos] = NULLCHAR;
2318 if(chattingPartner>=0) {
2320 sprintf(mess, "%s%s", talker, parse);
2321 OutputChatMessage(chattingPartner, mess);
2322 chattingPartner = -1;
2324 if(!suppressKibitz) // [HGM] kibitz
2325 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2326 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2327 int nrDigit = 0, nrAlph = 0, j;
2328 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2329 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2330 parse[parse_pos] = NULLCHAR;
2331 // try to be smart: if it does not look like search info, it should go to
2332 // ICS interaction window after all, not to engine-output window.
2333 for(j=0; j<parse_pos; j++) { // count letters and digits
2334 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2335 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2336 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2338 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2339 int depth=0; float score;
2340 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2341 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2342 pvInfoList[forwardMostMove-1].depth = depth;
2343 pvInfoList[forwardMostMove-1].score = 100*score;
2345 OutputKibitz(suppressKibitz, parse);
2346 next_out = i+1; // [HGM] suppress printing in ICS window
2349 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2350 SendToPlayer(tmp, strlen(tmp));
2353 started = STARTED_NONE;
2355 /* Don't match patterns against characters in comment */
2360 if (started == STARTED_CHATTER) {
2361 if (buf[i] != '\n') {
2362 /* Don't match patterns against characters in chatter */
2366 started = STARTED_NONE;
2369 /* Kludge to deal with rcmd protocol */
2370 if (firstTime && looking_at(buf, &i, "\001*")) {
2371 DisplayFatalError(&buf[1], 0, 1);
2377 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2380 if (appData.debugMode)
2381 fprintf(debugFP, "ics_type %d\n", ics_type);
2384 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2385 ics_type = ICS_FICS;
2387 if (appData.debugMode)
2388 fprintf(debugFP, "ics_type %d\n", ics_type);
2391 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2392 ics_type = ICS_CHESSNET;
2394 if (appData.debugMode)
2395 fprintf(debugFP, "ics_type %d\n", ics_type);
2400 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2401 looking_at(buf, &i, "Logging you in as \"*\"") ||
2402 looking_at(buf, &i, "will be \"*\""))) {
2403 strcpy(ics_handle, star_match[0]);
2407 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2409 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2410 DisplayIcsInteractionTitle(buf);
2411 have_set_title = TRUE;
2414 /* skip finger notes */
2415 if (started == STARTED_NONE &&
2416 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2417 (buf[i] == '1' && buf[i+1] == '0')) &&
2418 buf[i+2] == ':' && buf[i+3] == ' ') {
2419 started = STARTED_CHATTER;
2424 /* skip formula vars */
2425 if (started == STARTED_NONE &&
2426 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2427 started = STARTED_CHATTER;
2433 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2434 if (appData.autoKibitz && started == STARTED_NONE &&
2435 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2436 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2437 if(looking_at(buf, &i, "* kibitzes: ") &&
2438 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2439 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2440 suppressKibitz = TRUE;
2441 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2442 && (gameMode == IcsPlayingWhite)) ||
2443 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2444 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2445 started = STARTED_CHATTER; // own kibitz we simply discard
2447 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2448 parse_pos = 0; parse[0] = NULLCHAR;
2449 savingComment = TRUE;
2450 suppressKibitz = gameMode != IcsObserving ? 2 :
2451 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2455 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2456 // suppress the acknowledgements of our own autoKibitz
2457 SendToPlayer(star_match[0], strlen(star_match[0]));
2458 looking_at(buf, &i, "*% "); // eat prompt
2461 } // [HGM] kibitz: end of patch
2463 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2465 // [HGM] chat: intercept tells by users for which we have an open chat window
2467 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2468 looking_at(buf, &i, "* whispers:") ||
2469 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2470 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2472 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2473 chattingPartner = -1;
2475 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2476 for(p=0; p<MAX_CHAT; p++) {
2477 if(channel == atoi(chatPartner[p])) {
2478 talker[0] = '['; strcat(talker, "]");
2479 chattingPartner = p; break;
2482 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2483 for(p=0; p<MAX_CHAT; p++) {
2484 if(!strcmp("WHISPER", chatPartner[p])) {
2485 talker[0] = '['; strcat(talker, "]");
2486 chattingPartner = p; break;
2489 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2490 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2492 chattingPartner = p; break;
2494 if(chattingPartner<0) i = oldi; else {
2495 started = STARTED_COMMENT;
2496 parse_pos = 0; parse[0] = NULLCHAR;
2497 savingComment = TRUE;
2498 suppressKibitz = TRUE;
2500 } // [HGM] chat: end of patch
2502 if (appData.zippyTalk || appData.zippyPlay) {
2503 /* [DM] Backup address for color zippy lines */
2507 if (loggedOn == TRUE)
2508 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2509 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2511 if (ZippyControl(buf, &i) ||
2512 ZippyConverse(buf, &i) ||
2513 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2515 if (!appData.colorize) continue;
2519 } // [DM] 'else { ' deleted
2521 /* Regular tells and says */
2522 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2523 looking_at(buf, &i, "* (your partner) tells you: ") ||
2524 looking_at(buf, &i, "* says: ") ||
2525 /* Don't color "message" or "messages" output */
2526 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2527 looking_at(buf, &i, "*. * at *:*: ") ||
2528 looking_at(buf, &i, "--* (*:*): ") ||
2529 /* Message notifications (same color as tells) */
2530 looking_at(buf, &i, "* has left a message ") ||
2531 looking_at(buf, &i, "* just sent you a message:\n") ||
2532 /* Whispers and kibitzes */
2533 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2534 looking_at(buf, &i, "* kibitzes: ") ||
2536 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2538 if (tkind == 1 && strchr(star_match[0], ':')) {
2539 /* Avoid "tells you:" spoofs in channels */
2542 if (star_match[0][0] == NULLCHAR ||
2543 strchr(star_match[0], ' ') ||
2544 (tkind == 3 && strchr(star_match[1], ' '))) {
2545 /* Reject bogus matches */
2548 if (appData.colorize) {
2549 if (oldi > next_out) {
2550 SendToPlayer(&buf[next_out], oldi - next_out);
2555 Colorize(ColorTell, FALSE);
2556 curColor = ColorTell;
2559 Colorize(ColorKibitz, FALSE);
2560 curColor = ColorKibitz;
2563 p = strrchr(star_match[1], '(');
2570 Colorize(ColorChannel1, FALSE);
2571 curColor = ColorChannel1;
2573 Colorize(ColorChannel, FALSE);
2574 curColor = ColorChannel;
2578 curColor = ColorNormal;
2582 if (started == STARTED_NONE && appData.autoComment &&
2583 (gameMode == IcsObserving ||
2584 gameMode == IcsPlayingWhite ||
2585 gameMode == IcsPlayingBlack)) {
2586 parse_pos = i - oldi;
2587 memcpy(parse, &buf[oldi], parse_pos);
2588 parse[parse_pos] = NULLCHAR;
2589 started = STARTED_COMMENT;
2590 savingComment = TRUE;
2592 started = STARTED_CHATTER;
2593 savingComment = FALSE;
2600 if (looking_at(buf, &i, "* s-shouts: ") ||
2601 looking_at(buf, &i, "* c-shouts: ")) {
2602 if (appData.colorize) {
2603 if (oldi > next_out) {
2604 SendToPlayer(&buf[next_out], oldi - next_out);
2607 Colorize(ColorSShout, FALSE);
2608 curColor = ColorSShout;
2611 started = STARTED_CHATTER;
2615 if (looking_at(buf, &i, "--->")) {
2620 if (looking_at(buf, &i, "* shouts: ") ||
2621 looking_at(buf, &i, "--> ")) {
2622 if (appData.colorize) {
2623 if (oldi > next_out) {
2624 SendToPlayer(&buf[next_out], oldi - next_out);
2627 Colorize(ColorShout, FALSE);
2628 curColor = ColorShout;
2631 started = STARTED_CHATTER;
2635 if (looking_at( buf, &i, "Challenge:")) {
2636 if (appData.colorize) {
2637 if (oldi > next_out) {
2638 SendToPlayer(&buf[next_out], oldi - next_out);
2641 Colorize(ColorChallenge, FALSE);
2642 curColor = ColorChallenge;
2648 if (looking_at(buf, &i, "* offers you") ||
2649 looking_at(buf, &i, "* offers to be") ||
2650 looking_at(buf, &i, "* would like to") ||
2651 looking_at(buf, &i, "* requests to") ||
2652 looking_at(buf, &i, "Your opponent offers") ||
2653 looking_at(buf, &i, "Your opponent requests")) {
2655 if (appData.colorize) {
2656 if (oldi > next_out) {
2657 SendToPlayer(&buf[next_out], oldi - next_out);
2660 Colorize(ColorRequest, FALSE);
2661 curColor = ColorRequest;
2666 if (looking_at(buf, &i, "* (*) seeking")) {
2667 if (appData.colorize) {
2668 if (oldi > next_out) {
2669 SendToPlayer(&buf[next_out], oldi - next_out);
2672 Colorize(ColorSeek, FALSE);
2673 curColor = ColorSeek;
2678 if (looking_at(buf, &i, "\\ ")) {
2679 if (prevColor != ColorNormal) {
2680 if (oldi > next_out) {
2681 SendToPlayer(&buf[next_out], oldi - next_out);
2684 Colorize(prevColor, TRUE);
2685 curColor = prevColor;
2687 if (savingComment) {
2688 parse_pos = i - oldi;
2689 memcpy(parse, &buf[oldi], parse_pos);
2690 parse[parse_pos] = NULLCHAR;
2691 started = STARTED_COMMENT;
2693 started = STARTED_CHATTER;
2698 if (looking_at(buf, &i, "Black Strength :") ||
2699 looking_at(buf, &i, "<<< style 10 board >>>") ||
2700 looking_at(buf, &i, "<10>") ||
2701 looking_at(buf, &i, "#@#")) {
2702 /* Wrong board style */
2704 SendToICS(ics_prefix);
2705 SendToICS("set style 12\n");
2706 SendToICS(ics_prefix);
2707 SendToICS("refresh\n");
2711 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2713 have_sent_ICS_logon = 1;
2717 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2718 (looking_at(buf, &i, "\n<12> ") ||
2719 looking_at(buf, &i, "<12> "))) {
2721 if (oldi > next_out) {
2722 SendToPlayer(&buf[next_out], oldi - next_out);
2725 started = STARTED_BOARD;
2730 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2731 looking_at(buf, &i, "<b1> ")) {
2732 if (oldi > next_out) {
2733 SendToPlayer(&buf[next_out], oldi - next_out);
2736 started = STARTED_HOLDINGS;
2741 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2743 /* Header for a move list -- first line */
2745 switch (ics_getting_history) {
2749 case BeginningOfGame:
2750 /* User typed "moves" or "oldmoves" while we
2751 were idle. Pretend we asked for these
2752 moves and soak them up so user can step
2753 through them and/or save them.
2756 gameMode = IcsObserving;
2759 ics_getting_history = H_GOT_UNREQ_HEADER;
2761 case EditGame: /*?*/
2762 case EditPosition: /*?*/
2763 /* Should above feature work in these modes too? */
2764 /* For now it doesn't */
2765 ics_getting_history = H_GOT_UNWANTED_HEADER;
2768 ics_getting_history = H_GOT_UNWANTED_HEADER;
2773 /* Is this the right one? */
2774 if (gameInfo.white && gameInfo.black &&
2775 strcmp(gameInfo.white, star_match[0]) == 0 &&
2776 strcmp(gameInfo.black, star_match[2]) == 0) {
2778 ics_getting_history = H_GOT_REQ_HEADER;
2781 case H_GOT_REQ_HEADER:
2782 case H_GOT_UNREQ_HEADER:
2783 case H_GOT_UNWANTED_HEADER:
2784 case H_GETTING_MOVES:
2785 /* Should not happen */
2786 DisplayError(_("Error gathering move list: two headers"), 0);
2787 ics_getting_history = H_FALSE;
2791 /* Save player ratings into gameInfo if needed */
2792 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2793 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2794 (gameInfo.whiteRating == -1 ||
2795 gameInfo.blackRating == -1)) {
2797 gameInfo.whiteRating = string_to_rating(star_match[1]);
2798 gameInfo.blackRating = string_to_rating(star_match[3]);
2799 if (appData.debugMode)
2800 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2801 gameInfo.whiteRating, gameInfo.blackRating);
2806 if (looking_at(buf, &i,
2807 "* * match, initial time: * minute*, increment: * second")) {
2808 /* Header for a move list -- second line */
2809 /* Initial board will follow if this is a wild game */
2810 if (gameInfo.event != NULL) free(gameInfo.event);
2811 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2812 gameInfo.event = StrSave(str);
2813 /* [HGM] we switched variant. Translate boards if needed. */
2814 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2818 if (looking_at(buf, &i, "Move ")) {
2819 /* Beginning of a move list */
2820 switch (ics_getting_history) {
2822 /* Normally should not happen */
2823 /* Maybe user hit reset while we were parsing */
2826 /* Happens if we are ignoring a move list that is not
2827 * the one we just requested. Common if the user
2828 * tries to observe two games without turning off
2831 case H_GETTING_MOVES:
2832 /* Should not happen */
2833 DisplayError(_("Error gathering move list: nested"), 0);
2834 ics_getting_history = H_FALSE;
2836 case H_GOT_REQ_HEADER:
2837 ics_getting_history = H_GETTING_MOVES;
2838 started = STARTED_MOVES;
2840 if (oldi > next_out) {
2841 SendToPlayer(&buf[next_out], oldi - next_out);
2844 case H_GOT_UNREQ_HEADER:
2845 ics_getting_history = H_GETTING_MOVES;
2846 started = STARTED_MOVES_NOHIDE;
2849 case H_GOT_UNWANTED_HEADER:
2850 ics_getting_history = H_FALSE;
2856 if (looking_at(buf, &i, "% ") ||
2857 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2858 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2859 if(suppressKibitz) next_out = i;
2860 savingComment = FALSE;
2864 case STARTED_MOVES_NOHIDE:
2865 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2866 parse[parse_pos + i - oldi] = NULLCHAR;
2867 ParseGameHistory(parse);
2869 if (appData.zippyPlay && first.initDone) {
2870 FeedMovesToProgram(&first, forwardMostMove);
2871 if (gameMode == IcsPlayingWhite) {
2872 if (WhiteOnMove(forwardMostMove)) {
2873 if (first.sendTime) {
2874 if (first.useColors) {
2875 SendToProgram("black\n", &first);
2877 SendTimeRemaining(&first, TRUE);
2879 if (first.useColors) {
2880 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2882 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2883 first.maybeThinking = TRUE;
2885 if (first.usePlayother) {
2886 if (first.sendTime) {
2887 SendTimeRemaining(&first, TRUE);
2889 SendToProgram("playother\n", &first);
2895 } else if (gameMode == IcsPlayingBlack) {
2896 if (!WhiteOnMove(forwardMostMove)) {
2897 if (first.sendTime) {
2898 if (first.useColors) {
2899 SendToProgram("white\n", &first);
2901 SendTimeRemaining(&first, FALSE);
2903 if (first.useColors) {
2904 SendToProgram("black\n", &first);
2906 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2907 first.maybeThinking = TRUE;
2909 if (first.usePlayother) {
2910 if (first.sendTime) {
2911 SendTimeRemaining(&first, FALSE);
2913 SendToProgram("playother\n", &first);
2922 if (gameMode == IcsObserving && ics_gamenum == -1) {
2923 /* Moves came from oldmoves or moves command
2924 while we weren't doing anything else.
2926 currentMove = forwardMostMove;
2927 ClearHighlights();/*!!could figure this out*/
2928 flipView = appData.flipView;
2929 DrawPosition(TRUE, boards[currentMove]);
2930 DisplayBothClocks();
2931 sprintf(str, "%s vs. %s",
2932 gameInfo.white, gameInfo.black);
2936 /* Moves were history of an active game */
2937 if (gameInfo.resultDetails != NULL) {
2938 free(gameInfo.resultDetails);
2939 gameInfo.resultDetails = NULL;
2942 HistorySet(parseList, backwardMostMove,
2943 forwardMostMove, currentMove-1);
2944 DisplayMove(currentMove - 1);
2945 if (started == STARTED_MOVES) next_out = i;
2946 started = STARTED_NONE;
2947 ics_getting_history = H_FALSE;
2950 case STARTED_OBSERVE:
2951 started = STARTED_NONE;
2952 SendToICS(ics_prefix);
2953 SendToICS("refresh\n");
2959 if(bookHit) { // [HGM] book: simulate book reply
2960 static char bookMove[MSG_SIZ]; // a bit generous?
2962 programStats.nodes = programStats.depth = programStats.time =
2963 programStats.score = programStats.got_only_move = 0;
2964 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2966 strcpy(bookMove, "move ");
2967 strcat(bookMove, bookHit);
2968 HandleMachineMove(bookMove, &first);
2973 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2974 started == STARTED_HOLDINGS ||
2975 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2976 /* Accumulate characters in move list or board */
2977 parse[parse_pos++] = buf[i];
2980 /* Start of game messages. Mostly we detect start of game
2981 when the first board image arrives. On some versions
2982 of the ICS, though, we need to do a "refresh" after starting
2983 to observe in order to get the current board right away. */
2984 if (looking_at(buf, &i, "Adding game * to observation list")) {
2985 started = STARTED_OBSERVE;
2989 /* Handle auto-observe */
2990 if (appData.autoObserve &&
2991 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2992 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2994 /* Choose the player that was highlighted, if any. */
2995 if (star_match[0][0] == '\033' ||
2996 star_match[1][0] != '\033') {
2997 player = star_match[0];
2999 player = star_match[2];
3001 sprintf(str, "%sobserve %s\n",
3002 ics_prefix, StripHighlightAndTitle(player));
3005 /* Save ratings from notify string */
3006 strcpy(player1Name, star_match[0]);
3007 player1Rating = string_to_rating(star_match[1]);
3008 strcpy(player2Name, star_match[2]);
3009 player2Rating = string_to_rating(star_match[3]);
3011 if (appData.debugMode)
3013 "Ratings from 'Game notification:' %s %d, %s %d\n",
3014 player1Name, player1Rating,
3015 player2Name, player2Rating);
3020 /* Deal with automatic examine mode after a game,
3021 and with IcsObserving -> IcsExamining transition */
3022 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3023 looking_at(buf, &i, "has made you an examiner of game *")) {
3025 int gamenum = atoi(star_match[0]);
3026 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3027 gamenum == ics_gamenum) {
3028 /* We were already playing or observing this game;
3029 no need to refetch history */
3030 gameMode = IcsExamining;
3032 pauseExamForwardMostMove = forwardMostMove;
3033 } else if (currentMove < forwardMostMove) {
3034 ForwardInner(forwardMostMove);
3037 /* I don't think this case really can happen */
3038 SendToICS(ics_prefix);
3039 SendToICS("refresh\n");
3044 /* Error messages */
3045 // if (ics_user_moved) {
3046 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3047 if (looking_at(buf, &i, "Illegal move") ||
3048 looking_at(buf, &i, "Not a legal move") ||
3049 looking_at(buf, &i, "Your king is in check") ||
3050 looking_at(buf, &i, "It isn't your turn") ||
3051 looking_at(buf, &i, "It is not your move")) {
3053 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3054 currentMove = --forwardMostMove;
3055 DisplayMove(currentMove - 1); /* before DMError */
3056 DrawPosition(FALSE, boards[currentMove]);
3058 DisplayBothClocks();
3060 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3066 if (looking_at(buf, &i, "still have time") ||
3067 looking_at(buf, &i, "not out of time") ||
3068 looking_at(buf, &i, "either player is out of time") ||
3069 looking_at(buf, &i, "has timeseal; checking")) {
3070 /* We must have called his flag a little too soon */
3071 whiteFlag = blackFlag = FALSE;
3075 if (looking_at(buf, &i, "added * seconds to") ||
3076 looking_at(buf, &i, "seconds were added to")) {
3077 /* Update the clocks */
3078 SendToICS(ics_prefix);
3079 SendToICS("refresh\n");
3083 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3084 ics_clock_paused = TRUE;
3089 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3090 ics_clock_paused = FALSE;
3095 /* Grab player ratings from the Creating: message.
3096 Note we have to check for the special case when
3097 the ICS inserts things like [white] or [black]. */
3098 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3099 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3101 0 player 1 name (not necessarily white)
3103 2 empty, white, or black (IGNORED)
3104 3 player 2 name (not necessarily black)
3107 The names/ratings are sorted out when the game
3108 actually starts (below).
3110 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3111 player1Rating = string_to_rating(star_match[1]);
3112 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3113 player2Rating = string_to_rating(star_match[4]);
3115 if (appData.debugMode)
3117 "Ratings from 'Creating:' %s %d, %s %d\n",
3118 player1Name, player1Rating,
3119 player2Name, player2Rating);
3124 /* Improved generic start/end-of-game messages */
3125 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3126 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3127 /* If tkind == 0: */
3128 /* star_match[0] is the game number */
3129 /* [1] is the white player's name */
3130 /* [2] is the black player's name */
3131 /* For end-of-game: */
3132 /* [3] is the reason for the game end */
3133 /* [4] is a PGN end game-token, preceded by " " */
3134 /* For start-of-game: */
3135 /* [3] begins with "Creating" or "Continuing" */
3136 /* [4] is " *" or empty (don't care). */
3137 int gamenum = atoi(star_match[0]);
3138 char *whitename, *blackname, *why, *endtoken;
3139 ChessMove endtype = (ChessMove) 0;
3142 whitename = star_match[1];
3143 blackname = star_match[2];
3144 why = star_match[3];
3145 endtoken = star_match[4];
3147 whitename = star_match[1];
3148 blackname = star_match[3];
3149 why = star_match[5];
3150 endtoken = star_match[6];
3153 /* Game start messages */
3154 if (strncmp(why, "Creating ", 9) == 0 ||
3155 strncmp(why, "Continuing ", 11) == 0) {
3156 gs_gamenum = gamenum;
3157 strcpy(gs_kind, strchr(why, ' ') + 1);
3158 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3160 if (appData.zippyPlay) {
3161 ZippyGameStart(whitename, blackname);
3167 /* Game end messages */
3168 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3169 ics_gamenum != gamenum) {
3172 while (endtoken[0] == ' ') endtoken++;
3173 switch (endtoken[0]) {
3176 endtype = GameUnfinished;
3179 endtype = BlackWins;
3182 if (endtoken[1] == '/')
3183 endtype = GameIsDrawn;
3185 endtype = WhiteWins;
3188 GameEnds(endtype, why, GE_ICS);
3190 if (appData.zippyPlay && first.initDone) {
3191 ZippyGameEnd(endtype, why);
3192 if (first.pr == NULL) {
3193 /* Start the next process early so that we'll
3194 be ready for the next challenge */
3195 StartChessProgram(&first);
3197 /* Send "new" early, in case this command takes
3198 a long time to finish, so that we'll be ready
3199 for the next challenge. */
3200 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3207 if (looking_at(buf, &i, "Removing game * from observation") ||
3208 looking_at(buf, &i, "no longer observing game *") ||
3209 looking_at(buf, &i, "Game * (*) has no examiners")) {
3210 if (gameMode == IcsObserving &&
3211 atoi(star_match[0]) == ics_gamenum)
3213 /* icsEngineAnalyze */
3214 if (appData.icsEngineAnalyze) {
3221 ics_user_moved = FALSE;
3226 if (looking_at(buf, &i, "no longer examining game *")) {
3227 if (gameMode == IcsExamining &&
3228 atoi(star_match[0]) == ics_gamenum)
3232 ics_user_moved = FALSE;
3237 /* Advance leftover_start past any newlines we find,
3238 so only partial lines can get reparsed */
3239 if (looking_at(buf, &i, "\n")) {
3240 prevColor = curColor;
3241 if (curColor != ColorNormal) {
3242 if (oldi > next_out) {
3243 SendToPlayer(&buf[next_out], oldi - next_out);
3246 Colorize(ColorNormal, FALSE);
3247 curColor = ColorNormal;
3249 if (started == STARTED_BOARD) {
3250 started = STARTED_NONE;
3251 parse[parse_pos] = NULLCHAR;
3252 ParseBoard12(parse);
3255 /* Send premove here */
3256 if (appData.premove) {
3258 if (currentMove == 0 &&
3259 gameMode == IcsPlayingWhite &&
3260 appData.premoveWhite) {
3261 sprintf(str, "%s\n", appData.premoveWhiteText);
3262 if (appData.debugMode)
3263 fprintf(debugFP, "Sending premove:\n");
3265 } else if (currentMove == 1 &&
3266 gameMode == IcsPlayingBlack &&
3267 appData.premoveBlack) {
3268 sprintf(str, "%s\n", appData.premoveBlackText);
3269 if (appData.debugMode)
3270 fprintf(debugFP, "Sending premove:\n");
3272 } else if (gotPremove) {
3274 ClearPremoveHighlights();
3275 if (appData.debugMode)
3276 fprintf(debugFP, "Sending premove:\n");
3277 UserMoveEvent(premoveFromX, premoveFromY,
3278 premoveToX, premoveToY,
3283 /* Usually suppress following prompt */
3284 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3285 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3286 if (looking_at(buf, &i, "*% ")) {
3287 savingComment = FALSE;
3292 } else if (started == STARTED_HOLDINGS) {
3294 char new_piece[MSG_SIZ];
3295 started = STARTED_NONE;
3296 parse[parse_pos] = NULLCHAR;
3297 if (appData.debugMode)
3298 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3299 parse, currentMove);
3300 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3301 gamenum == ics_gamenum) {
3302 if (gameInfo.variant == VariantNormal) {
3303 /* [HGM] We seem to switch variant during a game!
3304 * Presumably no holdings were displayed, so we have
3305 * to move the position two files to the right to
3306 * create room for them!
3308 VariantClass newVariant;
3309 switch(gameInfo.boardWidth) { // base guess on board width
3310 case 9: newVariant = VariantShogi; break;
3311 case 10: newVariant = VariantGreat; break;
3312 default: newVariant = VariantCrazyhouse; break;
3314 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3315 /* Get a move list just to see the header, which
3316 will tell us whether this is really bug or zh */
3317 if (ics_getting_history == H_FALSE) {
3318 ics_getting_history = H_REQUESTED;
3319 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3323 new_piece[0] = NULLCHAR;
3324 sscanf(parse, "game %d white [%s black [%s <- %s",
3325 &gamenum, white_holding, black_holding,
3327 white_holding[strlen(white_holding)-1] = NULLCHAR;
3328 black_holding[strlen(black_holding)-1] = NULLCHAR;
3329 /* [HGM] copy holdings to board holdings area */
3330 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3331 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3332 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3334 if (appData.zippyPlay && first.initDone) {
3335 ZippyHoldings(white_holding, black_holding,
3339 if (tinyLayout || smallLayout) {
3340 char wh[16], bh[16];
3341 PackHolding(wh, white_holding);
3342 PackHolding(bh, black_holding);
3343 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3344 gameInfo.white, gameInfo.black);
3346 sprintf(str, "%s [%s] vs. %s [%s]",
3347 gameInfo.white, white_holding,
3348 gameInfo.black, black_holding);
3351 DrawPosition(FALSE, boards[currentMove]);
3354 /* Suppress following prompt */
3355 if (looking_at(buf, &i, "*% ")) {
3356 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3357 savingComment = FALSE;
3365 i++; /* skip unparsed character and loop back */
3368 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3369 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3370 // SendToPlayer(&buf[next_out], i - next_out);
3371 started != STARTED_HOLDINGS && leftover_start > next_out) {
3372 SendToPlayer(&buf[next_out], leftover_start - next_out);
3376 leftover_len = buf_len - leftover_start;
3377 /* if buffer ends with something we couldn't parse,
3378 reparse it after appending the next read */
3380 } else if (count == 0) {
3381 RemoveInputSource(isr);
3382 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3384 DisplayFatalError(_("Error reading from ICS"), error, 1);
3389 /* Board style 12 looks like this:
3391 <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
3393 * The "<12> " is stripped before it gets to this routine. The two
3394 * trailing 0's (flip state and clock ticking) are later addition, and
3395 * some chess servers may not have them, or may have only the first.
3396 * Additional trailing fields may be added in the future.
3399 #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"
3401 #define RELATION_OBSERVING_PLAYED 0
3402 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3403 #define RELATION_PLAYING_MYMOVE 1
3404 #define RELATION_PLAYING_NOTMYMOVE -1
3405 #define RELATION_EXAMINING 2
3406 #define RELATION_ISOLATED_BOARD -3
3407 #define RELATION_STARTING_POSITION -4 /* FICS only */
3410 ParseBoard12(string)
3413 GameMode newGameMode;
3414 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3415 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3416 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3417 char to_play, board_chars[200];
3418 char move_str[500], str[500], elapsed_time[500];
3419 char black[32], white[32];
3421 int prevMove = currentMove;
3424 int fromX, fromY, toX, toY;
3426 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3427 char *bookHit = NULL; // [HGM] book
3428 Boolean weird = FALSE, reqFlag = FALSE;
3430 fromX = fromY = toX = toY = -1;
3434 if (appData.debugMode)
3435 fprintf(debugFP, _("Parsing board: %s\n"), string);
3437 move_str[0] = NULLCHAR;
3438 elapsed_time[0] = NULLCHAR;
3439 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3441 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3442 if(string[i] == ' ') { ranks++; files = 0; }
3444 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3447 for(j = 0; j <i; j++) board_chars[j] = string[j];
3448 board_chars[i] = '\0';
3451 n = sscanf(string, PATTERN, &to_play, &double_push,
3452 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3453 &gamenum, white, black, &relation, &basetime, &increment,
3454 &white_stren, &black_stren, &white_time, &black_time,
3455 &moveNum, str, elapsed_time, move_str, &ics_flip,
3459 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3460 DisplayError(str, 0);
3464 /* Convert the move number to internal form */
3465 moveNum = (moveNum - 1) * 2;
3466 if (to_play == 'B') moveNum++;
3467 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3468 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3474 case RELATION_OBSERVING_PLAYED:
3475 case RELATION_OBSERVING_STATIC:
3476 if (gamenum == -1) {
3477 /* Old ICC buglet */
3478 relation = RELATION_OBSERVING_STATIC;
3480 newGameMode = IcsObserving;
3482 case RELATION_PLAYING_MYMOVE:
3483 case RELATION_PLAYING_NOTMYMOVE:
3485 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3486 IcsPlayingWhite : IcsPlayingBlack;
3488 case RELATION_EXAMINING:
3489 newGameMode = IcsExamining;
3491 case RELATION_ISOLATED_BOARD:
3493 /* Just display this board. If user was doing something else,
3494 we will forget about it until the next board comes. */
3495 newGameMode = IcsIdle;
3497 case RELATION_STARTING_POSITION:
3498 newGameMode = gameMode;
3502 /* Modify behavior for initial board display on move listing
3505 switch (ics_getting_history) {
3509 case H_GOT_REQ_HEADER:
3510 case H_GOT_UNREQ_HEADER:
3511 /* This is the initial position of the current game */
3512 gamenum = ics_gamenum;
3513 moveNum = 0; /* old ICS bug workaround */
3514 if (to_play == 'B') {
3515 startedFromSetupPosition = TRUE;
3516 blackPlaysFirst = TRUE;
3518 if (forwardMostMove == 0) forwardMostMove = 1;
3519 if (backwardMostMove == 0) backwardMostMove = 1;
3520 if (currentMove == 0) currentMove = 1;
3522 newGameMode = gameMode;
3523 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3525 case H_GOT_UNWANTED_HEADER:
3526 /* This is an initial board that we don't want */
3528 case H_GETTING_MOVES:
3529 /* Should not happen */
3530 DisplayError(_("Error gathering move list: extra board"), 0);
3531 ics_getting_history = H_FALSE;
3535 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3536 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3537 /* [HGM] We seem to have switched variant unexpectedly
3538 * Try to guess new variant from board size
3540 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3541 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3542 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3543 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3544 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3545 if(!weird) newVariant = VariantNormal;
3546 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3547 /* Get a move list just to see the header, which
3548 will tell us whether this is really bug or zh */
3549 if (ics_getting_history == H_FALSE) {
3550 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3551 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3556 /* Take action if this is the first board of a new game, or of a
3557 different game than is currently being displayed. */
3558 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3559 relation == RELATION_ISOLATED_BOARD) {
3561 /* Forget the old game and get the history (if any) of the new one */
3562 if (gameMode != BeginningOfGame) {
3566 if (appData.autoRaiseBoard) BoardToTop();
3568 if (gamenum == -1) {
3569 newGameMode = IcsIdle;
3570 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3571 appData.getMoveList && !reqFlag) {
3572 /* Need to get game history */
3573 ics_getting_history = H_REQUESTED;
3574 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3578 /* Initially flip the board to have black on the bottom if playing
3579 black or if the ICS flip flag is set, but let the user change
3580 it with the Flip View button. */
3581 flipView = appData.autoFlipView ?
3582 (newGameMode == IcsPlayingBlack) || ics_flip :
3585 /* Done with values from previous mode; copy in new ones */
3586 gameMode = newGameMode;
3588 ics_gamenum = gamenum;
3589 if (gamenum == gs_gamenum) {
3590 int klen = strlen(gs_kind);
3591 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3592 sprintf(str, "ICS %s", gs_kind);
3593 gameInfo.event = StrSave(str);
3595 gameInfo.event = StrSave("ICS game");
3597 gameInfo.site = StrSave(appData.icsHost);
3598 gameInfo.date = PGNDate();
3599 gameInfo.round = StrSave("-");
3600 gameInfo.white = StrSave(white);
3601 gameInfo.black = StrSave(black);
3602 timeControl = basetime * 60 * 1000;
3604 timeIncrement = increment * 1000;
3605 movesPerSession = 0;
3606 gameInfo.timeControl = TimeControlTagValue();
3607 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3608 if (appData.debugMode) {
3609 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3610 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3611 setbuf(debugFP, NULL);
3614 gameInfo.outOfBook = NULL;
3616 /* Do we have the ratings? */
3617 if (strcmp(player1Name, white) == 0 &&
3618 strcmp(player2Name, black) == 0) {
3619 if (appData.debugMode)
3620 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3621 player1Rating, player2Rating);
3622 gameInfo.whiteRating = player1Rating;
3623 gameInfo.blackRating = player2Rating;
3624 } else if (strcmp(player2Name, white) == 0 &&
3625 strcmp(player1Name, black) == 0) {
3626 if (appData.debugMode)
3627 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3628 player2Rating, player1Rating);
3629 gameInfo.whiteRating = player2Rating;
3630 gameInfo.blackRating = player1Rating;
3632 player1Name[0] = player2Name[0] = NULLCHAR;
3634 /* Silence shouts if requested */
3635 if (appData.quietPlay &&
3636 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3637 SendToICS(ics_prefix);
3638 SendToICS("set shout 0\n");
3642 /* Deal with midgame name changes */
3644 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3645 if (gameInfo.white) free(gameInfo.white);
3646 gameInfo.white = StrSave(white);
3648 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3649 if (gameInfo.black) free(gameInfo.black);
3650 gameInfo.black = StrSave(black);
3654 /* Throw away game result if anything actually changes in examine mode */
3655 if (gameMode == IcsExamining && !newGame) {
3656 gameInfo.result = GameUnfinished;
3657 if (gameInfo.resultDetails != NULL) {
3658 free(gameInfo.resultDetails);
3659 gameInfo.resultDetails = NULL;
3663 /* In pausing && IcsExamining mode, we ignore boards coming
3664 in if they are in a different variation than we are. */
3665 if (pauseExamInvalid) return;
3666 if (pausing && gameMode == IcsExamining) {
3667 if (moveNum <= pauseExamForwardMostMove) {
3668 pauseExamInvalid = TRUE;
3669 forwardMostMove = pauseExamForwardMostMove;
3674 if (appData.debugMode) {
3675 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3677 /* Parse the board */
3678 for (k = 0; k < ranks; k++) {
3679 for (j = 0; j < files; j++)
3680 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3681 if(gameInfo.holdingsWidth > 1) {
3682 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3683 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3686 CopyBoard(boards[moveNum], board);
3687 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3689 startedFromSetupPosition =
3690 !CompareBoards(board, initialPosition);
3691 if(startedFromSetupPosition)
3692 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3695 /* [HGM] Set castling rights. Take the outermost Rooks,
3696 to make it also work for FRC opening positions. Note that board12
3697 is really defective for later FRC positions, as it has no way to
3698 indicate which Rook can castle if they are on the same side of King.
3699 For the initial position we grant rights to the outermost Rooks,
3700 and remember thos rights, and we then copy them on positions
3701 later in an FRC game. This means WB might not recognize castlings with
3702 Rooks that have moved back to their original position as illegal,
3703 but in ICS mode that is not its job anyway.
3705 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3706 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3708 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3709 if(board[0][i] == WhiteRook) j = i;
3710 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3711 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3712 if(board[0][i] == WhiteRook) j = i;
3713 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3714 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3715 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3716 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3717 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3718 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3719 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3721 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3722 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3723 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3724 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3725 if(board[BOARD_HEIGHT-1][k] == bKing)
3726 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3728 r = boards[moveNum][CASTLING][0] = initialRights[0];
3729 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3730 r = boards[moveNum][CASTLING][1] = initialRights[1];
3731 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3732 r = boards[moveNum][CASTLING][3] = initialRights[3];
3733 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3734 r = boards[moveNum][CASTLING][4] = initialRights[4];
3735 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3736 /* wildcastle kludge: always assume King has rights */
3737 r = boards[moveNum][CASTLING][2] = initialRights[2];
3738 r = boards[moveNum][CASTLING][5] = initialRights[5];
3740 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3741 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3744 if (ics_getting_history == H_GOT_REQ_HEADER ||
3745 ics_getting_history == H_GOT_UNREQ_HEADER) {
3746 /* This was an initial position from a move list, not
3747 the current position */
3751 /* Update currentMove and known move number limits */
3752 newMove = newGame || moveNum > forwardMostMove;
3755 forwardMostMove = backwardMostMove = currentMove = moveNum;
3756 if (gameMode == IcsExamining && moveNum == 0) {
3757 /* Workaround for ICS limitation: we are not told the wild
3758 type when starting to examine a game. But if we ask for
3759 the move list, the move list header will tell us */
3760 ics_getting_history = H_REQUESTED;
3761 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3764 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3765 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3767 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3768 /* [HGM] applied this also to an engine that is silently watching */
3769 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3770 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3771 gameInfo.variant == currentlyInitializedVariant) {
3772 takeback = forwardMostMove - moveNum;
3773 for (i = 0; i < takeback; i++) {
3774 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3775 SendToProgram("undo\n", &first);
3780 forwardMostMove = moveNum;
3781 if (!pausing || currentMove > forwardMostMove)
3782 currentMove = forwardMostMove;
3784 /* New part of history that is not contiguous with old part */
3785 if (pausing && gameMode == IcsExamining) {
3786 pauseExamInvalid = TRUE;
3787 forwardMostMove = pauseExamForwardMostMove;
3790 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3792 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3793 // [HGM] when we will receive the move list we now request, it will be
3794 // fed to the engine from the first move on. So if the engine is not
3795 // in the initial position now, bring it there.
3796 InitChessProgram(&first, 0);
3799 ics_getting_history = H_REQUESTED;
3800 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3803 forwardMostMove = backwardMostMove = currentMove = moveNum;
3806 /* Update the clocks */
3807 if (strchr(elapsed_time, '.')) {
3809 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3810 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3812 /* Time is in seconds */
3813 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3814 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3819 if (appData.zippyPlay && newGame &&
3820 gameMode != IcsObserving && gameMode != IcsIdle &&
3821 gameMode != IcsExamining)
3822 ZippyFirstBoard(moveNum, basetime, increment);
3825 /* Put the move on the move list, first converting
3826 to canonical algebraic form. */
3828 if (appData.debugMode) {
3829 if (appData.debugMode) { int f = forwardMostMove;
3830 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3831 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3832 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3834 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3835 fprintf(debugFP, "moveNum = %d\n", moveNum);
3836 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3837 setbuf(debugFP, NULL);
3839 if (moveNum <= backwardMostMove) {
3840 /* We don't know what the board looked like before
3842 strcpy(parseList[moveNum - 1], move_str);
3843 strcat(parseList[moveNum - 1], " ");
3844 strcat(parseList[moveNum - 1], elapsed_time);
3845 moveList[moveNum - 1][0] = NULLCHAR;
3846 } else if (strcmp(move_str, "none") == 0) {
3847 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3848 /* Again, we don't know what the board looked like;
3849 this is really the start of the game. */
3850 parseList[moveNum - 1][0] = NULLCHAR;
3851 moveList[moveNum - 1][0] = NULLCHAR;
3852 backwardMostMove = moveNum;
3853 startedFromSetupPosition = TRUE;
3854 fromX = fromY = toX = toY = -1;
3856 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3857 // So we parse the long-algebraic move string in stead of the SAN move
3858 int valid; char buf[MSG_SIZ], *prom;
3860 // str looks something like "Q/a1-a2"; kill the slash
3862 sprintf(buf, "%c%s", str[0], str+2);
3863 else strcpy(buf, str); // might be castling
3864 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3865 strcat(buf, prom); // long move lacks promo specification!
3866 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3867 if(appData.debugMode)
3868 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3869 strcpy(move_str, buf);
3871 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3872 &fromX, &fromY, &toX, &toY, &promoChar)
3873 || ParseOneMove(buf, moveNum - 1, &moveType,
3874 &fromX, &fromY, &toX, &toY, &promoChar);
3875 // end of long SAN patch
3877 (void) CoordsToAlgebraic(boards[moveNum - 1],
3878 PosFlags(moveNum - 1),
3879 fromY, fromX, toY, toX, promoChar,
3880 parseList[moveNum-1]);
3881 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3887 if(gameInfo.variant != VariantShogi)
3888 strcat(parseList[moveNum - 1], "+");
3891 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3892 strcat(parseList[moveNum - 1], "#");
3895 strcat(parseList[moveNum - 1], " ");
3896 strcat(parseList[moveNum - 1], elapsed_time);
3897 /* currentMoveString is set as a side-effect of ParseOneMove */
3898 strcpy(moveList[moveNum - 1], currentMoveString);
3899 strcat(moveList[moveNum - 1], "\n");
3901 /* Move from ICS was illegal!? Punt. */
3902 if (appData.debugMode) {
3903 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3904 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3906 strcpy(parseList[moveNum - 1], move_str);
3907 strcat(parseList[moveNum - 1], " ");
3908 strcat(parseList[moveNum - 1], elapsed_time);
3909 moveList[moveNum - 1][0] = NULLCHAR;
3910 fromX = fromY = toX = toY = -1;
3913 if (appData.debugMode) {
3914 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3915 setbuf(debugFP, NULL);
3919 /* Send move to chess program (BEFORE animating it). */
3920 if (appData.zippyPlay && !newGame && newMove &&
3921 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3923 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3924 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3925 if (moveList[moveNum - 1][0] == NULLCHAR) {
3926 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3928 DisplayError(str, 0);
3930 if (first.sendTime) {
3931 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3933 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3934 if (firstMove && !bookHit) {
3936 if (first.useColors) {
3937 SendToProgram(gameMode == IcsPlayingWhite ?
3939 "black\ngo\n", &first);
3941 SendToProgram("go\n", &first);
3943 first.maybeThinking = TRUE;
3946 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3947 if (moveList[moveNum - 1][0] == NULLCHAR) {
3948 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3949 DisplayError(str, 0);
3951 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3952 SendMoveToProgram(moveNum - 1, &first);
3959 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3960 /* If move comes from a remote source, animate it. If it
3961 isn't remote, it will have already been animated. */
3962 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3963 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3965 if (!pausing && appData.highlightLastMove) {
3966 SetHighlights(fromX, fromY, toX, toY);
3970 /* Start the clocks */
3971 whiteFlag = blackFlag = FALSE;
3972 appData.clockMode = !(basetime == 0 && increment == 0);
3974 ics_clock_paused = TRUE;
3976 } else if (ticking == 1) {
3977 ics_clock_paused = FALSE;
3979 if (gameMode == IcsIdle ||
3980 relation == RELATION_OBSERVING_STATIC ||
3981 relation == RELATION_EXAMINING ||
3983 DisplayBothClocks();
3987 /* Display opponents and material strengths */
3988 if (gameInfo.variant != VariantBughouse &&
3989 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3990 if (tinyLayout || smallLayout) {
3991 if(gameInfo.variant == VariantNormal)
3992 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3993 gameInfo.white, white_stren, gameInfo.black, black_stren,
3994 basetime, increment);
3996 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3997 gameInfo.white, white_stren, gameInfo.black, black_stren,
3998 basetime, increment, (int) gameInfo.variant);
4000 if(gameInfo.variant == VariantNormal)
4001 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4002 gameInfo.white, white_stren, gameInfo.black, black_stren,
4003 basetime, increment);
4005 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4006 gameInfo.white, white_stren, gameInfo.black, black_stren,
4007 basetime, increment, VariantName(gameInfo.variant));
4010 if (appData.debugMode) {
4011 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4016 /* Display the board */
4017 if (!pausing && !appData.noGUI) {
4019 if (appData.premove)
4021 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4022 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4023 ClearPremoveHighlights();
4025 DrawPosition(FALSE, boards[currentMove]);
4026 DisplayMove(moveNum - 1);
4027 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4028 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4029 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4030 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4034 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4036 if(bookHit) { // [HGM] book: simulate book reply
4037 static char bookMove[MSG_SIZ]; // a bit generous?
4039 programStats.nodes = programStats.depth = programStats.time =
4040 programStats.score = programStats.got_only_move = 0;
4041 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4043 strcpy(bookMove, "move ");
4044 strcat(bookMove, bookHit);
4045 HandleMachineMove(bookMove, &first);
4054 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4055 ics_getting_history = H_REQUESTED;
4056 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4062 AnalysisPeriodicEvent(force)
4065 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4066 && !force) || !appData.periodicUpdates)
4069 /* Send . command to Crafty to collect stats */
4070 SendToProgram(".\n", &first);
4072 /* Don't send another until we get a response (this makes
4073 us stop sending to old Crafty's which don't understand
4074 the "." command (sending illegal cmds resets node count & time,
4075 which looks bad)) */
4076 programStats.ok_to_send = 0;
4079 void ics_update_width(new_width)
4082 ics_printf("set width %d\n", new_width);
4086 SendMoveToProgram(moveNum, cps)
4088 ChessProgramState *cps;
4092 if (cps->useUsermove) {
4093 SendToProgram("usermove ", cps);
4097 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4098 int len = space - parseList[moveNum];
4099 memcpy(buf, parseList[moveNum], len);
4101 buf[len] = NULLCHAR;
4103 sprintf(buf, "%s\n", parseList[moveNum]);
4105 SendToProgram(buf, cps);
4107 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4108 AlphaRank(moveList[moveNum], 4);
4109 SendToProgram(moveList[moveNum], cps);
4110 AlphaRank(moveList[moveNum], 4); // and back
4112 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4113 * the engine. It would be nice to have a better way to identify castle
4115 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4116 && cps->useOOCastle) {
4117 int fromX = moveList[moveNum][0] - AAA;
4118 int fromY = moveList[moveNum][1] - ONE;
4119 int toX = moveList[moveNum][2] - AAA;
4120 int toY = moveList[moveNum][3] - ONE;
4121 if((boards[moveNum][fromY][fromX] == WhiteKing
4122 && boards[moveNum][toY][toX] == WhiteRook)
4123 || (boards[moveNum][fromY][fromX] == BlackKing
4124 && boards[moveNum][toY][toX] == BlackRook)) {
4125 if(toX > fromX) SendToProgram("O-O\n", cps);
4126 else SendToProgram("O-O-O\n", cps);
4128 else SendToProgram(moveList[moveNum], cps);
4130 else SendToProgram(moveList[moveNum], cps);
4131 /* End of additions by Tord */
4134 /* [HGM] setting up the opening has brought engine in force mode! */
4135 /* Send 'go' if we are in a mode where machine should play. */
4136 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4137 (gameMode == TwoMachinesPlay ||
4139 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4141 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4142 SendToProgram("go\n", cps);
4143 if (appData.debugMode) {
4144 fprintf(debugFP, "(extra)\n");
4147 setboardSpoiledMachineBlack = 0;
4151 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4153 int fromX, fromY, toX, toY;
4155 char user_move[MSG_SIZ];
4159 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4160 (int)moveType, fromX, fromY, toX, toY);
4161 DisplayError(user_move + strlen("say "), 0);
4163 case WhiteKingSideCastle:
4164 case BlackKingSideCastle:
4165 case WhiteQueenSideCastleWild:
4166 case BlackQueenSideCastleWild:
4168 case WhiteHSideCastleFR:
4169 case BlackHSideCastleFR:
4171 sprintf(user_move, "o-o\n");
4173 case WhiteQueenSideCastle:
4174 case BlackQueenSideCastle:
4175 case WhiteKingSideCastleWild:
4176 case BlackKingSideCastleWild:
4178 case WhiteASideCastleFR:
4179 case BlackASideCastleFR:
4181 sprintf(user_move, "o-o-o\n");
4183 case WhitePromotionQueen:
4184 case BlackPromotionQueen:
4185 case WhitePromotionRook:
4186 case BlackPromotionRook:
4187 case WhitePromotionBishop:
4188 case BlackPromotionBishop:
4189 case WhitePromotionKnight:
4190 case BlackPromotionKnight:
4191 case WhitePromotionKing:
4192 case BlackPromotionKing:
4193 case WhitePromotionChancellor:
4194 case BlackPromotionChancellor:
4195 case WhitePromotionArchbishop:
4196 case BlackPromotionArchbishop:
4197 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4198 sprintf(user_move, "%c%c%c%c=%c\n",
4199 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4200 PieceToChar(WhiteFerz));
4201 else if(gameInfo.variant == VariantGreat)
4202 sprintf(user_move, "%c%c%c%c=%c\n",
4203 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4204 PieceToChar(WhiteMan));
4206 sprintf(user_move, "%c%c%c%c=%c\n",
4207 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4208 PieceToChar(PromoPiece(moveType)));
4212 sprintf(user_move, "%c@%c%c\n",
4213 ToUpper(PieceToChar((ChessSquare) fromX)),
4214 AAA + toX, ONE + toY);
4217 case WhiteCapturesEnPassant:
4218 case BlackCapturesEnPassant:
4219 case IllegalMove: /* could be a variant we don't quite understand */
4220 sprintf(user_move, "%c%c%c%c\n",
4221 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4224 SendToICS(user_move);
4225 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4226 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4230 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4235 if (rf == DROP_RANK) {
4236 sprintf(move, "%c@%c%c\n",
4237 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4239 if (promoChar == 'x' || promoChar == NULLCHAR) {
4240 sprintf(move, "%c%c%c%c\n",
4241 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4243 sprintf(move, "%c%c%c%c%c\n",
4244 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4250 ProcessICSInitScript(f)
4255 while (fgets(buf, MSG_SIZ, f)) {
4256 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4263 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4265 AlphaRank(char *move, int n)
4267 // char *p = move, c; int x, y;
4269 if (appData.debugMode) {
4270 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4274 move[2]>='0' && move[2]<='9' &&
4275 move[3]>='a' && move[3]<='x' ) {
4277 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4278 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4280 if(move[0]>='0' && move[0]<='9' &&
4281 move[1]>='a' && move[1]<='x' &&
4282 move[2]>='0' && move[2]<='9' &&
4283 move[3]>='a' && move[3]<='x' ) {
4284 /* input move, Shogi -> normal */
4285 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4286 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4287 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4288 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4291 move[3]>='0' && move[3]<='9' &&
4292 move[2]>='a' && move[2]<='x' ) {
4294 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4295 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4298 move[0]>='a' && move[0]<='x' &&
4299 move[3]>='0' && move[3]<='9' &&
4300 move[2]>='a' && move[2]<='x' ) {
4301 /* output move, normal -> Shogi */
4302 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4303 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4304 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4305 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4306 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4308 if (appData.debugMode) {
4309 fprintf(debugFP, " out = '%s'\n", move);
4313 /* Parser for moves from gnuchess, ICS, or user typein box */
4315 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4318 ChessMove *moveType;
4319 int *fromX, *fromY, *toX, *toY;
4322 if (appData.debugMode) {
4323 fprintf(debugFP, "move to parse: %s\n", move);
4325 *moveType = yylexstr(moveNum, move);
4327 switch (*moveType) {
4328 case WhitePromotionChancellor:
4329 case BlackPromotionChancellor:
4330 case WhitePromotionArchbishop:
4331 case BlackPromotionArchbishop:
4332 case WhitePromotionQueen:
4333 case BlackPromotionQueen:
4334 case WhitePromotionRook:
4335 case BlackPromotionRook:
4336 case WhitePromotionBishop:
4337 case BlackPromotionBishop:
4338 case WhitePromotionKnight:
4339 case BlackPromotionKnight:
4340 case WhitePromotionKing:
4341 case BlackPromotionKing:
4343 case WhiteCapturesEnPassant:
4344 case BlackCapturesEnPassant:
4345 case WhiteKingSideCastle:
4346 case WhiteQueenSideCastle:
4347 case BlackKingSideCastle:
4348 case BlackQueenSideCastle:
4349 case WhiteKingSideCastleWild:
4350 case WhiteQueenSideCastleWild:
4351 case BlackKingSideCastleWild:
4352 case BlackQueenSideCastleWild:
4353 /* Code added by Tord: */
4354 case WhiteHSideCastleFR:
4355 case WhiteASideCastleFR:
4356 case BlackHSideCastleFR:
4357 case BlackASideCastleFR:
4358 /* End of code added by Tord */
4359 case IllegalMove: /* bug or odd chess variant */
4360 *fromX = currentMoveString[0] - AAA;
4361 *fromY = currentMoveString[1] - ONE;
4362 *toX = currentMoveString[2] - AAA;
4363 *toY = currentMoveString[3] - ONE;
4364 *promoChar = currentMoveString[4];
4365 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4366 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4367 if (appData.debugMode) {
4368 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4370 *fromX = *fromY = *toX = *toY = 0;
4373 if (appData.testLegality) {
4374 return (*moveType != IllegalMove);
4376 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4377 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4382 *fromX = *moveType == WhiteDrop ?
4383 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4384 (int) CharToPiece(ToLower(currentMoveString[0]));
4386 *toX = currentMoveString[2] - AAA;
4387 *toY = currentMoveString[3] - ONE;
4388 *promoChar = NULLCHAR;
4392 case ImpossibleMove:
4393 case (ChessMove) 0: /* end of file */
4402 if (appData.debugMode) {
4403 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4406 *fromX = *fromY = *toX = *toY = 0;
4407 *promoChar = NULLCHAR;
4415 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4416 int fromX, fromY, toX, toY; char promoChar;
4421 endPV = forwardMostMove;
4423 while(*pv == ' ') pv++;
4424 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4425 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4426 if(appData.debugMode){
4427 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4429 if(!valid && nr == 0 &&
4430 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4431 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4433 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4434 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4436 if(endPV+1 > framePtr) break; // no space, truncate
4439 CopyBoard(boards[endPV], boards[endPV-1]);
4440 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4441 moveList[endPV-1][0] = fromX + AAA;
4442 moveList[endPV-1][1] = fromY + ONE;
4443 moveList[endPV-1][2] = toX + AAA;
4444 moveList[endPV-1][3] = toY + ONE;
4445 parseList[endPV-1][0] = NULLCHAR;
4447 currentMove = endPV;
4448 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4449 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4450 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4451 DrawPosition(TRUE, boards[currentMove]);
4454 static int lastX, lastY;
4457 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4461 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4462 lastX = x; lastY = y;
4463 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4465 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4467 while(buf[index] && buf[index] != '\n') index++;
4469 ParsePV(buf+startPV);
4470 *start = startPV; *end = index-1;
4475 LoadPV(int x, int y)
4476 { // called on right mouse click to load PV
4477 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4478 lastX = x; lastY = y;
4479 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4486 if(endPV < 0) return;
4488 currentMove = forwardMostMove;
4489 ClearPremoveHighlights();
4490 DrawPosition(TRUE, boards[currentMove]);
4494 MovePV(int x, int y, int h)
4495 { // step through PV based on mouse coordinates (called on mouse move)
4496 int margin = h>>3, step = 0;
4498 if(endPV < 0) return;
4499 // we must somehow check if right button is still down (might be released off board!)
4500 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4501 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4502 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4504 lastX = x; lastY = y;
4505 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4506 currentMove += step;
4507 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4508 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4509 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4510 DrawPosition(FALSE, boards[currentMove]);
4514 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4515 // All positions will have equal probability, but the current method will not provide a unique
4516 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4522 int piecesLeft[(int)BlackPawn];
4523 int seed, nrOfShuffles;
4525 void GetPositionNumber()
4526 { // sets global variable seed
4529 seed = appData.defaultFrcPosition;
4530 if(seed < 0) { // randomize based on time for negative FRC position numbers
4531 for(i=0; i<50; i++) seed += random();
4532 seed = random() ^ random() >> 8 ^ random() << 8;
4533 if(seed<0) seed = -seed;
4537 int put(Board board, int pieceType, int rank, int n, int shade)
4538 // put the piece on the (n-1)-th empty squares of the given shade
4542 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4543 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4544 board[rank][i] = (ChessSquare) pieceType;
4545 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4547 piecesLeft[pieceType]--;
4555 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4556 // calculate where the next piece goes, (any empty square), and put it there
4560 i = seed % squaresLeft[shade];
4561 nrOfShuffles *= squaresLeft[shade];
4562 seed /= squaresLeft[shade];
4563 put(board, pieceType, rank, i, shade);
4566 void AddTwoPieces(Board board, int pieceType, int rank)
4567 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4569 int i, n=squaresLeft[ANY], j=n-1, k;
4571 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4572 i = seed % k; // pick one
4575 while(i >= j) i -= j--;
4576 j = n - 1 - j; i += j;
4577 put(board, pieceType, rank, j, ANY);
4578 put(board, pieceType, rank, i, ANY);
4581 void SetUpShuffle(Board board, int number)
4585 GetPositionNumber(); nrOfShuffles = 1;
4587 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4588 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4589 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4591 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4593 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4594 p = (int) board[0][i];
4595 if(p < (int) BlackPawn) piecesLeft[p] ++;
4596 board[0][i] = EmptySquare;
4599 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4600 // shuffles restricted to allow normal castling put KRR first
4601 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4602 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4603 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4604 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4605 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4606 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4607 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4608 put(board, WhiteRook, 0, 0, ANY);
4609 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4612 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4613 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4614 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4615 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4616 while(piecesLeft[p] >= 2) {
4617 AddOnePiece(board, p, 0, LITE);
4618 AddOnePiece(board, p, 0, DARK);
4620 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4623 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4624 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4625 // but we leave King and Rooks for last, to possibly obey FRC restriction
4626 if(p == (int)WhiteRook) continue;
4627 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4628 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4631 // now everything is placed, except perhaps King (Unicorn) and Rooks
4633 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4634 // Last King gets castling rights
4635 while(piecesLeft[(int)WhiteUnicorn]) {
4636 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4637 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4640 while(piecesLeft[(int)WhiteKing]) {
4641 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4642 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4647 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4648 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4651 // Only Rooks can be left; simply place them all
4652 while(piecesLeft[(int)WhiteRook]) {
4653 i = put(board, WhiteRook, 0, 0, ANY);
4654 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4657 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4659 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4662 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4663 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4666 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4669 int SetCharTable( char *table, const char * map )
4670 /* [HGM] moved here from winboard.c because of its general usefulness */
4671 /* Basically a safe strcpy that uses the last character as King */
4673 int result = FALSE; int NrPieces;
4675 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4676 && NrPieces >= 12 && !(NrPieces&1)) {
4677 int i; /* [HGM] Accept even length from 12 to 34 */
4679 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4680 for( i=0; i<NrPieces/2-1; i++ ) {
4682 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4684 table[(int) WhiteKing] = map[NrPieces/2-1];
4685 table[(int) BlackKing] = map[NrPieces-1];
4693 void Prelude(Board board)
4694 { // [HGM] superchess: random selection of exo-pieces
4695 int i, j, k; ChessSquare p;
4696 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4698 GetPositionNumber(); // use FRC position number
4700 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4701 SetCharTable(pieceToChar, appData.pieceToCharTable);
4702 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4703 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4706 j = seed%4; seed /= 4;
4707 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4708 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4709 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4710 j = seed%3 + (seed%3 >= j); seed /= 3;
4711 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4712 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4713 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4714 j = seed%3; seed /= 3;
4715 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4716 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4717 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4718 j = seed%2 + (seed%2 >= j); seed /= 2;
4719 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4720 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4721 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4722 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4723 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4724 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4725 put(board, exoPieces[0], 0, 0, ANY);
4726 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4730 InitPosition(redraw)
4733 ChessSquare (* pieces)[BOARD_FILES];
4734 int i, j, pawnRow, overrule,
4735 oldx = gameInfo.boardWidth,
4736 oldy = gameInfo.boardHeight,
4737 oldh = gameInfo.holdingsWidth,
4738 oldv = gameInfo.variant;
4740 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4742 /* [AS] Initialize pv info list [HGM] and game status */
4744 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4745 pvInfoList[i].depth = 0;
4746 boards[i][EP_STATUS] = EP_NONE;
4747 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4750 initialRulePlies = 0; /* 50-move counter start */
4752 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4753 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4757 /* [HGM] logic here is completely changed. In stead of full positions */
4758 /* the initialized data only consist of the two backranks. The switch */
4759 /* selects which one we will use, which is than copied to the Board */
4760 /* initialPosition, which for the rest is initialized by Pawns and */
4761 /* empty squares. This initial position is then copied to boards[0], */
4762 /* possibly after shuffling, so that it remains available. */
4764 gameInfo.holdingsWidth = 0; /* default board sizes */
4765 gameInfo.boardWidth = 8;
4766 gameInfo.boardHeight = 8;
4767 gameInfo.holdingsSize = 0;
4768 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4769 for(i=0; i<BOARD_FILES-2; i++)
4770 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4771 initialPosition[EP_STATUS] = EP_NONE;
4772 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4774 switch (gameInfo.variant) {
4775 case VariantFischeRandom:
4776 shuffleOpenings = TRUE;
4780 case VariantShatranj:
4781 pieces = ShatranjArray;
4782 nrCastlingRights = 0;
4783 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4785 case VariantTwoKings:
4786 pieces = twoKingsArray;
4788 case VariantCapaRandom:
4789 shuffleOpenings = TRUE;
4790 case VariantCapablanca:
4791 pieces = CapablancaArray;
4792 gameInfo.boardWidth = 10;
4793 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4796 pieces = GothicArray;
4797 gameInfo.boardWidth = 10;
4798 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4801 pieces = JanusArray;
4802 gameInfo.boardWidth = 10;
4803 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4804 nrCastlingRights = 6;
4805 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4806 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4807 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4808 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4809 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4810 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4813 pieces = FalconArray;
4814 gameInfo.boardWidth = 10;
4815 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4817 case VariantXiangqi:
4818 pieces = XiangqiArray;
4819 gameInfo.boardWidth = 9;
4820 gameInfo.boardHeight = 10;
4821 nrCastlingRights = 0;
4822 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4825 pieces = ShogiArray;
4826 gameInfo.boardWidth = 9;
4827 gameInfo.boardHeight = 9;
4828 gameInfo.holdingsSize = 7;
4829 nrCastlingRights = 0;
4830 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4832 case VariantCourier:
4833 pieces = CourierArray;
4834 gameInfo.boardWidth = 12;
4835 nrCastlingRights = 0;
4836 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4838 case VariantKnightmate:
4839 pieces = KnightmateArray;
4840 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4843 pieces = fairyArray;
4844 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4847 pieces = GreatArray;
4848 gameInfo.boardWidth = 10;
4849 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4850 gameInfo.holdingsSize = 8;
4854 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4855 gameInfo.holdingsSize = 8;
4856 startedFromSetupPosition = TRUE;
4858 case VariantCrazyhouse:
4859 case VariantBughouse:
4861 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4862 gameInfo.holdingsSize = 5;
4864 case VariantWildCastle:
4866 /* !!?shuffle with kings guaranteed to be on d or e file */
4867 shuffleOpenings = 1;
4869 case VariantNoCastle:
4871 nrCastlingRights = 0;
4872 /* !!?unconstrained back-rank shuffle */
4873 shuffleOpenings = 1;
4878 if(appData.NrFiles >= 0) {
4879 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4880 gameInfo.boardWidth = appData.NrFiles;
4882 if(appData.NrRanks >= 0) {
4883 gameInfo.boardHeight = appData.NrRanks;
4885 if(appData.holdingsSize >= 0) {
4886 i = appData.holdingsSize;
4887 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4888 gameInfo.holdingsSize = i;
4890 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4891 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4892 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4894 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4895 if(pawnRow < 1) pawnRow = 1;
4897 /* User pieceToChar list overrules defaults */
4898 if(appData.pieceToCharTable != NULL)
4899 SetCharTable(pieceToChar, appData.pieceToCharTable);
4901 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4903 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4904 s = (ChessSquare) 0; /* account holding counts in guard band */
4905 for( i=0; i<BOARD_HEIGHT; i++ )
4906 initialPosition[i][j] = s;
4908 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4909 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4910 initialPosition[pawnRow][j] = WhitePawn;
4911 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4912 if(gameInfo.variant == VariantXiangqi) {
4914 initialPosition[pawnRow][j] =
4915 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4916 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4917 initialPosition[2][j] = WhiteCannon;
4918 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4922 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4924 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4927 initialPosition[1][j] = WhiteBishop;
4928 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4930 initialPosition[1][j] = WhiteRook;
4931 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4934 if( nrCastlingRights == -1) {
4935 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4936 /* This sets default castling rights from none to normal corners */
4937 /* Variants with other castling rights must set them themselves above */
4938 nrCastlingRights = 6;
4940 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4941 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4942 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4943 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4944 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4945 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4948 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4949 if(gameInfo.variant == VariantGreat) { // promotion commoners
4950 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4951 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4952 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4953 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4955 if (appData.debugMode) {
4956 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4958 if(shuffleOpenings) {
4959 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4960 startedFromSetupPosition = TRUE;
4962 if(startedFromPositionFile) {
4963 /* [HGM] loadPos: use PositionFile for every new game */
4964 CopyBoard(initialPosition, filePosition);
4965 for(i=0; i<nrCastlingRights; i++)
4966 initialRights[i] = filePosition[CASTLING][i];
4967 startedFromSetupPosition = TRUE;
4970 CopyBoard(boards[0], initialPosition);
4972 if(oldx != gameInfo.boardWidth ||
4973 oldy != gameInfo.boardHeight ||
4974 oldh != gameInfo.holdingsWidth
4976 || oldv == VariantGothic || // For licensing popups
4977 gameInfo.variant == VariantGothic
4980 || oldv == VariantFalcon ||
4981 gameInfo.variant == VariantFalcon
4984 InitDrawingSizes(-2 ,0);
4987 DrawPosition(TRUE, boards[currentMove]);
4991 SendBoard(cps, moveNum)
4992 ChessProgramState *cps;
4995 char message[MSG_SIZ];
4997 if (cps->useSetboard) {
4998 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4999 sprintf(message, "setboard %s\n", fen);
5000 SendToProgram(message, cps);
5006 /* Kludge to set black to move, avoiding the troublesome and now
5007 * deprecated "black" command.
5009 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5011 SendToProgram("edit\n", cps);
5012 SendToProgram("#\n", cps);
5013 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5014 bp = &boards[moveNum][i][BOARD_LEFT];
5015 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5016 if ((int) *bp < (int) BlackPawn) {
5017 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5019 if(message[0] == '+' || message[0] == '~') {
5020 sprintf(message, "%c%c%c+\n",
5021 PieceToChar((ChessSquare)(DEMOTED *bp)),
5024 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5025 message[1] = BOARD_RGHT - 1 - j + '1';
5026 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5028 SendToProgram(message, cps);
5033 SendToProgram("c\n", cps);
5034 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5035 bp = &boards[moveNum][i][BOARD_LEFT];
5036 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5037 if (((int) *bp != (int) EmptySquare)
5038 && ((int) *bp >= (int) BlackPawn)) {
5039 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5041 if(message[0] == '+' || message[0] == '~') {
5042 sprintf(message, "%c%c%c+\n",
5043 PieceToChar((ChessSquare)(DEMOTED *bp)),
5046 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5047 message[1] = BOARD_RGHT - 1 - j + '1';
5048 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5050 SendToProgram(message, cps);
5055 SendToProgram(".\n", cps);
5057 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5061 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5063 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5064 /* [HGM] add Shogi promotions */
5065 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5070 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5071 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5073 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5074 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5077 piece = boards[currentMove][fromY][fromX];
5078 if(gameInfo.variant == VariantShogi) {
5079 promotionZoneSize = 3;
5080 highestPromotingPiece = (int)WhiteFerz;
5083 // next weed out all moves that do not touch the promotion zone at all
5084 if((int)piece >= BlackPawn) {
5085 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5087 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5089 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5090 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5093 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5095 // weed out mandatory Shogi promotions
5096 if(gameInfo.variant == VariantShogi) {
5097 if(piece >= BlackPawn) {
5098 if(toY == 0 && piece == BlackPawn ||
5099 toY == 0 && piece == BlackQueen ||
5100 toY <= 1 && piece == BlackKnight) {
5105 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5106 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5107 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5114 // weed out obviously illegal Pawn moves
5115 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5116 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5117 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5118 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5119 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5120 // note we are not allowed to test for valid (non-)capture, due to premove
5123 // we either have a choice what to promote to, or (in Shogi) whether to promote
5124 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5125 *promoChoice = PieceToChar(BlackFerz); // no choice
5128 if(appData.alwaysPromoteToQueen) { // predetermined
5129 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5130 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5131 else *promoChoice = PieceToChar(BlackQueen);
5135 // suppress promotion popup on illegal moves that are not premoves
5136 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5137 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5138 if(appData.testLegality && !premove) {
5139 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5140 fromY, fromX, toY, toX, NULLCHAR);
5141 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5142 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5150 InPalace(row, column)
5152 { /* [HGM] for Xiangqi */
5153 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5154 column < (BOARD_WIDTH + 4)/2 &&
5155 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5160 PieceForSquare (x, y)
5164 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5167 return boards[currentMove][y][x];
5171 OKToStartUserMove(x, y)
5174 ChessSquare from_piece;
5177 if (matchMode) return FALSE;
5178 if (gameMode == EditPosition) return TRUE;
5180 if (x >= 0 && y >= 0)
5181 from_piece = boards[currentMove][y][x];
5183 from_piece = EmptySquare;
5185 if (from_piece == EmptySquare) return FALSE;
5187 white_piece = (int)from_piece >= (int)WhitePawn &&
5188 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5191 case PlayFromGameFile:
5193 case TwoMachinesPlay:
5201 case MachinePlaysWhite:
5202 case IcsPlayingBlack:
5203 if (appData.zippyPlay) return FALSE;
5205 DisplayMoveError(_("You are playing Black"));
5210 case MachinePlaysBlack:
5211 case IcsPlayingWhite:
5212 if (appData.zippyPlay) return FALSE;
5214 DisplayMoveError(_("You are playing White"));
5220 if (!white_piece && WhiteOnMove(currentMove)) {
5221 DisplayMoveError(_("It is White's turn"));
5224 if (white_piece && !WhiteOnMove(currentMove)) {
5225 DisplayMoveError(_("It is Black's turn"));
5228 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5229 /* Editing correspondence game history */
5230 /* Could disallow this or prompt for confirmation */
5235 case BeginningOfGame:
5236 if (appData.icsActive) return FALSE;
5237 if (!appData.noChessProgram) {
5239 DisplayMoveError(_("You are playing White"));
5246 if (!white_piece && WhiteOnMove(currentMove)) {
5247 DisplayMoveError(_("It is White's turn"));
5250 if (white_piece && !WhiteOnMove(currentMove)) {
5251 DisplayMoveError(_("It is Black's turn"));
5260 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5261 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5262 && gameMode != AnalyzeFile && gameMode != Training) {
5263 DisplayMoveError(_("Displayed position is not current"));
5269 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5270 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5271 int lastLoadGameUseList = FALSE;
5272 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5273 ChessMove lastLoadGameStart = (ChessMove) 0;
5276 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5277 int fromX, fromY, toX, toY;
5282 ChessSquare pdown, pup;
5284 /* Check if the user is playing in turn. This is complicated because we
5285 let the user "pick up" a piece before it is his turn. So the piece he
5286 tried to pick up may have been captured by the time he puts it down!
5287 Therefore we use the color the user is supposed to be playing in this
5288 test, not the color of the piece that is currently on the starting
5289 square---except in EditGame mode, where the user is playing both
5290 sides; fortunately there the capture race can't happen. (It can
5291 now happen in IcsExamining mode, but that's just too bad. The user
5292 will get a somewhat confusing message in that case.)
5296 case PlayFromGameFile:
5298 case TwoMachinesPlay:
5302 /* We switched into a game mode where moves are not accepted,
5303 perhaps while the mouse button was down. */
5304 return ImpossibleMove;
5306 case MachinePlaysWhite:
5307 /* User is moving for Black */
5308 if (WhiteOnMove(currentMove)) {
5309 DisplayMoveError(_("It is White's turn"));
5310 return ImpossibleMove;
5314 case MachinePlaysBlack:
5315 /* User is moving for White */
5316 if (!WhiteOnMove(currentMove)) {
5317 DisplayMoveError(_("It is Black's turn"));
5318 return ImpossibleMove;
5324 case BeginningOfGame:
5327 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5328 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5329 /* User is moving for Black */
5330 if (WhiteOnMove(currentMove)) {
5331 DisplayMoveError(_("It is White's turn"));
5332 return ImpossibleMove;
5335 /* User is moving for White */
5336 if (!WhiteOnMove(currentMove)) {
5337 DisplayMoveError(_("It is Black's turn"));
5338 return ImpossibleMove;
5343 case IcsPlayingBlack:
5344 /* User is moving for Black */
5345 if (WhiteOnMove(currentMove)) {
5346 if (!appData.premove) {
5347 DisplayMoveError(_("It is White's turn"));
5348 } else if (toX >= 0 && toY >= 0) {
5351 premoveFromX = fromX;
5352 premoveFromY = fromY;
5353 premovePromoChar = promoChar;
5355 if (appData.debugMode)
5356 fprintf(debugFP, "Got premove: fromX %d,"
5357 "fromY %d, toX %d, toY %d\n",
5358 fromX, fromY, toX, toY);
5360 return ImpossibleMove;
5364 case IcsPlayingWhite:
5365 /* User is moving for White */
5366 if (!WhiteOnMove(currentMove)) {
5367 if (!appData.premove) {
5368 DisplayMoveError(_("It is Black's turn"));
5369 } else if (toX >= 0 && toY >= 0) {
5372 premoveFromX = fromX;
5373 premoveFromY = fromY;
5374 premovePromoChar = promoChar;
5376 if (appData.debugMode)
5377 fprintf(debugFP, "Got premove: fromX %d,"
5378 "fromY %d, toX %d, toY %d\n",
5379 fromX, fromY, toX, toY);
5381 return ImpossibleMove;
5389 /* EditPosition, empty square, or different color piece;
5390 click-click move is possible */
5391 if (toX == -2 || toY == -2) {
5392 boards[0][fromY][fromX] = EmptySquare;
5393 return AmbiguousMove;
5394 } else if (toX >= 0 && toY >= 0) {
5395 boards[0][toY][toX] = boards[0][fromY][fromX];
5396 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5397 if(boards[0][fromY][0] != EmptySquare) {
5398 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5399 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5402 if(fromX == BOARD_RGHT+1) {
5403 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5404 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5405 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5408 boards[0][fromY][fromX] = EmptySquare;
5409 return AmbiguousMove;
5411 return ImpossibleMove;
5414 if(toX < 0 || toY < 0) return ImpossibleMove;
5415 pdown = boards[currentMove][fromY][fromX];
5416 pup = boards[currentMove][toY][toX];
5418 /* [HGM] If move started in holdings, it means a drop */
5419 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5420 if( pup != EmptySquare ) return ImpossibleMove;
5421 if(appData.testLegality) {
5422 /* it would be more logical if LegalityTest() also figured out
5423 * which drops are legal. For now we forbid pawns on back rank.
5424 * Shogi is on its own here...
5426 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5427 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5428 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5430 return WhiteDrop; /* Not needed to specify white or black yet */
5433 userOfferedDraw = FALSE;
5435 /* [HGM] always test for legality, to get promotion info */
5436 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5437 fromY, fromX, toY, toX, promoChar);
5438 /* [HGM] but possibly ignore an IllegalMove result */
5439 if (appData.testLegality) {
5440 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5441 DisplayMoveError(_("Illegal move"));
5442 return ImpossibleMove;
5447 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5448 function is made into one that returns an OK move type if FinishMove
5449 should be called. This to give the calling driver routine the
5450 opportunity to finish the userMove input with a promotion popup,
5451 without bothering the user with this for invalid or illegal moves */
5453 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5456 /* Common tail of UserMoveEvent and DropMenuEvent */
5458 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5460 int fromX, fromY, toX, toY;
5461 /*char*/int promoChar;
5465 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5466 // [HGM] superchess: suppress promotions to non-available piece
5467 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5468 if(WhiteOnMove(currentMove)) {
5469 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5471 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5475 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5476 move type in caller when we know the move is a legal promotion */
5477 if(moveType == NormalMove && promoChar)
5478 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5480 /* [HGM] convert drag-and-drop piece drops to standard form */
5481 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5482 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5483 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5484 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5485 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5486 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5487 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5488 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5492 /* [HGM] <popupFix> The following if has been moved here from
5493 UserMoveEvent(). Because it seemed to belong here (why not allow
5494 piece drops in training games?), and because it can only be
5495 performed after it is known to what we promote. */
5496 if (gameMode == Training) {
5497 /* compare the move played on the board to the next move in the
5498 * game. If they match, display the move and the opponent's response.
5499 * If they don't match, display an error message.
5503 CopyBoard(testBoard, boards[currentMove]);
5504 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5506 if (CompareBoards(testBoard, boards[currentMove+1])) {
5507 ForwardInner(currentMove+1);
5509 /* Autoplay the opponent's response.
5510 * if appData.animate was TRUE when Training mode was entered,
5511 * the response will be animated.
5513 saveAnimate = appData.animate;
5514 appData.animate = animateTraining;
5515 ForwardInner(currentMove+1);
5516 appData.animate = saveAnimate;
5518 /* check for the end of the game */
5519 if (currentMove >= forwardMostMove) {
5520 gameMode = PlayFromGameFile;
5522 SetTrainingModeOff();
5523 DisplayInformation(_("End of game"));
5526 DisplayError(_("Incorrect move"), 0);
5531 /* Ok, now we know that the move is good, so we can kill
5532 the previous line in Analysis Mode */
5533 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5534 && currentMove < forwardMostMove) {
5535 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5538 /* If we need the chess program but it's dead, restart it */
5539 ResurrectChessProgram();
5541 /* A user move restarts a paused game*/
5545 thinkOutput[0] = NULLCHAR;
5547 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5549 if (gameMode == BeginningOfGame) {
5550 if (appData.noChessProgram) {
5551 gameMode = EditGame;
5555 gameMode = MachinePlaysBlack;
5558 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5560 if (first.sendName) {
5561 sprintf(buf, "name %s\n", gameInfo.white);
5562 SendToProgram(buf, &first);
5569 /* Relay move to ICS or chess engine */
5570 if (appData.icsActive) {
5571 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5572 gameMode == IcsExamining) {
5573 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5577 if (first.sendTime && (gameMode == BeginningOfGame ||
5578 gameMode == MachinePlaysWhite ||
5579 gameMode == MachinePlaysBlack)) {
5580 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5582 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5583 // [HGM] book: if program might be playing, let it use book
5584 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5585 first.maybeThinking = TRUE;
5586 } else SendMoveToProgram(forwardMostMove-1, &first);
5587 if (currentMove == cmailOldMove + 1) {
5588 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5592 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5596 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5602 if (WhiteOnMove(currentMove)) {
5603 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5605 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5609 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5614 case MachinePlaysBlack:
5615 case MachinePlaysWhite:
5616 /* disable certain menu options while machine is thinking */
5617 SetMachineThinkingEnables();
5624 if(bookHit) { // [HGM] book: simulate book reply
5625 static char bookMove[MSG_SIZ]; // a bit generous?
5627 programStats.nodes = programStats.depth = programStats.time =
5628 programStats.score = programStats.got_only_move = 0;
5629 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5631 strcpy(bookMove, "move ");
5632 strcat(bookMove, bookHit);
5633 HandleMachineMove(bookMove, &first);
5639 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5640 int fromX, fromY, toX, toY;
5643 /* [HGM] This routine was added to allow calling of its two logical
5644 parts from other modules in the old way. Before, UserMoveEvent()
5645 automatically called FinishMove() if the move was OK, and returned
5646 otherwise. I separated the two, in order to make it possible to
5647 slip a promotion popup in between. But that it always needs two
5648 calls, to the first part, (now called UserMoveTest() ), and to
5649 FinishMove if the first part succeeded. Calls that do not need
5650 to do anything in between, can call this routine the old way.
5652 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5653 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5654 if(moveType == AmbiguousMove)
5655 DrawPosition(FALSE, boards[currentMove]);
5656 else if(moveType != ImpossibleMove && moveType != Comment)
5657 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5661 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5668 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5669 Markers *m = (Markers *) closure;
5670 if(rf == fromY && ff == fromX)
5671 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5672 || kind == WhiteCapturesEnPassant
5673 || kind == BlackCapturesEnPassant);
5674 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5678 MarkTargetSquares(int clear)
5681 if(!appData.markers || !appData.highlightDragging ||
5682 !appData.testLegality || gameMode == EditPosition) return;
5684 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5687 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5688 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5689 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5691 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5694 DrawPosition(TRUE, NULL);
5697 void LeftClick(ClickType clickType, int xPix, int yPix)
5700 Boolean saveAnimate;
5701 static int second = 0, promotionChoice = 0;
5702 char promoChoice = NULLCHAR;
5704 if (clickType == Press) ErrorPopDown();
5705 MarkTargetSquares(1);
5707 x = EventToSquare(xPix, BOARD_WIDTH);
5708 y = EventToSquare(yPix, BOARD_HEIGHT);
5709 if (!flipView && y >= 0) {
5710 y = BOARD_HEIGHT - 1 - y;
5712 if (flipView && x >= 0) {
5713 x = BOARD_WIDTH - 1 - x;
5716 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5717 if(clickType == Release) return; // ignore upclick of click-click destination
5718 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5719 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5720 if(gameInfo.holdingsWidth &&
5721 (WhiteOnMove(currentMove)
5722 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5723 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5724 // click in right holdings, for determining promotion piece
5725 ChessSquare p = boards[currentMove][y][x];
5726 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5727 if(p != EmptySquare) {
5728 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5733 DrawPosition(FALSE, boards[currentMove]);
5737 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5738 if(clickType == Press
5739 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5740 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5741 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5745 if (clickType == Press) {
5747 if (OKToStartUserMove(x, y)) {
5751 MarkTargetSquares(0);
5752 DragPieceBegin(xPix, yPix);
5753 if (appData.highlightDragging) {
5754 SetHighlights(x, y, -1, -1);
5762 if (clickType == Press && gameMode != EditPosition) {
5767 // ignore off-board to clicks
5768 if(y < 0 || x < 0) return;
5770 /* Check if clicking again on the same color piece */
5771 fromP = boards[currentMove][fromY][fromX];
5772 toP = boards[currentMove][y][x];
5773 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5774 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5775 WhitePawn <= toP && toP <= WhiteKing &&
5776 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5777 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5778 (BlackPawn <= fromP && fromP <= BlackKing &&
5779 BlackPawn <= toP && toP <= BlackKing &&
5780 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5781 !(fromP == BlackKing && toP == BlackRook && frc))) {
5782 /* Clicked again on same color piece -- changed his mind */
5783 second = (x == fromX && y == fromY);
5784 if (appData.highlightDragging) {
5785 SetHighlights(x, y, -1, -1);
5789 if (OKToStartUserMove(x, y)) {
5792 MarkTargetSquares(0);
5793 DragPieceBegin(xPix, yPix);
5797 // ignore clicks on holdings
5798 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5801 if (clickType == Release && x == fromX && y == fromY) {
5802 DragPieceEnd(xPix, yPix);
5803 if (appData.animateDragging) {
5804 /* Undo animation damage if any */
5805 DrawPosition(FALSE, NULL);
5808 /* Second up/down in same square; just abort move */
5813 ClearPremoveHighlights();
5815 /* First upclick in same square; start click-click mode */
5816 SetHighlights(x, y, -1, -1);
5821 /* we now have a different from- and (possibly off-board) to-square */
5822 /* Completed move */
5825 saveAnimate = appData.animate;
5826 if (clickType == Press) {
5827 /* Finish clickclick move */
5828 if (appData.animate || appData.highlightLastMove) {
5829 SetHighlights(fromX, fromY, toX, toY);
5834 /* Finish drag move */
5835 if (appData.highlightLastMove) {
5836 SetHighlights(fromX, fromY, toX, toY);
5840 DragPieceEnd(xPix, yPix);
5841 /* Don't animate move and drag both */
5842 appData.animate = FALSE;
5845 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5846 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5847 ChessSquare piece = boards[currentMove][fromY][fromX];
5848 if(gameMode == EditPosition && piece != EmptySquare &&
5849 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5852 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5853 n = PieceToNumber(piece - (int)BlackPawn);
5854 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5855 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5856 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5858 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5859 n = PieceToNumber(piece);
5860 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5861 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5862 boards[currentMove][n][BOARD_WIDTH-2]++;
5864 boards[currentMove][fromY][fromX] = EmptySquare;
5868 DrawPosition(TRUE, boards[currentMove]);
5872 // off-board moves should not be highlighted
5873 if(x < 0 || x < 0) ClearHighlights();
5875 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5876 SetHighlights(fromX, fromY, toX, toY);
5877 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5878 // [HGM] super: promotion to captured piece selected from holdings
5879 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5880 promotionChoice = TRUE;
5881 // kludge follows to temporarily execute move on display, without promoting yet
5882 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5883 boards[currentMove][toY][toX] = p;
5884 DrawPosition(FALSE, boards[currentMove]);
5885 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5886 boards[currentMove][toY][toX] = q;
5887 DisplayMessage("Click in holdings to choose piece", "");
5892 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5893 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5894 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5897 appData.animate = saveAnimate;
5898 if (appData.animate || appData.animateDragging) {
5899 /* Undo animation damage if needed */
5900 DrawPosition(FALSE, NULL);
5904 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5906 // char * hint = lastHint;
5907 FrontEndProgramStats stats;
5909 stats.which = cps == &first ? 0 : 1;
5910 stats.depth = cpstats->depth;
5911 stats.nodes = cpstats->nodes;
5912 stats.score = cpstats->score;
5913 stats.time = cpstats->time;
5914 stats.pv = cpstats->movelist;
5915 stats.hint = lastHint;
5916 stats.an_move_index = 0;
5917 stats.an_move_count = 0;
5919 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5920 stats.hint = cpstats->move_name;
5921 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5922 stats.an_move_count = cpstats->nr_moves;
5925 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5927 SetProgramStats( &stats );
5930 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5931 { // [HGM] book: this routine intercepts moves to simulate book replies
5932 char *bookHit = NULL;
5934 //first determine if the incoming move brings opponent into his book
5935 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5936 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5937 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5938 if(bookHit != NULL && !cps->bookSuspend) {
5939 // make sure opponent is not going to reply after receiving move to book position
5940 SendToProgram("force\n", cps);
5941 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5943 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5944 // now arrange restart after book miss
5946 // after a book hit we never send 'go', and the code after the call to this routine
5947 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5949 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5950 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5951 SendToProgram(buf, cps);
5952 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5953 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5954 SendToProgram("go\n", cps);
5955 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5956 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5957 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5958 SendToProgram("go\n", cps);
5959 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5961 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5965 ChessProgramState *savedState;
5966 void DeferredBookMove(void)
5968 if(savedState->lastPing != savedState->lastPong)
5969 ScheduleDelayedEvent(DeferredBookMove, 10);
5971 HandleMachineMove(savedMessage, savedState);
5975 HandleMachineMove(message, cps)
5977 ChessProgramState *cps;
5979 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5980 char realname[MSG_SIZ];
5981 int fromX, fromY, toX, toY;
5990 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5992 * Kludge to ignore BEL characters
5994 while (*message == '\007') message++;
5997 * [HGM] engine debug message: ignore lines starting with '#' character
5999 if(cps->debug && *message == '#') return;
6002 * Look for book output
6004 if (cps == &first && bookRequested) {
6005 if (message[0] == '\t' || message[0] == ' ') {
6006 /* Part of the book output is here; append it */
6007 strcat(bookOutput, message);
6008 strcat(bookOutput, " \n");
6010 } else if (bookOutput[0] != NULLCHAR) {
6011 /* All of book output has arrived; display it */
6012 char *p = bookOutput;
6013 while (*p != NULLCHAR) {
6014 if (*p == '\t') *p = ' ';
6017 DisplayInformation(bookOutput);
6018 bookRequested = FALSE;
6019 /* Fall through to parse the current output */
6024 * Look for machine move.
6026 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6027 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6029 /* This method is only useful on engines that support ping */
6030 if (cps->lastPing != cps->lastPong) {
6031 if (gameMode == BeginningOfGame) {
6032 /* Extra move from before last new; ignore */
6033 if (appData.debugMode) {
6034 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6037 if (appData.debugMode) {
6038 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6039 cps->which, gameMode);
6042 SendToProgram("undo\n", cps);
6048 case BeginningOfGame:
6049 /* Extra move from before last reset; ignore */
6050 if (appData.debugMode) {
6051 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6058 /* Extra move after we tried to stop. The mode test is
6059 not a reliable way of detecting this problem, but it's
6060 the best we can do on engines that don't support ping.
6062 if (appData.debugMode) {
6063 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6064 cps->which, gameMode);
6066 SendToProgram("undo\n", cps);
6069 case MachinePlaysWhite:
6070 case IcsPlayingWhite:
6071 machineWhite = TRUE;
6074 case MachinePlaysBlack:
6075 case IcsPlayingBlack:
6076 machineWhite = FALSE;
6079 case TwoMachinesPlay:
6080 machineWhite = (cps->twoMachinesColor[0] == 'w');
6083 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6084 if (appData.debugMode) {
6086 "Ignoring move out of turn by %s, gameMode %d"
6087 ", forwardMost %d\n",
6088 cps->which, gameMode, forwardMostMove);
6093 if (appData.debugMode) { int f = forwardMostMove;
6094 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6095 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6096 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6098 if(cps->alphaRank) AlphaRank(machineMove, 4);
6099 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6100 &fromX, &fromY, &toX, &toY, &promoChar)) {
6101 /* Machine move could not be parsed; ignore it. */
6102 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6103 machineMove, cps->which);
6104 DisplayError(buf1, 0);
6105 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6106 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6107 if (gameMode == TwoMachinesPlay) {
6108 GameEnds(machineWhite ? BlackWins : WhiteWins,
6114 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6115 /* So we have to redo legality test with true e.p. status here, */
6116 /* to make sure an illegal e.p. capture does not slip through, */
6117 /* to cause a forfeit on a justified illegal-move complaint */
6118 /* of the opponent. */
6119 if( gameMode==TwoMachinesPlay && appData.testLegality
6120 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6123 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6124 fromY, fromX, toY, toX, promoChar);
6125 if (appData.debugMode) {
6127 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6128 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6129 fprintf(debugFP, "castling rights\n");
6131 if(moveType == IllegalMove) {
6132 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6133 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6134 GameEnds(machineWhite ? BlackWins : WhiteWins,
6137 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6138 /* [HGM] Kludge to handle engines that send FRC-style castling
6139 when they shouldn't (like TSCP-Gothic) */
6141 case WhiteASideCastleFR:
6142 case BlackASideCastleFR:
6144 currentMoveString[2]++;
6146 case WhiteHSideCastleFR:
6147 case BlackHSideCastleFR:
6149 currentMoveString[2]--;
6151 default: ; // nothing to do, but suppresses warning of pedantic compilers
6154 hintRequested = FALSE;
6155 lastHint[0] = NULLCHAR;
6156 bookRequested = FALSE;
6157 /* Program may be pondering now */
6158 cps->maybeThinking = TRUE;
6159 if (cps->sendTime == 2) cps->sendTime = 1;
6160 if (cps->offeredDraw) cps->offeredDraw--;
6163 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6165 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6167 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6168 char buf[3*MSG_SIZ];
6170 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6171 programStats.score / 100.,
6173 programStats.time / 100.,
6174 (unsigned int)programStats.nodes,
6175 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6176 programStats.movelist);
6178 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6182 /* currentMoveString is set as a side-effect of ParseOneMove */
6183 strcpy(machineMove, currentMoveString);
6184 strcat(machineMove, "\n");
6185 strcpy(moveList[forwardMostMove], machineMove);
6187 /* [AS] Save move info and clear stats for next move */
6188 pvInfoList[ forwardMostMove ].score = programStats.score;
6189 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6190 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6191 ClearProgramStats();
6192 thinkOutput[0] = NULLCHAR;
6193 hiddenThinkOutputState = 0;
6195 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6197 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6198 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6201 while( count < adjudicateLossPlies ) {
6202 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6205 score = -score; /* Flip score for winning side */
6208 if( score > adjudicateLossThreshold ) {
6215 if( count >= adjudicateLossPlies ) {
6216 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6218 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6219 "Xboard adjudication",
6226 if( gameMode == TwoMachinesPlay ) {
6227 // [HGM] some adjudications useful with buggy engines
6228 int k, count = 0; static int bare = 1;
6229 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6232 if( appData.testLegality )
6233 { /* [HGM] Some more adjudications for obstinate engines */
6234 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6235 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6236 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6237 static int moveCount = 6;
6239 char *reason = NULL;
6241 /* Count what is on board. */
6242 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6243 { ChessSquare p = boards[forwardMostMove][i][j];
6247 { /* count B,N,R and other of each side */
6250 NrK++; break; // [HGM] atomic: count Kings
6254 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6255 bishopsColor |= 1 << ((i^j)&1);
6260 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6261 bishopsColor |= 1 << ((i^j)&1);
6276 PawnAdvance += m; NrPawns++;
6278 NrPieces += (p != EmptySquare);
6279 NrW += ((int)p < (int)BlackPawn);
6280 if(gameInfo.variant == VariantXiangqi &&
6281 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6282 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6283 NrW -= ((int)p < (int)BlackPawn);
6287 /* Some material-based adjudications that have to be made before stalemate test */
6288 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6289 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6290 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6291 if(appData.checkMates) {
6292 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6293 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6294 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6295 "Xboard adjudication: King destroyed", GE_XBOARD );
6300 /* Bare King in Shatranj (loses) or Losers (wins) */
6301 if( NrW == 1 || NrPieces - NrW == 1) {
6302 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6303 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6304 if(appData.checkMates) {
6305 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6306 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6307 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6308 "Xboard adjudication: Bare king", GE_XBOARD );
6312 if( gameInfo.variant == VariantShatranj && --bare < 0)
6314 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6315 if(appData.checkMates) {
6316 /* but only adjudicate if adjudication enabled */
6317 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6318 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6319 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6320 "Xboard adjudication: Bare king", GE_XBOARD );
6327 // don't wait for engine to announce game end if we can judge ourselves
6328 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6330 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6331 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6332 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6333 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6336 reason = "Xboard adjudication: 3rd check";
6337 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6347 reason = "Xboard adjudication: Stalemate";
6348 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6349 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6350 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6351 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6352 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6353 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6354 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6355 EP_CHECKMATE : EP_WINS);
6356 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6357 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6361 reason = "Xboard adjudication: Checkmate";
6362 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6366 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6368 result = GameIsDrawn; break;
6370 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6372 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6374 result = (ChessMove) 0;
6376 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6377 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6378 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6379 GameEnds( result, reason, GE_XBOARD );
6383 /* Next absolutely insufficient mating material. */
6384 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6385 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6386 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6387 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6388 { /* KBK, KNK, KK of KBKB with like Bishops */
6390 /* always flag draws, for judging claims */
6391 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6393 if(appData.materialDraws) {
6394 /* but only adjudicate them if adjudication enabled */
6395 SendToProgram("force\n", cps->other); // suppress reply
6396 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6397 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6398 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6403 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6405 ( NrWR == 1 && NrBR == 1 /* KRKR */
6406 || NrWQ==1 && NrBQ==1 /* KQKQ */
6407 || NrWN==2 || NrBN==2 /* KNNK */
6408 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6410 if(--moveCount < 0 && appData.trivialDraws)
6411 { /* if the first 3 moves do not show a tactical win, declare draw */
6412 SendToProgram("force\n", cps->other); // suppress reply
6413 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6414 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6415 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6418 } else moveCount = 6;
6422 if (appData.debugMode) { int i;
6423 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6424 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6425 appData.drawRepeats);
6426 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6427 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6431 /* Check for rep-draws */
6433 for(k = forwardMostMove-2;
6434 k>=backwardMostMove && k>=forwardMostMove-100 &&
6435 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6436 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6439 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6440 /* compare castling rights */
6441 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6442 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6443 rights++; /* King lost rights, while rook still had them */
6444 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6445 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6446 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6447 rights++; /* but at least one rook lost them */
6449 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6450 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6452 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6453 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6454 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6457 if( rights == 0 && ++count > appData.drawRepeats-2
6458 && appData.drawRepeats > 1) {
6459 /* adjudicate after user-specified nr of repeats */
6460 SendToProgram("force\n", cps->other); // suppress reply
6461 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6462 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6463 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6464 // [HGM] xiangqi: check for forbidden perpetuals
6465 int m, ourPerpetual = 1, hisPerpetual = 1;
6466 for(m=forwardMostMove; m>k; m-=2) {
6467 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6468 ourPerpetual = 0; // the current mover did not always check
6469 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6470 hisPerpetual = 0; // the opponent did not always check
6472 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6473 ourPerpetual, hisPerpetual);
6474 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6475 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6476 "Xboard adjudication: perpetual checking", GE_XBOARD );
6479 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6480 break; // (or we would have caught him before). Abort repetition-checking loop.
6481 // Now check for perpetual chases
6482 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6483 hisPerpetual = PerpetualChase(k, forwardMostMove);
6484 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6485 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6486 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6487 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6490 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6491 break; // Abort repetition-checking loop.
6493 // if neither of us is checking or chasing all the time, or both are, it is draw
6495 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6498 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6499 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6503 /* Now we test for 50-move draws. Determine ply count */
6504 count = forwardMostMove;
6505 /* look for last irreversble move */
6506 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6508 /* if we hit starting position, add initial plies */
6509 if( count == backwardMostMove )
6510 count -= initialRulePlies;
6511 count = forwardMostMove - count;
6513 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6514 /* this is used to judge if draw claims are legal */
6515 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6516 SendToProgram("force\n", cps->other); // suppress reply
6517 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6518 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6519 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6523 /* if draw offer is pending, treat it as a draw claim
6524 * when draw condition present, to allow engines a way to
6525 * claim draws before making their move to avoid a race
6526 * condition occurring after their move
6528 if( cps->other->offeredDraw || cps->offeredDraw ) {
6530 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6531 p = "Draw claim: 50-move rule";
6532 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6533 p = "Draw claim: 3-fold repetition";
6534 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6535 p = "Draw claim: insufficient mating material";
6537 SendToProgram("force\n", cps->other); // suppress reply
6538 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6539 GameEnds( GameIsDrawn, p, GE_XBOARD );
6540 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6546 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6547 SendToProgram("force\n", cps->other); // suppress reply
6548 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6549 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6551 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6558 if (gameMode == TwoMachinesPlay) {
6559 /* [HGM] relaying draw offers moved to after reception of move */
6560 /* and interpreting offer as claim if it brings draw condition */
6561 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6562 SendToProgram("draw\n", cps->other);
6564 if (cps->other->sendTime) {
6565 SendTimeRemaining(cps->other,
6566 cps->other->twoMachinesColor[0] == 'w');
6568 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6569 if (firstMove && !bookHit) {
6571 if (cps->other->useColors) {
6572 SendToProgram(cps->other->twoMachinesColor, cps->other);
6574 SendToProgram("go\n", cps->other);
6576 cps->other->maybeThinking = TRUE;
6579 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6581 if (!pausing && appData.ringBellAfterMoves) {
6586 * Reenable menu items that were disabled while
6587 * machine was thinking
6589 if (gameMode != TwoMachinesPlay)
6590 SetUserThinkingEnables();
6592 // [HGM] book: after book hit opponent has received move and is now in force mode
6593 // force the book reply into it, and then fake that it outputted this move by jumping
6594 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6596 static char bookMove[MSG_SIZ]; // a bit generous?
6598 strcpy(bookMove, "move ");
6599 strcat(bookMove, bookHit);
6602 programStats.nodes = programStats.depth = programStats.time =
6603 programStats.score = programStats.got_only_move = 0;
6604 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6606 if(cps->lastPing != cps->lastPong) {
6607 savedMessage = message; // args for deferred call
6609 ScheduleDelayedEvent(DeferredBookMove, 10);
6618 /* Set special modes for chess engines. Later something general
6619 * could be added here; for now there is just one kludge feature,
6620 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6621 * when "xboard" is given as an interactive command.
6623 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6624 cps->useSigint = FALSE;
6625 cps->useSigterm = FALSE;
6627 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6628 ParseFeatures(message+8, cps);
6629 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6632 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6633 * want this, I was asked to put it in, and obliged.
6635 if (!strncmp(message, "setboard ", 9)) {
6636 Board initial_position;
6638 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6640 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6641 DisplayError(_("Bad FEN received from engine"), 0);
6645 CopyBoard(boards[0], initial_position);
6646 initialRulePlies = FENrulePlies;
6647 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6648 else gameMode = MachinePlaysBlack;
6649 DrawPosition(FALSE, boards[currentMove]);
6655 * Look for communication commands
6657 if (!strncmp(message, "telluser ", 9)) {
6658 DisplayNote(message + 9);
6661 if (!strncmp(message, "tellusererror ", 14)) {
6663 DisplayError(message + 14, 0);
6666 if (!strncmp(message, "tellopponent ", 13)) {
6667 if (appData.icsActive) {
6669 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6673 DisplayNote(message + 13);
6677 if (!strncmp(message, "tellothers ", 11)) {
6678 if (appData.icsActive) {
6680 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6686 if (!strncmp(message, "tellall ", 8)) {
6687 if (appData.icsActive) {
6689 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6693 DisplayNote(message + 8);
6697 if (strncmp(message, "warning", 7) == 0) {
6698 /* Undocumented feature, use tellusererror in new code */
6699 DisplayError(message, 0);
6702 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6703 strcpy(realname, cps->tidy);
6704 strcat(realname, " query");
6705 AskQuestion(realname, buf2, buf1, cps->pr);
6708 /* Commands from the engine directly to ICS. We don't allow these to be
6709 * sent until we are logged on. Crafty kibitzes have been known to
6710 * interfere with the login process.
6713 if (!strncmp(message, "tellics ", 8)) {
6714 SendToICS(message + 8);
6718 if (!strncmp(message, "tellicsnoalias ", 15)) {
6719 SendToICS(ics_prefix);
6720 SendToICS(message + 15);
6724 /* The following are for backward compatibility only */
6725 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6726 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6727 SendToICS(ics_prefix);
6733 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6737 * If the move is illegal, cancel it and redraw the board.
6738 * Also deal with other error cases. Matching is rather loose
6739 * here to accommodate engines written before the spec.
6741 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6742 strncmp(message, "Error", 5) == 0) {
6743 if (StrStr(message, "name") ||
6744 StrStr(message, "rating") || StrStr(message, "?") ||
6745 StrStr(message, "result") || StrStr(message, "board") ||
6746 StrStr(message, "bk") || StrStr(message, "computer") ||
6747 StrStr(message, "variant") || StrStr(message, "hint") ||
6748 StrStr(message, "random") || StrStr(message, "depth") ||
6749 StrStr(message, "accepted")) {
6752 if (StrStr(message, "protover")) {
6753 /* Program is responding to input, so it's apparently done
6754 initializing, and this error message indicates it is
6755 protocol version 1. So we don't need to wait any longer
6756 for it to initialize and send feature commands. */
6757 FeatureDone(cps, 1);
6758 cps->protocolVersion = 1;
6761 cps->maybeThinking = FALSE;
6763 if (StrStr(message, "draw")) {
6764 /* Program doesn't have "draw" command */
6765 cps->sendDrawOffers = 0;
6768 if (cps->sendTime != 1 &&
6769 (StrStr(message, "time") || StrStr(message, "otim"))) {
6770 /* Program apparently doesn't have "time" or "otim" command */
6774 if (StrStr(message, "analyze")) {
6775 cps->analysisSupport = FALSE;
6776 cps->analyzing = FALSE;
6778 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6779 DisplayError(buf2, 0);
6782 if (StrStr(message, "(no matching move)st")) {
6783 /* Special kludge for GNU Chess 4 only */
6784 cps->stKludge = TRUE;
6785 SendTimeControl(cps, movesPerSession, timeControl,
6786 timeIncrement, appData.searchDepth,
6790 if (StrStr(message, "(no matching move)sd")) {
6791 /* Special kludge for GNU Chess 4 only */
6792 cps->sdKludge = TRUE;
6793 SendTimeControl(cps, movesPerSession, timeControl,
6794 timeIncrement, appData.searchDepth,
6798 if (!StrStr(message, "llegal")) {
6801 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6802 gameMode == IcsIdle) return;
6803 if (forwardMostMove <= backwardMostMove) return;
6804 if (pausing) PauseEvent();
6805 if(appData.forceIllegal) {
6806 // [HGM] illegal: machine refused move; force position after move into it
6807 SendToProgram("force\n", cps);
6808 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6809 // we have a real problem now, as SendBoard will use the a2a3 kludge
6810 // when black is to move, while there might be nothing on a2 or black
6811 // might already have the move. So send the board as if white has the move.
6812 // But first we must change the stm of the engine, as it refused the last move
6813 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6814 if(WhiteOnMove(forwardMostMove)) {
6815 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6816 SendBoard(cps, forwardMostMove); // kludgeless board
6818 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6819 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6820 SendBoard(cps, forwardMostMove+1); // kludgeless board
6822 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6823 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6824 gameMode == TwoMachinesPlay)
6825 SendToProgram("go\n", cps);
6828 if (gameMode == PlayFromGameFile) {
6829 /* Stop reading this game file */
6830 gameMode = EditGame;
6833 currentMove = --forwardMostMove;
6834 DisplayMove(currentMove-1); /* before DisplayMoveError */
6836 DisplayBothClocks();
6837 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6838 parseList[currentMove], cps->which);
6839 DisplayMoveError(buf1);
6840 DrawPosition(FALSE, boards[currentMove]);
6842 /* [HGM] illegal-move claim should forfeit game when Xboard */
6843 /* only passes fully legal moves */
6844 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6845 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6846 "False illegal-move claim", GE_XBOARD );
6850 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6851 /* Program has a broken "time" command that
6852 outputs a string not ending in newline.
6858 * If chess program startup fails, exit with an error message.
6859 * Attempts to recover here are futile.
6861 if ((StrStr(message, "unknown host") != NULL)
6862 || (StrStr(message, "No remote directory") != NULL)
6863 || (StrStr(message, "not found") != NULL)
6864 || (StrStr(message, "No such file") != NULL)
6865 || (StrStr(message, "can't alloc") != NULL)
6866 || (StrStr(message, "Permission denied") != NULL)) {
6868 cps->maybeThinking = FALSE;
6869 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6870 cps->which, cps->program, cps->host, message);
6871 RemoveInputSource(cps->isr);
6872 DisplayFatalError(buf1, 0, 1);
6877 * Look for hint output
6879 if (sscanf(message, "Hint: %s", buf1) == 1) {
6880 if (cps == &first && hintRequested) {
6881 hintRequested = FALSE;
6882 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6883 &fromX, &fromY, &toX, &toY, &promoChar)) {
6884 (void) CoordsToAlgebraic(boards[forwardMostMove],
6885 PosFlags(forwardMostMove),
6886 fromY, fromX, toY, toX, promoChar, buf1);
6887 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6888 DisplayInformation(buf2);
6890 /* Hint move could not be parsed!? */
6891 snprintf(buf2, sizeof(buf2),
6892 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6894 DisplayError(buf2, 0);
6897 strcpy(lastHint, buf1);
6903 * Ignore other messages if game is not in progress
6905 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6906 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6909 * look for win, lose, draw, or draw offer
6911 if (strncmp(message, "1-0", 3) == 0) {
6912 char *p, *q, *r = "";
6913 p = strchr(message, '{');
6921 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6923 } else if (strncmp(message, "0-1", 3) == 0) {
6924 char *p, *q, *r = "";
6925 p = strchr(message, '{');
6933 /* Kludge for Arasan 4.1 bug */
6934 if (strcmp(r, "Black resigns") == 0) {
6935 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6938 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6940 } else if (strncmp(message, "1/2", 3) == 0) {
6941 char *p, *q, *r = "";
6942 p = strchr(message, '{');
6951 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6954 } else if (strncmp(message, "White resign", 12) == 0) {
6955 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6957 } else if (strncmp(message, "Black resign", 12) == 0) {
6958 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6960 } else if (strncmp(message, "White matches", 13) == 0 ||
6961 strncmp(message, "Black matches", 13) == 0 ) {
6962 /* [HGM] ignore GNUShogi noises */
6964 } else if (strncmp(message, "White", 5) == 0 &&
6965 message[5] != '(' &&
6966 StrStr(message, "Black") == NULL) {
6967 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6969 } else if (strncmp(message, "Black", 5) == 0 &&
6970 message[5] != '(') {
6971 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6973 } else if (strcmp(message, "resign") == 0 ||
6974 strcmp(message, "computer resigns") == 0) {
6976 case MachinePlaysBlack:
6977 case IcsPlayingBlack:
6978 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6980 case MachinePlaysWhite:
6981 case IcsPlayingWhite:
6982 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6984 case TwoMachinesPlay:
6985 if (cps->twoMachinesColor[0] == 'w')
6986 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6988 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6995 } else if (strncmp(message, "opponent mates", 14) == 0) {
6997 case MachinePlaysBlack:
6998 case IcsPlayingBlack:
6999 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7001 case MachinePlaysWhite:
7002 case IcsPlayingWhite:
7003 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7005 case TwoMachinesPlay:
7006 if (cps->twoMachinesColor[0] == 'w')
7007 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7009 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7016 } else if (strncmp(message, "computer mates", 14) == 0) {
7018 case MachinePlaysBlack:
7019 case IcsPlayingBlack:
7020 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7022 case MachinePlaysWhite:
7023 case IcsPlayingWhite:
7024 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7026 case TwoMachinesPlay:
7027 if (cps->twoMachinesColor[0] == 'w')
7028 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7030 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7037 } else if (strncmp(message, "checkmate", 9) == 0) {
7038 if (WhiteOnMove(forwardMostMove)) {
7039 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7041 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7044 } else if (strstr(message, "Draw") != NULL ||
7045 strstr(message, "game is a draw") != NULL) {
7046 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7048 } else if (strstr(message, "offer") != NULL &&
7049 strstr(message, "draw") != NULL) {
7051 if (appData.zippyPlay && first.initDone) {
7052 /* Relay offer to ICS */
7053 SendToICS(ics_prefix);
7054 SendToICS("draw\n");
7057 cps->offeredDraw = 2; /* valid until this engine moves twice */
7058 if (gameMode == TwoMachinesPlay) {
7059 if (cps->other->offeredDraw) {
7060 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7061 /* [HGM] in two-machine mode we delay relaying draw offer */
7062 /* until after we also have move, to see if it is really claim */
7064 } else if (gameMode == MachinePlaysWhite ||
7065 gameMode == MachinePlaysBlack) {
7066 if (userOfferedDraw) {
7067 DisplayInformation(_("Machine accepts your draw offer"));
7068 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7070 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7077 * Look for thinking output
7079 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7080 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7082 int plylev, mvleft, mvtot, curscore, time;
7083 char mvname[MOVE_LEN];
7087 int prefixHint = FALSE;
7088 mvname[0] = NULLCHAR;
7091 case MachinePlaysBlack:
7092 case IcsPlayingBlack:
7093 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7095 case MachinePlaysWhite:
7096 case IcsPlayingWhite:
7097 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7102 case IcsObserving: /* [DM] icsEngineAnalyze */
7103 if (!appData.icsEngineAnalyze) ignore = TRUE;
7105 case TwoMachinesPlay:
7106 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7117 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7118 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7120 if (plyext != ' ' && plyext != '\t') {
7124 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7125 if( cps->scoreIsAbsolute &&
7126 ( gameMode == MachinePlaysBlack ||
7127 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7128 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7129 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7130 !WhiteOnMove(currentMove)
7133 curscore = -curscore;
7137 programStats.depth = plylev;
7138 programStats.nodes = nodes;
7139 programStats.time = time;
7140 programStats.score = curscore;
7141 programStats.got_only_move = 0;
7143 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7146 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7147 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7148 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7149 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7150 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7151 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7152 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7153 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7156 /* Buffer overflow protection */
7157 if (buf1[0] != NULLCHAR) {
7158 if (strlen(buf1) >= sizeof(programStats.movelist)
7159 && appData.debugMode) {
7161 "PV is too long; using the first %u bytes.\n",
7162 (unsigned) sizeof(programStats.movelist) - 1);
7165 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7167 sprintf(programStats.movelist, " no PV\n");
7170 if (programStats.seen_stat) {
7171 programStats.ok_to_send = 1;
7174 if (strchr(programStats.movelist, '(') != NULL) {
7175 programStats.line_is_book = 1;
7176 programStats.nr_moves = 0;
7177 programStats.moves_left = 0;
7179 programStats.line_is_book = 0;
7182 SendProgramStatsToFrontend( cps, &programStats );
7185 [AS] Protect the thinkOutput buffer from overflow... this
7186 is only useful if buf1 hasn't overflowed first!
7188 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7190 (gameMode == TwoMachinesPlay ?
7191 ToUpper(cps->twoMachinesColor[0]) : ' '),
7192 ((double) curscore) / 100.0,
7193 prefixHint ? lastHint : "",
7194 prefixHint ? " " : "" );
7196 if( buf1[0] != NULLCHAR ) {
7197 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7199 if( strlen(buf1) > max_len ) {
7200 if( appData.debugMode) {
7201 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7203 buf1[max_len+1] = '\0';
7206 strcat( thinkOutput, buf1 );
7209 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7210 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7211 DisplayMove(currentMove - 1);
7215 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7216 /* crafty (9.25+) says "(only move) <move>"
7217 * if there is only 1 legal move
7219 sscanf(p, "(only move) %s", buf1);
7220 sprintf(thinkOutput, "%s (only move)", buf1);
7221 sprintf(programStats.movelist, "%s (only move)", buf1);
7222 programStats.depth = 1;
7223 programStats.nr_moves = 1;
7224 programStats.moves_left = 1;
7225 programStats.nodes = 1;
7226 programStats.time = 1;
7227 programStats.got_only_move = 1;
7229 /* Not really, but we also use this member to
7230 mean "line isn't going to change" (Crafty
7231 isn't searching, so stats won't change) */
7232 programStats.line_is_book = 1;
7234 SendProgramStatsToFrontend( cps, &programStats );
7236 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7237 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7238 DisplayMove(currentMove - 1);
7241 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7242 &time, &nodes, &plylev, &mvleft,
7243 &mvtot, mvname) >= 5) {
7244 /* The stat01: line is from Crafty (9.29+) in response
7245 to the "." command */
7246 programStats.seen_stat = 1;
7247 cps->maybeThinking = TRUE;
7249 if (programStats.got_only_move || !appData.periodicUpdates)
7252 programStats.depth = plylev;
7253 programStats.time = time;
7254 programStats.nodes = nodes;
7255 programStats.moves_left = mvleft;
7256 programStats.nr_moves = mvtot;
7257 strcpy(programStats.move_name, mvname);
7258 programStats.ok_to_send = 1;
7259 programStats.movelist[0] = '\0';
7261 SendProgramStatsToFrontend( cps, &programStats );
7265 } else if (strncmp(message,"++",2) == 0) {
7266 /* Crafty 9.29+ outputs this */
7267 programStats.got_fail = 2;
7270 } else if (strncmp(message,"--",2) == 0) {
7271 /* Crafty 9.29+ outputs this */
7272 programStats.got_fail = 1;
7275 } else if (thinkOutput[0] != NULLCHAR &&
7276 strncmp(message, " ", 4) == 0) {
7277 unsigned message_len;
7280 while (*p && *p == ' ') p++;
7282 message_len = strlen( p );
7284 /* [AS] Avoid buffer overflow */
7285 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7286 strcat(thinkOutput, " ");
7287 strcat(thinkOutput, p);
7290 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7291 strcat(programStats.movelist, " ");
7292 strcat(programStats.movelist, p);
7295 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7296 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7297 DisplayMove(currentMove - 1);
7305 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7306 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7308 ChessProgramStats cpstats;
7310 if (plyext != ' ' && plyext != '\t') {
7314 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7315 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7316 curscore = -curscore;
7319 cpstats.depth = plylev;
7320 cpstats.nodes = nodes;
7321 cpstats.time = time;
7322 cpstats.score = curscore;
7323 cpstats.got_only_move = 0;
7324 cpstats.movelist[0] = '\0';
7326 if (buf1[0] != NULLCHAR) {
7327 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7330 cpstats.ok_to_send = 0;
7331 cpstats.line_is_book = 0;
7332 cpstats.nr_moves = 0;
7333 cpstats.moves_left = 0;
7335 SendProgramStatsToFrontend( cps, &cpstats );
7342 /* Parse a game score from the character string "game", and
7343 record it as the history of the current game. The game
7344 score is NOT assumed to start from the standard position.
7345 The display is not updated in any way.
7348 ParseGameHistory(game)
7352 int fromX, fromY, toX, toY, boardIndex;
7357 if (appData.debugMode)
7358 fprintf(debugFP, "Parsing game history: %s\n", game);
7360 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7361 gameInfo.site = StrSave(appData.icsHost);
7362 gameInfo.date = PGNDate();
7363 gameInfo.round = StrSave("-");
7365 /* Parse out names of players */
7366 while (*game == ' ') game++;
7368 while (*game != ' ') *p++ = *game++;
7370 gameInfo.white = StrSave(buf);
7371 while (*game == ' ') game++;
7373 while (*game != ' ' && *game != '\n') *p++ = *game++;
7375 gameInfo.black = StrSave(buf);
7378 boardIndex = blackPlaysFirst ? 1 : 0;
7381 yyboardindex = boardIndex;
7382 moveType = (ChessMove) yylex();
7384 case IllegalMove: /* maybe suicide chess, etc. */
7385 if (appData.debugMode) {
7386 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7387 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7388 setbuf(debugFP, NULL);
7390 case WhitePromotionChancellor:
7391 case BlackPromotionChancellor:
7392 case WhitePromotionArchbishop:
7393 case BlackPromotionArchbishop:
7394 case WhitePromotionQueen:
7395 case BlackPromotionQueen:
7396 case WhitePromotionRook:
7397 case BlackPromotionRook:
7398 case WhitePromotionBishop:
7399 case BlackPromotionBishop:
7400 case WhitePromotionKnight:
7401 case BlackPromotionKnight:
7402 case WhitePromotionKing:
7403 case BlackPromotionKing:
7405 case WhiteCapturesEnPassant:
7406 case BlackCapturesEnPassant:
7407 case WhiteKingSideCastle:
7408 case WhiteQueenSideCastle:
7409 case BlackKingSideCastle:
7410 case BlackQueenSideCastle:
7411 case WhiteKingSideCastleWild:
7412 case WhiteQueenSideCastleWild:
7413 case BlackKingSideCastleWild:
7414 case BlackQueenSideCastleWild:
7416 case WhiteHSideCastleFR:
7417 case WhiteASideCastleFR:
7418 case BlackHSideCastleFR:
7419 case BlackASideCastleFR:
7421 fromX = currentMoveString[0] - AAA;
7422 fromY = currentMoveString[1] - ONE;
7423 toX = currentMoveString[2] - AAA;
7424 toY = currentMoveString[3] - ONE;
7425 promoChar = currentMoveString[4];
7429 fromX = moveType == WhiteDrop ?
7430 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7431 (int) CharToPiece(ToLower(currentMoveString[0]));
7433 toX = currentMoveString[2] - AAA;
7434 toY = currentMoveString[3] - ONE;
7435 promoChar = NULLCHAR;
7439 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7440 if (appData.debugMode) {
7441 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7442 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7443 setbuf(debugFP, NULL);
7445 DisplayError(buf, 0);
7447 case ImpossibleMove:
7449 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7450 if (appData.debugMode) {
7451 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7452 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7453 setbuf(debugFP, NULL);
7455 DisplayError(buf, 0);
7457 case (ChessMove) 0: /* end of file */
7458 if (boardIndex < backwardMostMove) {
7459 /* Oops, gap. How did that happen? */
7460 DisplayError(_("Gap in move list"), 0);
7463 backwardMostMove = blackPlaysFirst ? 1 : 0;
7464 if (boardIndex > forwardMostMove) {
7465 forwardMostMove = boardIndex;
7469 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7470 strcat(parseList[boardIndex-1], " ");
7471 strcat(parseList[boardIndex-1], yy_text);
7483 case GameUnfinished:
7484 if (gameMode == IcsExamining) {
7485 if (boardIndex < backwardMostMove) {
7486 /* Oops, gap. How did that happen? */
7489 backwardMostMove = blackPlaysFirst ? 1 : 0;
7492 gameInfo.result = moveType;
7493 p = strchr(yy_text, '{');
7494 if (p == NULL) p = strchr(yy_text, '(');
7497 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7499 q = strchr(p, *p == '{' ? '}' : ')');
7500 if (q != NULL) *q = NULLCHAR;
7503 gameInfo.resultDetails = StrSave(p);
7506 if (boardIndex >= forwardMostMove &&
7507 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7508 backwardMostMove = blackPlaysFirst ? 1 : 0;
7511 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7512 fromY, fromX, toY, toX, promoChar,
7513 parseList[boardIndex]);
7514 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7515 /* currentMoveString is set as a side-effect of yylex */
7516 strcpy(moveList[boardIndex], currentMoveString);
7517 strcat(moveList[boardIndex], "\n");
7519 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7520 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7526 if(gameInfo.variant != VariantShogi)
7527 strcat(parseList[boardIndex - 1], "+");
7531 strcat(parseList[boardIndex - 1], "#");
7538 /* Apply a move to the given board */
7540 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7541 int fromX, fromY, toX, toY;
7545 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7547 /* [HGM] compute & store e.p. status and castling rights for new position */
7548 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7551 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7552 oldEP = (signed char)board[EP_STATUS];
7553 board[EP_STATUS] = EP_NONE;
7555 if( board[toY][toX] != EmptySquare )
7556 board[EP_STATUS] = EP_CAPTURE;
7558 if( board[fromY][fromX] == WhitePawn ) {
7559 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7560 board[EP_STATUS] = EP_PAWN_MOVE;
7562 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7563 gameInfo.variant != VariantBerolina || toX < fromX)
7564 board[EP_STATUS] = toX | berolina;
7565 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7566 gameInfo.variant != VariantBerolina || toX > fromX)
7567 board[EP_STATUS] = toX;
7570 if( board[fromY][fromX] == BlackPawn ) {
7571 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7572 board[EP_STATUS] = EP_PAWN_MOVE;
7573 if( toY-fromY== -2) {
7574 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7575 gameInfo.variant != VariantBerolina || toX < fromX)
7576 board[EP_STATUS] = toX | berolina;
7577 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7578 gameInfo.variant != VariantBerolina || toX > fromX)
7579 board[EP_STATUS] = toX;
7583 for(i=0; i<nrCastlingRights; i++) {
7584 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7585 board[CASTLING][i] == toX && castlingRank[i] == toY
7586 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7591 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7592 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7593 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7595 if (fromX == toX && fromY == toY) return;
7597 if (fromY == DROP_RANK) {
7599 piece = board[toY][toX] = (ChessSquare) fromX;
7601 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7602 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7603 if(gameInfo.variant == VariantKnightmate)
7604 king += (int) WhiteUnicorn - (int) WhiteKing;
7606 /* Code added by Tord: */
7607 /* FRC castling assumed when king captures friendly rook. */
7608 if (board[fromY][fromX] == WhiteKing &&
7609 board[toY][toX] == WhiteRook) {
7610 board[fromY][fromX] = EmptySquare;
7611 board[toY][toX] = EmptySquare;
7613 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7615 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7617 } else if (board[fromY][fromX] == BlackKing &&
7618 board[toY][toX] == BlackRook) {
7619 board[fromY][fromX] = EmptySquare;
7620 board[toY][toX] = EmptySquare;
7622 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7624 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7626 /* End of code added by Tord */
7628 } else if (board[fromY][fromX] == king
7629 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7630 && toY == fromY && toX > fromX+1) {
7631 board[fromY][fromX] = EmptySquare;
7632 board[toY][toX] = king;
7633 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7634 board[fromY][BOARD_RGHT-1] = EmptySquare;
7635 } else if (board[fromY][fromX] == king
7636 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7637 && toY == fromY && toX < fromX-1) {
7638 board[fromY][fromX] = EmptySquare;
7639 board[toY][toX] = king;
7640 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7641 board[fromY][BOARD_LEFT] = EmptySquare;
7642 } else if (board[fromY][fromX] == WhitePawn
7643 && toY == BOARD_HEIGHT-1
7644 && gameInfo.variant != VariantXiangqi
7646 /* white pawn promotion */
7647 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7648 if (board[toY][toX] == EmptySquare) {
7649 board[toY][toX] = WhiteQueen;
7651 if(gameInfo.variant==VariantBughouse ||
7652 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7653 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7654 board[fromY][fromX] = EmptySquare;
7655 } else if ((fromY == BOARD_HEIGHT-4)
7657 && gameInfo.variant != VariantXiangqi
7658 && gameInfo.variant != VariantBerolina
7659 && (board[fromY][fromX] == WhitePawn)
7660 && (board[toY][toX] == EmptySquare)) {
7661 board[fromY][fromX] = EmptySquare;
7662 board[toY][toX] = WhitePawn;
7663 captured = board[toY - 1][toX];
7664 board[toY - 1][toX] = EmptySquare;
7665 } else if ((fromY == BOARD_HEIGHT-4)
7667 && gameInfo.variant == VariantBerolina
7668 && (board[fromY][fromX] == WhitePawn)
7669 && (board[toY][toX] == EmptySquare)) {
7670 board[fromY][fromX] = EmptySquare;
7671 board[toY][toX] = WhitePawn;
7672 if(oldEP & EP_BEROLIN_A) {
7673 captured = board[fromY][fromX-1];
7674 board[fromY][fromX-1] = EmptySquare;
7675 }else{ captured = board[fromY][fromX+1];
7676 board[fromY][fromX+1] = EmptySquare;
7678 } else if (board[fromY][fromX] == king
7679 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7680 && toY == fromY && toX > fromX+1) {
7681 board[fromY][fromX] = EmptySquare;
7682 board[toY][toX] = king;
7683 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7684 board[fromY][BOARD_RGHT-1] = EmptySquare;
7685 } else if (board[fromY][fromX] == king
7686 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7687 && toY == fromY && toX < fromX-1) {
7688 board[fromY][fromX] = EmptySquare;
7689 board[toY][toX] = king;
7690 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7691 board[fromY][BOARD_LEFT] = EmptySquare;
7692 } else if (fromY == 7 && fromX == 3
7693 && board[fromY][fromX] == BlackKing
7694 && toY == 7 && toX == 5) {
7695 board[fromY][fromX] = EmptySquare;
7696 board[toY][toX] = BlackKing;
7697 board[fromY][7] = EmptySquare;
7698 board[toY][4] = BlackRook;
7699 } else if (fromY == 7 && fromX == 3
7700 && board[fromY][fromX] == BlackKing
7701 && toY == 7 && toX == 1) {
7702 board[fromY][fromX] = EmptySquare;
7703 board[toY][toX] = BlackKing;
7704 board[fromY][0] = EmptySquare;
7705 board[toY][2] = BlackRook;
7706 } else if (board[fromY][fromX] == BlackPawn
7708 && gameInfo.variant != VariantXiangqi
7710 /* black pawn promotion */
7711 board[0][toX] = CharToPiece(ToLower(promoChar));
7712 if (board[0][toX] == EmptySquare) {
7713 board[0][toX] = BlackQueen;
7715 if(gameInfo.variant==VariantBughouse ||
7716 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7717 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7718 board[fromY][fromX] = EmptySquare;
7719 } else if ((fromY == 3)
7721 && gameInfo.variant != VariantXiangqi
7722 && gameInfo.variant != VariantBerolina
7723 && (board[fromY][fromX] == BlackPawn)
7724 && (board[toY][toX] == EmptySquare)) {
7725 board[fromY][fromX] = EmptySquare;
7726 board[toY][toX] = BlackPawn;
7727 captured = board[toY + 1][toX];
7728 board[toY + 1][toX] = EmptySquare;
7729 } else if ((fromY == 3)
7731 && gameInfo.variant == VariantBerolina
7732 && (board[fromY][fromX] == BlackPawn)
7733 && (board[toY][toX] == EmptySquare)) {
7734 board[fromY][fromX] = EmptySquare;
7735 board[toY][toX] = BlackPawn;
7736 if(oldEP & EP_BEROLIN_A) {
7737 captured = board[fromY][fromX-1];
7738 board[fromY][fromX-1] = EmptySquare;
7739 }else{ captured = board[fromY][fromX+1];
7740 board[fromY][fromX+1] = EmptySquare;
7743 board[toY][toX] = board[fromY][fromX];
7744 board[fromY][fromX] = EmptySquare;
7747 /* [HGM] now we promote for Shogi, if needed */
7748 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7749 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7752 if (gameInfo.holdingsWidth != 0) {
7754 /* !!A lot more code needs to be written to support holdings */
7755 /* [HGM] OK, so I have written it. Holdings are stored in the */
7756 /* penultimate board files, so they are automaticlly stored */
7757 /* in the game history. */
7758 if (fromY == DROP_RANK) {
7759 /* Delete from holdings, by decreasing count */
7760 /* and erasing image if necessary */
7762 if(p < (int) BlackPawn) { /* white drop */
7763 p -= (int)WhitePawn;
7764 p = PieceToNumber((ChessSquare)p);
7765 if(p >= gameInfo.holdingsSize) p = 0;
7766 if(--board[p][BOARD_WIDTH-2] <= 0)
7767 board[p][BOARD_WIDTH-1] = EmptySquare;
7768 if((int)board[p][BOARD_WIDTH-2] < 0)
7769 board[p][BOARD_WIDTH-2] = 0;
7770 } else { /* black drop */
7771 p -= (int)BlackPawn;
7772 p = PieceToNumber((ChessSquare)p);
7773 if(p >= gameInfo.holdingsSize) p = 0;
7774 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7775 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7776 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7777 board[BOARD_HEIGHT-1-p][1] = 0;
7780 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7781 && gameInfo.variant != VariantBughouse ) {
7782 /* [HGM] holdings: Add to holdings, if holdings exist */
7783 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7784 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7785 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7788 if (p >= (int) BlackPawn) {
7789 p -= (int)BlackPawn;
7790 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7791 /* in Shogi restore piece to its original first */
7792 captured = (ChessSquare) (DEMOTED captured);
7795 p = PieceToNumber((ChessSquare)p);
7796 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7797 board[p][BOARD_WIDTH-2]++;
7798 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7800 p -= (int)WhitePawn;
7801 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7802 captured = (ChessSquare) (DEMOTED captured);
7805 p = PieceToNumber((ChessSquare)p);
7806 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7807 board[BOARD_HEIGHT-1-p][1]++;
7808 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7811 } else if (gameInfo.variant == VariantAtomic) {
7812 if (captured != EmptySquare) {
7814 for (y = toY-1; y <= toY+1; y++) {
7815 for (x = toX-1; x <= toX+1; x++) {
7816 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7817 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7818 board[y][x] = EmptySquare;
7822 board[toY][toX] = EmptySquare;
7825 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7826 /* [HGM] Shogi promotions */
7827 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7830 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7831 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7832 // [HGM] superchess: take promotion piece out of holdings
7833 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7834 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7835 if(!--board[k][BOARD_WIDTH-2])
7836 board[k][BOARD_WIDTH-1] = EmptySquare;
7838 if(!--board[BOARD_HEIGHT-1-k][1])
7839 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7845 /* Updates forwardMostMove */
7847 MakeMove(fromX, fromY, toX, toY, promoChar)
7848 int fromX, fromY, toX, toY;
7851 // forwardMostMove++; // [HGM] bare: moved downstream
7853 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7854 int timeLeft; static int lastLoadFlag=0; int king, piece;
7855 piece = boards[forwardMostMove][fromY][fromX];
7856 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7857 if(gameInfo.variant == VariantKnightmate)
7858 king += (int) WhiteUnicorn - (int) WhiteKing;
7859 if(forwardMostMove == 0) {
7861 fprintf(serverMoves, "%s;", second.tidy);
7862 fprintf(serverMoves, "%s;", first.tidy);
7863 if(!blackPlaysFirst)
7864 fprintf(serverMoves, "%s;", second.tidy);
7865 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7866 lastLoadFlag = loadFlag;
7868 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7869 // print castling suffix
7870 if( toY == fromY && piece == king ) {
7872 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7874 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7877 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7878 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7879 boards[forwardMostMove][toY][toX] == EmptySquare
7881 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7883 if(promoChar != NULLCHAR)
7884 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7886 fprintf(serverMoves, "/%d/%d",
7887 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7888 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7889 else timeLeft = blackTimeRemaining/1000;
7890 fprintf(serverMoves, "/%d", timeLeft);
7892 fflush(serverMoves);
7895 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7896 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7900 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7901 if (commentList[forwardMostMove+1] != NULL) {
7902 free(commentList[forwardMostMove+1]);
7903 commentList[forwardMostMove+1] = NULL;
7905 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7906 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7907 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7908 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7909 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7910 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7911 gameInfo.result = GameUnfinished;
7912 if (gameInfo.resultDetails != NULL) {
7913 free(gameInfo.resultDetails);
7914 gameInfo.resultDetails = NULL;
7916 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7917 moveList[forwardMostMove - 1]);
7918 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7919 PosFlags(forwardMostMove - 1),
7920 fromY, fromX, toY, toX, promoChar,
7921 parseList[forwardMostMove - 1]);
7922 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7928 if(gameInfo.variant != VariantShogi)
7929 strcat(parseList[forwardMostMove - 1], "+");
7933 strcat(parseList[forwardMostMove - 1], "#");
7936 if (appData.debugMode) {
7937 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7942 /* Updates currentMove if not pausing */
7944 ShowMove(fromX, fromY, toX, toY)
7946 int instant = (gameMode == PlayFromGameFile) ?
7947 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7948 if(appData.noGUI) return;
7949 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7951 if (forwardMostMove == currentMove + 1) {
7952 AnimateMove(boards[forwardMostMove - 1],
7953 fromX, fromY, toX, toY);
7955 if (appData.highlightLastMove) {
7956 SetHighlights(fromX, fromY, toX, toY);
7959 currentMove = forwardMostMove;
7962 if (instant) return;
7964 DisplayMove(currentMove - 1);
7965 DrawPosition(FALSE, boards[currentMove]);
7966 DisplayBothClocks();
7967 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7970 void SendEgtPath(ChessProgramState *cps)
7971 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7972 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7974 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7977 char c, *q = name+1, *r, *s;
7979 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7980 while(*p && *p != ',') *q++ = *p++;
7982 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7983 strcmp(name, ",nalimov:") == 0 ) {
7984 // take nalimov path from the menu-changeable option first, if it is defined
7985 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7986 SendToProgram(buf,cps); // send egtbpath command for nalimov
7988 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7989 (s = StrStr(appData.egtFormats, name)) != NULL) {
7990 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7991 s = r = StrStr(s, ":") + 1; // beginning of path info
7992 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7993 c = *r; *r = 0; // temporarily null-terminate path info
7994 *--q = 0; // strip of trailig ':' from name
7995 sprintf(buf, "egtpath %s %s\n", name+1, s);
7997 SendToProgram(buf,cps); // send egtbpath command for this format
7999 if(*p == ',') p++; // read away comma to position for next format name
8004 InitChessProgram(cps, setup)
8005 ChessProgramState *cps;
8006 int setup; /* [HGM] needed to setup FRC opening position */
8008 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8009 if (appData.noChessProgram) return;
8010 hintRequested = FALSE;
8011 bookRequested = FALSE;
8013 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8014 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8015 if(cps->memSize) { /* [HGM] memory */
8016 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8017 SendToProgram(buf, cps);
8019 SendEgtPath(cps); /* [HGM] EGT */
8020 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8021 sprintf(buf, "cores %d\n", appData.smpCores);
8022 SendToProgram(buf, cps);
8025 SendToProgram(cps->initString, cps);
8026 if (gameInfo.variant != VariantNormal &&
8027 gameInfo.variant != VariantLoadable
8028 /* [HGM] also send variant if board size non-standard */
8029 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8031 char *v = VariantName(gameInfo.variant);
8032 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8033 /* [HGM] in protocol 1 we have to assume all variants valid */
8034 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8035 DisplayFatalError(buf, 0, 1);
8039 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8040 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8041 if( gameInfo.variant == VariantXiangqi )
8042 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8043 if( gameInfo.variant == VariantShogi )
8044 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8045 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8046 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8047 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8048 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8049 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8050 if( gameInfo.variant == VariantCourier )
8051 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8052 if( gameInfo.variant == VariantSuper )
8053 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8054 if( gameInfo.variant == VariantGreat )
8055 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8058 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8059 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8060 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8061 if(StrStr(cps->variants, b) == NULL) {
8062 // specific sized variant not known, check if general sizing allowed
8063 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8064 if(StrStr(cps->variants, "boardsize") == NULL) {
8065 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8066 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8067 DisplayFatalError(buf, 0, 1);
8070 /* [HGM] here we really should compare with the maximum supported board size */
8073 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8074 sprintf(buf, "variant %s\n", b);
8075 SendToProgram(buf, cps);
8077 currentlyInitializedVariant = gameInfo.variant;
8079 /* [HGM] send opening position in FRC to first engine */
8081 SendToProgram("force\n", cps);
8083 /* engine is now in force mode! Set flag to wake it up after first move. */
8084 setboardSpoiledMachineBlack = 1;
8088 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8089 SendToProgram(buf, cps);
8091 cps->maybeThinking = FALSE;
8092 cps->offeredDraw = 0;
8093 if (!appData.icsActive) {
8094 SendTimeControl(cps, movesPerSession, timeControl,
8095 timeIncrement, appData.searchDepth,
8098 if (appData.showThinking
8099 // [HGM] thinking: four options require thinking output to be sent
8100 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8102 SendToProgram("post\n", cps);
8104 SendToProgram("hard\n", cps);
8105 if (!appData.ponderNextMove) {
8106 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8107 it without being sure what state we are in first. "hard"
8108 is not a toggle, so that one is OK.
8110 SendToProgram("easy\n", cps);
8113 sprintf(buf, "ping %d\n", ++cps->lastPing);
8114 SendToProgram(buf, cps);
8116 cps->initDone = TRUE;
8121 StartChessProgram(cps)
8122 ChessProgramState *cps;
8127 if (appData.noChessProgram) return;
8128 cps->initDone = FALSE;
8130 if (strcmp(cps->host, "localhost") == 0) {
8131 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8132 } else if (*appData.remoteShell == NULLCHAR) {
8133 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8135 if (*appData.remoteUser == NULLCHAR) {
8136 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8139 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8140 cps->host, appData.remoteUser, cps->program);
8142 err = StartChildProcess(buf, "", &cps->pr);
8146 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8147 DisplayFatalError(buf, err, 1);
8153 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8154 if (cps->protocolVersion > 1) {
8155 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8156 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8157 cps->comboCnt = 0; // and values of combo boxes
8158 SendToProgram(buf, cps);
8160 SendToProgram("xboard\n", cps);
8166 TwoMachinesEventIfReady P((void))
8168 if (first.lastPing != first.lastPong) {
8169 DisplayMessage("", _("Waiting for first chess program"));
8170 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8173 if (second.lastPing != second.lastPong) {
8174 DisplayMessage("", _("Waiting for second chess program"));
8175 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8183 NextMatchGame P((void))
8185 int index; /* [HGM] autoinc: step load index during match */
8187 if (*appData.loadGameFile != NULLCHAR) {
8188 index = appData.loadGameIndex;
8189 if(index < 0) { // [HGM] autoinc
8190 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8191 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8193 LoadGameFromFile(appData.loadGameFile,
8195 appData.loadGameFile, FALSE);
8196 } else if (*appData.loadPositionFile != NULLCHAR) {
8197 index = appData.loadPositionIndex;
8198 if(index < 0) { // [HGM] autoinc
8199 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8200 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8202 LoadPositionFromFile(appData.loadPositionFile,
8204 appData.loadPositionFile);
8206 TwoMachinesEventIfReady();
8209 void UserAdjudicationEvent( int result )
8211 ChessMove gameResult = GameIsDrawn;
8214 gameResult = WhiteWins;
8216 else if( result < 0 ) {
8217 gameResult = BlackWins;
8220 if( gameMode == TwoMachinesPlay ) {
8221 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8226 // [HGM] save: calculate checksum of game to make games easily identifiable
8227 int StringCheckSum(char *s)
8230 if(s==NULL) return 0;
8231 while(*s) i = i*259 + *s++;
8238 for(i=backwardMostMove; i<forwardMostMove; i++) {
8239 sum += pvInfoList[i].depth;
8240 sum += StringCheckSum(parseList[i]);
8241 sum += StringCheckSum(commentList[i]);
8244 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8245 return sum + StringCheckSum(commentList[i]);
8246 } // end of save patch
8249 GameEnds(result, resultDetails, whosays)
8251 char *resultDetails;
8254 GameMode nextGameMode;
8258 if(endingGame) return; /* [HGM] crash: forbid recursion */
8261 if (appData.debugMode) {
8262 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8263 result, resultDetails ? resultDetails : "(null)", whosays);
8266 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8267 /* If we are playing on ICS, the server decides when the
8268 game is over, but the engine can offer to draw, claim
8272 if (appData.zippyPlay && first.initDone) {
8273 if (result == GameIsDrawn) {
8274 /* In case draw still needs to be claimed */
8275 SendToICS(ics_prefix);
8276 SendToICS("draw\n");
8277 } else if (StrCaseStr(resultDetails, "resign")) {
8278 SendToICS(ics_prefix);
8279 SendToICS("resign\n");
8283 endingGame = 0; /* [HGM] crash */
8287 /* If we're loading the game from a file, stop */
8288 if (whosays == GE_FILE) {
8289 (void) StopLoadGameTimer();
8293 /* Cancel draw offers */
8294 first.offeredDraw = second.offeredDraw = 0;
8296 /* If this is an ICS game, only ICS can really say it's done;
8297 if not, anyone can. */
8298 isIcsGame = (gameMode == IcsPlayingWhite ||
8299 gameMode == IcsPlayingBlack ||
8300 gameMode == IcsObserving ||
8301 gameMode == IcsExamining);
8303 if (!isIcsGame || whosays == GE_ICS) {
8304 /* OK -- not an ICS game, or ICS said it was done */
8306 if (!isIcsGame && !appData.noChessProgram)
8307 SetUserThinkingEnables();
8309 /* [HGM] if a machine claims the game end we verify this claim */
8310 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8311 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8313 ChessMove trueResult = (ChessMove) -1;
8315 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8316 first.twoMachinesColor[0] :
8317 second.twoMachinesColor[0] ;
8319 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8320 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8321 /* [HGM] verify: engine mate claims accepted if they were flagged */
8322 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8324 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8325 /* [HGM] verify: engine mate claims accepted if they were flagged */
8326 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8328 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8329 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8332 // now verify win claims, but not in drop games, as we don't understand those yet
8333 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8334 || gameInfo.variant == VariantGreat) &&
8335 (result == WhiteWins && claimer == 'w' ||
8336 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8337 if (appData.debugMode) {
8338 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8339 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8341 if(result != trueResult) {
8342 sprintf(buf, "False win claim: '%s'", resultDetails);
8343 result = claimer == 'w' ? BlackWins : WhiteWins;
8344 resultDetails = buf;
8347 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8348 && (forwardMostMove <= backwardMostMove ||
8349 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8350 (claimer=='b')==(forwardMostMove&1))
8352 /* [HGM] verify: draws that were not flagged are false claims */
8353 sprintf(buf, "False draw claim: '%s'", resultDetails);
8354 result = claimer == 'w' ? BlackWins : WhiteWins;
8355 resultDetails = buf;
8357 /* (Claiming a loss is accepted no questions asked!) */
8359 /* [HGM] bare: don't allow bare King to win */
8360 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8361 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8362 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8363 && result != GameIsDrawn)
8364 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8365 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8366 int p = (signed char)boards[forwardMostMove][i][j] - color;
8367 if(p >= 0 && p <= (int)WhiteKing) k++;
8369 if (appData.debugMode) {
8370 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8371 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8374 result = GameIsDrawn;
8375 sprintf(buf, "%s but bare king", resultDetails);
8376 resultDetails = buf;
8382 if(serverMoves != NULL && !loadFlag) { char c = '=';
8383 if(result==WhiteWins) c = '+';
8384 if(result==BlackWins) c = '-';
8385 if(resultDetails != NULL)
8386 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8388 if (resultDetails != NULL) {
8389 gameInfo.result = result;
8390 gameInfo.resultDetails = StrSave(resultDetails);
8392 /* display last move only if game was not loaded from file */
8393 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8394 DisplayMove(currentMove - 1);
8396 if (forwardMostMove != 0) {
8397 if (gameMode != PlayFromGameFile && gameMode != EditGame
8398 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8400 if (*appData.saveGameFile != NULLCHAR) {
8401 SaveGameToFile(appData.saveGameFile, TRUE);
8402 } else if (appData.autoSaveGames) {
8405 if (*appData.savePositionFile != NULLCHAR) {
8406 SavePositionToFile(appData.savePositionFile);
8411 /* Tell program how game ended in case it is learning */
8412 /* [HGM] Moved this to after saving the PGN, just in case */
8413 /* engine died and we got here through time loss. In that */
8414 /* case we will get a fatal error writing the pipe, which */
8415 /* would otherwise lose us the PGN. */
8416 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8417 /* output during GameEnds should never be fatal anymore */
8418 if (gameMode == MachinePlaysWhite ||
8419 gameMode == MachinePlaysBlack ||
8420 gameMode == TwoMachinesPlay ||
8421 gameMode == IcsPlayingWhite ||
8422 gameMode == IcsPlayingBlack ||
8423 gameMode == BeginningOfGame) {
8425 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8427 if (first.pr != NoProc) {
8428 SendToProgram(buf, &first);
8430 if (second.pr != NoProc &&
8431 gameMode == TwoMachinesPlay) {
8432 SendToProgram(buf, &second);
8437 if (appData.icsActive) {
8438 if (appData.quietPlay &&
8439 (gameMode == IcsPlayingWhite ||
8440 gameMode == IcsPlayingBlack)) {
8441 SendToICS(ics_prefix);
8442 SendToICS("set shout 1\n");
8444 nextGameMode = IcsIdle;
8445 ics_user_moved = FALSE;
8446 /* clean up premove. It's ugly when the game has ended and the
8447 * premove highlights are still on the board.
8451 ClearPremoveHighlights();
8452 DrawPosition(FALSE, boards[currentMove]);
8454 if (whosays == GE_ICS) {
8457 if (gameMode == IcsPlayingWhite)
8459 else if(gameMode == IcsPlayingBlack)
8463 if (gameMode == IcsPlayingBlack)
8465 else if(gameMode == IcsPlayingWhite)
8472 PlayIcsUnfinishedSound();
8475 } else if (gameMode == EditGame ||
8476 gameMode == PlayFromGameFile ||
8477 gameMode == AnalyzeMode ||
8478 gameMode == AnalyzeFile) {
8479 nextGameMode = gameMode;
8481 nextGameMode = EndOfGame;
8486 nextGameMode = gameMode;
8489 if (appData.noChessProgram) {
8490 gameMode = nextGameMode;
8492 endingGame = 0; /* [HGM] crash */
8497 /* Put first chess program into idle state */
8498 if (first.pr != NoProc &&
8499 (gameMode == MachinePlaysWhite ||
8500 gameMode == MachinePlaysBlack ||
8501 gameMode == TwoMachinesPlay ||
8502 gameMode == IcsPlayingWhite ||
8503 gameMode == IcsPlayingBlack ||
8504 gameMode == BeginningOfGame)) {
8505 SendToProgram("force\n", &first);
8506 if (first.usePing) {
8508 sprintf(buf, "ping %d\n", ++first.lastPing);
8509 SendToProgram(buf, &first);
8512 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8513 /* Kill off first chess program */
8514 if (first.isr != NULL)
8515 RemoveInputSource(first.isr);
8518 if (first.pr != NoProc) {
8520 DoSleep( appData.delayBeforeQuit );
8521 SendToProgram("quit\n", &first);
8522 DoSleep( appData.delayAfterQuit );
8523 DestroyChildProcess(first.pr, first.useSigterm);
8528 /* Put second chess program into idle state */
8529 if (second.pr != NoProc &&
8530 gameMode == TwoMachinesPlay) {
8531 SendToProgram("force\n", &second);
8532 if (second.usePing) {
8534 sprintf(buf, "ping %d\n", ++second.lastPing);
8535 SendToProgram(buf, &second);
8538 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8539 /* Kill off second chess program */
8540 if (second.isr != NULL)
8541 RemoveInputSource(second.isr);
8544 if (second.pr != NoProc) {
8545 DoSleep( appData.delayBeforeQuit );
8546 SendToProgram("quit\n", &second);
8547 DoSleep( appData.delayAfterQuit );
8548 DestroyChildProcess(second.pr, second.useSigterm);
8553 if (matchMode && gameMode == TwoMachinesPlay) {
8556 if (first.twoMachinesColor[0] == 'w') {
8563 if (first.twoMachinesColor[0] == 'b') {
8572 if (matchGame < appData.matchGames) {
8574 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8575 tmp = first.twoMachinesColor;
8576 first.twoMachinesColor = second.twoMachinesColor;
8577 second.twoMachinesColor = tmp;
8579 gameMode = nextGameMode;
8581 if(appData.matchPause>10000 || appData.matchPause<10)
8582 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8583 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8584 endingGame = 0; /* [HGM] crash */
8588 gameMode = nextGameMode;
8589 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8590 first.tidy, second.tidy,
8591 first.matchWins, second.matchWins,
8592 appData.matchGames - (first.matchWins + second.matchWins));
8593 DisplayFatalError(buf, 0, 0);
8596 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8597 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8599 gameMode = nextGameMode;
8601 endingGame = 0; /* [HGM] crash */
8604 /* Assumes program was just initialized (initString sent).
8605 Leaves program in force mode. */
8607 FeedMovesToProgram(cps, upto)
8608 ChessProgramState *cps;
8613 if (appData.debugMode)
8614 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8615 startedFromSetupPosition ? "position and " : "",
8616 backwardMostMove, upto, cps->which);
8617 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8618 // [HGM] variantswitch: make engine aware of new variant
8619 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8620 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8621 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8622 SendToProgram(buf, cps);
8623 currentlyInitializedVariant = gameInfo.variant;
8625 SendToProgram("force\n", cps);
8626 if (startedFromSetupPosition) {
8627 SendBoard(cps, backwardMostMove);
8628 if (appData.debugMode) {
8629 fprintf(debugFP, "feedMoves\n");
8632 for (i = backwardMostMove; i < upto; i++) {
8633 SendMoveToProgram(i, cps);
8639 ResurrectChessProgram()
8641 /* The chess program may have exited.
8642 If so, restart it and feed it all the moves made so far. */
8644 if (appData.noChessProgram || first.pr != NoProc) return;
8646 StartChessProgram(&first);
8647 InitChessProgram(&first, FALSE);
8648 FeedMovesToProgram(&first, currentMove);
8650 if (!first.sendTime) {
8651 /* can't tell gnuchess what its clock should read,
8652 so we bow to its notion. */
8654 timeRemaining[0][currentMove] = whiteTimeRemaining;
8655 timeRemaining[1][currentMove] = blackTimeRemaining;
8658 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8659 appData.icsEngineAnalyze) && first.analysisSupport) {
8660 SendToProgram("analyze\n", &first);
8661 first.analyzing = TRUE;
8674 if (appData.debugMode) {
8675 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8676 redraw, init, gameMode);
8678 CleanupTail(); // [HGM] vari: delete any stored variations
8679 pausing = pauseExamInvalid = FALSE;
8680 startedFromSetupPosition = blackPlaysFirst = FALSE;
8682 whiteFlag = blackFlag = FALSE;
8683 userOfferedDraw = FALSE;
8684 hintRequested = bookRequested = FALSE;
8685 first.maybeThinking = FALSE;
8686 second.maybeThinking = FALSE;
8687 first.bookSuspend = FALSE; // [HGM] book
8688 second.bookSuspend = FALSE;
8689 thinkOutput[0] = NULLCHAR;
8690 lastHint[0] = NULLCHAR;
8691 ClearGameInfo(&gameInfo);
8692 gameInfo.variant = StringToVariant(appData.variant);
8693 ics_user_moved = ics_clock_paused = FALSE;
8694 ics_getting_history = H_FALSE;
8696 white_holding[0] = black_holding[0] = NULLCHAR;
8697 ClearProgramStats();
8698 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8702 flipView = appData.flipView;
8703 ClearPremoveHighlights();
8705 alarmSounded = FALSE;
8707 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8708 if(appData.serverMovesName != NULL) {
8709 /* [HGM] prepare to make moves file for broadcasting */
8710 clock_t t = clock();
8711 if(serverMoves != NULL) fclose(serverMoves);
8712 serverMoves = fopen(appData.serverMovesName, "r");
8713 if(serverMoves != NULL) {
8714 fclose(serverMoves);
8715 /* delay 15 sec before overwriting, so all clients can see end */
8716 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8718 serverMoves = fopen(appData.serverMovesName, "w");
8722 gameMode = BeginningOfGame;
8724 if(appData.icsActive) gameInfo.variant = VariantNormal;
8725 currentMove = forwardMostMove = backwardMostMove = 0;
8726 InitPosition(redraw);
8727 for (i = 0; i < MAX_MOVES; i++) {
8728 if (commentList[i] != NULL) {
8729 free(commentList[i]);
8730 commentList[i] = NULL;
8734 timeRemaining[0][0] = whiteTimeRemaining;
8735 timeRemaining[1][0] = blackTimeRemaining;
8736 if (first.pr == NULL) {
8737 StartChessProgram(&first);
8740 InitChessProgram(&first, startedFromSetupPosition);
8743 DisplayMessage("", "");
8744 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8745 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8752 if (!AutoPlayOneMove())
8754 if (matchMode || appData.timeDelay == 0)
8756 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8758 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8767 int fromX, fromY, toX, toY;
8769 if (appData.debugMode) {
8770 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8773 if (gameMode != PlayFromGameFile)
8776 if (currentMove >= forwardMostMove) {
8777 gameMode = EditGame;
8780 /* [AS] Clear current move marker at the end of a game */
8781 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8786 toX = moveList[currentMove][2] - AAA;
8787 toY = moveList[currentMove][3] - ONE;
8789 if (moveList[currentMove][1] == '@') {
8790 if (appData.highlightLastMove) {
8791 SetHighlights(-1, -1, toX, toY);
8794 fromX = moveList[currentMove][0] - AAA;
8795 fromY = moveList[currentMove][1] - ONE;
8797 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8799 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8801 if (appData.highlightLastMove) {
8802 SetHighlights(fromX, fromY, toX, toY);
8805 DisplayMove(currentMove);
8806 SendMoveToProgram(currentMove++, &first);
8807 DisplayBothClocks();
8808 DrawPosition(FALSE, boards[currentMove]);
8809 // [HGM] PV info: always display, routine tests if empty
8810 DisplayComment(currentMove - 1, commentList[currentMove]);
8816 LoadGameOneMove(readAhead)
8817 ChessMove readAhead;
8819 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8820 char promoChar = NULLCHAR;
8825 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8826 gameMode != AnalyzeMode && gameMode != Training) {
8831 yyboardindex = forwardMostMove;
8832 if (readAhead != (ChessMove)0) {
8833 moveType = readAhead;
8835 if (gameFileFP == NULL)
8837 moveType = (ChessMove) yylex();
8843 if (appData.debugMode)
8844 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8847 /* append the comment but don't display it */
8848 AppendComment(currentMove, p, FALSE);
8851 case WhiteCapturesEnPassant:
8852 case BlackCapturesEnPassant:
8853 case WhitePromotionChancellor:
8854 case BlackPromotionChancellor:
8855 case WhitePromotionArchbishop:
8856 case BlackPromotionArchbishop:
8857 case WhitePromotionCentaur:
8858 case BlackPromotionCentaur:
8859 case WhitePromotionQueen:
8860 case BlackPromotionQueen:
8861 case WhitePromotionRook:
8862 case BlackPromotionRook:
8863 case WhitePromotionBishop:
8864 case BlackPromotionBishop:
8865 case WhitePromotionKnight:
8866 case BlackPromotionKnight:
8867 case WhitePromotionKing:
8868 case BlackPromotionKing:
8870 case WhiteKingSideCastle:
8871 case WhiteQueenSideCastle:
8872 case BlackKingSideCastle:
8873 case BlackQueenSideCastle:
8874 case WhiteKingSideCastleWild:
8875 case WhiteQueenSideCastleWild:
8876 case BlackKingSideCastleWild:
8877 case BlackQueenSideCastleWild:
8879 case WhiteHSideCastleFR:
8880 case WhiteASideCastleFR:
8881 case BlackHSideCastleFR:
8882 case BlackASideCastleFR:
8884 if (appData.debugMode)
8885 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8886 fromX = currentMoveString[0] - AAA;
8887 fromY = currentMoveString[1] - ONE;
8888 toX = currentMoveString[2] - AAA;
8889 toY = currentMoveString[3] - ONE;
8890 promoChar = currentMoveString[4];
8895 if (appData.debugMode)
8896 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8897 fromX = moveType == WhiteDrop ?
8898 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8899 (int) CharToPiece(ToLower(currentMoveString[0]));
8901 toX = currentMoveString[2] - AAA;
8902 toY = currentMoveString[3] - ONE;
8908 case GameUnfinished:
8909 if (appData.debugMode)
8910 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8911 p = strchr(yy_text, '{');
8912 if (p == NULL) p = strchr(yy_text, '(');
8915 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8917 q = strchr(p, *p == '{' ? '}' : ')');
8918 if (q != NULL) *q = NULLCHAR;
8921 GameEnds(moveType, p, GE_FILE);
8923 if (cmailMsgLoaded) {
8925 flipView = WhiteOnMove(currentMove);
8926 if (moveType == GameUnfinished) flipView = !flipView;
8927 if (appData.debugMode)
8928 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8932 case (ChessMove) 0: /* end of file */
8933 if (appData.debugMode)
8934 fprintf(debugFP, "Parser hit end of file\n");
8935 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8941 if (WhiteOnMove(currentMove)) {
8942 GameEnds(BlackWins, "Black mates", GE_FILE);
8944 GameEnds(WhiteWins, "White mates", GE_FILE);
8948 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8955 if (lastLoadGameStart == GNUChessGame) {
8956 /* GNUChessGames have numbers, but they aren't move numbers */
8957 if (appData.debugMode)
8958 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8959 yy_text, (int) moveType);
8960 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8962 /* else fall thru */
8967 /* Reached start of next game in file */
8968 if (appData.debugMode)
8969 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8970 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8976 if (WhiteOnMove(currentMove)) {
8977 GameEnds(BlackWins, "Black mates", GE_FILE);
8979 GameEnds(WhiteWins, "White mates", GE_FILE);
8983 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8989 case PositionDiagram: /* should not happen; ignore */
8990 case ElapsedTime: /* ignore */
8991 case NAG: /* ignore */
8992 if (appData.debugMode)
8993 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8994 yy_text, (int) moveType);
8995 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8998 if (appData.testLegality) {
8999 if (appData.debugMode)
9000 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9001 sprintf(move, _("Illegal move: %d.%s%s"),
9002 (forwardMostMove / 2) + 1,
9003 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9004 DisplayError(move, 0);
9007 if (appData.debugMode)
9008 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9009 yy_text, currentMoveString);
9010 fromX = currentMoveString[0] - AAA;
9011 fromY = currentMoveString[1] - ONE;
9012 toX = currentMoveString[2] - AAA;
9013 toY = currentMoveString[3] - ONE;
9014 promoChar = currentMoveString[4];
9019 if (appData.debugMode)
9020 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9021 sprintf(move, _("Ambiguous move: %d.%s%s"),
9022 (forwardMostMove / 2) + 1,
9023 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9024 DisplayError(move, 0);
9029 case ImpossibleMove:
9030 if (appData.debugMode)
9031 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9032 sprintf(move, _("Illegal move: %d.%s%s"),
9033 (forwardMostMove / 2) + 1,
9034 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9035 DisplayError(move, 0);
9041 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9042 DrawPosition(FALSE, boards[currentMove]);
9043 DisplayBothClocks();
9044 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9045 DisplayComment(currentMove - 1, commentList[currentMove]);
9047 (void) StopLoadGameTimer();
9049 cmailOldMove = forwardMostMove;
9052 /* currentMoveString is set as a side-effect of yylex */
9053 strcat(currentMoveString, "\n");
9054 strcpy(moveList[forwardMostMove], currentMoveString);
9056 thinkOutput[0] = NULLCHAR;
9057 MakeMove(fromX, fromY, toX, toY, promoChar);
9058 currentMove = forwardMostMove;
9063 /* Load the nth game from the given file */
9065 LoadGameFromFile(filename, n, title, useList)
9069 /*Boolean*/ int useList;
9074 if (strcmp(filename, "-") == 0) {
9078 f = fopen(filename, "rb");
9080 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9081 DisplayError(buf, errno);
9085 if (fseek(f, 0, 0) == -1) {
9086 /* f is not seekable; probably a pipe */
9089 if (useList && n == 0) {
9090 int error = GameListBuild(f);
9092 DisplayError(_("Cannot build game list"), error);
9093 } else if (!ListEmpty(&gameList) &&
9094 ((ListGame *) gameList.tailPred)->number > 1) {
9095 GameListPopUp(f, title);
9102 return LoadGame(f, n, title, FALSE);
9107 MakeRegisteredMove()
9109 int fromX, fromY, toX, toY;
9111 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9112 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9115 if (appData.debugMode)
9116 fprintf(debugFP, "Restoring %s for game %d\n",
9117 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9119 thinkOutput[0] = NULLCHAR;
9120 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9121 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9122 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9123 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9124 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9125 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9126 MakeMove(fromX, fromY, toX, toY, promoChar);
9127 ShowMove(fromX, fromY, toX, toY);
9129 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9136 if (WhiteOnMove(currentMove)) {
9137 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9139 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9144 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9151 if (WhiteOnMove(currentMove)) {
9152 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9154 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9159 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9170 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9172 CmailLoadGame(f, gameNumber, title, useList)
9180 if (gameNumber > nCmailGames) {
9181 DisplayError(_("No more games in this message"), 0);
9184 if (f == lastLoadGameFP) {
9185 int offset = gameNumber - lastLoadGameNumber;
9187 cmailMsg[0] = NULLCHAR;
9188 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9189 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9190 nCmailMovesRegistered--;
9192 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9193 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9194 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9197 if (! RegisterMove()) return FALSE;
9201 retVal = LoadGame(f, gameNumber, title, useList);
9203 /* Make move registered during previous look at this game, if any */
9204 MakeRegisteredMove();
9206 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9207 commentList[currentMove]
9208 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9209 DisplayComment(currentMove - 1, commentList[currentMove]);
9215 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9220 int gameNumber = lastLoadGameNumber + offset;
9221 if (lastLoadGameFP == NULL) {
9222 DisplayError(_("No game has been loaded yet"), 0);
9225 if (gameNumber <= 0) {
9226 DisplayError(_("Can't back up any further"), 0);
9229 if (cmailMsgLoaded) {
9230 return CmailLoadGame(lastLoadGameFP, gameNumber,
9231 lastLoadGameTitle, lastLoadGameUseList);
9233 return LoadGame(lastLoadGameFP, gameNumber,
9234 lastLoadGameTitle, lastLoadGameUseList);
9240 /* Load the nth game from open file f */
9242 LoadGame(f, gameNumber, title, useList)
9250 int gn = gameNumber;
9251 ListGame *lg = NULL;
9254 GameMode oldGameMode;
9255 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9257 if (appData.debugMode)
9258 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9260 if (gameMode == Training )
9261 SetTrainingModeOff();
9263 oldGameMode = gameMode;
9264 if (gameMode != BeginningOfGame) {
9269 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9270 fclose(lastLoadGameFP);
9274 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9277 fseek(f, lg->offset, 0);
9278 GameListHighlight(gameNumber);
9282 DisplayError(_("Game number out of range"), 0);
9287 if (fseek(f, 0, 0) == -1) {
9288 if (f == lastLoadGameFP ?
9289 gameNumber == lastLoadGameNumber + 1 :
9293 DisplayError(_("Can't seek on game file"), 0);
9299 lastLoadGameNumber = gameNumber;
9300 strcpy(lastLoadGameTitle, title);
9301 lastLoadGameUseList = useList;
9305 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9306 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9307 lg->gameInfo.black);
9309 } else if (*title != NULLCHAR) {
9310 if (gameNumber > 1) {
9311 sprintf(buf, "%s %d", title, gameNumber);
9314 DisplayTitle(title);
9318 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9319 gameMode = PlayFromGameFile;
9323 currentMove = forwardMostMove = backwardMostMove = 0;
9324 CopyBoard(boards[0], initialPosition);
9328 * Skip the first gn-1 games in the file.
9329 * Also skip over anything that precedes an identifiable
9330 * start of game marker, to avoid being confused by
9331 * garbage at the start of the file. Currently
9332 * recognized start of game markers are the move number "1",
9333 * the pattern "gnuchess .* game", the pattern
9334 * "^[#;%] [^ ]* game file", and a PGN tag block.
9335 * A game that starts with one of the latter two patterns
9336 * will also have a move number 1, possibly
9337 * following a position diagram.
9338 * 5-4-02: Let's try being more lenient and allowing a game to
9339 * start with an unnumbered move. Does that break anything?
9341 cm = lastLoadGameStart = (ChessMove) 0;
9343 yyboardindex = forwardMostMove;
9344 cm = (ChessMove) yylex();
9347 if (cmailMsgLoaded) {
9348 nCmailGames = CMAIL_MAX_GAMES - gn;
9351 DisplayError(_("Game not found in file"), 0);
9358 lastLoadGameStart = cm;
9362 switch (lastLoadGameStart) {
9369 gn--; /* count this game */
9370 lastLoadGameStart = cm;
9379 switch (lastLoadGameStart) {
9384 gn--; /* count this game */
9385 lastLoadGameStart = cm;
9388 lastLoadGameStart = cm; /* game counted already */
9396 yyboardindex = forwardMostMove;
9397 cm = (ChessMove) yylex();
9398 } while (cm == PGNTag || cm == Comment);
9405 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9406 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9407 != CMAIL_OLD_RESULT) {
9409 cmailResult[ CMAIL_MAX_GAMES
9410 - gn - 1] = CMAIL_OLD_RESULT;
9416 /* Only a NormalMove can be at the start of a game
9417 * without a position diagram. */
9418 if (lastLoadGameStart == (ChessMove) 0) {
9420 lastLoadGameStart = MoveNumberOne;
9429 if (appData.debugMode)
9430 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9432 if (cm == XBoardGame) {
9433 /* Skip any header junk before position diagram and/or move 1 */
9435 yyboardindex = forwardMostMove;
9436 cm = (ChessMove) yylex();
9438 if (cm == (ChessMove) 0 ||
9439 cm == GNUChessGame || cm == XBoardGame) {
9440 /* Empty game; pretend end-of-file and handle later */
9445 if (cm == MoveNumberOne || cm == PositionDiagram ||
9446 cm == PGNTag || cm == Comment)
9449 } else if (cm == GNUChessGame) {
9450 if (gameInfo.event != NULL) {
9451 free(gameInfo.event);
9453 gameInfo.event = StrSave(yy_text);
9456 startedFromSetupPosition = FALSE;
9457 while (cm == PGNTag) {
9458 if (appData.debugMode)
9459 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9460 err = ParsePGNTag(yy_text, &gameInfo);
9461 if (!err) numPGNTags++;
9463 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9464 if(gameInfo.variant != oldVariant) {
9465 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9467 oldVariant = gameInfo.variant;
9468 if (appData.debugMode)
9469 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9473 if (gameInfo.fen != NULL) {
9474 Board initial_position;
9475 startedFromSetupPosition = TRUE;
9476 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9478 DisplayError(_("Bad FEN position in file"), 0);
9481 CopyBoard(boards[0], initial_position);
9482 if (blackPlaysFirst) {
9483 currentMove = forwardMostMove = backwardMostMove = 1;
9484 CopyBoard(boards[1], initial_position);
9485 strcpy(moveList[0], "");
9486 strcpy(parseList[0], "");
9487 timeRemaining[0][1] = whiteTimeRemaining;
9488 timeRemaining[1][1] = blackTimeRemaining;
9489 if (commentList[0] != NULL) {
9490 commentList[1] = commentList[0];
9491 commentList[0] = NULL;
9494 currentMove = forwardMostMove = backwardMostMove = 0;
9496 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9498 initialRulePlies = FENrulePlies;
9499 for( i=0; i< nrCastlingRights; i++ )
9500 initialRights[i] = initial_position[CASTLING][i];
9502 yyboardindex = forwardMostMove;
9504 gameInfo.fen = NULL;
9507 yyboardindex = forwardMostMove;
9508 cm = (ChessMove) yylex();
9510 /* Handle comments interspersed among the tags */
9511 while (cm == Comment) {
9513 if (appData.debugMode)
9514 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9516 AppendComment(currentMove, p, FALSE);
9517 yyboardindex = forwardMostMove;
9518 cm = (ChessMove) yylex();
9522 /* don't rely on existence of Event tag since if game was
9523 * pasted from clipboard the Event tag may not exist
9525 if (numPGNTags > 0){
9527 if (gameInfo.variant == VariantNormal) {
9528 gameInfo.variant = StringToVariant(gameInfo.event);
9531 if( appData.autoDisplayTags ) {
9532 tags = PGNTags(&gameInfo);
9533 TagsPopUp(tags, CmailMsg());
9538 /* Make something up, but don't display it now */
9543 if (cm == PositionDiagram) {
9546 Board initial_position;
9548 if (appData.debugMode)
9549 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9551 if (!startedFromSetupPosition) {
9553 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9554 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9564 initial_position[i][j++] = CharToPiece(*p);
9567 while (*p == ' ' || *p == '\t' ||
9568 *p == '\n' || *p == '\r') p++;
9570 if (strncmp(p, "black", strlen("black"))==0)
9571 blackPlaysFirst = TRUE;
9573 blackPlaysFirst = FALSE;
9574 startedFromSetupPosition = TRUE;
9576 CopyBoard(boards[0], initial_position);
9577 if (blackPlaysFirst) {
9578 currentMove = forwardMostMove = backwardMostMove = 1;
9579 CopyBoard(boards[1], initial_position);
9580 strcpy(moveList[0], "");
9581 strcpy(parseList[0], "");
9582 timeRemaining[0][1] = whiteTimeRemaining;
9583 timeRemaining[1][1] = blackTimeRemaining;
9584 if (commentList[0] != NULL) {
9585 commentList[1] = commentList[0];
9586 commentList[0] = NULL;
9589 currentMove = forwardMostMove = backwardMostMove = 0;
9592 yyboardindex = forwardMostMove;
9593 cm = (ChessMove) yylex();
9596 if (first.pr == NoProc) {
9597 StartChessProgram(&first);
9599 InitChessProgram(&first, FALSE);
9600 SendToProgram("force\n", &first);
9601 if (startedFromSetupPosition) {
9602 SendBoard(&first, forwardMostMove);
9603 if (appData.debugMode) {
9604 fprintf(debugFP, "Load Game\n");
9606 DisplayBothClocks();
9609 /* [HGM] server: flag to write setup moves in broadcast file as one */
9610 loadFlag = appData.suppressLoadMoves;
9612 while (cm == Comment) {
9614 if (appData.debugMode)
9615 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9617 AppendComment(currentMove, p, FALSE);
9618 yyboardindex = forwardMostMove;
9619 cm = (ChessMove) yylex();
9622 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9623 cm == WhiteWins || cm == BlackWins ||
9624 cm == GameIsDrawn || cm == GameUnfinished) {
9625 DisplayMessage("", _("No moves in game"));
9626 if (cmailMsgLoaded) {
9627 if (appData.debugMode)
9628 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9632 DrawPosition(FALSE, boards[currentMove]);
9633 DisplayBothClocks();
9634 gameMode = EditGame;
9641 // [HGM] PV info: routine tests if comment empty
9642 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9643 DisplayComment(currentMove - 1, commentList[currentMove]);
9645 if (!matchMode && appData.timeDelay != 0)
9646 DrawPosition(FALSE, boards[currentMove]);
9648 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9649 programStats.ok_to_send = 1;
9652 /* if the first token after the PGN tags is a move
9653 * and not move number 1, retrieve it from the parser
9655 if (cm != MoveNumberOne)
9656 LoadGameOneMove(cm);
9658 /* load the remaining moves from the file */
9659 while (LoadGameOneMove((ChessMove)0)) {
9660 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9661 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9664 /* rewind to the start of the game */
9665 currentMove = backwardMostMove;
9667 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9669 if (oldGameMode == AnalyzeFile ||
9670 oldGameMode == AnalyzeMode) {
9674 if (matchMode || appData.timeDelay == 0) {
9676 gameMode = EditGame;
9678 } else if (appData.timeDelay > 0) {
9682 if (appData.debugMode)
9683 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9685 loadFlag = 0; /* [HGM] true game starts */
9689 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9691 ReloadPosition(offset)
9694 int positionNumber = lastLoadPositionNumber + offset;
9695 if (lastLoadPositionFP == NULL) {
9696 DisplayError(_("No position has been loaded yet"), 0);
9699 if (positionNumber <= 0) {
9700 DisplayError(_("Can't back up any further"), 0);
9703 return LoadPosition(lastLoadPositionFP, positionNumber,
9704 lastLoadPositionTitle);
9707 /* Load the nth position from the given file */
9709 LoadPositionFromFile(filename, n, title)
9717 if (strcmp(filename, "-") == 0) {
9718 return LoadPosition(stdin, n, "stdin");
9720 f = fopen(filename, "rb");
9722 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9723 DisplayError(buf, errno);
9726 return LoadPosition(f, n, title);
9731 /* Load the nth position from the given open file, and close it */
9733 LoadPosition(f, positionNumber, title)
9738 char *p, line[MSG_SIZ];
9739 Board initial_position;
9740 int i, j, fenMode, pn;
9742 if (gameMode == Training )
9743 SetTrainingModeOff();
9745 if (gameMode != BeginningOfGame) {
9748 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9749 fclose(lastLoadPositionFP);
9751 if (positionNumber == 0) positionNumber = 1;
9752 lastLoadPositionFP = f;
9753 lastLoadPositionNumber = positionNumber;
9754 strcpy(lastLoadPositionTitle, title);
9755 if (first.pr == NoProc) {
9756 StartChessProgram(&first);
9757 InitChessProgram(&first, FALSE);
9759 pn = positionNumber;
9760 if (positionNumber < 0) {
9761 /* Negative position number means to seek to that byte offset */
9762 if (fseek(f, -positionNumber, 0) == -1) {
9763 DisplayError(_("Can't seek on position file"), 0);
9768 if (fseek(f, 0, 0) == -1) {
9769 if (f == lastLoadPositionFP ?
9770 positionNumber == lastLoadPositionNumber + 1 :
9771 positionNumber == 1) {
9774 DisplayError(_("Can't seek on position file"), 0);
9779 /* See if this file is FEN or old-style xboard */
9780 if (fgets(line, MSG_SIZ, f) == NULL) {
9781 DisplayError(_("Position not found in file"), 0);
9784 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9785 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9788 if (fenMode || line[0] == '#') pn--;
9790 /* skip positions before number pn */
9791 if (fgets(line, MSG_SIZ, f) == NULL) {
9793 DisplayError(_("Position not found in file"), 0);
9796 if (fenMode || line[0] == '#') pn--;
9801 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9802 DisplayError(_("Bad FEN position in file"), 0);
9806 (void) fgets(line, MSG_SIZ, f);
9807 (void) fgets(line, MSG_SIZ, f);
9809 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9810 (void) fgets(line, MSG_SIZ, f);
9811 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9814 initial_position[i][j++] = CharToPiece(*p);
9818 blackPlaysFirst = FALSE;
9820 (void) fgets(line, MSG_SIZ, f);
9821 if (strncmp(line, "black", strlen("black"))==0)
9822 blackPlaysFirst = TRUE;
9825 startedFromSetupPosition = TRUE;
9827 SendToProgram("force\n", &first);
9828 CopyBoard(boards[0], initial_position);
9829 if (blackPlaysFirst) {
9830 currentMove = forwardMostMove = backwardMostMove = 1;
9831 strcpy(moveList[0], "");
9832 strcpy(parseList[0], "");
9833 CopyBoard(boards[1], initial_position);
9834 DisplayMessage("", _("Black to play"));
9836 currentMove = forwardMostMove = backwardMostMove = 0;
9837 DisplayMessage("", _("White to play"));
9839 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9840 SendBoard(&first, forwardMostMove);
9841 if (appData.debugMode) {
9843 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9844 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9845 fprintf(debugFP, "Load Position\n");
9848 if (positionNumber > 1) {
9849 sprintf(line, "%s %d", title, positionNumber);
9852 DisplayTitle(title);
9854 gameMode = EditGame;
9857 timeRemaining[0][1] = whiteTimeRemaining;
9858 timeRemaining[1][1] = blackTimeRemaining;
9859 DrawPosition(FALSE, boards[currentMove]);
9866 CopyPlayerNameIntoFileName(dest, src)
9869 while (*src != NULLCHAR && *src != ',') {
9874 *(*dest)++ = *src++;
9879 char *DefaultFileName(ext)
9882 static char def[MSG_SIZ];
9885 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9887 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9889 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9898 /* Save the current game to the given file */
9900 SaveGameToFile(filename, append)
9907 if (strcmp(filename, "-") == 0) {
9908 return SaveGame(stdout, 0, NULL);
9910 f = fopen(filename, append ? "a" : "w");
9912 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9913 DisplayError(buf, errno);
9916 return SaveGame(f, 0, NULL);
9925 static char buf[MSG_SIZ];
9928 p = strchr(str, ' ');
9929 if (p == NULL) return str;
9930 strncpy(buf, str, p - str);
9931 buf[p - str] = NULLCHAR;
9935 #define PGN_MAX_LINE 75
9937 #define PGN_SIDE_WHITE 0
9938 #define PGN_SIDE_BLACK 1
9941 static int FindFirstMoveOutOfBook( int side )
9945 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9946 int index = backwardMostMove;
9947 int has_book_hit = 0;
9949 if( (index % 2) != side ) {
9953 while( index < forwardMostMove ) {
9954 /* Check to see if engine is in book */
9955 int depth = pvInfoList[index].depth;
9956 int score = pvInfoList[index].score;
9962 else if( score == 0 && depth == 63 ) {
9963 in_book = 1; /* Zappa */
9965 else if( score == 2 && depth == 99 ) {
9966 in_book = 1; /* Abrok */
9969 has_book_hit += in_book;
9985 void GetOutOfBookInfo( char * buf )
9989 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9991 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9992 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9996 if( oob[0] >= 0 || oob[1] >= 0 ) {
9997 for( i=0; i<2; i++ ) {
10001 if( i > 0 && oob[0] >= 0 ) {
10002 strcat( buf, " " );
10005 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10006 sprintf( buf+strlen(buf), "%s%.2f",
10007 pvInfoList[idx].score >= 0 ? "+" : "",
10008 pvInfoList[idx].score / 100.0 );
10014 /* Save game in PGN style and close the file */
10019 int i, offset, linelen, newblock;
10023 int movelen, numlen, blank;
10024 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10026 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10028 tm = time((time_t *) NULL);
10030 PrintPGNTags(f, &gameInfo);
10032 if (backwardMostMove > 0 || startedFromSetupPosition) {
10033 char *fen = PositionToFEN(backwardMostMove, NULL);
10034 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10035 fprintf(f, "\n{--------------\n");
10036 PrintPosition(f, backwardMostMove);
10037 fprintf(f, "--------------}\n");
10041 /* [AS] Out of book annotation */
10042 if( appData.saveOutOfBookInfo ) {
10045 GetOutOfBookInfo( buf );
10047 if( buf[0] != '\0' ) {
10048 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10055 i = backwardMostMove;
10059 while (i < forwardMostMove) {
10060 /* Print comments preceding this move */
10061 if (commentList[i] != NULL) {
10062 if (linelen > 0) fprintf(f, "\n");
10063 fprintf(f, "%s", commentList[i]);
10068 /* Format move number */
10069 if ((i % 2) == 0) {
10070 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10073 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10075 numtext[0] = NULLCHAR;
10078 numlen = strlen(numtext);
10081 /* Print move number */
10082 blank = linelen > 0 && numlen > 0;
10083 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10092 fprintf(f, "%s", numtext);
10096 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10097 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10100 blank = linelen > 0 && movelen > 0;
10101 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10110 fprintf(f, "%s", move_buffer);
10111 linelen += movelen;
10113 /* [AS] Add PV info if present */
10114 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10115 /* [HGM] add time */
10116 char buf[MSG_SIZ]; int seconds;
10118 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10120 if( seconds <= 0) buf[0] = 0; else
10121 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10122 seconds = (seconds + 4)/10; // round to full seconds
10123 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10124 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10127 sprintf( move_buffer, "{%s%.2f/%d%s}",
10128 pvInfoList[i].score >= 0 ? "+" : "",
10129 pvInfoList[i].score / 100.0,
10130 pvInfoList[i].depth,
10133 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10135 /* Print score/depth */
10136 blank = linelen > 0 && movelen > 0;
10137 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10146 fprintf(f, "%s", move_buffer);
10147 linelen += movelen;
10153 /* Start a new line */
10154 if (linelen > 0) fprintf(f, "\n");
10156 /* Print comments after last move */
10157 if (commentList[i] != NULL) {
10158 fprintf(f, "%s\n", commentList[i]);
10162 if (gameInfo.resultDetails != NULL &&
10163 gameInfo.resultDetails[0] != NULLCHAR) {
10164 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10165 PGNResult(gameInfo.result));
10167 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10171 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10175 /* Save game in old style and close the file */
10177 SaveGameOldStyle(f)
10183 tm = time((time_t *) NULL);
10185 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10188 if (backwardMostMove > 0 || startedFromSetupPosition) {
10189 fprintf(f, "\n[--------------\n");
10190 PrintPosition(f, backwardMostMove);
10191 fprintf(f, "--------------]\n");
10196 i = backwardMostMove;
10197 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10199 while (i < forwardMostMove) {
10200 if (commentList[i] != NULL) {
10201 fprintf(f, "[%s]\n", commentList[i]);
10204 if ((i % 2) == 1) {
10205 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10208 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10210 if (commentList[i] != NULL) {
10214 if (i >= forwardMostMove) {
10218 fprintf(f, "%s\n", parseList[i]);
10223 if (commentList[i] != NULL) {
10224 fprintf(f, "[%s]\n", commentList[i]);
10227 /* This isn't really the old style, but it's close enough */
10228 if (gameInfo.resultDetails != NULL &&
10229 gameInfo.resultDetails[0] != NULLCHAR) {
10230 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10231 gameInfo.resultDetails);
10233 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10240 /* Save the current game to open file f and close the file */
10242 SaveGame(f, dummy, dummy2)
10247 if (gameMode == EditPosition) EditPositionDone(TRUE);
10248 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10249 if (appData.oldSaveStyle)
10250 return SaveGameOldStyle(f);
10252 return SaveGamePGN(f);
10255 /* Save the current position to the given file */
10257 SavePositionToFile(filename)
10263 if (strcmp(filename, "-") == 0) {
10264 return SavePosition(stdout, 0, NULL);
10266 f = fopen(filename, "a");
10268 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10269 DisplayError(buf, errno);
10272 SavePosition(f, 0, NULL);
10278 /* Save the current position to the given open file and close the file */
10280 SavePosition(f, dummy, dummy2)
10288 if (gameMode == EditPosition) EditPositionDone(TRUE);
10289 if (appData.oldSaveStyle) {
10290 tm = time((time_t *) NULL);
10292 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10294 fprintf(f, "[--------------\n");
10295 PrintPosition(f, currentMove);
10296 fprintf(f, "--------------]\n");
10298 fen = PositionToFEN(currentMove, NULL);
10299 fprintf(f, "%s\n", fen);
10307 ReloadCmailMsgEvent(unregister)
10311 static char *inFilename = NULL;
10312 static char *outFilename;
10314 struct stat inbuf, outbuf;
10317 /* Any registered moves are unregistered if unregister is set, */
10318 /* i.e. invoked by the signal handler */
10320 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10321 cmailMoveRegistered[i] = FALSE;
10322 if (cmailCommentList[i] != NULL) {
10323 free(cmailCommentList[i]);
10324 cmailCommentList[i] = NULL;
10327 nCmailMovesRegistered = 0;
10330 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10331 cmailResult[i] = CMAIL_NOT_RESULT;
10335 if (inFilename == NULL) {
10336 /* Because the filenames are static they only get malloced once */
10337 /* and they never get freed */
10338 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10339 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10341 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10342 sprintf(outFilename, "%s.out", appData.cmailGameName);
10345 status = stat(outFilename, &outbuf);
10347 cmailMailedMove = FALSE;
10349 status = stat(inFilename, &inbuf);
10350 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10353 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10354 counts the games, notes how each one terminated, etc.
10356 It would be nice to remove this kludge and instead gather all
10357 the information while building the game list. (And to keep it
10358 in the game list nodes instead of having a bunch of fixed-size
10359 parallel arrays.) Note this will require getting each game's
10360 termination from the PGN tags, as the game list builder does
10361 not process the game moves. --mann
10363 cmailMsgLoaded = TRUE;
10364 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10366 /* Load first game in the file or popup game menu */
10367 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10369 #endif /* !WIN32 */
10377 char string[MSG_SIZ];
10379 if ( cmailMailedMove
10380 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10381 return TRUE; /* Allow free viewing */
10384 /* Unregister move to ensure that we don't leave RegisterMove */
10385 /* with the move registered when the conditions for registering no */
10387 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10388 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10389 nCmailMovesRegistered --;
10391 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10393 free(cmailCommentList[lastLoadGameNumber - 1]);
10394 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10398 if (cmailOldMove == -1) {
10399 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10403 if (currentMove > cmailOldMove + 1) {
10404 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10408 if (currentMove < cmailOldMove) {
10409 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10413 if (forwardMostMove > currentMove) {
10414 /* Silently truncate extra moves */
10418 if ( (currentMove == cmailOldMove + 1)
10419 || ( (currentMove == cmailOldMove)
10420 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10421 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10422 if (gameInfo.result != GameUnfinished) {
10423 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10426 if (commentList[currentMove] != NULL) {
10427 cmailCommentList[lastLoadGameNumber - 1]
10428 = StrSave(commentList[currentMove]);
10430 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10432 if (appData.debugMode)
10433 fprintf(debugFP, "Saving %s for game %d\n",
10434 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10437 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10439 f = fopen(string, "w");
10440 if (appData.oldSaveStyle) {
10441 SaveGameOldStyle(f); /* also closes the file */
10443 sprintf(string, "%s.pos.out", appData.cmailGameName);
10444 f = fopen(string, "w");
10445 SavePosition(f, 0, NULL); /* also closes the file */
10447 fprintf(f, "{--------------\n");
10448 PrintPosition(f, currentMove);
10449 fprintf(f, "--------------}\n\n");
10451 SaveGame(f, 0, NULL); /* also closes the file*/
10454 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10455 nCmailMovesRegistered ++;
10456 } else if (nCmailGames == 1) {
10457 DisplayError(_("You have not made a move yet"), 0);
10468 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10469 FILE *commandOutput;
10470 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10471 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10477 if (! cmailMsgLoaded) {
10478 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10482 if (nCmailGames == nCmailResults) {
10483 DisplayError(_("No unfinished games"), 0);
10487 #if CMAIL_PROHIBIT_REMAIL
10488 if (cmailMailedMove) {
10489 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);
10490 DisplayError(msg, 0);
10495 if (! (cmailMailedMove || RegisterMove())) return;
10497 if ( cmailMailedMove
10498 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10499 sprintf(string, partCommandString,
10500 appData.debugMode ? " -v" : "", appData.cmailGameName);
10501 commandOutput = popen(string, "r");
10503 if (commandOutput == NULL) {
10504 DisplayError(_("Failed to invoke cmail"), 0);
10506 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10507 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10509 if (nBuffers > 1) {
10510 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10511 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10512 nBytes = MSG_SIZ - 1;
10514 (void) memcpy(msg, buffer, nBytes);
10516 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10518 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10519 cmailMailedMove = TRUE; /* Prevent >1 moves */
10522 for (i = 0; i < nCmailGames; i ++) {
10523 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10528 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10530 sprintf(buffer, "%s/%s.%s.archive",
10532 appData.cmailGameName,
10534 LoadGameFromFile(buffer, 1, buffer, FALSE);
10535 cmailMsgLoaded = FALSE;
10539 DisplayInformation(msg);
10540 pclose(commandOutput);
10543 if ((*cmailMsg) != '\0') {
10544 DisplayInformation(cmailMsg);
10549 #endif /* !WIN32 */
10558 int prependComma = 0;
10560 char string[MSG_SIZ]; /* Space for game-list */
10563 if (!cmailMsgLoaded) return "";
10565 if (cmailMailedMove) {
10566 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10568 /* Create a list of games left */
10569 sprintf(string, "[");
10570 for (i = 0; i < nCmailGames; i ++) {
10571 if (! ( cmailMoveRegistered[i]
10572 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10573 if (prependComma) {
10574 sprintf(number, ",%d", i + 1);
10576 sprintf(number, "%d", i + 1);
10580 strcat(string, number);
10583 strcat(string, "]");
10585 if (nCmailMovesRegistered + nCmailResults == 0) {
10586 switch (nCmailGames) {
10589 _("Still need to make move for game\n"));
10594 _("Still need to make moves for both games\n"));
10599 _("Still need to make moves for all %d games\n"),
10604 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10607 _("Still need to make a move for game %s\n"),
10612 if (nCmailResults == nCmailGames) {
10613 sprintf(cmailMsg, _("No unfinished games\n"));
10615 sprintf(cmailMsg, _("Ready to send mail\n"));
10621 _("Still need to make moves for games %s\n"),
10633 if (gameMode == Training)
10634 SetTrainingModeOff();
10637 cmailMsgLoaded = FALSE;
10638 if (appData.icsActive) {
10639 SendToICS(ics_prefix);
10640 SendToICS("refresh\n");
10650 /* Give up on clean exit */
10654 /* Keep trying for clean exit */
10658 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10660 if (telnetISR != NULL) {
10661 RemoveInputSource(telnetISR);
10663 if (icsPR != NoProc) {
10664 DestroyChildProcess(icsPR, TRUE);
10667 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10668 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10670 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10671 /* make sure this other one finishes before killing it! */
10672 if(endingGame) { int count = 0;
10673 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10674 while(endingGame && count++ < 10) DoSleep(1);
10675 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10678 /* Kill off chess programs */
10679 if (first.pr != NoProc) {
10682 DoSleep( appData.delayBeforeQuit );
10683 SendToProgram("quit\n", &first);
10684 DoSleep( appData.delayAfterQuit );
10685 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10687 if (second.pr != NoProc) {
10688 DoSleep( appData.delayBeforeQuit );
10689 SendToProgram("quit\n", &second);
10690 DoSleep( appData.delayAfterQuit );
10691 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10693 if (first.isr != NULL) {
10694 RemoveInputSource(first.isr);
10696 if (second.isr != NULL) {
10697 RemoveInputSource(second.isr);
10700 ShutDownFrontEnd();
10707 if (appData.debugMode)
10708 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10712 if (gameMode == MachinePlaysWhite ||
10713 gameMode == MachinePlaysBlack) {
10716 DisplayBothClocks();
10718 if (gameMode == PlayFromGameFile) {
10719 if (appData.timeDelay >= 0)
10720 AutoPlayGameLoop();
10721 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10722 Reset(FALSE, TRUE);
10723 SendToICS(ics_prefix);
10724 SendToICS("refresh\n");
10725 } else if (currentMove < forwardMostMove) {
10726 ForwardInner(forwardMostMove);
10728 pauseExamInvalid = FALSE;
10730 switch (gameMode) {
10734 pauseExamForwardMostMove = forwardMostMove;
10735 pauseExamInvalid = FALSE;
10738 case IcsPlayingWhite:
10739 case IcsPlayingBlack:
10743 case PlayFromGameFile:
10744 (void) StopLoadGameTimer();
10748 case BeginningOfGame:
10749 if (appData.icsActive) return;
10750 /* else fall through */
10751 case MachinePlaysWhite:
10752 case MachinePlaysBlack:
10753 case TwoMachinesPlay:
10754 if (forwardMostMove == 0)
10755 return; /* don't pause if no one has moved */
10756 if ((gameMode == MachinePlaysWhite &&
10757 !WhiteOnMove(forwardMostMove)) ||
10758 (gameMode == MachinePlaysBlack &&
10759 WhiteOnMove(forwardMostMove))) {
10772 char title[MSG_SIZ];
10774 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10775 strcpy(title, _("Edit comment"));
10777 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10778 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10779 parseList[currentMove - 1]);
10782 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10789 char *tags = PGNTags(&gameInfo);
10790 EditTagsPopUp(tags);
10797 if (appData.noChessProgram || gameMode == AnalyzeMode)
10800 if (gameMode != AnalyzeFile) {
10801 if (!appData.icsEngineAnalyze) {
10803 if (gameMode != EditGame) return;
10805 ResurrectChessProgram();
10806 SendToProgram("analyze\n", &first);
10807 first.analyzing = TRUE;
10808 /*first.maybeThinking = TRUE;*/
10809 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10810 EngineOutputPopUp();
10812 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10817 StartAnalysisClock();
10818 GetTimeMark(&lastNodeCountTime);
10825 if (appData.noChessProgram || gameMode == AnalyzeFile)
10828 if (gameMode != AnalyzeMode) {
10830 if (gameMode != EditGame) return;
10831 ResurrectChessProgram();
10832 SendToProgram("analyze\n", &first);
10833 first.analyzing = TRUE;
10834 /*first.maybeThinking = TRUE;*/
10835 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10836 EngineOutputPopUp();
10838 gameMode = AnalyzeFile;
10843 StartAnalysisClock();
10844 GetTimeMark(&lastNodeCountTime);
10849 MachineWhiteEvent()
10852 char *bookHit = NULL;
10854 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10858 if (gameMode == PlayFromGameFile ||
10859 gameMode == TwoMachinesPlay ||
10860 gameMode == Training ||
10861 gameMode == AnalyzeMode ||
10862 gameMode == EndOfGame)
10865 if (gameMode == EditPosition)
10866 EditPositionDone(TRUE);
10868 if (!WhiteOnMove(currentMove)) {
10869 DisplayError(_("It is not White's turn"), 0);
10873 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10876 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10877 gameMode == AnalyzeFile)
10880 ResurrectChessProgram(); /* in case it isn't running */
10881 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10882 gameMode = MachinePlaysWhite;
10885 gameMode = MachinePlaysWhite;
10889 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10891 if (first.sendName) {
10892 sprintf(buf, "name %s\n", gameInfo.black);
10893 SendToProgram(buf, &first);
10895 if (first.sendTime) {
10896 if (first.useColors) {
10897 SendToProgram("black\n", &first); /*gnu kludge*/
10899 SendTimeRemaining(&first, TRUE);
10901 if (first.useColors) {
10902 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10904 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10905 SetMachineThinkingEnables();
10906 first.maybeThinking = TRUE;
10910 if (appData.autoFlipView && !flipView) {
10911 flipView = !flipView;
10912 DrawPosition(FALSE, NULL);
10913 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10916 if(bookHit) { // [HGM] book: simulate book reply
10917 static char bookMove[MSG_SIZ]; // a bit generous?
10919 programStats.nodes = programStats.depth = programStats.time =
10920 programStats.score = programStats.got_only_move = 0;
10921 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10923 strcpy(bookMove, "move ");
10924 strcat(bookMove, bookHit);
10925 HandleMachineMove(bookMove, &first);
10930 MachineBlackEvent()
10933 char *bookHit = NULL;
10935 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10939 if (gameMode == PlayFromGameFile ||
10940 gameMode == TwoMachinesPlay ||
10941 gameMode == Training ||
10942 gameMode == AnalyzeMode ||
10943 gameMode == EndOfGame)
10946 if (gameMode == EditPosition)
10947 EditPositionDone(TRUE);
10949 if (WhiteOnMove(currentMove)) {
10950 DisplayError(_("It is not Black's turn"), 0);
10954 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10957 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10958 gameMode == AnalyzeFile)
10961 ResurrectChessProgram(); /* in case it isn't running */
10962 gameMode = MachinePlaysBlack;
10966 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10968 if (first.sendName) {
10969 sprintf(buf, "name %s\n", gameInfo.white);
10970 SendToProgram(buf, &first);
10972 if (first.sendTime) {
10973 if (first.useColors) {
10974 SendToProgram("white\n", &first); /*gnu kludge*/
10976 SendTimeRemaining(&first, FALSE);
10978 if (first.useColors) {
10979 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10981 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10982 SetMachineThinkingEnables();
10983 first.maybeThinking = TRUE;
10986 if (appData.autoFlipView && flipView) {
10987 flipView = !flipView;
10988 DrawPosition(FALSE, NULL);
10989 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10991 if(bookHit) { // [HGM] book: simulate book reply
10992 static char bookMove[MSG_SIZ]; // a bit generous?
10994 programStats.nodes = programStats.depth = programStats.time =
10995 programStats.score = programStats.got_only_move = 0;
10996 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10998 strcpy(bookMove, "move ");
10999 strcat(bookMove, bookHit);
11000 HandleMachineMove(bookMove, &first);
11006 DisplayTwoMachinesTitle()
11009 if (appData.matchGames > 0) {
11010 if (first.twoMachinesColor[0] == 'w') {
11011 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11012 gameInfo.white, gameInfo.black,
11013 first.matchWins, second.matchWins,
11014 matchGame - 1 - (first.matchWins + second.matchWins));
11016 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11017 gameInfo.white, gameInfo.black,
11018 second.matchWins, first.matchWins,
11019 matchGame - 1 - (first.matchWins + second.matchWins));
11022 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11028 TwoMachinesEvent P((void))
11032 ChessProgramState *onmove;
11033 char *bookHit = NULL;
11035 if (appData.noChessProgram) return;
11037 switch (gameMode) {
11038 case TwoMachinesPlay:
11040 case MachinePlaysWhite:
11041 case MachinePlaysBlack:
11042 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11043 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11047 case BeginningOfGame:
11048 case PlayFromGameFile:
11051 if (gameMode != EditGame) return;
11054 EditPositionDone(TRUE);
11065 // forwardMostMove = currentMove;
11066 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11067 ResurrectChessProgram(); /* in case first program isn't running */
11069 if (second.pr == NULL) {
11070 StartChessProgram(&second);
11071 if (second.protocolVersion == 1) {
11072 TwoMachinesEventIfReady();
11074 /* kludge: allow timeout for initial "feature" command */
11076 DisplayMessage("", _("Starting second chess program"));
11077 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11081 DisplayMessage("", "");
11082 InitChessProgram(&second, FALSE);
11083 SendToProgram("force\n", &second);
11084 if (startedFromSetupPosition) {
11085 SendBoard(&second, backwardMostMove);
11086 if (appData.debugMode) {
11087 fprintf(debugFP, "Two Machines\n");
11090 for (i = backwardMostMove; i < forwardMostMove; i++) {
11091 SendMoveToProgram(i, &second);
11094 gameMode = TwoMachinesPlay;
11098 DisplayTwoMachinesTitle();
11100 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11106 SendToProgram(first.computerString, &first);
11107 if (first.sendName) {
11108 sprintf(buf, "name %s\n", second.tidy);
11109 SendToProgram(buf, &first);
11111 SendToProgram(second.computerString, &second);
11112 if (second.sendName) {
11113 sprintf(buf, "name %s\n", first.tidy);
11114 SendToProgram(buf, &second);
11118 if (!first.sendTime || !second.sendTime) {
11119 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11120 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11122 if (onmove->sendTime) {
11123 if (onmove->useColors) {
11124 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11126 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11128 if (onmove->useColors) {
11129 SendToProgram(onmove->twoMachinesColor, onmove);
11131 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11132 // SendToProgram("go\n", onmove);
11133 onmove->maybeThinking = TRUE;
11134 SetMachineThinkingEnables();
11138 if(bookHit) { // [HGM] book: simulate book reply
11139 static char bookMove[MSG_SIZ]; // a bit generous?
11141 programStats.nodes = programStats.depth = programStats.time =
11142 programStats.score = programStats.got_only_move = 0;
11143 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11145 strcpy(bookMove, "move ");
11146 strcat(bookMove, bookHit);
11147 savedMessage = bookMove; // args for deferred call
11148 savedState = onmove;
11149 ScheduleDelayedEvent(DeferredBookMove, 1);
11156 if (gameMode == Training) {
11157 SetTrainingModeOff();
11158 gameMode = PlayFromGameFile;
11159 DisplayMessage("", _("Training mode off"));
11161 gameMode = Training;
11162 animateTraining = appData.animate;
11164 /* make sure we are not already at the end of the game */
11165 if (currentMove < forwardMostMove) {
11166 SetTrainingModeOn();
11167 DisplayMessage("", _("Training mode on"));
11169 gameMode = PlayFromGameFile;
11170 DisplayError(_("Already at end of game"), 0);
11179 if (!appData.icsActive) return;
11180 switch (gameMode) {
11181 case IcsPlayingWhite:
11182 case IcsPlayingBlack:
11185 case BeginningOfGame:
11193 EditPositionDone(TRUE);
11206 gameMode = IcsIdle;
11217 switch (gameMode) {
11219 SetTrainingModeOff();
11221 case MachinePlaysWhite:
11222 case MachinePlaysBlack:
11223 case BeginningOfGame:
11224 SendToProgram("force\n", &first);
11225 SetUserThinkingEnables();
11227 case PlayFromGameFile:
11228 (void) StopLoadGameTimer();
11229 if (gameFileFP != NULL) {
11234 EditPositionDone(TRUE);
11239 SendToProgram("force\n", &first);
11241 case TwoMachinesPlay:
11242 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11243 ResurrectChessProgram();
11244 SetUserThinkingEnables();
11247 ResurrectChessProgram();
11249 case IcsPlayingBlack:
11250 case IcsPlayingWhite:
11251 DisplayError(_("Warning: You are still playing a game"), 0);
11254 DisplayError(_("Warning: You are still observing a game"), 0);
11257 DisplayError(_("Warning: You are still examining a game"), 0);
11268 first.offeredDraw = second.offeredDraw = 0;
11270 if (gameMode == PlayFromGameFile) {
11271 whiteTimeRemaining = timeRemaining[0][currentMove];
11272 blackTimeRemaining = timeRemaining[1][currentMove];
11276 if (gameMode == MachinePlaysWhite ||
11277 gameMode == MachinePlaysBlack ||
11278 gameMode == TwoMachinesPlay ||
11279 gameMode == EndOfGame) {
11280 i = forwardMostMove;
11281 while (i > currentMove) {
11282 SendToProgram("undo\n", &first);
11285 whiteTimeRemaining = timeRemaining[0][currentMove];
11286 blackTimeRemaining = timeRemaining[1][currentMove];
11287 DisplayBothClocks();
11288 if (whiteFlag || blackFlag) {
11289 whiteFlag = blackFlag = 0;
11294 gameMode = EditGame;
11301 EditPositionEvent()
11303 if (gameMode == EditPosition) {
11309 if (gameMode != EditGame) return;
11311 gameMode = EditPosition;
11314 if (currentMove > 0)
11315 CopyBoard(boards[0], boards[currentMove]);
11317 blackPlaysFirst = !WhiteOnMove(currentMove);
11319 currentMove = forwardMostMove = backwardMostMove = 0;
11320 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11327 /* [DM] icsEngineAnalyze - possible call from other functions */
11328 if (appData.icsEngineAnalyze) {
11329 appData.icsEngineAnalyze = FALSE;
11331 DisplayMessage("",_("Close ICS engine analyze..."));
11333 if (first.analysisSupport && first.analyzing) {
11334 SendToProgram("exit\n", &first);
11335 first.analyzing = FALSE;
11337 thinkOutput[0] = NULLCHAR;
11341 EditPositionDone(Boolean fakeRights)
11343 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11345 startedFromSetupPosition = TRUE;
11346 InitChessProgram(&first, FALSE);
11347 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11348 boards[0][EP_STATUS] = EP_NONE;
11349 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11350 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11351 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11352 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11353 } else boards[0][CASTLING][2] = NoRights;
11354 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11355 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11356 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11357 } else boards[0][CASTLING][5] = NoRights;
11359 SendToProgram("force\n", &first);
11360 if (blackPlaysFirst) {
11361 strcpy(moveList[0], "");
11362 strcpy(parseList[0], "");
11363 currentMove = forwardMostMove = backwardMostMove = 1;
11364 CopyBoard(boards[1], boards[0]);
11366 currentMove = forwardMostMove = backwardMostMove = 0;
11368 SendBoard(&first, forwardMostMove);
11369 if (appData.debugMode) {
11370 fprintf(debugFP, "EditPosDone\n");
11373 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11374 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11375 gameMode = EditGame;
11377 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11378 ClearHighlights(); /* [AS] */
11381 /* Pause for `ms' milliseconds */
11382 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11392 } while (SubtractTimeMarks(&m2, &m1) < ms);
11395 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11397 SendMultiLineToICS(buf)
11400 char temp[MSG_SIZ+1], *p;
11407 strncpy(temp, buf, len);
11412 if (*p == '\n' || *p == '\r')
11417 strcat(temp, "\n");
11419 SendToPlayer(temp, strlen(temp));
11423 SetWhiteToPlayEvent()
11425 if (gameMode == EditPosition) {
11426 blackPlaysFirst = FALSE;
11427 DisplayBothClocks(); /* works because currentMove is 0 */
11428 } else if (gameMode == IcsExamining) {
11429 SendToICS(ics_prefix);
11430 SendToICS("tomove white\n");
11435 SetBlackToPlayEvent()
11437 if (gameMode == EditPosition) {
11438 blackPlaysFirst = TRUE;
11439 currentMove = 1; /* kludge */
11440 DisplayBothClocks();
11442 } else if (gameMode == IcsExamining) {
11443 SendToICS(ics_prefix);
11444 SendToICS("tomove black\n");
11449 EditPositionMenuEvent(selection, x, y)
11450 ChessSquare selection;
11454 ChessSquare piece = boards[0][y][x];
11456 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11458 switch (selection) {
11460 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11461 SendToICS(ics_prefix);
11462 SendToICS("bsetup clear\n");
11463 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11464 SendToICS(ics_prefix);
11465 SendToICS("clearboard\n");
11467 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11468 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11469 for (y = 0; y < BOARD_HEIGHT; y++) {
11470 if (gameMode == IcsExamining) {
11471 if (boards[currentMove][y][x] != EmptySquare) {
11472 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11477 boards[0][y][x] = p;
11482 if (gameMode == EditPosition) {
11483 DrawPosition(FALSE, boards[0]);
11488 SetWhiteToPlayEvent();
11492 SetBlackToPlayEvent();
11496 if (gameMode == IcsExamining) {
11497 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11498 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11501 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11502 if(x == BOARD_LEFT-2) {
11503 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11504 boards[0][y][1] = 0;
11506 if(x == BOARD_RGHT+1) {
11507 if(y >= gameInfo.holdingsSize) break;
11508 boards[0][y][BOARD_WIDTH-2] = 0;
11511 boards[0][y][x] = EmptySquare;
11512 DrawPosition(FALSE, boards[0]);
11517 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11518 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11519 selection = (ChessSquare) (PROMOTED piece);
11520 } else if(piece == EmptySquare) selection = WhiteSilver;
11521 else selection = (ChessSquare)((int)piece - 1);
11525 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11526 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11527 selection = (ChessSquare) (DEMOTED piece);
11528 } else if(piece == EmptySquare) selection = BlackSilver;
11529 else selection = (ChessSquare)((int)piece + 1);
11534 if(gameInfo.variant == VariantShatranj ||
11535 gameInfo.variant == VariantXiangqi ||
11536 gameInfo.variant == VariantCourier )
11537 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11542 if(gameInfo.variant == VariantXiangqi)
11543 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11544 if(gameInfo.variant == VariantKnightmate)
11545 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11548 if (gameMode == IcsExamining) {
11549 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11550 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11551 PieceToChar(selection), AAA + x, ONE + y);
11554 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11556 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11557 n = PieceToNumber(selection - BlackPawn);
11558 if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11559 boards[0][BOARD_HEIGHT-1-n][0] = selection;
11560 boards[0][BOARD_HEIGHT-1-n][1]++;
11562 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11563 n = PieceToNumber(selection);
11564 if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11565 boards[0][n][BOARD_WIDTH-1] = selection;
11566 boards[0][n][BOARD_WIDTH-2]++;
11569 boards[0][y][x] = selection;
11570 DrawPosition(TRUE, boards[0]);
11578 DropMenuEvent(selection, x, y)
11579 ChessSquare selection;
11582 ChessMove moveType;
11584 switch (gameMode) {
11585 case IcsPlayingWhite:
11586 case MachinePlaysBlack:
11587 if (!WhiteOnMove(currentMove)) {
11588 DisplayMoveError(_("It is Black's turn"));
11591 moveType = WhiteDrop;
11593 case IcsPlayingBlack:
11594 case MachinePlaysWhite:
11595 if (WhiteOnMove(currentMove)) {
11596 DisplayMoveError(_("It is White's turn"));
11599 moveType = BlackDrop;
11602 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11608 if (moveType == BlackDrop && selection < BlackPawn) {
11609 selection = (ChessSquare) ((int) selection
11610 + (int) BlackPawn - (int) WhitePawn);
11612 if (boards[currentMove][y][x] != EmptySquare) {
11613 DisplayMoveError(_("That square is occupied"));
11617 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11623 /* Accept a pending offer of any kind from opponent */
11625 if (appData.icsActive) {
11626 SendToICS(ics_prefix);
11627 SendToICS("accept\n");
11628 } else if (cmailMsgLoaded) {
11629 if (currentMove == cmailOldMove &&
11630 commentList[cmailOldMove] != NULL &&
11631 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11632 "Black offers a draw" : "White offers a draw")) {
11634 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11635 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11637 DisplayError(_("There is no pending offer on this move"), 0);
11638 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11641 /* Not used for offers from chess program */
11648 /* Decline a pending offer of any kind from opponent */
11650 if (appData.icsActive) {
11651 SendToICS(ics_prefix);
11652 SendToICS("decline\n");
11653 } else if (cmailMsgLoaded) {
11654 if (currentMove == cmailOldMove &&
11655 commentList[cmailOldMove] != NULL &&
11656 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11657 "Black offers a draw" : "White offers a draw")) {
11659 AppendComment(cmailOldMove, "Draw declined", TRUE);
11660 DisplayComment(cmailOldMove - 1, "Draw declined");
11663 DisplayError(_("There is no pending offer on this move"), 0);
11666 /* Not used for offers from chess program */
11673 /* Issue ICS rematch command */
11674 if (appData.icsActive) {
11675 SendToICS(ics_prefix);
11676 SendToICS("rematch\n");
11683 /* Call your opponent's flag (claim a win on time) */
11684 if (appData.icsActive) {
11685 SendToICS(ics_prefix);
11686 SendToICS("flag\n");
11688 switch (gameMode) {
11691 case MachinePlaysWhite:
11694 GameEnds(GameIsDrawn, "Both players ran out of time",
11697 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11699 DisplayError(_("Your opponent is not out of time"), 0);
11702 case MachinePlaysBlack:
11705 GameEnds(GameIsDrawn, "Both players ran out of time",
11708 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11710 DisplayError(_("Your opponent is not out of time"), 0);
11720 /* Offer draw or accept pending draw offer from opponent */
11722 if (appData.icsActive) {
11723 /* Note: tournament rules require draw offers to be
11724 made after you make your move but before you punch
11725 your clock. Currently ICS doesn't let you do that;
11726 instead, you immediately punch your clock after making
11727 a move, but you can offer a draw at any time. */
11729 SendToICS(ics_prefix);
11730 SendToICS("draw\n");
11731 } else if (cmailMsgLoaded) {
11732 if (currentMove == cmailOldMove &&
11733 commentList[cmailOldMove] != NULL &&
11734 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11735 "Black offers a draw" : "White offers a draw")) {
11736 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11737 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11738 } else if (currentMove == cmailOldMove + 1) {
11739 char *offer = WhiteOnMove(cmailOldMove) ?
11740 "White offers a draw" : "Black offers a draw";
11741 AppendComment(currentMove, offer, TRUE);
11742 DisplayComment(currentMove - 1, offer);
11743 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11745 DisplayError(_("You must make your move before offering a draw"), 0);
11746 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11748 } else if (first.offeredDraw) {
11749 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11751 if (first.sendDrawOffers) {
11752 SendToProgram("draw\n", &first);
11753 userOfferedDraw = TRUE;
11761 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11763 if (appData.icsActive) {
11764 SendToICS(ics_prefix);
11765 SendToICS("adjourn\n");
11767 /* Currently GNU Chess doesn't offer or accept Adjourns */
11775 /* Offer Abort or accept pending Abort offer from opponent */
11777 if (appData.icsActive) {
11778 SendToICS(ics_prefix);
11779 SendToICS("abort\n");
11781 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11788 /* Resign. You can do this even if it's not your turn. */
11790 if (appData.icsActive) {
11791 SendToICS(ics_prefix);
11792 SendToICS("resign\n");
11794 switch (gameMode) {
11795 case MachinePlaysWhite:
11796 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11798 case MachinePlaysBlack:
11799 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11802 if (cmailMsgLoaded) {
11804 if (WhiteOnMove(cmailOldMove)) {
11805 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11807 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11809 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11820 StopObservingEvent()
11822 /* Stop observing current games */
11823 SendToICS(ics_prefix);
11824 SendToICS("unobserve\n");
11828 StopExaminingEvent()
11830 /* Stop observing current game */
11831 SendToICS(ics_prefix);
11832 SendToICS("unexamine\n");
11836 ForwardInner(target)
11841 if (appData.debugMode)
11842 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11843 target, currentMove, forwardMostMove);
11845 if (gameMode == EditPosition)
11848 if (gameMode == PlayFromGameFile && !pausing)
11851 if (gameMode == IcsExamining && pausing)
11852 limit = pauseExamForwardMostMove;
11854 limit = forwardMostMove;
11856 if (target > limit) target = limit;
11858 if (target > 0 && moveList[target - 1][0]) {
11859 int fromX, fromY, toX, toY;
11860 toX = moveList[target - 1][2] - AAA;
11861 toY = moveList[target - 1][3] - ONE;
11862 if (moveList[target - 1][1] == '@') {
11863 if (appData.highlightLastMove) {
11864 SetHighlights(-1, -1, toX, toY);
11867 fromX = moveList[target - 1][0] - AAA;
11868 fromY = moveList[target - 1][1] - ONE;
11869 if (target == currentMove + 1) {
11870 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11872 if (appData.highlightLastMove) {
11873 SetHighlights(fromX, fromY, toX, toY);
11877 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11878 gameMode == Training || gameMode == PlayFromGameFile ||
11879 gameMode == AnalyzeFile) {
11880 while (currentMove < target) {
11881 SendMoveToProgram(currentMove++, &first);
11884 currentMove = target;
11887 if (gameMode == EditGame || gameMode == EndOfGame) {
11888 whiteTimeRemaining = timeRemaining[0][currentMove];
11889 blackTimeRemaining = timeRemaining[1][currentMove];
11891 DisplayBothClocks();
11892 DisplayMove(currentMove - 1);
11893 DrawPosition(FALSE, boards[currentMove]);
11894 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11895 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11896 DisplayComment(currentMove - 1, commentList[currentMove]);
11904 if (gameMode == IcsExamining && !pausing) {
11905 SendToICS(ics_prefix);
11906 SendToICS("forward\n");
11908 ForwardInner(currentMove + 1);
11915 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11916 /* to optimze, we temporarily turn off analysis mode while we feed
11917 * the remaining moves to the engine. Otherwise we get analysis output
11920 if (first.analysisSupport) {
11921 SendToProgram("exit\nforce\n", &first);
11922 first.analyzing = FALSE;
11926 if (gameMode == IcsExamining && !pausing) {
11927 SendToICS(ics_prefix);
11928 SendToICS("forward 999999\n");
11930 ForwardInner(forwardMostMove);
11933 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11934 /* we have fed all the moves, so reactivate analysis mode */
11935 SendToProgram("analyze\n", &first);
11936 first.analyzing = TRUE;
11937 /*first.maybeThinking = TRUE;*/
11938 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11943 BackwardInner(target)
11946 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11948 if (appData.debugMode)
11949 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11950 target, currentMove, forwardMostMove);
11952 if (gameMode == EditPosition) return;
11953 if (currentMove <= backwardMostMove) {
11955 DrawPosition(full_redraw, boards[currentMove]);
11958 if (gameMode == PlayFromGameFile && !pausing)
11961 if (moveList[target][0]) {
11962 int fromX, fromY, toX, toY;
11963 toX = moveList[target][2] - AAA;
11964 toY = moveList[target][3] - ONE;
11965 if (moveList[target][1] == '@') {
11966 if (appData.highlightLastMove) {
11967 SetHighlights(-1, -1, toX, toY);
11970 fromX = moveList[target][0] - AAA;
11971 fromY = moveList[target][1] - ONE;
11972 if (target == currentMove - 1) {
11973 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11975 if (appData.highlightLastMove) {
11976 SetHighlights(fromX, fromY, toX, toY);
11980 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11981 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11982 while (currentMove > target) {
11983 SendToProgram("undo\n", &first);
11987 currentMove = target;
11990 if (gameMode == EditGame || gameMode == EndOfGame) {
11991 whiteTimeRemaining = timeRemaining[0][currentMove];
11992 blackTimeRemaining = timeRemaining[1][currentMove];
11994 DisplayBothClocks();
11995 DisplayMove(currentMove - 1);
11996 DrawPosition(full_redraw, boards[currentMove]);
11997 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11998 // [HGM] PV info: routine tests if comment empty
11999 DisplayComment(currentMove - 1, commentList[currentMove]);
12005 if (gameMode == IcsExamining && !pausing) {
12006 SendToICS(ics_prefix);
12007 SendToICS("backward\n");
12009 BackwardInner(currentMove - 1);
12016 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12017 /* to optimize, we temporarily turn off analysis mode while we undo
12018 * all the moves. Otherwise we get analysis output after each undo.
12020 if (first.analysisSupport) {
12021 SendToProgram("exit\nforce\n", &first);
12022 first.analyzing = FALSE;
12026 if (gameMode == IcsExamining && !pausing) {
12027 SendToICS(ics_prefix);
12028 SendToICS("backward 999999\n");
12030 BackwardInner(backwardMostMove);
12033 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12034 /* we have fed all the moves, so reactivate analysis mode */
12035 SendToProgram("analyze\n", &first);
12036 first.analyzing = TRUE;
12037 /*first.maybeThinking = TRUE;*/
12038 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12045 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12046 if (to >= forwardMostMove) to = forwardMostMove;
12047 if (to <= backwardMostMove) to = backwardMostMove;
12048 if (to < currentMove) {
12058 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12061 if (gameMode != IcsExamining) {
12062 DisplayError(_("You are not examining a game"), 0);
12066 DisplayError(_("You can't revert while pausing"), 0);
12069 SendToICS(ics_prefix);
12070 SendToICS("revert\n");
12076 switch (gameMode) {
12077 case MachinePlaysWhite:
12078 case MachinePlaysBlack:
12079 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12080 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12083 if (forwardMostMove < 2) return;
12084 currentMove = forwardMostMove = forwardMostMove - 2;
12085 whiteTimeRemaining = timeRemaining[0][currentMove];
12086 blackTimeRemaining = timeRemaining[1][currentMove];
12087 DisplayBothClocks();
12088 DisplayMove(currentMove - 1);
12089 ClearHighlights();/*!! could figure this out*/
12090 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12091 SendToProgram("remove\n", &first);
12092 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12095 case BeginningOfGame:
12099 case IcsPlayingWhite:
12100 case IcsPlayingBlack:
12101 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12102 SendToICS(ics_prefix);
12103 SendToICS("takeback 2\n");
12105 SendToICS(ics_prefix);
12106 SendToICS("takeback 1\n");
12115 ChessProgramState *cps;
12117 switch (gameMode) {
12118 case MachinePlaysWhite:
12119 if (!WhiteOnMove(forwardMostMove)) {
12120 DisplayError(_("It is your turn"), 0);
12125 case MachinePlaysBlack:
12126 if (WhiteOnMove(forwardMostMove)) {
12127 DisplayError(_("It is your turn"), 0);
12132 case TwoMachinesPlay:
12133 if (WhiteOnMove(forwardMostMove) ==
12134 (first.twoMachinesColor[0] == 'w')) {
12140 case BeginningOfGame:
12144 SendToProgram("?\n", cps);
12148 TruncateGameEvent()
12151 if (gameMode != EditGame) return;
12158 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12159 if (forwardMostMove > currentMove) {
12160 if (gameInfo.resultDetails != NULL) {
12161 free(gameInfo.resultDetails);
12162 gameInfo.resultDetails = NULL;
12163 gameInfo.result = GameUnfinished;
12165 forwardMostMove = currentMove;
12166 HistorySet(parseList, backwardMostMove, forwardMostMove,
12174 if (appData.noChessProgram) return;
12175 switch (gameMode) {
12176 case MachinePlaysWhite:
12177 if (WhiteOnMove(forwardMostMove)) {
12178 DisplayError(_("Wait until your turn"), 0);
12182 case BeginningOfGame:
12183 case MachinePlaysBlack:
12184 if (!WhiteOnMove(forwardMostMove)) {
12185 DisplayError(_("Wait until your turn"), 0);
12190 DisplayError(_("No hint available"), 0);
12193 SendToProgram("hint\n", &first);
12194 hintRequested = TRUE;
12200 if (appData.noChessProgram) return;
12201 switch (gameMode) {
12202 case MachinePlaysWhite:
12203 if (WhiteOnMove(forwardMostMove)) {
12204 DisplayError(_("Wait until your turn"), 0);
12208 case BeginningOfGame:
12209 case MachinePlaysBlack:
12210 if (!WhiteOnMove(forwardMostMove)) {
12211 DisplayError(_("Wait until your turn"), 0);
12216 EditPositionDone(TRUE);
12218 case TwoMachinesPlay:
12223 SendToProgram("bk\n", &first);
12224 bookOutput[0] = NULLCHAR;
12225 bookRequested = TRUE;
12231 char *tags = PGNTags(&gameInfo);
12232 TagsPopUp(tags, CmailMsg());
12236 /* end button procedures */
12239 PrintPosition(fp, move)
12245 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12246 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12247 char c = PieceToChar(boards[move][i][j]);
12248 fputc(c == 'x' ? '.' : c, fp);
12249 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12252 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12253 fprintf(fp, "white to play\n");
12255 fprintf(fp, "black to play\n");
12262 if (gameInfo.white != NULL) {
12263 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12269 /* Find last component of program's own name, using some heuristics */
12271 TidyProgramName(prog, host, buf)
12272 char *prog, *host, buf[MSG_SIZ];
12275 int local = (strcmp(host, "localhost") == 0);
12276 while (!local && (p = strchr(prog, ';')) != NULL) {
12278 while (*p == ' ') p++;
12281 if (*prog == '"' || *prog == '\'') {
12282 q = strchr(prog + 1, *prog);
12284 q = strchr(prog, ' ');
12286 if (q == NULL) q = prog + strlen(prog);
12288 while (p >= prog && *p != '/' && *p != '\\') p--;
12290 if(p == prog && *p == '"') p++;
12291 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12292 memcpy(buf, p, q - p);
12293 buf[q - p] = NULLCHAR;
12301 TimeControlTagValue()
12304 if (!appData.clockMode) {
12306 } else if (movesPerSession > 0) {
12307 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12308 } else if (timeIncrement == 0) {
12309 sprintf(buf, "%ld", timeControl/1000);
12311 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12313 return StrSave(buf);
12319 /* This routine is used only for certain modes */
12320 VariantClass v = gameInfo.variant;
12321 ChessMove r = GameUnfinished;
12324 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12325 r = gameInfo.result;
12326 p = gameInfo.resultDetails;
12327 gameInfo.resultDetails = NULL;
12329 ClearGameInfo(&gameInfo);
12330 gameInfo.variant = v;
12332 switch (gameMode) {
12333 case MachinePlaysWhite:
12334 gameInfo.event = StrSave( appData.pgnEventHeader );
12335 gameInfo.site = StrSave(HostName());
12336 gameInfo.date = PGNDate();
12337 gameInfo.round = StrSave("-");
12338 gameInfo.white = StrSave(first.tidy);
12339 gameInfo.black = StrSave(UserName());
12340 gameInfo.timeControl = TimeControlTagValue();
12343 case MachinePlaysBlack:
12344 gameInfo.event = StrSave( appData.pgnEventHeader );
12345 gameInfo.site = StrSave(HostName());
12346 gameInfo.date = PGNDate();
12347 gameInfo.round = StrSave("-");
12348 gameInfo.white = StrSave(UserName());
12349 gameInfo.black = StrSave(first.tidy);
12350 gameInfo.timeControl = TimeControlTagValue();
12353 case TwoMachinesPlay:
12354 gameInfo.event = StrSave( appData.pgnEventHeader );
12355 gameInfo.site = StrSave(HostName());
12356 gameInfo.date = PGNDate();
12357 if (matchGame > 0) {
12359 sprintf(buf, "%d", matchGame);
12360 gameInfo.round = StrSave(buf);
12362 gameInfo.round = StrSave("-");
12364 if (first.twoMachinesColor[0] == 'w') {
12365 gameInfo.white = StrSave(first.tidy);
12366 gameInfo.black = StrSave(second.tidy);
12368 gameInfo.white = StrSave(second.tidy);
12369 gameInfo.black = StrSave(first.tidy);
12371 gameInfo.timeControl = TimeControlTagValue();
12375 gameInfo.event = StrSave("Edited game");
12376 gameInfo.site = StrSave(HostName());
12377 gameInfo.date = PGNDate();
12378 gameInfo.round = StrSave("-");
12379 gameInfo.white = StrSave("-");
12380 gameInfo.black = StrSave("-");
12381 gameInfo.result = r;
12382 gameInfo.resultDetails = p;
12386 gameInfo.event = StrSave("Edited position");
12387 gameInfo.site = StrSave(HostName());
12388 gameInfo.date = PGNDate();
12389 gameInfo.round = StrSave("-");
12390 gameInfo.white = StrSave("-");
12391 gameInfo.black = StrSave("-");
12394 case IcsPlayingWhite:
12395 case IcsPlayingBlack:
12400 case PlayFromGameFile:
12401 gameInfo.event = StrSave("Game from non-PGN file");
12402 gameInfo.site = StrSave(HostName());
12403 gameInfo.date = PGNDate();
12404 gameInfo.round = StrSave("-");
12405 gameInfo.white = StrSave("?");
12406 gameInfo.black = StrSave("?");
12415 ReplaceComment(index, text)
12421 while (*text == '\n') text++;
12422 len = strlen(text);
12423 while (len > 0 && text[len - 1] == '\n') len--;
12425 if (commentList[index] != NULL)
12426 free(commentList[index]);
12429 commentList[index] = NULL;
12432 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12433 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12434 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12435 commentList[index] = (char *) malloc(len + 2);
12436 strncpy(commentList[index], text, len);
12437 commentList[index][len] = '\n';
12438 commentList[index][len + 1] = NULLCHAR;
12440 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12442 commentList[index] = (char *) malloc(len + 6);
12443 strcpy(commentList[index], "{\n");
12444 strncpy(commentList[index]+2, text, len);
12445 commentList[index][len+2] = NULLCHAR;
12446 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12447 strcat(commentList[index], "\n}\n");
12461 if (ch == '\r') continue;
12463 } while (ch != '\0');
12467 AppendComment(index, text, addBraces)
12470 Boolean addBraces; // [HGM] braces: tells if we should add {}
12475 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12476 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12479 while (*text == '\n') text++;
12480 len = strlen(text);
12481 while (len > 0 && text[len - 1] == '\n') len--;
12483 if (len == 0) return;
12485 if (commentList[index] != NULL) {
12486 old = commentList[index];
12487 oldlen = strlen(old);
12488 while(commentList[index][oldlen-1] == '\n')
12489 commentList[index][--oldlen] = NULLCHAR;
12490 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12491 strcpy(commentList[index], old);
12493 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12494 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12495 if(addBraces) addBraces = FALSE; else { text++; len--; }
12496 while (*text == '\n') { text++; len--; }
12497 commentList[index][--oldlen] = NULLCHAR;
12499 if(addBraces) strcat(commentList[index], "\n{\n");
12500 else strcat(commentList[index], "\n");
12501 strcat(commentList[index], text);
12502 if(addBraces) strcat(commentList[index], "\n}\n");
12503 else strcat(commentList[index], "\n");
12505 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12507 strcpy(commentList[index], "{\n");
12508 else commentList[index][0] = NULLCHAR;
12509 strcat(commentList[index], text);
12510 strcat(commentList[index], "\n");
12511 if(addBraces) strcat(commentList[index], "}\n");
12515 static char * FindStr( char * text, char * sub_text )
12517 char * result = strstr( text, sub_text );
12519 if( result != NULL ) {
12520 result += strlen( sub_text );
12526 /* [AS] Try to extract PV info from PGN comment */
12527 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12528 char *GetInfoFromComment( int index, char * text )
12532 if( text != NULL && index > 0 ) {
12535 int time = -1, sec = 0, deci;
12536 char * s_eval = FindStr( text, "[%eval " );
12537 char * s_emt = FindStr( text, "[%emt " );
12539 if( s_eval != NULL || s_emt != NULL ) {
12543 if( s_eval != NULL ) {
12544 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12548 if( delim != ']' ) {
12553 if( s_emt != NULL ) {
12558 /* We expect something like: [+|-]nnn.nn/dd */
12561 if(*text != '{') return text; // [HGM] braces: must be normal comment
12563 sep = strchr( text, '/' );
12564 if( sep == NULL || sep < (text+4) ) {
12568 time = -1; sec = -1; deci = -1;
12569 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12570 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12571 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12572 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12576 if( score_lo < 0 || score_lo >= 100 ) {
12580 if(sec >= 0) time = 600*time + 10*sec; else
12581 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12583 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12585 /* [HGM] PV time: now locate end of PV info */
12586 while( *++sep >= '0' && *sep <= '9'); // strip depth
12588 while( *++sep >= '0' && *sep <= '9'); // strip time
12590 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12592 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12593 while(*sep == ' ') sep++;
12604 pvInfoList[index-1].depth = depth;
12605 pvInfoList[index-1].score = score;
12606 pvInfoList[index-1].time = 10*time; // centi-sec
12607 if(*sep == '}') *sep = 0; else *--sep = '{';
12613 SendToProgram(message, cps)
12615 ChessProgramState *cps;
12617 int count, outCount, error;
12620 if (cps->pr == NULL) return;
12623 if (appData.debugMode) {
12626 fprintf(debugFP, "%ld >%-6s: %s",
12627 SubtractTimeMarks(&now, &programStartTime),
12628 cps->which, message);
12631 count = strlen(message);
12632 outCount = OutputToProcess(cps->pr, message, count, &error);
12633 if (outCount < count && !exiting
12634 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12635 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12636 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12637 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12638 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12639 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12641 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12643 gameInfo.resultDetails = StrSave(buf);
12645 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12650 ReceiveFromProgram(isr, closure, message, count, error)
12651 InputSourceRef isr;
12659 ChessProgramState *cps = (ChessProgramState *)closure;
12661 if (isr != cps->isr) return; /* Killed intentionally */
12665 _("Error: %s chess program (%s) exited unexpectedly"),
12666 cps->which, cps->program);
12667 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12668 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12669 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12670 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12672 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12674 gameInfo.resultDetails = StrSave(buf);
12676 RemoveInputSource(cps->isr);
12677 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12680 _("Error reading from %s chess program (%s)"),
12681 cps->which, cps->program);
12682 RemoveInputSource(cps->isr);
12684 /* [AS] Program is misbehaving badly... kill it */
12685 if( count == -2 ) {
12686 DestroyChildProcess( cps->pr, 9 );
12690 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12695 if ((end_str = strchr(message, '\r')) != NULL)
12696 *end_str = NULLCHAR;
12697 if ((end_str = strchr(message, '\n')) != NULL)
12698 *end_str = NULLCHAR;
12700 if (appData.debugMode) {
12701 TimeMark now; int print = 1;
12702 char *quote = ""; char c; int i;
12704 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12705 char start = message[0];
12706 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12707 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12708 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12709 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12710 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12711 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12712 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12713 sscanf(message, "pong %c", &c)!=1 && start != '#')
12714 { quote = "# "; print = (appData.engineComments == 2); }
12715 message[0] = start; // restore original message
12719 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12720 SubtractTimeMarks(&now, &programStartTime), cps->which,
12726 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12727 if (appData.icsEngineAnalyze) {
12728 if (strstr(message, "whisper") != NULL ||
12729 strstr(message, "kibitz") != NULL ||
12730 strstr(message, "tellics") != NULL) return;
12733 HandleMachineMove(message, cps);
12738 SendTimeControl(cps, mps, tc, inc, sd, st)
12739 ChessProgramState *cps;
12740 int mps, inc, sd, st;
12746 if( timeControl_2 > 0 ) {
12747 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12748 tc = timeControl_2;
12751 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12752 inc /= cps->timeOdds;
12753 st /= cps->timeOdds;
12755 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12758 /* Set exact time per move, normally using st command */
12759 if (cps->stKludge) {
12760 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12762 if (seconds == 0) {
12763 sprintf(buf, "level 1 %d\n", st/60);
12765 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12768 sprintf(buf, "st %d\n", st);
12771 /* Set conventional or incremental time control, using level command */
12772 if (seconds == 0) {
12773 /* Note old gnuchess bug -- minutes:seconds used to not work.
12774 Fixed in later versions, but still avoid :seconds
12775 when seconds is 0. */
12776 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12778 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12779 seconds, inc/1000);
12782 SendToProgram(buf, cps);
12784 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12785 /* Orthogonally, limit search to given depth */
12787 if (cps->sdKludge) {
12788 sprintf(buf, "depth\n%d\n", sd);
12790 sprintf(buf, "sd %d\n", sd);
12792 SendToProgram(buf, cps);
12795 if(cps->nps > 0) { /* [HGM] nps */
12796 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12798 sprintf(buf, "nps %d\n", cps->nps);
12799 SendToProgram(buf, cps);
12804 ChessProgramState *WhitePlayer()
12805 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12807 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12808 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12814 SendTimeRemaining(cps, machineWhite)
12815 ChessProgramState *cps;
12816 int /*boolean*/ machineWhite;
12818 char message[MSG_SIZ];
12821 /* Note: this routine must be called when the clocks are stopped
12822 or when they have *just* been set or switched; otherwise
12823 it will be off by the time since the current tick started.
12825 if (machineWhite) {
12826 time = whiteTimeRemaining / 10;
12827 otime = blackTimeRemaining / 10;
12829 time = blackTimeRemaining / 10;
12830 otime = whiteTimeRemaining / 10;
12832 /* [HGM] translate opponent's time by time-odds factor */
12833 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12834 if (appData.debugMode) {
12835 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12838 if (time <= 0) time = 1;
12839 if (otime <= 0) otime = 1;
12841 sprintf(message, "time %ld\n", time);
12842 SendToProgram(message, cps);
12844 sprintf(message, "otim %ld\n", otime);
12845 SendToProgram(message, cps);
12849 BoolFeature(p, name, loc, cps)
12853 ChessProgramState *cps;
12856 int len = strlen(name);
12858 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12860 sscanf(*p, "%d", &val);
12862 while (**p && **p != ' ') (*p)++;
12863 sprintf(buf, "accepted %s\n", name);
12864 SendToProgram(buf, cps);
12871 IntFeature(p, name, loc, cps)
12875 ChessProgramState *cps;
12878 int len = strlen(name);
12879 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12881 sscanf(*p, "%d", loc);
12882 while (**p && **p != ' ') (*p)++;
12883 sprintf(buf, "accepted %s\n", name);
12884 SendToProgram(buf, cps);
12891 StringFeature(p, name, loc, cps)
12895 ChessProgramState *cps;
12898 int len = strlen(name);
12899 if (strncmp((*p), name, len) == 0
12900 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12902 sscanf(*p, "%[^\"]", loc);
12903 while (**p && **p != '\"') (*p)++;
12904 if (**p == '\"') (*p)++;
12905 sprintf(buf, "accepted %s\n", name);
12906 SendToProgram(buf, cps);
12913 ParseOption(Option *opt, ChessProgramState *cps)
12914 // [HGM] options: process the string that defines an engine option, and determine
12915 // name, type, default value, and allowed value range
12917 char *p, *q, buf[MSG_SIZ];
12918 int n, min = (-1)<<31, max = 1<<31, def;
12920 if(p = strstr(opt->name, " -spin ")) {
12921 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12922 if(max < min) max = min; // enforce consistency
12923 if(def < min) def = min;
12924 if(def > max) def = max;
12929 } else if((p = strstr(opt->name, " -slider "))) {
12930 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12931 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12932 if(max < min) max = min; // enforce consistency
12933 if(def < min) def = min;
12934 if(def > max) def = max;
12938 opt->type = Spin; // Slider;
12939 } else if((p = strstr(opt->name, " -string "))) {
12940 opt->textValue = p+9;
12941 opt->type = TextBox;
12942 } else if((p = strstr(opt->name, " -file "))) {
12943 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12944 opt->textValue = p+7;
12945 opt->type = TextBox; // FileName;
12946 } else if((p = strstr(opt->name, " -path "))) {
12947 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12948 opt->textValue = p+7;
12949 opt->type = TextBox; // PathName;
12950 } else if(p = strstr(opt->name, " -check ")) {
12951 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12952 opt->value = (def != 0);
12953 opt->type = CheckBox;
12954 } else if(p = strstr(opt->name, " -combo ")) {
12955 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12956 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12957 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12958 opt->value = n = 0;
12959 while(q = StrStr(q, " /// ")) {
12960 n++; *q = 0; // count choices, and null-terminate each of them
12962 if(*q == '*') { // remember default, which is marked with * prefix
12966 cps->comboList[cps->comboCnt++] = q;
12968 cps->comboList[cps->comboCnt++] = NULL;
12970 opt->type = ComboBox;
12971 } else if(p = strstr(opt->name, " -button")) {
12972 opt->type = Button;
12973 } else if(p = strstr(opt->name, " -save")) {
12974 opt->type = SaveButton;
12975 } else return FALSE;
12976 *p = 0; // terminate option name
12977 // now look if the command-line options define a setting for this engine option.
12978 if(cps->optionSettings && cps->optionSettings[0])
12979 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12980 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12981 sprintf(buf, "option %s", p);
12982 if(p = strstr(buf, ",")) *p = 0;
12984 SendToProgram(buf, cps);
12990 FeatureDone(cps, val)
12991 ChessProgramState* cps;
12994 DelayedEventCallback cb = GetDelayedEvent();
12995 if ((cb == InitBackEnd3 && cps == &first) ||
12996 (cb == TwoMachinesEventIfReady && cps == &second)) {
12997 CancelDelayedEvent();
12998 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13000 cps->initDone = val;
13003 /* Parse feature command from engine */
13005 ParseFeatures(args, cps)
13007 ChessProgramState *cps;
13015 while (*p == ' ') p++;
13016 if (*p == NULLCHAR) return;
13018 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13019 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13020 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13021 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13022 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13023 if (BoolFeature(&p, "reuse", &val, cps)) {
13024 /* Engine can disable reuse, but can't enable it if user said no */
13025 if (!val) cps->reuse = FALSE;
13028 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13029 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13030 if (gameMode == TwoMachinesPlay) {
13031 DisplayTwoMachinesTitle();
13037 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13038 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13039 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13040 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13041 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13042 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13043 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13044 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13045 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13046 if (IntFeature(&p, "done", &val, cps)) {
13047 FeatureDone(cps, val);
13050 /* Added by Tord: */
13051 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13052 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13053 /* End of additions by Tord */
13055 /* [HGM] added features: */
13056 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13057 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13058 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13059 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13060 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13061 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13062 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13063 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13064 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13065 SendToProgram(buf, cps);
13068 if(cps->nrOptions >= MAX_OPTIONS) {
13070 sprintf(buf, "%s engine has too many options\n", cps->which);
13071 DisplayError(buf, 0);
13075 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13076 /* End of additions by HGM */
13078 /* unknown feature: complain and skip */
13080 while (*q && *q != '=') q++;
13081 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13082 SendToProgram(buf, cps);
13088 while (*p && *p != '\"') p++;
13089 if (*p == '\"') p++;
13091 while (*p && *p != ' ') p++;
13099 PeriodicUpdatesEvent(newState)
13102 if (newState == appData.periodicUpdates)
13105 appData.periodicUpdates=newState;
13107 /* Display type changes, so update it now */
13108 // DisplayAnalysis();
13110 /* Get the ball rolling again... */
13112 AnalysisPeriodicEvent(1);
13113 StartAnalysisClock();
13118 PonderNextMoveEvent(newState)
13121 if (newState == appData.ponderNextMove) return;
13122 if (gameMode == EditPosition) EditPositionDone(TRUE);
13124 SendToProgram("hard\n", &first);
13125 if (gameMode == TwoMachinesPlay) {
13126 SendToProgram("hard\n", &second);
13129 SendToProgram("easy\n", &first);
13130 thinkOutput[0] = NULLCHAR;
13131 if (gameMode == TwoMachinesPlay) {
13132 SendToProgram("easy\n", &second);
13135 appData.ponderNextMove = newState;
13139 NewSettingEvent(option, command, value)
13145 if (gameMode == EditPosition) EditPositionDone(TRUE);
13146 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13147 SendToProgram(buf, &first);
13148 if (gameMode == TwoMachinesPlay) {
13149 SendToProgram(buf, &second);
13154 ShowThinkingEvent()
13155 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13157 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13158 int newState = appData.showThinking
13159 // [HGM] thinking: other features now need thinking output as well
13160 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13162 if (oldState == newState) return;
13163 oldState = newState;
13164 if (gameMode == EditPosition) EditPositionDone(TRUE);
13166 SendToProgram("post\n", &first);
13167 if (gameMode == TwoMachinesPlay) {
13168 SendToProgram("post\n", &second);
13171 SendToProgram("nopost\n", &first);
13172 thinkOutput[0] = NULLCHAR;
13173 if (gameMode == TwoMachinesPlay) {
13174 SendToProgram("nopost\n", &second);
13177 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13181 AskQuestionEvent(title, question, replyPrefix, which)
13182 char *title; char *question; char *replyPrefix; char *which;
13184 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13185 if (pr == NoProc) return;
13186 AskQuestion(title, question, replyPrefix, pr);
13190 DisplayMove(moveNumber)
13193 char message[MSG_SIZ];
13195 char cpThinkOutput[MSG_SIZ];
13197 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13199 if (moveNumber == forwardMostMove - 1 ||
13200 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13202 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13204 if (strchr(cpThinkOutput, '\n')) {
13205 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13208 *cpThinkOutput = NULLCHAR;
13211 /* [AS] Hide thinking from human user */
13212 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13213 *cpThinkOutput = NULLCHAR;
13214 if( thinkOutput[0] != NULLCHAR ) {
13217 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13218 cpThinkOutput[i] = '.';
13220 cpThinkOutput[i] = NULLCHAR;
13221 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13225 if (moveNumber == forwardMostMove - 1 &&
13226 gameInfo.resultDetails != NULL) {
13227 if (gameInfo.resultDetails[0] == NULLCHAR) {
13228 sprintf(res, " %s", PGNResult(gameInfo.result));
13230 sprintf(res, " {%s} %s",
13231 gameInfo.resultDetails, PGNResult(gameInfo.result));
13237 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13238 DisplayMessage(res, cpThinkOutput);
13240 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13241 WhiteOnMove(moveNumber) ? " " : ".. ",
13242 parseList[moveNumber], res);
13243 DisplayMessage(message, cpThinkOutput);
13248 DisplayComment(moveNumber, text)
13252 char title[MSG_SIZ];
13253 char buf[8000]; // comment can be long!
13256 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13257 strcpy(title, "Comment");
13259 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13260 WhiteOnMove(moveNumber) ? " " : ".. ",
13261 parseList[moveNumber]);
13263 // [HGM] PV info: display PV info together with (or as) comment
13264 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13265 if(text == NULL) text = "";
13266 score = pvInfoList[moveNumber].score;
13267 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13268 depth, (pvInfoList[moveNumber].time+50)/100, text);
13271 if (text != NULL && (appData.autoDisplayComment || commentUp))
13272 CommentPopUp(title, text);
13275 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13276 * might be busy thinking or pondering. It can be omitted if your
13277 * gnuchess is configured to stop thinking immediately on any user
13278 * input. However, that gnuchess feature depends on the FIONREAD
13279 * ioctl, which does not work properly on some flavors of Unix.
13283 ChessProgramState *cps;
13286 if (!cps->useSigint) return;
13287 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13288 switch (gameMode) {
13289 case MachinePlaysWhite:
13290 case MachinePlaysBlack:
13291 case TwoMachinesPlay:
13292 case IcsPlayingWhite:
13293 case IcsPlayingBlack:
13296 /* Skip if we know it isn't thinking */
13297 if (!cps->maybeThinking) return;
13298 if (appData.debugMode)
13299 fprintf(debugFP, "Interrupting %s\n", cps->which);
13300 InterruptChildProcess(cps->pr);
13301 cps->maybeThinking = FALSE;
13306 #endif /*ATTENTION*/
13312 if (whiteTimeRemaining <= 0) {
13315 if (appData.icsActive) {
13316 if (appData.autoCallFlag &&
13317 gameMode == IcsPlayingBlack && !blackFlag) {
13318 SendToICS(ics_prefix);
13319 SendToICS("flag\n");
13323 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13325 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13326 if (appData.autoCallFlag) {
13327 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13334 if (blackTimeRemaining <= 0) {
13337 if (appData.icsActive) {
13338 if (appData.autoCallFlag &&
13339 gameMode == IcsPlayingWhite && !whiteFlag) {
13340 SendToICS(ics_prefix);
13341 SendToICS("flag\n");
13345 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13347 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13348 if (appData.autoCallFlag) {
13349 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13362 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13363 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13366 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13368 if ( !WhiteOnMove(forwardMostMove) )
13369 /* White made time control */
13370 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13371 /* [HGM] time odds: correct new time quota for time odds! */
13372 / WhitePlayer()->timeOdds;
13374 /* Black made time control */
13375 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13376 / WhitePlayer()->other->timeOdds;
13380 DisplayBothClocks()
13382 int wom = gameMode == EditPosition ?
13383 !blackPlaysFirst : WhiteOnMove(currentMove);
13384 DisplayWhiteClock(whiteTimeRemaining, wom);
13385 DisplayBlackClock(blackTimeRemaining, !wom);
13389 /* Timekeeping seems to be a portability nightmare. I think everyone
13390 has ftime(), but I'm really not sure, so I'm including some ifdefs
13391 to use other calls if you don't. Clocks will be less accurate if
13392 you have neither ftime nor gettimeofday.
13395 /* VS 2008 requires the #include outside of the function */
13396 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13397 #include <sys/timeb.h>
13400 /* Get the current time as a TimeMark */
13405 #if HAVE_GETTIMEOFDAY
13407 struct timeval timeVal;
13408 struct timezone timeZone;
13410 gettimeofday(&timeVal, &timeZone);
13411 tm->sec = (long) timeVal.tv_sec;
13412 tm->ms = (int) (timeVal.tv_usec / 1000L);
13414 #else /*!HAVE_GETTIMEOFDAY*/
13417 // include <sys/timeb.h> / moved to just above start of function
13418 struct timeb timeB;
13421 tm->sec = (long) timeB.time;
13422 tm->ms = (int) timeB.millitm;
13424 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13425 tm->sec = (long) time(NULL);
13431 /* Return the difference in milliseconds between two
13432 time marks. We assume the difference will fit in a long!
13435 SubtractTimeMarks(tm2, tm1)
13436 TimeMark *tm2, *tm1;
13438 return 1000L*(tm2->sec - tm1->sec) +
13439 (long) (tm2->ms - tm1->ms);
13444 * Code to manage the game clocks.
13446 * In tournament play, black starts the clock and then white makes a move.
13447 * We give the human user a slight advantage if he is playing white---the
13448 * clocks don't run until he makes his first move, so it takes zero time.
13449 * Also, we don't account for network lag, so we could get out of sync
13450 * with GNU Chess's clock -- but then, referees are always right.
13453 static TimeMark tickStartTM;
13454 static long intendedTickLength;
13457 NextTickLength(timeRemaining)
13458 long timeRemaining;
13460 long nominalTickLength, nextTickLength;
13462 if (timeRemaining > 0L && timeRemaining <= 10000L)
13463 nominalTickLength = 100L;
13465 nominalTickLength = 1000L;
13466 nextTickLength = timeRemaining % nominalTickLength;
13467 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13469 return nextTickLength;
13472 /* Adjust clock one minute up or down */
13474 AdjustClock(Boolean which, int dir)
13476 if(which) blackTimeRemaining += 60000*dir;
13477 else whiteTimeRemaining += 60000*dir;
13478 DisplayBothClocks();
13481 /* Stop clocks and reset to a fresh time control */
13485 (void) StopClockTimer();
13486 if (appData.icsActive) {
13487 whiteTimeRemaining = blackTimeRemaining = 0;
13488 } else if (searchTime) {
13489 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13490 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13491 } else { /* [HGM] correct new time quote for time odds */
13492 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13493 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13495 if (whiteFlag || blackFlag) {
13497 whiteFlag = blackFlag = FALSE;
13499 DisplayBothClocks();
13502 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13504 /* Decrement running clock by amount of time that has passed */
13508 long timeRemaining;
13509 long lastTickLength, fudge;
13512 if (!appData.clockMode) return;
13513 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13517 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13519 /* Fudge if we woke up a little too soon */
13520 fudge = intendedTickLength - lastTickLength;
13521 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13523 if (WhiteOnMove(forwardMostMove)) {
13524 if(whiteNPS >= 0) lastTickLength = 0;
13525 timeRemaining = whiteTimeRemaining -= lastTickLength;
13526 DisplayWhiteClock(whiteTimeRemaining - fudge,
13527 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13529 if(blackNPS >= 0) lastTickLength = 0;
13530 timeRemaining = blackTimeRemaining -= lastTickLength;
13531 DisplayBlackClock(blackTimeRemaining - fudge,
13532 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13535 if (CheckFlags()) return;
13538 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13539 StartClockTimer(intendedTickLength);
13541 /* if the time remaining has fallen below the alarm threshold, sound the
13542 * alarm. if the alarm has sounded and (due to a takeback or time control
13543 * with increment) the time remaining has increased to a level above the
13544 * threshold, reset the alarm so it can sound again.
13547 if (appData.icsActive && appData.icsAlarm) {
13549 /* make sure we are dealing with the user's clock */
13550 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13551 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13554 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13555 alarmSounded = FALSE;
13556 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13558 alarmSounded = TRUE;
13564 /* A player has just moved, so stop the previously running
13565 clock and (if in clock mode) start the other one.
13566 We redisplay both clocks in case we're in ICS mode, because
13567 ICS gives us an update to both clocks after every move.
13568 Note that this routine is called *after* forwardMostMove
13569 is updated, so the last fractional tick must be subtracted
13570 from the color that is *not* on move now.
13575 long lastTickLength;
13577 int flagged = FALSE;
13581 if (StopClockTimer() && appData.clockMode) {
13582 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13583 if (WhiteOnMove(forwardMostMove)) {
13584 if(blackNPS >= 0) lastTickLength = 0;
13585 blackTimeRemaining -= lastTickLength;
13586 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13587 // if(pvInfoList[forwardMostMove-1].time == -1)
13588 pvInfoList[forwardMostMove-1].time = // use GUI time
13589 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13591 if(whiteNPS >= 0) lastTickLength = 0;
13592 whiteTimeRemaining -= lastTickLength;
13593 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13594 // if(pvInfoList[forwardMostMove-1].time == -1)
13595 pvInfoList[forwardMostMove-1].time =
13596 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13598 flagged = CheckFlags();
13600 CheckTimeControl();
13602 if (flagged || !appData.clockMode) return;
13604 switch (gameMode) {
13605 case MachinePlaysBlack:
13606 case MachinePlaysWhite:
13607 case BeginningOfGame:
13608 if (pausing) return;
13612 case PlayFromGameFile:
13620 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13621 if(WhiteOnMove(forwardMostMove))
13622 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13623 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13627 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13628 whiteTimeRemaining : blackTimeRemaining);
13629 StartClockTimer(intendedTickLength);
13633 /* Stop both clocks */
13637 long lastTickLength;
13640 if (!StopClockTimer()) return;
13641 if (!appData.clockMode) return;
13645 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13646 if (WhiteOnMove(forwardMostMove)) {
13647 if(whiteNPS >= 0) lastTickLength = 0;
13648 whiteTimeRemaining -= lastTickLength;
13649 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13651 if(blackNPS >= 0) lastTickLength = 0;
13652 blackTimeRemaining -= lastTickLength;
13653 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13658 /* Start clock of player on move. Time may have been reset, so
13659 if clock is already running, stop and restart it. */
13663 (void) StopClockTimer(); /* in case it was running already */
13664 DisplayBothClocks();
13665 if (CheckFlags()) return;
13667 if (!appData.clockMode) return;
13668 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13670 GetTimeMark(&tickStartTM);
13671 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13672 whiteTimeRemaining : blackTimeRemaining);
13674 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13675 whiteNPS = blackNPS = -1;
13676 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13677 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13678 whiteNPS = first.nps;
13679 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13680 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13681 blackNPS = first.nps;
13682 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13683 whiteNPS = second.nps;
13684 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13685 blackNPS = second.nps;
13686 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13688 StartClockTimer(intendedTickLength);
13695 long second, minute, hour, day;
13697 static char buf[32];
13699 if (ms > 0 && ms <= 9900) {
13700 /* convert milliseconds to tenths, rounding up */
13701 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13703 sprintf(buf, " %03.1f ", tenths/10.0);
13707 /* convert milliseconds to seconds, rounding up */
13708 /* use floating point to avoid strangeness of integer division
13709 with negative dividends on many machines */
13710 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13717 day = second / (60 * 60 * 24);
13718 second = second % (60 * 60 * 24);
13719 hour = second / (60 * 60);
13720 second = second % (60 * 60);
13721 minute = second / 60;
13722 second = second % 60;
13725 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13726 sign, day, hour, minute, second);
13728 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13730 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13737 * This is necessary because some C libraries aren't ANSI C compliant yet.
13740 StrStr(string, match)
13741 char *string, *match;
13745 length = strlen(match);
13747 for (i = strlen(string) - length; i >= 0; i--, string++)
13748 if (!strncmp(match, string, length))
13755 StrCaseStr(string, match)
13756 char *string, *match;
13760 length = strlen(match);
13762 for (i = strlen(string) - length; i >= 0; i--, string++) {
13763 for (j = 0; j < length; j++) {
13764 if (ToLower(match[j]) != ToLower(string[j]))
13767 if (j == length) return string;
13781 c1 = ToLower(*s1++);
13782 c2 = ToLower(*s2++);
13783 if (c1 > c2) return 1;
13784 if (c1 < c2) return -1;
13785 if (c1 == NULLCHAR) return 0;
13794 return isupper(c) ? tolower(c) : c;
13802 return islower(c) ? toupper(c) : c;
13804 #endif /* !_amigados */
13812 if ((ret = (char *) malloc(strlen(s) + 1))) {
13819 StrSavePtr(s, savePtr)
13820 char *s, **savePtr;
13825 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13826 strcpy(*savePtr, s);
13838 clock = time((time_t *)NULL);
13839 tm = localtime(&clock);
13840 sprintf(buf, "%04d.%02d.%02d",
13841 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13842 return StrSave(buf);
13847 PositionToFEN(move, overrideCastling)
13849 char *overrideCastling;
13851 int i, j, fromX, fromY, toX, toY;
13858 whiteToPlay = (gameMode == EditPosition) ?
13859 !blackPlaysFirst : (move % 2 == 0);
13862 /* Piece placement data */
13863 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13865 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13866 if (boards[move][i][j] == EmptySquare) {
13868 } else { ChessSquare piece = boards[move][i][j];
13869 if (emptycount > 0) {
13870 if(emptycount<10) /* [HGM] can be >= 10 */
13871 *p++ = '0' + emptycount;
13872 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13875 if(PieceToChar(piece) == '+') {
13876 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13878 piece = (ChessSquare)(DEMOTED piece);
13880 *p++ = PieceToChar(piece);
13882 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13883 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13888 if (emptycount > 0) {
13889 if(emptycount<10) /* [HGM] can be >= 10 */
13890 *p++ = '0' + emptycount;
13891 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13898 /* [HGM] print Crazyhouse or Shogi holdings */
13899 if( gameInfo.holdingsWidth ) {
13900 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13902 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13903 piece = boards[move][i][BOARD_WIDTH-1];
13904 if( piece != EmptySquare )
13905 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13906 *p++ = PieceToChar(piece);
13908 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13909 piece = boards[move][BOARD_HEIGHT-i-1][0];
13910 if( piece != EmptySquare )
13911 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13912 *p++ = PieceToChar(piece);
13915 if( q == p ) *p++ = '-';
13921 *p++ = whiteToPlay ? 'w' : 'b';
13924 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13925 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13927 if(nrCastlingRights) {
13929 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13930 /* [HGM] write directly from rights */
13931 if(boards[move][CASTLING][2] != NoRights &&
13932 boards[move][CASTLING][0] != NoRights )
13933 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13934 if(boards[move][CASTLING][2] != NoRights &&
13935 boards[move][CASTLING][1] != NoRights )
13936 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13937 if(boards[move][CASTLING][5] != NoRights &&
13938 boards[move][CASTLING][3] != NoRights )
13939 *p++ = boards[move][CASTLING][3] + AAA;
13940 if(boards[move][CASTLING][5] != NoRights &&
13941 boards[move][CASTLING][4] != NoRights )
13942 *p++ = boards[move][CASTLING][4] + AAA;
13945 /* [HGM] write true castling rights */
13946 if( nrCastlingRights == 6 ) {
13947 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13948 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13949 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13950 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13951 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13952 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13953 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13954 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13957 if (q == p) *p++ = '-'; /* No castling rights */
13961 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13962 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13963 /* En passant target square */
13964 if (move > backwardMostMove) {
13965 fromX = moveList[move - 1][0] - AAA;
13966 fromY = moveList[move - 1][1] - ONE;
13967 toX = moveList[move - 1][2] - AAA;
13968 toY = moveList[move - 1][3] - ONE;
13969 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13970 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13971 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13973 /* 2-square pawn move just happened */
13975 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13979 } else if(move == backwardMostMove) {
13980 // [HGM] perhaps we should always do it like this, and forget the above?
13981 if((signed char)boards[move][EP_STATUS] >= 0) {
13982 *p++ = boards[move][EP_STATUS] + AAA;
13983 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13994 /* [HGM] find reversible plies */
13995 { int i = 0, j=move;
13997 if (appData.debugMode) { int k;
13998 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13999 for(k=backwardMostMove; k<=forwardMostMove; k++)
14000 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14004 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14005 if( j == backwardMostMove ) i += initialRulePlies;
14006 sprintf(p, "%d ", i);
14007 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14009 /* Fullmove number */
14010 sprintf(p, "%d", (move / 2) + 1);
14012 return StrSave(buf);
14016 ParseFEN(board, blackPlaysFirst, fen)
14018 int *blackPlaysFirst;
14028 /* [HGM] by default clear Crazyhouse holdings, if present */
14029 if(gameInfo.holdingsWidth) {
14030 for(i=0; i<BOARD_HEIGHT; i++) {
14031 board[i][0] = EmptySquare; /* black holdings */
14032 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14033 board[i][1] = (ChessSquare) 0; /* black counts */
14034 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14038 /* Piece placement data */
14039 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14042 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14043 if (*p == '/') p++;
14044 emptycount = gameInfo.boardWidth - j;
14045 while (emptycount--)
14046 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14048 #if(BOARD_FILES >= 10)
14049 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14050 p++; emptycount=10;
14051 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14052 while (emptycount--)
14053 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14055 } else if (isdigit(*p)) {
14056 emptycount = *p++ - '0';
14057 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14058 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14059 while (emptycount--)
14060 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14061 } else if (*p == '+' || isalpha(*p)) {
14062 if (j >= gameInfo.boardWidth) return FALSE;
14064 piece = CharToPiece(*++p);
14065 if(piece == EmptySquare) return FALSE; /* unknown piece */
14066 piece = (ChessSquare) (PROMOTED piece ); p++;
14067 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14068 } else piece = CharToPiece(*p++);
14070 if(piece==EmptySquare) return FALSE; /* unknown piece */
14071 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14072 piece = (ChessSquare) (PROMOTED piece);
14073 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14076 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14082 while (*p == '/' || *p == ' ') p++;
14084 /* [HGM] look for Crazyhouse holdings here */
14085 while(*p==' ') p++;
14086 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14088 if(*p == '-' ) *p++; /* empty holdings */ else {
14089 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14090 /* if we would allow FEN reading to set board size, we would */
14091 /* have to add holdings and shift the board read so far here */
14092 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14094 if((int) piece >= (int) BlackPawn ) {
14095 i = (int)piece - (int)BlackPawn;
14096 i = PieceToNumber((ChessSquare)i);
14097 if( i >= gameInfo.holdingsSize ) return FALSE;
14098 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14099 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14101 i = (int)piece - (int)WhitePawn;
14102 i = PieceToNumber((ChessSquare)i);
14103 if( i >= gameInfo.holdingsSize ) return FALSE;
14104 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14105 board[i][BOARD_WIDTH-2]++; /* black holdings */
14109 if(*p == ']') *p++;
14112 while(*p == ' ') p++;
14117 *blackPlaysFirst = FALSE;
14120 *blackPlaysFirst = TRUE;
14126 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14127 /* return the extra info in global variiables */
14129 /* set defaults in case FEN is incomplete */
14130 board[EP_STATUS] = EP_UNKNOWN;
14131 for(i=0; i<nrCastlingRights; i++ ) {
14132 board[CASTLING][i] =
14133 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14134 } /* assume possible unless obviously impossible */
14135 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14136 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14137 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14138 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14139 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14140 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14143 while(*p==' ') p++;
14144 if(nrCastlingRights) {
14145 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14146 /* castling indicator present, so default becomes no castlings */
14147 for(i=0; i<nrCastlingRights; i++ ) {
14148 board[CASTLING][i] = NoRights;
14151 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14152 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14153 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14154 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14155 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14157 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14158 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14159 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14163 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14164 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14165 board[CASTLING][2] = whiteKingFile;
14168 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14169 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14170 board[CASTLING][2] = whiteKingFile;
14173 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14174 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14175 board[CASTLING][5] = blackKingFile;
14178 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14179 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14180 board[CASTLING][5] = blackKingFile;
14183 default: /* FRC castlings */
14184 if(c >= 'a') { /* black rights */
14185 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14186 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14187 if(i == BOARD_RGHT) break;
14188 board[CASTLING][5] = i;
14190 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14191 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14193 board[CASTLING][3] = c;
14195 board[CASTLING][4] = c;
14196 } else { /* white rights */
14197 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14198 if(board[0][i] == WhiteKing) break;
14199 if(i == BOARD_RGHT) break;
14200 board[CASTLING][2] = i;
14201 c -= AAA - 'a' + 'A';
14202 if(board[0][c] >= WhiteKing) break;
14204 board[CASTLING][0] = c;
14206 board[CASTLING][1] = c;
14210 if (appData.debugMode) {
14211 fprintf(debugFP, "FEN castling rights:");
14212 for(i=0; i<nrCastlingRights; i++)
14213 fprintf(debugFP, " %d", board[CASTLING][i]);
14214 fprintf(debugFP, "\n");
14217 while(*p==' ') p++;
14220 /* read e.p. field in games that know e.p. capture */
14221 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14222 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14224 p++; board[EP_STATUS] = EP_NONE;
14226 char c = *p++ - AAA;
14228 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14229 if(*p >= '0' && *p <='9') *p++;
14230 board[EP_STATUS] = c;
14235 if(sscanf(p, "%d", &i) == 1) {
14236 FENrulePlies = i; /* 50-move ply counter */
14237 /* (The move number is still ignored) */
14244 EditPositionPasteFEN(char *fen)
14247 Board initial_position;
14249 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14250 DisplayError(_("Bad FEN position in clipboard"), 0);
14253 int savedBlackPlaysFirst = blackPlaysFirst;
14254 EditPositionEvent();
14255 blackPlaysFirst = savedBlackPlaysFirst;
14256 CopyBoard(boards[0], initial_position);
14257 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14258 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14259 DisplayBothClocks();
14260 DrawPosition(FALSE, boards[currentMove]);
14265 static char cseq[12] = "\\ ";
14267 Boolean set_cont_sequence(char *new_seq)
14272 // handle bad attempts to set the sequence
14274 return 0; // acceptable error - no debug
14276 len = strlen(new_seq);
14277 ret = (len > 0) && (len < sizeof(cseq));
14279 strcpy(cseq, new_seq);
14280 else if (appData.debugMode)
14281 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14286 reformat a source message so words don't cross the width boundary. internal
14287 newlines are not removed. returns the wrapped size (no null character unless
14288 included in source message). If dest is NULL, only calculate the size required
14289 for the dest buffer. lp argument indicats line position upon entry, and it's
14290 passed back upon exit.
14292 int wrap(char *dest, char *src, int count, int width, int *lp)
14294 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14296 cseq_len = strlen(cseq);
14297 old_line = line = *lp;
14298 ansi = len = clen = 0;
14300 for (i=0; i < count; i++)
14302 if (src[i] == '\033')
14305 // if we hit the width, back up
14306 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14308 // store i & len in case the word is too long
14309 old_i = i, old_len = len;
14311 // find the end of the last word
14312 while (i && src[i] != ' ' && src[i] != '\n')
14318 // word too long? restore i & len before splitting it
14319 if ((old_i-i+clen) >= width)
14326 if (i && src[i-1] == ' ')
14329 if (src[i] != ' ' && src[i] != '\n')
14336 // now append the newline and continuation sequence
14341 strncpy(dest+len, cseq, cseq_len);
14349 dest[len] = src[i];
14353 if (src[i] == '\n')
14358 if (dest && appData.debugMode)
14360 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14361 count, width, line, len, *lp);
14362 show_bytes(debugFP, src, count);
14363 fprintf(debugFP, "\ndest: ");
14364 show_bytes(debugFP, dest, len);
14365 fprintf(debugFP, "\n");
14367 *lp = dest ? line : old_line;
14372 // [HGM] vari: routines for shelving variations
14375 PushTail(int firstMove, int lastMove)
14377 int i, j, nrMoves = lastMove - firstMove;
14379 if(appData.icsActive) { // only in local mode
14380 forwardMostMove = currentMove; // mimic old ICS behavior
14383 if(storedGames >= MAX_VARIATIONS-1) return;
14385 // push current tail of game on stack
14386 savedResult[storedGames] = gameInfo.result;
14387 savedDetails[storedGames] = gameInfo.resultDetails;
14388 gameInfo.resultDetails = NULL;
14389 savedFirst[storedGames] = firstMove;
14390 savedLast [storedGames] = lastMove;
14391 savedFramePtr[storedGames] = framePtr;
14392 framePtr -= nrMoves; // reserve space for the boards
14393 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14394 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14395 for(j=0; j<MOVE_LEN; j++)
14396 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14397 for(j=0; j<2*MOVE_LEN; j++)
14398 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14399 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14400 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14401 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14402 pvInfoList[firstMove+i-1].depth = 0;
14403 commentList[framePtr+i] = commentList[firstMove+i];
14404 commentList[firstMove+i] = NULL;
14408 forwardMostMove = currentMove; // truncte game so we can start variation
14409 if(storedGames == 1) GreyRevert(FALSE);
14413 PopTail(Boolean annotate)
14416 char buf[8000], moveBuf[20];
14418 if(appData.icsActive) return FALSE; // only in local mode
14419 if(!storedGames) return FALSE; // sanity
14422 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14423 nrMoves = savedLast[storedGames] - currentMove;
14426 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14427 else strcpy(buf, "(");
14428 for(i=currentMove; i<forwardMostMove; i++) {
14430 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14431 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14432 strcat(buf, moveBuf);
14433 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14437 for(i=1; i<nrMoves; i++) { // copy last variation back
14438 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14439 for(j=0; j<MOVE_LEN; j++)
14440 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14441 for(j=0; j<2*MOVE_LEN; j++)
14442 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14443 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14444 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14445 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14446 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14447 commentList[currentMove+i] = commentList[framePtr+i];
14448 commentList[framePtr+i] = NULL;
14450 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14451 framePtr = savedFramePtr[storedGames];
14452 gameInfo.result = savedResult[storedGames];
14453 if(gameInfo.resultDetails != NULL) {
14454 free(gameInfo.resultDetails);
14456 gameInfo.resultDetails = savedDetails[storedGames];
14457 forwardMostMove = currentMove + nrMoves;
14458 if(storedGames == 0) GreyRevert(TRUE);
14464 { // remove all shelved variations
14466 for(i=0; i<storedGames; i++) {
14467 if(savedDetails[i])
14468 free(savedDetails[i]);
14469 savedDetails[i] = NULL;
14471 for(i=framePtr; i<MAX_MOVES; i++) {
14472 if(commentList[i]) free(commentList[i]);
14473 commentList[i] = NULL;
14475 framePtr = MAX_MOVES-1;