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 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
241 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
251 /* States for ics_getting_history */
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
259 /* whosays values for GameEnds */
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
271 /* Different types of move when calling RegisterMove */
273 #define CMAIL_RESIGN 1
275 #define CMAIL_ACCEPT 3
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
282 /* Telnet protocol constants */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 assert( dst != NULL );
296 assert( src != NULL );
299 strncpy( dst, src, count );
300 dst[ count-1 ] = '\0';
304 /* Some compiler can't cast u64 to double
305 * This function do the job for us:
307 * We use the highest bit for cast, this only
308 * works if the highest bit is not
309 * in use (This should not happen)
311 * We used this for all compiler
314 u64ToDouble(u64 value)
317 u64 tmp = value & u64Const(0x7fffffffffffffff);
318 r = (double)(s64)tmp;
319 if (value & u64Const(0x8000000000000000))
320 r += 9.2233720368547758080e18; /* 2^63 */
324 /* Fake up flags for now, as we aren't keeping track of castling
325 availability yet. [HGM] Change of logic: the flag now only
326 indicates the type of castlings allowed by the rule of the game.
327 The actual rights themselves are maintained in the array
328 castlingRights, as part of the game history, and are not probed
334 int flags = F_ALL_CASTLE_OK;
335 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336 switch (gameInfo.variant) {
338 flags &= ~F_ALL_CASTLE_OK;
339 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340 flags |= F_IGNORE_CHECK;
342 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347 case VariantKriegspiel:
348 flags |= F_KRIEGSPIEL_CAPTURE;
350 case VariantCapaRandom:
351 case VariantFischeRandom:
352 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353 case VariantNoCastle:
354 case VariantShatranj:
356 flags &= ~F_ALL_CASTLE_OK;
364 FILE *gameFileFP, *debugFP;
367 [AS] Note: sometimes, the sscanf() function is used to parse the input
368 into a fixed-size buffer. Because of this, we must be prepared to
369 receive strings as long as the size of the input buffer, which is currently
370 set to 4K for Windows and 8K for the rest.
371 So, we must either allocate sufficiently large buffers here, or
372 reduce the size of the input buffer in the input reading part.
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
379 ChessProgramState first, second;
381 /* premove variables */
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
431 /* animateTraining preserves the state of appData.animate
432 * when Training mode is activated. This allows the
433 * response to be animated when appData.animate == TRUE and
434 * appData.animateDragging == TRUE.
436 Boolean animateTraining;
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
445 signed char initialRights[BOARD_FILES];
446 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int initialRulePlies, FENrulePlies;
448 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
451 int mute; // mute all sounds
453 // [HGM] vari: next 12 to save and restore variations
454 #define MAX_VARIATIONS 10
455 int framePtr = MAX_MOVES-1; // points to free stack entry
457 int savedFirst[MAX_VARIATIONS];
458 int savedLast[MAX_VARIATIONS];
459 int savedFramePtr[MAX_VARIATIONS];
460 char *savedDetails[MAX_VARIATIONS];
461 ChessMove savedResult[MAX_VARIATIONS];
463 void PushTail P((int firstMove, int lastMove));
464 Boolean PopTail P((void));
465 void CleanupTail P((void));
467 ChessSquare FIDEArray[2][BOARD_FILES] = {
468 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
469 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
470 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
471 BlackKing, BlackBishop, BlackKnight, BlackRook }
474 ChessSquare twoKingsArray[2][BOARD_FILES] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackKing, BlackKnight, BlackRook }
481 ChessSquare KnightmateArray[2][BOARD_FILES] = {
482 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
483 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
484 { BlackRook, BlackMan, BlackBishop, BlackQueen,
485 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
488 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
489 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
490 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
491 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
492 BlackKing, BlackBishop, BlackKnight, BlackRook }
495 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
496 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
497 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
498 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
499 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 #if (BOARD_FILES>=10)
504 ChessSquare ShogiArray[2][BOARD_FILES] = {
505 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
506 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
507 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
508 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
511 ChessSquare XiangqiArray[2][BOARD_FILES] = {
512 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
513 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
514 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
515 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
518 ChessSquare CapablancaArray[2][BOARD_FILES] = {
519 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
520 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
522 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
525 ChessSquare GreatArray[2][BOARD_FILES] = {
526 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
527 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
528 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
529 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
532 ChessSquare JanusArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
534 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
535 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
536 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 ChessSquare GothicArray[2][BOARD_FILES] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
542 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
544 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
547 #define GothicArray CapablancaArray
551 ChessSquare FalconArray[2][BOARD_FILES] = {
552 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
553 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
555 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
558 #define FalconArray CapablancaArray
561 #else // !(BOARD_FILES>=10)
562 #define XiangqiPosition FIDEArray
563 #define CapablancaArray FIDEArray
564 #define GothicArray FIDEArray
565 #define GreatArray FIDEArray
566 #endif // !(BOARD_FILES>=10)
568 #if (BOARD_FILES>=12)
569 ChessSquare CourierArray[2][BOARD_FILES] = {
570 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
571 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
572 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
573 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
575 #else // !(BOARD_FILES>=12)
576 #define CourierArray CapablancaArray
577 #endif // !(BOARD_FILES>=12)
580 Board initialPosition;
583 /* Convert str to a rating. Checks for special cases of "----",
585 "++++", etc. Also strips ()'s */
587 string_to_rating(str)
590 while(*str && !isdigit(*str)) ++str;
592 return 0; /* One of the special "no rating" cases */
600 /* Init programStats */
601 programStats.movelist[0] = 0;
602 programStats.depth = 0;
603 programStats.nr_moves = 0;
604 programStats.moves_left = 0;
605 programStats.nodes = 0;
606 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
607 programStats.score = 0;
608 programStats.got_only_move = 0;
609 programStats.got_fail = 0;
610 programStats.line_is_book = 0;
616 int matched, min, sec;
618 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
620 GetTimeMark(&programStartTime);
621 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
624 programStats.ok_to_send = 1;
625 programStats.seen_stat = 0;
628 * Initialize game list
634 * Internet chess server status
636 if (appData.icsActive) {
637 appData.matchMode = FALSE;
638 appData.matchGames = 0;
640 appData.noChessProgram = !appData.zippyPlay;
642 appData.zippyPlay = FALSE;
643 appData.zippyTalk = FALSE;
644 appData.noChessProgram = TRUE;
646 if (*appData.icsHelper != NULLCHAR) {
647 appData.useTelnet = TRUE;
648 appData.telnetProgram = appData.icsHelper;
651 appData.zippyTalk = appData.zippyPlay = FALSE;
654 /* [AS] Initialize pv info list [HGM] and game state */
658 for( i=0; i<=framePtr; i++ ) {
659 pvInfoList[i].depth = -1;
660 boards[i][EP_STATUS] = EP_NONE;
661 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
666 * Parse timeControl resource
668 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
669 appData.movesPerSession)) {
671 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
672 DisplayFatalError(buf, 0, 2);
676 * Parse searchTime resource
678 if (*appData.searchTime != NULLCHAR) {
679 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
681 searchTime = min * 60;
682 } else if (matched == 2) {
683 searchTime = min * 60 + sec;
686 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
687 DisplayFatalError(buf, 0, 2);
691 /* [AS] Adjudication threshold */
692 adjudicateLossThreshold = appData.adjudicateLossThreshold;
694 first.which = "first";
695 second.which = "second";
696 first.maybeThinking = second.maybeThinking = FALSE;
697 first.pr = second.pr = NoProc;
698 first.isr = second.isr = NULL;
699 first.sendTime = second.sendTime = 2;
700 first.sendDrawOffers = 1;
701 if (appData.firstPlaysBlack) {
702 first.twoMachinesColor = "black\n";
703 second.twoMachinesColor = "white\n";
705 first.twoMachinesColor = "white\n";
706 second.twoMachinesColor = "black\n";
708 first.program = appData.firstChessProgram;
709 second.program = appData.secondChessProgram;
710 first.host = appData.firstHost;
711 second.host = appData.secondHost;
712 first.dir = appData.firstDirectory;
713 second.dir = appData.secondDirectory;
714 first.other = &second;
715 second.other = &first;
716 first.initString = appData.initString;
717 second.initString = appData.secondInitString;
718 first.computerString = appData.firstComputerString;
719 second.computerString = appData.secondComputerString;
720 first.useSigint = second.useSigint = TRUE;
721 first.useSigterm = second.useSigterm = TRUE;
722 first.reuse = appData.reuseFirst;
723 second.reuse = appData.reuseSecond;
724 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
725 second.nps = appData.secondNPS;
726 first.useSetboard = second.useSetboard = FALSE;
727 first.useSAN = second.useSAN = FALSE;
728 first.usePing = second.usePing = FALSE;
729 first.lastPing = second.lastPing = 0;
730 first.lastPong = second.lastPong = 0;
731 first.usePlayother = second.usePlayother = FALSE;
732 first.useColors = second.useColors = TRUE;
733 first.useUsermove = second.useUsermove = FALSE;
734 first.sendICS = second.sendICS = FALSE;
735 first.sendName = second.sendName = appData.icsActive;
736 first.sdKludge = second.sdKludge = FALSE;
737 first.stKludge = second.stKludge = FALSE;
738 TidyProgramName(first.program, first.host, first.tidy);
739 TidyProgramName(second.program, second.host, second.tidy);
740 first.matchWins = second.matchWins = 0;
741 strcpy(first.variants, appData.variant);
742 strcpy(second.variants, appData.variant);
743 first.analysisSupport = second.analysisSupport = 2; /* detect */
744 first.analyzing = second.analyzing = FALSE;
745 first.initDone = second.initDone = FALSE;
747 /* New features added by Tord: */
748 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
749 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
750 /* End of new features added by Tord. */
751 first.fenOverride = appData.fenOverride1;
752 second.fenOverride = appData.fenOverride2;
754 /* [HGM] time odds: set factor for each machine */
755 first.timeOdds = appData.firstTimeOdds;
756 second.timeOdds = appData.secondTimeOdds;
758 if(appData.timeOddsMode) {
759 norm = first.timeOdds;
760 if(norm > second.timeOdds) norm = second.timeOdds;
762 first.timeOdds /= norm;
763 second.timeOdds /= norm;
766 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
767 first.accumulateTC = appData.firstAccumulateTC;
768 second.accumulateTC = appData.secondAccumulateTC;
769 first.maxNrOfSessions = second.maxNrOfSessions = 1;
772 first.debug = second.debug = FALSE;
773 first.supportsNPS = second.supportsNPS = UNKNOWN;
776 first.optionSettings = appData.firstOptions;
777 second.optionSettings = appData.secondOptions;
779 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
780 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
781 first.isUCI = appData.firstIsUCI; /* [AS] */
782 second.isUCI = appData.secondIsUCI; /* [AS] */
783 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
784 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
786 if (appData.firstProtocolVersion > PROTOVER ||
787 appData.firstProtocolVersion < 1) {
789 sprintf(buf, _("protocol version %d not supported"),
790 appData.firstProtocolVersion);
791 DisplayFatalError(buf, 0, 2);
793 first.protocolVersion = appData.firstProtocolVersion;
796 if (appData.secondProtocolVersion > PROTOVER ||
797 appData.secondProtocolVersion < 1) {
799 sprintf(buf, _("protocol version %d not supported"),
800 appData.secondProtocolVersion);
801 DisplayFatalError(buf, 0, 2);
803 second.protocolVersion = appData.secondProtocolVersion;
806 if (appData.icsActive) {
807 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
808 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
809 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
810 appData.clockMode = FALSE;
811 first.sendTime = second.sendTime = 0;
815 /* Override some settings from environment variables, for backward
816 compatibility. Unfortunately it's not feasible to have the env
817 vars just set defaults, at least in xboard. Ugh.
819 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
824 if (appData.noChessProgram) {
825 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
826 sprintf(programVersion, "%s", PACKAGE_STRING);
828 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
829 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
830 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
833 if (!appData.icsActive) {
835 /* Check for variants that are supported only in ICS mode,
836 or not at all. Some that are accepted here nevertheless
837 have bugs; see comments below.
839 VariantClass variant = StringToVariant(appData.variant);
841 case VariantBughouse: /* need four players and two boards */
842 case VariantKriegspiel: /* need to hide pieces and move details */
843 /* case VariantFischeRandom: (Fabien: moved below) */
844 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
845 DisplayFatalError(buf, 0, 2);
849 case VariantLoadable:
859 sprintf(buf, _("Unknown variant name %s"), appData.variant);
860 DisplayFatalError(buf, 0, 2);
863 case VariantXiangqi: /* [HGM] repetition rules not implemented */
864 case VariantFairy: /* [HGM] TestLegality definitely off! */
865 case VariantGothic: /* [HGM] should work */
866 case VariantCapablanca: /* [HGM] should work */
867 case VariantCourier: /* [HGM] initial forced moves not implemented */
868 case VariantShogi: /* [HGM] drops not tested for legality */
869 case VariantKnightmate: /* [HGM] should work */
870 case VariantCylinder: /* [HGM] untested */
871 case VariantFalcon: /* [HGM] untested */
872 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
873 offboard interposition not understood */
874 case VariantNormal: /* definitely works! */
875 case VariantWildCastle: /* pieces not automatically shuffled */
876 case VariantNoCastle: /* pieces not automatically shuffled */
877 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
878 case VariantLosers: /* should work except for win condition,
879 and doesn't know captures are mandatory */
880 case VariantSuicide: /* should work except for win condition,
881 and doesn't know captures are mandatory */
882 case VariantGiveaway: /* should work except for win condition,
883 and doesn't know captures are mandatory */
884 case VariantTwoKings: /* should work */
885 case VariantAtomic: /* should work except for win condition */
886 case Variant3Check: /* should work except for win condition */
887 case VariantShatranj: /* should work except for all win conditions */
888 case VariantBerolina: /* might work if TestLegality is off */
889 case VariantCapaRandom: /* should work */
890 case VariantJanus: /* should work */
891 case VariantSuper: /* experimental */
892 case VariantGreat: /* experimental, requires legality testing to be off */
897 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
898 InitEngineUCI( installDir, &second );
901 int NextIntegerFromString( char ** str, long * value )
906 while( *s == ' ' || *s == '\t' ) {
912 if( *s >= '0' && *s <= '9' ) {
913 while( *s >= '0' && *s <= '9' ) {
914 *value = *value * 10 + (*s - '0');
926 int NextTimeControlFromString( char ** str, long * value )
929 int result = NextIntegerFromString( str, &temp );
932 *value = temp * 60; /* Minutes */
935 result = NextIntegerFromString( str, &temp );
936 *value += temp; /* Seconds */
943 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
944 { /* [HGM] routine added to read '+moves/time' for secondary time control */
945 int result = -1; long temp, temp2;
947 if(**str != '+') return -1; // old params remain in force!
949 if( NextTimeControlFromString( str, &temp ) ) return -1;
952 /* time only: incremental or sudden-death time control */
953 if(**str == '+') { /* increment follows; read it */
955 if(result = NextIntegerFromString( str, &temp2)) return -1;
958 *moves = 0; *tc = temp * 1000;
960 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
962 (*str)++; /* classical time control */
963 result = NextTimeControlFromString( str, &temp2);
972 int GetTimeQuota(int movenr)
973 { /* [HGM] get time to add from the multi-session time-control string */
974 int moves=1; /* kludge to force reading of first session */
975 long time, increment;
976 char *s = fullTimeControlString;
978 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
980 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
981 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
982 if(movenr == -1) return time; /* last move before new session */
983 if(!moves) return increment; /* current session is incremental */
984 if(movenr >= 0) movenr -= moves; /* we already finished this session */
985 } while(movenr >= -1); /* try again for next session */
987 return 0; // no new time quota on this move
991 ParseTimeControl(tc, ti, mps)
1000 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1003 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1004 else sprintf(buf, "+%s+%d", tc, ti);
1007 sprintf(buf, "+%d/%s", mps, tc);
1008 else sprintf(buf, "+%s", tc);
1010 fullTimeControlString = StrSave(buf);
1012 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1017 /* Parse second time control */
1020 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1028 timeControl_2 = tc2 * 1000;
1038 timeControl = tc1 * 1000;
1041 timeIncrement = ti * 1000; /* convert to ms */
1042 movesPerSession = 0;
1045 movesPerSession = mps;
1053 if (appData.debugMode) {
1054 fprintf(debugFP, "%s\n", programVersion);
1057 set_cont_sequence(appData.wrapContSeq);
1058 if (appData.matchGames > 0) {
1059 appData.matchMode = TRUE;
1060 } else if (appData.matchMode) {
1061 appData.matchGames = 1;
1063 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1064 appData.matchGames = appData.sameColorGames;
1065 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1066 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1067 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1070 if (appData.noChessProgram || first.protocolVersion == 1) {
1073 /* kludge: allow timeout for initial "feature" commands */
1075 DisplayMessage("", _("Starting chess program"));
1076 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1081 InitBackEnd3 P((void))
1083 GameMode initialMode;
1087 InitChessProgram(&first, startedFromSetupPosition);
1090 if (appData.icsActive) {
1092 /* [DM] Make a console window if needed [HGM] merged ifs */
1097 if (*appData.icsCommPort != NULLCHAR) {
1098 sprintf(buf, _("Could not open comm port %s"),
1099 appData.icsCommPort);
1101 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1102 appData.icsHost, appData.icsPort);
1104 DisplayFatalError(buf, err, 1);
1109 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1111 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1112 } else if (appData.noChessProgram) {
1118 if (*appData.cmailGameName != NULLCHAR) {
1120 OpenLoopback(&cmailPR);
1122 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1126 DisplayMessage("", "");
1127 if (StrCaseCmp(appData.initialMode, "") == 0) {
1128 initialMode = BeginningOfGame;
1129 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1130 initialMode = TwoMachinesPlay;
1131 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1132 initialMode = AnalyzeFile;
1133 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1134 initialMode = AnalyzeMode;
1135 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1136 initialMode = MachinePlaysWhite;
1137 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1138 initialMode = MachinePlaysBlack;
1139 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1140 initialMode = EditGame;
1141 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1142 initialMode = EditPosition;
1143 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1144 initialMode = Training;
1146 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1147 DisplayFatalError(buf, 0, 2);
1151 if (appData.matchMode) {
1152 /* Set up machine vs. machine match */
1153 if (appData.noChessProgram) {
1154 DisplayFatalError(_("Can't have a match with no chess programs"),
1160 if (*appData.loadGameFile != NULLCHAR) {
1161 int index = appData.loadGameIndex; // [HGM] autoinc
1162 if(index<0) lastIndex = index = 1;
1163 if (!LoadGameFromFile(appData.loadGameFile,
1165 appData.loadGameFile, FALSE)) {
1166 DisplayFatalError(_("Bad game file"), 0, 1);
1169 } else if (*appData.loadPositionFile != NULLCHAR) {
1170 int index = appData.loadPositionIndex; // [HGM] autoinc
1171 if(index<0) lastIndex = index = 1;
1172 if (!LoadPositionFromFile(appData.loadPositionFile,
1174 appData.loadPositionFile)) {
1175 DisplayFatalError(_("Bad position file"), 0, 1);
1180 } else if (*appData.cmailGameName != NULLCHAR) {
1181 /* Set up cmail mode */
1182 ReloadCmailMsgEvent(TRUE);
1184 /* Set up other modes */
1185 if (initialMode == AnalyzeFile) {
1186 if (*appData.loadGameFile == NULLCHAR) {
1187 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1191 if (*appData.loadGameFile != NULLCHAR) {
1192 (void) LoadGameFromFile(appData.loadGameFile,
1193 appData.loadGameIndex,
1194 appData.loadGameFile, TRUE);
1195 } else if (*appData.loadPositionFile != NULLCHAR) {
1196 (void) LoadPositionFromFile(appData.loadPositionFile,
1197 appData.loadPositionIndex,
1198 appData.loadPositionFile);
1199 /* [HGM] try to make self-starting even after FEN load */
1200 /* to allow automatic setup of fairy variants with wtm */
1201 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1202 gameMode = BeginningOfGame;
1203 setboardSpoiledMachineBlack = 1;
1205 /* [HGM] loadPos: make that every new game uses the setup */
1206 /* from file as long as we do not switch variant */
1207 if(!blackPlaysFirst) {
1208 startedFromPositionFile = TRUE;
1209 CopyBoard(filePosition, boards[0]);
1212 if (initialMode == AnalyzeMode) {
1213 if (appData.noChessProgram) {
1214 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1217 if (appData.icsActive) {
1218 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1222 } else if (initialMode == AnalyzeFile) {
1223 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1224 ShowThinkingEvent();
1226 AnalysisPeriodicEvent(1);
1227 } else if (initialMode == MachinePlaysWhite) {
1228 if (appData.noChessProgram) {
1229 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1233 if (appData.icsActive) {
1234 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1238 MachineWhiteEvent();
1239 } else if (initialMode == MachinePlaysBlack) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1245 if (appData.icsActive) {
1246 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1250 MachineBlackEvent();
1251 } else if (initialMode == TwoMachinesPlay) {
1252 if (appData.noChessProgram) {
1253 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1257 if (appData.icsActive) {
1258 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1263 } else if (initialMode == EditGame) {
1265 } else if (initialMode == EditPosition) {
1266 EditPositionEvent();
1267 } else if (initialMode == Training) {
1268 if (*appData.loadGameFile == NULLCHAR) {
1269 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1278 * Establish will establish a contact to a remote host.port.
1279 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1280 * used to talk to the host.
1281 * Returns 0 if okay, error code if not.
1288 if (*appData.icsCommPort != NULLCHAR) {
1289 /* Talk to the host through a serial comm port */
1290 return OpenCommPort(appData.icsCommPort, &icsPR);
1292 } else if (*appData.gateway != NULLCHAR) {
1293 if (*appData.remoteShell == NULLCHAR) {
1294 /* Use the rcmd protocol to run telnet program on a gateway host */
1295 snprintf(buf, sizeof(buf), "%s %s %s",
1296 appData.telnetProgram, appData.icsHost, appData.icsPort);
1297 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1300 /* Use the rsh program to run telnet program on a gateway host */
1301 if (*appData.remoteUser == NULLCHAR) {
1302 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1303 appData.gateway, appData.telnetProgram,
1304 appData.icsHost, appData.icsPort);
1306 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1307 appData.remoteShell, appData.gateway,
1308 appData.remoteUser, appData.telnetProgram,
1309 appData.icsHost, appData.icsPort);
1311 return StartChildProcess(buf, "", &icsPR);
1314 } else if (appData.useTelnet) {
1315 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1318 /* TCP socket interface differs somewhat between
1319 Unix and NT; handle details in the front end.
1321 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1326 show_bytes(fp, buf, count)
1332 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1333 fprintf(fp, "\\%03o", *buf & 0xff);
1342 /* Returns an errno value */
1344 OutputMaybeTelnet(pr, message, count, outError)
1350 char buf[8192], *p, *q, *buflim;
1351 int left, newcount, outcount;
1353 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1354 *appData.gateway != NULLCHAR) {
1355 if (appData.debugMode) {
1356 fprintf(debugFP, ">ICS: ");
1357 show_bytes(debugFP, message, count);
1358 fprintf(debugFP, "\n");
1360 return OutputToProcess(pr, message, count, outError);
1363 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1370 if (appData.debugMode) {
1371 fprintf(debugFP, ">ICS: ");
1372 show_bytes(debugFP, buf, newcount);
1373 fprintf(debugFP, "\n");
1375 outcount = OutputToProcess(pr, buf, newcount, outError);
1376 if (outcount < newcount) return -1; /* to be sure */
1383 } else if (((unsigned char) *p) == TN_IAC) {
1384 *q++ = (char) TN_IAC;
1391 if (appData.debugMode) {
1392 fprintf(debugFP, ">ICS: ");
1393 show_bytes(debugFP, buf, newcount);
1394 fprintf(debugFP, "\n");
1396 outcount = OutputToProcess(pr, buf, newcount, outError);
1397 if (outcount < newcount) return -1; /* to be sure */
1402 read_from_player(isr, closure, message, count, error)
1409 int outError, outCount;
1410 static int gotEof = 0;
1412 /* Pass data read from player on to ICS */
1415 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1416 if (outCount < count) {
1417 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1419 } else if (count < 0) {
1420 RemoveInputSource(isr);
1421 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1422 } else if (gotEof++ > 0) {
1423 RemoveInputSource(isr);
1424 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1430 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1431 SendToICS("date\n");
1432 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1435 /* added routine for printf style output to ics */
1436 void ics_printf(char *format, ...)
1438 char buffer[MSG_SIZ];
1441 va_start(args, format);
1442 vsnprintf(buffer, sizeof(buffer), format, args);
1443 buffer[sizeof(buffer)-1] = '\0';
1452 int count, outCount, outError;
1454 if (icsPR == NULL) return;
1457 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1458 if (outCount < count) {
1459 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1463 /* This is used for sending logon scripts to the ICS. Sending
1464 without a delay causes problems when using timestamp on ICC
1465 (at least on my machine). */
1467 SendToICSDelayed(s,msdelay)
1471 int count, outCount, outError;
1473 if (icsPR == NULL) return;
1476 if (appData.debugMode) {
1477 fprintf(debugFP, ">ICS: ");
1478 show_bytes(debugFP, s, count);
1479 fprintf(debugFP, "\n");
1481 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1483 if (outCount < count) {
1484 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1489 /* Remove all highlighting escape sequences in s
1490 Also deletes any suffix starting with '('
1493 StripHighlightAndTitle(s)
1496 static char retbuf[MSG_SIZ];
1499 while (*s != NULLCHAR) {
1500 while (*s == '\033') {
1501 while (*s != NULLCHAR && !isalpha(*s)) s++;
1502 if (*s != NULLCHAR) s++;
1504 while (*s != NULLCHAR && *s != '\033') {
1505 if (*s == '(' || *s == '[') {
1516 /* Remove all highlighting escape sequences in s */
1521 static char retbuf[MSG_SIZ];
1524 while (*s != NULLCHAR) {
1525 while (*s == '\033') {
1526 while (*s != NULLCHAR && !isalpha(*s)) s++;
1527 if (*s != NULLCHAR) s++;
1529 while (*s != NULLCHAR && *s != '\033') {
1537 char *variantNames[] = VARIANT_NAMES;
1542 return variantNames[v];
1546 /* Identify a variant from the strings the chess servers use or the
1547 PGN Variant tag names we use. */
1554 VariantClass v = VariantNormal;
1555 int i, found = FALSE;
1560 /* [HGM] skip over optional board-size prefixes */
1561 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1562 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1563 while( *e++ != '_');
1566 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1570 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1571 if (StrCaseStr(e, variantNames[i])) {
1572 v = (VariantClass) i;
1579 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1580 || StrCaseStr(e, "wild/fr")
1581 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1582 v = VariantFischeRandom;
1583 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1584 (i = 1, p = StrCaseStr(e, "w"))) {
1586 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1593 case 0: /* FICS only, actually */
1595 /* Castling legal even if K starts on d-file */
1596 v = VariantWildCastle;
1601 /* Castling illegal even if K & R happen to start in
1602 normal positions. */
1603 v = VariantNoCastle;
1616 /* Castling legal iff K & R start in normal positions */
1622 /* Special wilds for position setup; unclear what to do here */
1623 v = VariantLoadable;
1626 /* Bizarre ICC game */
1627 v = VariantTwoKings;
1630 v = VariantKriegspiel;
1636 v = VariantFischeRandom;
1639 v = VariantCrazyhouse;
1642 v = VariantBughouse;
1648 /* Not quite the same as FICS suicide! */
1649 v = VariantGiveaway;
1655 v = VariantShatranj;
1658 /* Temporary names for future ICC types. The name *will* change in
1659 the next xboard/WinBoard release after ICC defines it. */
1697 v = VariantCapablanca;
1700 v = VariantKnightmate;
1706 v = VariantCylinder;
1712 v = VariantCapaRandom;
1715 v = VariantBerolina;
1727 /* Found "wild" or "w" in the string but no number;
1728 must assume it's normal chess. */
1732 sprintf(buf, _("Unknown wild type %d"), wnum);
1733 DisplayError(buf, 0);
1739 if (appData.debugMode) {
1740 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1741 e, wnum, VariantName(v));
1746 static int leftover_start = 0, leftover_len = 0;
1747 char star_match[STAR_MATCH_N][MSG_SIZ];
1749 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1750 advance *index beyond it, and set leftover_start to the new value of
1751 *index; else return FALSE. If pattern contains the character '*', it
1752 matches any sequence of characters not containing '\r', '\n', or the
1753 character following the '*' (if any), and the matched sequence(s) are
1754 copied into star_match.
1757 looking_at(buf, index, pattern)
1762 char *bufp = &buf[*index], *patternp = pattern;
1764 char *matchp = star_match[0];
1767 if (*patternp == NULLCHAR) {
1768 *index = leftover_start = bufp - buf;
1772 if (*bufp == NULLCHAR) return FALSE;
1773 if (*patternp == '*') {
1774 if (*bufp == *(patternp + 1)) {
1776 matchp = star_match[++star_count];
1780 } else if (*bufp == '\n' || *bufp == '\r') {
1782 if (*patternp == NULLCHAR)
1787 *matchp++ = *bufp++;
1791 if (*patternp != *bufp) return FALSE;
1798 SendToPlayer(data, length)
1802 int error, outCount;
1803 outCount = OutputToProcess(NoProc, data, length, &error);
1804 if (outCount < length) {
1805 DisplayFatalError(_("Error writing to display"), error, 1);
1810 PackHolding(packed, holding)
1822 switch (runlength) {
1833 sprintf(q, "%d", runlength);
1845 /* Telnet protocol requests from the front end */
1847 TelnetRequest(ddww, option)
1848 unsigned char ddww, option;
1850 unsigned char msg[3];
1851 int outCount, outError;
1853 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1855 if (appData.debugMode) {
1856 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1872 sprintf(buf1, "%d", ddww);
1881 sprintf(buf2, "%d", option);
1884 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1889 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1891 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1898 if (!appData.icsActive) return;
1899 TelnetRequest(TN_DO, TN_ECHO);
1905 if (!appData.icsActive) return;
1906 TelnetRequest(TN_DONT, TN_ECHO);
1910 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1912 /* put the holdings sent to us by the server on the board holdings area */
1913 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1917 if(gameInfo.holdingsWidth < 2) return;
1918 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1919 return; // prevent overwriting by pre-board holdings
1921 if( (int)lowestPiece >= BlackPawn ) {
1924 holdingsStartRow = BOARD_HEIGHT-1;
1927 holdingsColumn = BOARD_WIDTH-1;
1928 countsColumn = BOARD_WIDTH-2;
1929 holdingsStartRow = 0;
1933 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934 board[i][holdingsColumn] = EmptySquare;
1935 board[i][countsColumn] = (ChessSquare) 0;
1937 while( (p=*holdings++) != NULLCHAR ) {
1938 piece = CharToPiece( ToUpper(p) );
1939 if(piece == EmptySquare) continue;
1940 /*j = (int) piece - (int) WhitePawn;*/
1941 j = PieceToNumber(piece);
1942 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943 if(j < 0) continue; /* should not happen */
1944 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946 board[holdingsStartRow+j*direction][countsColumn]++;
1952 VariantSwitch(Board board, VariantClass newVariant)
1954 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1957 startedFromPositionFile = FALSE;
1958 if(gameInfo.variant == newVariant) return;
1960 /* [HGM] This routine is called each time an assignment is made to
1961 * gameInfo.variant during a game, to make sure the board sizes
1962 * are set to match the new variant. If that means adding or deleting
1963 * holdings, we shift the playing board accordingly
1964 * This kludge is needed because in ICS observe mode, we get boards
1965 * of an ongoing game without knowing the variant, and learn about the
1966 * latter only later. This can be because of the move list we requested,
1967 * in which case the game history is refilled from the beginning anyway,
1968 * but also when receiving holdings of a crazyhouse game. In the latter
1969 * case we want to add those holdings to the already received position.
1973 if (appData.debugMode) {
1974 fprintf(debugFP, "Switch board from %s to %s\n",
1975 VariantName(gameInfo.variant), VariantName(newVariant));
1976 setbuf(debugFP, NULL);
1978 shuffleOpenings = 0; /* [HGM] shuffle */
1979 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1983 newWidth = 9; newHeight = 9;
1984 gameInfo.holdingsSize = 7;
1985 case VariantBughouse:
1986 case VariantCrazyhouse:
1987 newHoldingsWidth = 2; break;
1991 newHoldingsWidth = 2;
1992 gameInfo.holdingsSize = 8;
1995 case VariantCapablanca:
1996 case VariantCapaRandom:
1999 newHoldingsWidth = gameInfo.holdingsSize = 0;
2002 if(newWidth != gameInfo.boardWidth ||
2003 newHeight != gameInfo.boardHeight ||
2004 newHoldingsWidth != gameInfo.holdingsWidth ) {
2006 /* shift position to new playing area, if needed */
2007 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2008 for(i=0; i<BOARD_HEIGHT; i++)
2009 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2010 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2012 for(i=0; i<newHeight; i++) {
2013 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2014 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2016 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2017 for(i=0; i<BOARD_HEIGHT; i++)
2018 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2019 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2022 gameInfo.boardWidth = newWidth;
2023 gameInfo.boardHeight = newHeight;
2024 gameInfo.holdingsWidth = newHoldingsWidth;
2025 gameInfo.variant = newVariant;
2026 InitDrawingSizes(-2, 0);
2027 } else gameInfo.variant = newVariant;
2028 CopyBoard(oldBoard, board); // remember correctly formatted board
2029 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2030 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2033 static int loggedOn = FALSE;
2035 /*-- Game start info cache: --*/
2037 char gs_kind[MSG_SIZ];
2038 static char player1Name[128] = "";
2039 static char player2Name[128] = "";
2040 static char cont_seq[] = "\n\\ ";
2041 static int player1Rating = -1;
2042 static int player2Rating = -1;
2043 /*----------------------------*/
2045 ColorClass curColor = ColorNormal;
2046 int suppressKibitz = 0;
2049 read_from_ics(isr, closure, data, count, error)
2056 #define BUF_SIZE 8192
2057 #define STARTED_NONE 0
2058 #define STARTED_MOVES 1
2059 #define STARTED_BOARD 2
2060 #define STARTED_OBSERVE 3
2061 #define STARTED_HOLDINGS 4
2062 #define STARTED_CHATTER 5
2063 #define STARTED_COMMENT 6
2064 #define STARTED_MOVES_NOHIDE 7
2066 static int started = STARTED_NONE;
2067 static char parse[20000];
2068 static int parse_pos = 0;
2069 static char buf[BUF_SIZE + 1];
2070 static int firstTime = TRUE, intfSet = FALSE;
2071 static ColorClass prevColor = ColorNormal;
2072 static int savingComment = FALSE;
2073 static int cmatch = 0; // continuation sequence match
2080 int backup; /* [DM] For zippy color lines */
2082 char talker[MSG_SIZ]; // [HGM] chat
2085 if (appData.debugMode) {
2087 fprintf(debugFP, "<ICS: ");
2088 show_bytes(debugFP, data, count);
2089 fprintf(debugFP, "\n");
2093 if (appData.debugMode) { int f = forwardMostMove;
2094 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2095 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2096 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2099 /* If last read ended with a partial line that we couldn't parse,
2100 prepend it to the new read and try again. */
2101 if (leftover_len > 0) {
2102 for (i=0; i<leftover_len; i++)
2103 buf[i] = buf[leftover_start + i];
2106 /* copy new characters into the buffer */
2107 bp = buf + leftover_len;
2108 buf_len=leftover_len;
2109 for (i=0; i<count; i++)
2112 if (data[i] == '\r')
2115 // join lines split by ICS?
2116 if (!appData.noJoin)
2119 Joining just consists of finding matches against the
2120 continuation sequence, and discarding that sequence
2121 if found instead of copying it. So, until a match
2122 fails, there's nothing to do since it might be the
2123 complete sequence, and thus, something we don't want
2126 if (data[i] == cont_seq[cmatch])
2129 if (cmatch == strlen(cont_seq))
2131 cmatch = 0; // complete match. just reset the counter
2134 it's possible for the ICS to not include the space
2135 at the end of the last word, making our [correct]
2136 join operation fuse two separate words. the server
2137 does this when the space occurs at the width setting.
2139 if (!buf_len || buf[buf_len-1] != ' ')
2150 match failed, so we have to copy what matched before
2151 falling through and copying this character. In reality,
2152 this will only ever be just the newline character, but
2153 it doesn't hurt to be precise.
2155 strncpy(bp, cont_seq, cmatch);
2167 buf[buf_len] = NULLCHAR;
2168 next_out = leftover_len;
2172 while (i < buf_len) {
2173 /* Deal with part of the TELNET option negotiation
2174 protocol. We refuse to do anything beyond the
2175 defaults, except that we allow the WILL ECHO option,
2176 which ICS uses to turn off password echoing when we are
2177 directly connected to it. We reject this option
2178 if localLineEditing mode is on (always on in xboard)
2179 and we are talking to port 23, which might be a real
2180 telnet server that will try to keep WILL ECHO on permanently.
2182 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2183 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2184 unsigned char option;
2186 switch ((unsigned char) buf[++i]) {
2188 if (appData.debugMode)
2189 fprintf(debugFP, "\n<WILL ");
2190 switch (option = (unsigned char) buf[++i]) {
2192 if (appData.debugMode)
2193 fprintf(debugFP, "ECHO ");
2194 /* Reply only if this is a change, according
2195 to the protocol rules. */
2196 if (remoteEchoOption) break;
2197 if (appData.localLineEditing &&
2198 atoi(appData.icsPort) == TN_PORT) {
2199 TelnetRequest(TN_DONT, TN_ECHO);
2202 TelnetRequest(TN_DO, TN_ECHO);
2203 remoteEchoOption = TRUE;
2207 if (appData.debugMode)
2208 fprintf(debugFP, "%d ", option);
2209 /* Whatever this is, we don't want it. */
2210 TelnetRequest(TN_DONT, option);
2215 if (appData.debugMode)
2216 fprintf(debugFP, "\n<WONT ");
2217 switch (option = (unsigned char) buf[++i]) {
2219 if (appData.debugMode)
2220 fprintf(debugFP, "ECHO ");
2221 /* Reply only if this is a change, according
2222 to the protocol rules. */
2223 if (!remoteEchoOption) break;
2225 TelnetRequest(TN_DONT, TN_ECHO);
2226 remoteEchoOption = FALSE;
2229 if (appData.debugMode)
2230 fprintf(debugFP, "%d ", (unsigned char) option);
2231 /* Whatever this is, it must already be turned
2232 off, because we never agree to turn on
2233 anything non-default, so according to the
2234 protocol rules, we don't reply. */
2239 if (appData.debugMode)
2240 fprintf(debugFP, "\n<DO ");
2241 switch (option = (unsigned char) buf[++i]) {
2243 /* Whatever this is, we refuse to do it. */
2244 if (appData.debugMode)
2245 fprintf(debugFP, "%d ", option);
2246 TelnetRequest(TN_WONT, option);
2251 if (appData.debugMode)
2252 fprintf(debugFP, "\n<DONT ");
2253 switch (option = (unsigned char) buf[++i]) {
2255 if (appData.debugMode)
2256 fprintf(debugFP, "%d ", option);
2257 /* Whatever this is, we are already not doing
2258 it, because we never agree to do anything
2259 non-default, so according to the protocol
2260 rules, we don't reply. */
2265 if (appData.debugMode)
2266 fprintf(debugFP, "\n<IAC ");
2267 /* Doubled IAC; pass it through */
2271 if (appData.debugMode)
2272 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2273 /* Drop all other telnet commands on the floor */
2276 if (oldi > next_out)
2277 SendToPlayer(&buf[next_out], oldi - next_out);
2283 /* OK, this at least will *usually* work */
2284 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2288 if (loggedOn && !intfSet) {
2289 if (ics_type == ICS_ICC) {
2291 "/set-quietly interface %s\n/set-quietly style 12\n",
2293 } else if (ics_type == ICS_CHESSNET) {
2294 sprintf(str, "/style 12\n");
2296 strcpy(str, "alias $ @\n$set interface ");
2297 strcat(str, programVersion);
2298 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2300 strcat(str, "$iset nohighlight 1\n");
2302 strcat(str, "$iset lock 1\n$style 12\n");
2305 NotifyFrontendLogin();
2309 if (started == STARTED_COMMENT) {
2310 /* Accumulate characters in comment */
2311 parse[parse_pos++] = buf[i];
2312 if (buf[i] == '\n') {
2313 parse[parse_pos] = NULLCHAR;
2314 if(chattingPartner>=0) {
2316 sprintf(mess, "%s%s", talker, parse);
2317 OutputChatMessage(chattingPartner, mess);
2318 chattingPartner = -1;
2320 if(!suppressKibitz) // [HGM] kibitz
2321 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2322 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2323 int nrDigit = 0, nrAlph = 0, i;
2324 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2325 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2326 parse[parse_pos] = NULLCHAR;
2327 // try to be smart: if it does not look like search info, it should go to
2328 // ICS interaction window after all, not to engine-output window.
2329 for(i=0; i<parse_pos; i++) { // count letters and digits
2330 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2331 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2332 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2334 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2335 int depth=0; float score;
2336 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2337 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2338 pvInfoList[forwardMostMove-1].depth = depth;
2339 pvInfoList[forwardMostMove-1].score = 100*score;
2341 OutputKibitz(suppressKibitz, parse);
2344 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2345 SendToPlayer(tmp, strlen(tmp));
2348 started = STARTED_NONE;
2350 /* Don't match patterns against characters in chatter */
2355 if (started == STARTED_CHATTER) {
2356 if (buf[i] != '\n') {
2357 /* Don't match patterns against characters in chatter */
2361 started = STARTED_NONE;
2364 /* Kludge to deal with rcmd protocol */
2365 if (firstTime && looking_at(buf, &i, "\001*")) {
2366 DisplayFatalError(&buf[1], 0, 1);
2372 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2375 if (appData.debugMode)
2376 fprintf(debugFP, "ics_type %d\n", ics_type);
2379 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2380 ics_type = ICS_FICS;
2382 if (appData.debugMode)
2383 fprintf(debugFP, "ics_type %d\n", ics_type);
2386 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2387 ics_type = ICS_CHESSNET;
2389 if (appData.debugMode)
2390 fprintf(debugFP, "ics_type %d\n", ics_type);
2395 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2396 looking_at(buf, &i, "Logging you in as \"*\"") ||
2397 looking_at(buf, &i, "will be \"*\""))) {
2398 strcpy(ics_handle, star_match[0]);
2402 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2404 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2405 DisplayIcsInteractionTitle(buf);
2406 have_set_title = TRUE;
2409 /* skip finger notes */
2410 if (started == STARTED_NONE &&
2411 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2412 (buf[i] == '1' && buf[i+1] == '0')) &&
2413 buf[i+2] == ':' && buf[i+3] == ' ') {
2414 started = STARTED_CHATTER;
2419 /* skip formula vars */
2420 if (started == STARTED_NONE &&
2421 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2422 started = STARTED_CHATTER;
2428 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2429 if (appData.autoKibitz && started == STARTED_NONE &&
2430 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2431 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2432 if(looking_at(buf, &i, "* kibitzes: ") &&
2433 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2434 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2435 suppressKibitz = TRUE;
2436 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2437 && (gameMode == IcsPlayingWhite)) ||
2438 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2439 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2440 started = STARTED_CHATTER; // own kibitz we simply discard
2442 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2443 parse_pos = 0; parse[0] = NULLCHAR;
2444 savingComment = TRUE;
2445 suppressKibitz = gameMode != IcsObserving ? 2 :
2446 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2450 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2451 started = STARTED_CHATTER;
2452 suppressKibitz = TRUE;
2454 } // [HGM] kibitz: end of patch
2456 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2458 // [HGM] chat: intercept tells by users for which we have an open chat window
2460 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2461 looking_at(buf, &i, "* whispers:") ||
2462 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2463 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2465 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2466 chattingPartner = -1;
2468 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2469 for(p=0; p<MAX_CHAT; p++) {
2470 if(channel == atoi(chatPartner[p])) {
2471 talker[0] = '['; strcat(talker, "]");
2472 chattingPartner = p; break;
2475 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2476 for(p=0; p<MAX_CHAT; p++) {
2477 if(!strcmp("WHISPER", chatPartner[p])) {
2478 talker[0] = '['; strcat(talker, "]");
2479 chattingPartner = p; break;
2482 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2483 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2485 chattingPartner = p; break;
2487 if(chattingPartner<0) i = oldi; else {
2488 started = STARTED_COMMENT;
2489 parse_pos = 0; parse[0] = NULLCHAR;
2490 savingComment = TRUE;
2491 suppressKibitz = TRUE;
2493 } // [HGM] chat: end of patch
2495 if (appData.zippyTalk || appData.zippyPlay) {
2496 /* [DM] Backup address for color zippy lines */
2500 if (loggedOn == TRUE)
2501 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2502 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2504 if (ZippyControl(buf, &i) ||
2505 ZippyConverse(buf, &i) ||
2506 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2508 if (!appData.colorize) continue;
2512 } // [DM] 'else { ' deleted
2514 /* Regular tells and says */
2515 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2516 looking_at(buf, &i, "* (your partner) tells you: ") ||
2517 looking_at(buf, &i, "* says: ") ||
2518 /* Don't color "message" or "messages" output */
2519 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2520 looking_at(buf, &i, "*. * at *:*: ") ||
2521 looking_at(buf, &i, "--* (*:*): ") ||
2522 /* Message notifications (same color as tells) */
2523 looking_at(buf, &i, "* has left a message ") ||
2524 looking_at(buf, &i, "* just sent you a message:\n") ||
2525 /* Whispers and kibitzes */
2526 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2527 looking_at(buf, &i, "* kibitzes: ") ||
2529 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2531 if (tkind == 1 && strchr(star_match[0], ':')) {
2532 /* Avoid "tells you:" spoofs in channels */
2535 if (star_match[0][0] == NULLCHAR ||
2536 strchr(star_match[0], ' ') ||
2537 (tkind == 3 && strchr(star_match[1], ' '))) {
2538 /* Reject bogus matches */
2541 if (appData.colorize) {
2542 if (oldi > next_out) {
2543 SendToPlayer(&buf[next_out], oldi - next_out);
2548 Colorize(ColorTell, FALSE);
2549 curColor = ColorTell;
2552 Colorize(ColorKibitz, FALSE);
2553 curColor = ColorKibitz;
2556 p = strrchr(star_match[1], '(');
2563 Colorize(ColorChannel1, FALSE);
2564 curColor = ColorChannel1;
2566 Colorize(ColorChannel, FALSE);
2567 curColor = ColorChannel;
2571 curColor = ColorNormal;
2575 if (started == STARTED_NONE && appData.autoComment &&
2576 (gameMode == IcsObserving ||
2577 gameMode == IcsPlayingWhite ||
2578 gameMode == IcsPlayingBlack)) {
2579 parse_pos = i - oldi;
2580 memcpy(parse, &buf[oldi], parse_pos);
2581 parse[parse_pos] = NULLCHAR;
2582 started = STARTED_COMMENT;
2583 savingComment = TRUE;
2585 started = STARTED_CHATTER;
2586 savingComment = FALSE;
2593 if (looking_at(buf, &i, "* s-shouts: ") ||
2594 looking_at(buf, &i, "* c-shouts: ")) {
2595 if (appData.colorize) {
2596 if (oldi > next_out) {
2597 SendToPlayer(&buf[next_out], oldi - next_out);
2600 Colorize(ColorSShout, FALSE);
2601 curColor = ColorSShout;
2604 started = STARTED_CHATTER;
2608 if (looking_at(buf, &i, "--->")) {
2613 if (looking_at(buf, &i, "* shouts: ") ||
2614 looking_at(buf, &i, "--> ")) {
2615 if (appData.colorize) {
2616 if (oldi > next_out) {
2617 SendToPlayer(&buf[next_out], oldi - next_out);
2620 Colorize(ColorShout, FALSE);
2621 curColor = ColorShout;
2624 started = STARTED_CHATTER;
2628 if (looking_at( buf, &i, "Challenge:")) {
2629 if (appData.colorize) {
2630 if (oldi > next_out) {
2631 SendToPlayer(&buf[next_out], oldi - next_out);
2634 Colorize(ColorChallenge, FALSE);
2635 curColor = ColorChallenge;
2641 if (looking_at(buf, &i, "* offers you") ||
2642 looking_at(buf, &i, "* offers to be") ||
2643 looking_at(buf, &i, "* would like to") ||
2644 looking_at(buf, &i, "* requests to") ||
2645 looking_at(buf, &i, "Your opponent offers") ||
2646 looking_at(buf, &i, "Your opponent requests")) {
2648 if (appData.colorize) {
2649 if (oldi > next_out) {
2650 SendToPlayer(&buf[next_out], oldi - next_out);
2653 Colorize(ColorRequest, FALSE);
2654 curColor = ColorRequest;
2659 if (looking_at(buf, &i, "* (*) seeking")) {
2660 if (appData.colorize) {
2661 if (oldi > next_out) {
2662 SendToPlayer(&buf[next_out], oldi - next_out);
2665 Colorize(ColorSeek, FALSE);
2666 curColor = ColorSeek;
2671 if (looking_at(buf, &i, "\\ ")) {
2672 if (prevColor != ColorNormal) {
2673 if (oldi > next_out) {
2674 SendToPlayer(&buf[next_out], oldi - next_out);
2677 Colorize(prevColor, TRUE);
2678 curColor = prevColor;
2680 if (savingComment) {
2681 parse_pos = i - oldi;
2682 memcpy(parse, &buf[oldi], parse_pos);
2683 parse[parse_pos] = NULLCHAR;
2684 started = STARTED_COMMENT;
2686 started = STARTED_CHATTER;
2691 if (looking_at(buf, &i, "Black Strength :") ||
2692 looking_at(buf, &i, "<<< style 10 board >>>") ||
2693 looking_at(buf, &i, "<10>") ||
2694 looking_at(buf, &i, "#@#")) {
2695 /* Wrong board style */
2697 SendToICS(ics_prefix);
2698 SendToICS("set style 12\n");
2699 SendToICS(ics_prefix);
2700 SendToICS("refresh\n");
2704 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2706 have_sent_ICS_logon = 1;
2710 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2711 (looking_at(buf, &i, "\n<12> ") ||
2712 looking_at(buf, &i, "<12> "))) {
2714 if (oldi > next_out) {
2715 SendToPlayer(&buf[next_out], oldi - next_out);
2718 started = STARTED_BOARD;
2723 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2724 looking_at(buf, &i, "<b1> ")) {
2725 if (oldi > next_out) {
2726 SendToPlayer(&buf[next_out], oldi - next_out);
2729 started = STARTED_HOLDINGS;
2734 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2736 /* Header for a move list -- first line */
2738 switch (ics_getting_history) {
2742 case BeginningOfGame:
2743 /* User typed "moves" or "oldmoves" while we
2744 were idle. Pretend we asked for these
2745 moves and soak them up so user can step
2746 through them and/or save them.
2749 gameMode = IcsObserving;
2752 ics_getting_history = H_GOT_UNREQ_HEADER;
2754 case EditGame: /*?*/
2755 case EditPosition: /*?*/
2756 /* Should above feature work in these modes too? */
2757 /* For now it doesn't */
2758 ics_getting_history = H_GOT_UNWANTED_HEADER;
2761 ics_getting_history = H_GOT_UNWANTED_HEADER;
2766 /* Is this the right one? */
2767 if (gameInfo.white && gameInfo.black &&
2768 strcmp(gameInfo.white, star_match[0]) == 0 &&
2769 strcmp(gameInfo.black, star_match[2]) == 0) {
2771 ics_getting_history = H_GOT_REQ_HEADER;
2774 case H_GOT_REQ_HEADER:
2775 case H_GOT_UNREQ_HEADER:
2776 case H_GOT_UNWANTED_HEADER:
2777 case H_GETTING_MOVES:
2778 /* Should not happen */
2779 DisplayError(_("Error gathering move list: two headers"), 0);
2780 ics_getting_history = H_FALSE;
2784 /* Save player ratings into gameInfo if needed */
2785 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2786 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2787 (gameInfo.whiteRating == -1 ||
2788 gameInfo.blackRating == -1)) {
2790 gameInfo.whiteRating = string_to_rating(star_match[1]);
2791 gameInfo.blackRating = string_to_rating(star_match[3]);
2792 if (appData.debugMode)
2793 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2794 gameInfo.whiteRating, gameInfo.blackRating);
2799 if (looking_at(buf, &i,
2800 "* * match, initial time: * minute*, increment: * second")) {
2801 /* Header for a move list -- second line */
2802 /* Initial board will follow if this is a wild game */
2803 if (gameInfo.event != NULL) free(gameInfo.event);
2804 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2805 gameInfo.event = StrSave(str);
2806 /* [HGM] we switched variant. Translate boards if needed. */
2807 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2811 if (looking_at(buf, &i, "Move ")) {
2812 /* Beginning of a move list */
2813 switch (ics_getting_history) {
2815 /* Normally should not happen */
2816 /* Maybe user hit reset while we were parsing */
2819 /* Happens if we are ignoring a move list that is not
2820 * the one we just requested. Common if the user
2821 * tries to observe two games without turning off
2824 case H_GETTING_MOVES:
2825 /* Should not happen */
2826 DisplayError(_("Error gathering move list: nested"), 0);
2827 ics_getting_history = H_FALSE;
2829 case H_GOT_REQ_HEADER:
2830 ics_getting_history = H_GETTING_MOVES;
2831 started = STARTED_MOVES;
2833 if (oldi > next_out) {
2834 SendToPlayer(&buf[next_out], oldi - next_out);
2837 case H_GOT_UNREQ_HEADER:
2838 ics_getting_history = H_GETTING_MOVES;
2839 started = STARTED_MOVES_NOHIDE;
2842 case H_GOT_UNWANTED_HEADER:
2843 ics_getting_history = H_FALSE;
2849 if (looking_at(buf, &i, "% ") ||
2850 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2851 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2852 savingComment = FALSE;
2855 case STARTED_MOVES_NOHIDE:
2856 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2857 parse[parse_pos + i - oldi] = NULLCHAR;
2858 ParseGameHistory(parse);
2860 if (appData.zippyPlay && first.initDone) {
2861 FeedMovesToProgram(&first, forwardMostMove);
2862 if (gameMode == IcsPlayingWhite) {
2863 if (WhiteOnMove(forwardMostMove)) {
2864 if (first.sendTime) {
2865 if (first.useColors) {
2866 SendToProgram("black\n", &first);
2868 SendTimeRemaining(&first, TRUE);
2870 if (first.useColors) {
2871 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2873 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2874 first.maybeThinking = TRUE;
2876 if (first.usePlayother) {
2877 if (first.sendTime) {
2878 SendTimeRemaining(&first, TRUE);
2880 SendToProgram("playother\n", &first);
2886 } else if (gameMode == IcsPlayingBlack) {
2887 if (!WhiteOnMove(forwardMostMove)) {
2888 if (first.sendTime) {
2889 if (first.useColors) {
2890 SendToProgram("white\n", &first);
2892 SendTimeRemaining(&first, FALSE);
2894 if (first.useColors) {
2895 SendToProgram("black\n", &first);
2897 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2898 first.maybeThinking = TRUE;
2900 if (first.usePlayother) {
2901 if (first.sendTime) {
2902 SendTimeRemaining(&first, FALSE);
2904 SendToProgram("playother\n", &first);
2913 if (gameMode == IcsObserving && ics_gamenum == -1) {
2914 /* Moves came from oldmoves or moves command
2915 while we weren't doing anything else.
2917 currentMove = forwardMostMove;
2918 ClearHighlights();/*!!could figure this out*/
2919 flipView = appData.flipView;
2920 DrawPosition(TRUE, boards[currentMove]);
2921 DisplayBothClocks();
2922 sprintf(str, "%s vs. %s",
2923 gameInfo.white, gameInfo.black);
2927 /* Moves were history of an active game */
2928 if (gameInfo.resultDetails != NULL) {
2929 free(gameInfo.resultDetails);
2930 gameInfo.resultDetails = NULL;
2933 HistorySet(parseList, backwardMostMove,
2934 forwardMostMove, currentMove-1);
2935 DisplayMove(currentMove - 1);
2936 if (started == STARTED_MOVES) next_out = i;
2937 started = STARTED_NONE;
2938 ics_getting_history = H_FALSE;
2941 case STARTED_OBSERVE:
2942 started = STARTED_NONE;
2943 SendToICS(ics_prefix);
2944 SendToICS("refresh\n");
2950 if(bookHit) { // [HGM] book: simulate book reply
2951 static char bookMove[MSG_SIZ]; // a bit generous?
2953 programStats.nodes = programStats.depth = programStats.time =
2954 programStats.score = programStats.got_only_move = 0;
2955 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2957 strcpy(bookMove, "move ");
2958 strcat(bookMove, bookHit);
2959 HandleMachineMove(bookMove, &first);
2964 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2965 started == STARTED_HOLDINGS ||
2966 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2967 /* Accumulate characters in move list or board */
2968 parse[parse_pos++] = buf[i];
2971 /* Start of game messages. Mostly we detect start of game
2972 when the first board image arrives. On some versions
2973 of the ICS, though, we need to do a "refresh" after starting
2974 to observe in order to get the current board right away. */
2975 if (looking_at(buf, &i, "Adding game * to observation list")) {
2976 started = STARTED_OBSERVE;
2980 /* Handle auto-observe */
2981 if (appData.autoObserve &&
2982 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2983 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2985 /* Choose the player that was highlighted, if any. */
2986 if (star_match[0][0] == '\033' ||
2987 star_match[1][0] != '\033') {
2988 player = star_match[0];
2990 player = star_match[2];
2992 sprintf(str, "%sobserve %s\n",
2993 ics_prefix, StripHighlightAndTitle(player));
2996 /* Save ratings from notify string */
2997 strcpy(player1Name, star_match[0]);
2998 player1Rating = string_to_rating(star_match[1]);
2999 strcpy(player2Name, star_match[2]);
3000 player2Rating = string_to_rating(star_match[3]);
3002 if (appData.debugMode)
3004 "Ratings from 'Game notification:' %s %d, %s %d\n",
3005 player1Name, player1Rating,
3006 player2Name, player2Rating);
3011 /* Deal with automatic examine mode after a game,
3012 and with IcsObserving -> IcsExamining transition */
3013 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3014 looking_at(buf, &i, "has made you an examiner of game *")) {
3016 int gamenum = atoi(star_match[0]);
3017 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3018 gamenum == ics_gamenum) {
3019 /* We were already playing or observing this game;
3020 no need to refetch history */
3021 gameMode = IcsExamining;
3023 pauseExamForwardMostMove = forwardMostMove;
3024 } else if (currentMove < forwardMostMove) {
3025 ForwardInner(forwardMostMove);
3028 /* I don't think this case really can happen */
3029 SendToICS(ics_prefix);
3030 SendToICS("refresh\n");
3035 /* Error messages */
3036 // if (ics_user_moved) {
3037 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3038 if (looking_at(buf, &i, "Illegal move") ||
3039 looking_at(buf, &i, "Not a legal move") ||
3040 looking_at(buf, &i, "Your king is in check") ||
3041 looking_at(buf, &i, "It isn't your turn") ||
3042 looking_at(buf, &i, "It is not your move")) {
3044 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3045 currentMove = --forwardMostMove;
3046 DisplayMove(currentMove - 1); /* before DMError */
3047 DrawPosition(FALSE, boards[currentMove]);
3049 DisplayBothClocks();
3051 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3057 if (looking_at(buf, &i, "still have time") ||
3058 looking_at(buf, &i, "not out of time") ||
3059 looking_at(buf, &i, "either player is out of time") ||
3060 looking_at(buf, &i, "has timeseal; checking")) {
3061 /* We must have called his flag a little too soon */
3062 whiteFlag = blackFlag = FALSE;
3066 if (looking_at(buf, &i, "added * seconds to") ||
3067 looking_at(buf, &i, "seconds were added to")) {
3068 /* Update the clocks */
3069 SendToICS(ics_prefix);
3070 SendToICS("refresh\n");
3074 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3075 ics_clock_paused = TRUE;
3080 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3081 ics_clock_paused = FALSE;
3086 /* Grab player ratings from the Creating: message.
3087 Note we have to check for the special case when
3088 the ICS inserts things like [white] or [black]. */
3089 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3090 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3092 0 player 1 name (not necessarily white)
3094 2 empty, white, or black (IGNORED)
3095 3 player 2 name (not necessarily black)
3098 The names/ratings are sorted out when the game
3099 actually starts (below).
3101 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3102 player1Rating = string_to_rating(star_match[1]);
3103 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3104 player2Rating = string_to_rating(star_match[4]);
3106 if (appData.debugMode)
3108 "Ratings from 'Creating:' %s %d, %s %d\n",
3109 player1Name, player1Rating,
3110 player2Name, player2Rating);
3115 /* Improved generic start/end-of-game messages */
3116 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3117 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3118 /* If tkind == 0: */
3119 /* star_match[0] is the game number */
3120 /* [1] is the white player's name */
3121 /* [2] is the black player's name */
3122 /* For end-of-game: */
3123 /* [3] is the reason for the game end */
3124 /* [4] is a PGN end game-token, preceded by " " */
3125 /* For start-of-game: */
3126 /* [3] begins with "Creating" or "Continuing" */
3127 /* [4] is " *" or empty (don't care). */
3128 int gamenum = atoi(star_match[0]);
3129 char *whitename, *blackname, *why, *endtoken;
3130 ChessMove endtype = (ChessMove) 0;
3133 whitename = star_match[1];
3134 blackname = star_match[2];
3135 why = star_match[3];
3136 endtoken = star_match[4];
3138 whitename = star_match[1];
3139 blackname = star_match[3];
3140 why = star_match[5];
3141 endtoken = star_match[6];
3144 /* Game start messages */
3145 if (strncmp(why, "Creating ", 9) == 0 ||
3146 strncmp(why, "Continuing ", 11) == 0) {
3147 gs_gamenum = gamenum;
3148 strcpy(gs_kind, strchr(why, ' ') + 1);
3150 if (appData.zippyPlay) {
3151 ZippyGameStart(whitename, blackname);
3157 /* Game end messages */
3158 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3159 ics_gamenum != gamenum) {
3162 while (endtoken[0] == ' ') endtoken++;
3163 switch (endtoken[0]) {
3166 endtype = GameUnfinished;
3169 endtype = BlackWins;
3172 if (endtoken[1] == '/')
3173 endtype = GameIsDrawn;
3175 endtype = WhiteWins;
3178 GameEnds(endtype, why, GE_ICS);
3180 if (appData.zippyPlay && first.initDone) {
3181 ZippyGameEnd(endtype, why);
3182 if (first.pr == NULL) {
3183 /* Start the next process early so that we'll
3184 be ready for the next challenge */
3185 StartChessProgram(&first);
3187 /* Send "new" early, in case this command takes
3188 a long time to finish, so that we'll be ready
3189 for the next challenge. */
3190 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3197 if (looking_at(buf, &i, "Removing game * from observation") ||
3198 looking_at(buf, &i, "no longer observing game *") ||
3199 looking_at(buf, &i, "Game * (*) has no examiners")) {
3200 if (gameMode == IcsObserving &&
3201 atoi(star_match[0]) == ics_gamenum)
3203 /* icsEngineAnalyze */
3204 if (appData.icsEngineAnalyze) {
3211 ics_user_moved = FALSE;
3216 if (looking_at(buf, &i, "no longer examining game *")) {
3217 if (gameMode == IcsExamining &&
3218 atoi(star_match[0]) == ics_gamenum)
3222 ics_user_moved = FALSE;
3227 /* Advance leftover_start past any newlines we find,
3228 so only partial lines can get reparsed */
3229 if (looking_at(buf, &i, "\n")) {
3230 prevColor = curColor;
3231 if (curColor != ColorNormal) {
3232 if (oldi > next_out) {
3233 SendToPlayer(&buf[next_out], oldi - next_out);
3236 Colorize(ColorNormal, FALSE);
3237 curColor = ColorNormal;
3239 if (started == STARTED_BOARD) {
3240 started = STARTED_NONE;
3241 parse[parse_pos] = NULLCHAR;
3242 ParseBoard12(parse);
3245 /* Send premove here */
3246 if (appData.premove) {
3248 if (currentMove == 0 &&
3249 gameMode == IcsPlayingWhite &&
3250 appData.premoveWhite) {
3251 sprintf(str, "%s\n", appData.premoveWhiteText);
3252 if (appData.debugMode)
3253 fprintf(debugFP, "Sending premove:\n");
3255 } else if (currentMove == 1 &&
3256 gameMode == IcsPlayingBlack &&
3257 appData.premoveBlack) {
3258 sprintf(str, "%s\n", appData.premoveBlackText);
3259 if (appData.debugMode)
3260 fprintf(debugFP, "Sending premove:\n");
3262 } else if (gotPremove) {
3264 ClearPremoveHighlights();
3265 if (appData.debugMode)
3266 fprintf(debugFP, "Sending premove:\n");
3267 UserMoveEvent(premoveFromX, premoveFromY,
3268 premoveToX, premoveToY,
3273 /* Usually suppress following prompt */
3274 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3275 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3276 if (looking_at(buf, &i, "*% ")) {
3277 savingComment = FALSE;
3281 } else if (started == STARTED_HOLDINGS) {
3283 char new_piece[MSG_SIZ];
3284 started = STARTED_NONE;
3285 parse[parse_pos] = NULLCHAR;
3286 if (appData.debugMode)
3287 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3288 parse, currentMove);
3289 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3290 gamenum == ics_gamenum) {
3291 if (gameInfo.variant == VariantNormal) {
3292 /* [HGM] We seem to switch variant during a game!
3293 * Presumably no holdings were displayed, so we have
3294 * to move the position two files to the right to
3295 * create room for them!
3297 VariantClass newVariant;
3298 switch(gameInfo.boardWidth) { // base guess on board width
3299 case 9: newVariant = VariantShogi; break;
3300 case 10: newVariant = VariantGreat; break;
3301 default: newVariant = VariantCrazyhouse; break;
3303 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3304 /* Get a move list just to see the header, which
3305 will tell us whether this is really bug or zh */
3306 if (ics_getting_history == H_FALSE) {
3307 ics_getting_history = H_REQUESTED;
3308 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3312 new_piece[0] = NULLCHAR;
3313 sscanf(parse, "game %d white [%s black [%s <- %s",
3314 &gamenum, white_holding, black_holding,
3316 white_holding[strlen(white_holding)-1] = NULLCHAR;
3317 black_holding[strlen(black_holding)-1] = NULLCHAR;
3318 /* [HGM] copy holdings to board holdings area */
3319 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3320 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3321 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3323 if (appData.zippyPlay && first.initDone) {
3324 ZippyHoldings(white_holding, black_holding,
3328 if (tinyLayout || smallLayout) {
3329 char wh[16], bh[16];
3330 PackHolding(wh, white_holding);
3331 PackHolding(bh, black_holding);
3332 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3333 gameInfo.white, gameInfo.black);
3335 sprintf(str, "%s [%s] vs. %s [%s]",
3336 gameInfo.white, white_holding,
3337 gameInfo.black, black_holding);
3340 DrawPosition(FALSE, boards[currentMove]);
3343 /* Suppress following prompt */
3344 if (looking_at(buf, &i, "*% ")) {
3345 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3346 savingComment = FALSE;
3353 i++; /* skip unparsed character and loop back */
3356 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3357 started != STARTED_HOLDINGS && i > next_out) {
3358 SendToPlayer(&buf[next_out], i - next_out);
3361 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3363 leftover_len = buf_len - leftover_start;
3364 /* if buffer ends with something we couldn't parse,
3365 reparse it after appending the next read */
3367 } else if (count == 0) {
3368 RemoveInputSource(isr);
3369 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3371 DisplayFatalError(_("Error reading from ICS"), error, 1);
3376 /* Board style 12 looks like this:
3378 <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
3380 * The "<12> " is stripped before it gets to this routine. The two
3381 * trailing 0's (flip state and clock ticking) are later addition, and
3382 * some chess servers may not have them, or may have only the first.
3383 * Additional trailing fields may be added in the future.
3386 #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"
3388 #define RELATION_OBSERVING_PLAYED 0
3389 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3390 #define RELATION_PLAYING_MYMOVE 1
3391 #define RELATION_PLAYING_NOTMYMOVE -1
3392 #define RELATION_EXAMINING 2
3393 #define RELATION_ISOLATED_BOARD -3
3394 #define RELATION_STARTING_POSITION -4 /* FICS only */
3397 ParseBoard12(string)
3400 GameMode newGameMode;
3401 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3402 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3403 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3404 char to_play, board_chars[200];
3405 char move_str[500], str[500], elapsed_time[500];
3406 char black[32], white[32];
3408 int prevMove = currentMove;
3411 int fromX, fromY, toX, toY;
3413 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3414 char *bookHit = NULL; // [HGM] book
3415 Boolean weird = FALSE, reqFlag = FALSE;
3417 fromX = fromY = toX = toY = -1;
3421 if (appData.debugMode)
3422 fprintf(debugFP, _("Parsing board: %s\n"), string);
3424 move_str[0] = NULLCHAR;
3425 elapsed_time[0] = NULLCHAR;
3426 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3428 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3429 if(string[i] == ' ') { ranks++; files = 0; }
3431 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3434 for(j = 0; j <i; j++) board_chars[j] = string[j];
3435 board_chars[i] = '\0';
3438 n = sscanf(string, PATTERN, &to_play, &double_push,
3439 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3440 &gamenum, white, black, &relation, &basetime, &increment,
3441 &white_stren, &black_stren, &white_time, &black_time,
3442 &moveNum, str, elapsed_time, move_str, &ics_flip,
3446 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3447 DisplayError(str, 0);
3451 /* Convert the move number to internal form */
3452 moveNum = (moveNum - 1) * 2;
3453 if (to_play == 'B') moveNum++;
3454 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3455 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3461 case RELATION_OBSERVING_PLAYED:
3462 case RELATION_OBSERVING_STATIC:
3463 if (gamenum == -1) {
3464 /* Old ICC buglet */
3465 relation = RELATION_OBSERVING_STATIC;
3467 newGameMode = IcsObserving;
3469 case RELATION_PLAYING_MYMOVE:
3470 case RELATION_PLAYING_NOTMYMOVE:
3472 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3473 IcsPlayingWhite : IcsPlayingBlack;
3475 case RELATION_EXAMINING:
3476 newGameMode = IcsExamining;
3478 case RELATION_ISOLATED_BOARD:
3480 /* Just display this board. If user was doing something else,
3481 we will forget about it until the next board comes. */
3482 newGameMode = IcsIdle;
3484 case RELATION_STARTING_POSITION:
3485 newGameMode = gameMode;
3489 /* Modify behavior for initial board display on move listing
3492 switch (ics_getting_history) {
3496 case H_GOT_REQ_HEADER:
3497 case H_GOT_UNREQ_HEADER:
3498 /* This is the initial position of the current game */
3499 gamenum = ics_gamenum;
3500 moveNum = 0; /* old ICS bug workaround */
3501 if (to_play == 'B') {
3502 startedFromSetupPosition = TRUE;
3503 blackPlaysFirst = TRUE;
3505 if (forwardMostMove == 0) forwardMostMove = 1;
3506 if (backwardMostMove == 0) backwardMostMove = 1;
3507 if (currentMove == 0) currentMove = 1;
3509 newGameMode = gameMode;
3510 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3512 case H_GOT_UNWANTED_HEADER:
3513 /* This is an initial board that we don't want */
3515 case H_GETTING_MOVES:
3516 /* Should not happen */
3517 DisplayError(_("Error gathering move list: extra board"), 0);
3518 ics_getting_history = H_FALSE;
3522 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3523 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3524 /* [HGM] We seem to have switched variant unexpectedly
3525 * Try to guess new variant from board size
3527 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3528 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3529 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3530 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3531 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3532 if(!weird) newVariant = VariantNormal;
3533 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3534 /* Get a move list just to see the header, which
3535 will tell us whether this is really bug or zh */
3536 if (ics_getting_history == H_FALSE) {
3537 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3538 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3543 /* Take action if this is the first board of a new game, or of a
3544 different game than is currently being displayed. */
3545 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3546 relation == RELATION_ISOLATED_BOARD) {
3548 /* Forget the old game and get the history (if any) of the new one */
3549 if (gameMode != BeginningOfGame) {
3553 if (appData.autoRaiseBoard) BoardToTop();
3555 if (gamenum == -1) {
3556 newGameMode = IcsIdle;
3557 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3558 appData.getMoveList && !reqFlag) {
3559 /* Need to get game history */
3560 ics_getting_history = H_REQUESTED;
3561 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3565 /* Initially flip the board to have black on the bottom if playing
3566 black or if the ICS flip flag is set, but let the user change
3567 it with the Flip View button. */
3568 flipView = appData.autoFlipView ?
3569 (newGameMode == IcsPlayingBlack) || ics_flip :
3572 /* Done with values from previous mode; copy in new ones */
3573 gameMode = newGameMode;
3575 ics_gamenum = gamenum;
3576 if (gamenum == gs_gamenum) {
3577 int klen = strlen(gs_kind);
3578 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3579 sprintf(str, "ICS %s", gs_kind);
3580 gameInfo.event = StrSave(str);
3582 gameInfo.event = StrSave("ICS game");
3584 gameInfo.site = StrSave(appData.icsHost);
3585 gameInfo.date = PGNDate();
3586 gameInfo.round = StrSave("-");
3587 gameInfo.white = StrSave(white);
3588 gameInfo.black = StrSave(black);
3589 timeControl = basetime * 60 * 1000;
3591 timeIncrement = increment * 1000;
3592 movesPerSession = 0;
3593 gameInfo.timeControl = TimeControlTagValue();
3594 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3595 if (appData.debugMode) {
3596 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3597 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3598 setbuf(debugFP, NULL);
3601 gameInfo.outOfBook = NULL;
3603 /* Do we have the ratings? */
3604 if (strcmp(player1Name, white) == 0 &&
3605 strcmp(player2Name, black) == 0) {
3606 if (appData.debugMode)
3607 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3608 player1Rating, player2Rating);
3609 gameInfo.whiteRating = player1Rating;
3610 gameInfo.blackRating = player2Rating;
3611 } else if (strcmp(player2Name, white) == 0 &&
3612 strcmp(player1Name, black) == 0) {
3613 if (appData.debugMode)
3614 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3615 player2Rating, player1Rating);
3616 gameInfo.whiteRating = player2Rating;
3617 gameInfo.blackRating = player1Rating;
3619 player1Name[0] = player2Name[0] = NULLCHAR;
3621 /* Silence shouts if requested */
3622 if (appData.quietPlay &&
3623 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3624 SendToICS(ics_prefix);
3625 SendToICS("set shout 0\n");
3629 /* Deal with midgame name changes */
3631 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3632 if (gameInfo.white) free(gameInfo.white);
3633 gameInfo.white = StrSave(white);
3635 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3636 if (gameInfo.black) free(gameInfo.black);
3637 gameInfo.black = StrSave(black);
3641 /* Throw away game result if anything actually changes in examine mode */
3642 if (gameMode == IcsExamining && !newGame) {
3643 gameInfo.result = GameUnfinished;
3644 if (gameInfo.resultDetails != NULL) {
3645 free(gameInfo.resultDetails);
3646 gameInfo.resultDetails = NULL;
3650 /* In pausing && IcsExamining mode, we ignore boards coming
3651 in if they are in a different variation than we are. */
3652 if (pauseExamInvalid) return;
3653 if (pausing && gameMode == IcsExamining) {
3654 if (moveNum <= pauseExamForwardMostMove) {
3655 pauseExamInvalid = TRUE;
3656 forwardMostMove = pauseExamForwardMostMove;
3661 if (appData.debugMode) {
3662 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3664 /* Parse the board */
3665 for (k = 0; k < ranks; k++) {
3666 for (j = 0; j < files; j++)
3667 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3668 if(gameInfo.holdingsWidth > 1) {
3669 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3670 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3673 CopyBoard(boards[moveNum], board);
3674 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3676 startedFromSetupPosition =
3677 !CompareBoards(board, initialPosition);
3678 if(startedFromSetupPosition)
3679 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3682 /* [HGM] Set castling rights. Take the outermost Rooks,
3683 to make it also work for FRC opening positions. Note that board12
3684 is really defective for later FRC positions, as it has no way to
3685 indicate which Rook can castle if they are on the same side of King.
3686 For the initial position we grant rights to the outermost Rooks,
3687 and remember thos rights, and we then copy them on positions
3688 later in an FRC game. This means WB might not recognize castlings with
3689 Rooks that have moved back to their original position as illegal,
3690 but in ICS mode that is not its job anyway.
3692 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3693 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3695 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3696 if(board[0][i] == WhiteRook) j = i;
3697 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3698 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3699 if(board[0][i] == WhiteRook) j = i;
3700 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3701 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3702 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3703 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3704 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3705 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3706 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3708 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3709 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3710 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3711 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3712 if(board[BOARD_HEIGHT-1][k] == bKing)
3713 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3715 r = boards[moveNum][CASTLING][0] = initialRights[0];
3716 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3717 r = boards[moveNum][CASTLING][1] = initialRights[1];
3718 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3719 r = boards[moveNum][CASTLING][3] = initialRights[3];
3720 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3721 r = boards[moveNum][CASTLING][4] = initialRights[4];
3722 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3723 /* wildcastle kludge: always assume King has rights */
3724 r = boards[moveNum][CASTLING][2] = initialRights[2];
3725 r = boards[moveNum][CASTLING][5] = initialRights[5];
3727 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3728 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3731 if (ics_getting_history == H_GOT_REQ_HEADER ||
3732 ics_getting_history == H_GOT_UNREQ_HEADER) {
3733 /* This was an initial position from a move list, not
3734 the current position */
3738 /* Update currentMove and known move number limits */
3739 newMove = newGame || moveNum > forwardMostMove;
3742 forwardMostMove = backwardMostMove = currentMove = moveNum;
3743 if (gameMode == IcsExamining && moveNum == 0) {
3744 /* Workaround for ICS limitation: we are not told the wild
3745 type when starting to examine a game. But if we ask for
3746 the move list, the move list header will tell us */
3747 ics_getting_history = H_REQUESTED;
3748 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3751 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3752 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3754 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3755 /* [HGM] applied this also to an engine that is silently watching */
3756 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3757 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3758 gameInfo.variant == currentlyInitializedVariant) {
3759 takeback = forwardMostMove - moveNum;
3760 for (i = 0; i < takeback; i++) {
3761 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3762 SendToProgram("undo\n", &first);
3767 forwardMostMove = moveNum;
3768 if (!pausing || currentMove > forwardMostMove)
3769 currentMove = forwardMostMove;
3771 /* New part of history that is not contiguous with old part */
3772 if (pausing && gameMode == IcsExamining) {
3773 pauseExamInvalid = TRUE;
3774 forwardMostMove = pauseExamForwardMostMove;
3777 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3779 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3780 // [HGM] when we will receive the move list we now request, it will be
3781 // fed to the engine from the first move on. So if the engine is not
3782 // in the initial position now, bring it there.
3783 InitChessProgram(&first, 0);
3786 ics_getting_history = H_REQUESTED;
3787 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3790 forwardMostMove = backwardMostMove = currentMove = moveNum;
3793 /* Update the clocks */
3794 if (strchr(elapsed_time, '.')) {
3796 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3797 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3799 /* Time is in seconds */
3800 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3801 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3806 if (appData.zippyPlay && newGame &&
3807 gameMode != IcsObserving && gameMode != IcsIdle &&
3808 gameMode != IcsExamining)
3809 ZippyFirstBoard(moveNum, basetime, increment);
3812 /* Put the move on the move list, first converting
3813 to canonical algebraic form. */
3815 if (appData.debugMode) {
3816 if (appData.debugMode) { int f = forwardMostMove;
3817 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3818 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3819 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3821 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3822 fprintf(debugFP, "moveNum = %d\n", moveNum);
3823 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3824 setbuf(debugFP, NULL);
3826 if (moveNum <= backwardMostMove) {
3827 /* We don't know what the board looked like before
3829 strcpy(parseList[moveNum - 1], move_str);
3830 strcat(parseList[moveNum - 1], " ");
3831 strcat(parseList[moveNum - 1], elapsed_time);
3832 moveList[moveNum - 1][0] = NULLCHAR;
3833 } else if (strcmp(move_str, "none") == 0) {
3834 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3835 /* Again, we don't know what the board looked like;
3836 this is really the start of the game. */
3837 parseList[moveNum - 1][0] = NULLCHAR;
3838 moveList[moveNum - 1][0] = NULLCHAR;
3839 backwardMostMove = moveNum;
3840 startedFromSetupPosition = TRUE;
3841 fromX = fromY = toX = toY = -1;
3843 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3844 // So we parse the long-algebraic move string in stead of the SAN move
3845 int valid; char buf[MSG_SIZ], *prom;
3847 // str looks something like "Q/a1-a2"; kill the slash
3849 sprintf(buf, "%c%s", str[0], str+2);
3850 else strcpy(buf, str); // might be castling
3851 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3852 strcat(buf, prom); // long move lacks promo specification!
3853 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3854 if(appData.debugMode)
3855 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3856 strcpy(move_str, buf);
3858 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3859 &fromX, &fromY, &toX, &toY, &promoChar)
3860 || ParseOneMove(buf, moveNum - 1, &moveType,
3861 &fromX, &fromY, &toX, &toY, &promoChar);
3862 // end of long SAN patch
3864 (void) CoordsToAlgebraic(boards[moveNum - 1],
3865 PosFlags(moveNum - 1),
3866 fromY, fromX, toY, toX, promoChar,
3867 parseList[moveNum-1]);
3868 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3874 if(gameInfo.variant != VariantShogi)
3875 strcat(parseList[moveNum - 1], "+");
3878 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3879 strcat(parseList[moveNum - 1], "#");
3882 strcat(parseList[moveNum - 1], " ");
3883 strcat(parseList[moveNum - 1], elapsed_time);
3884 /* currentMoveString is set as a side-effect of ParseOneMove */
3885 strcpy(moveList[moveNum - 1], currentMoveString);
3886 strcat(moveList[moveNum - 1], "\n");
3888 /* Move from ICS was illegal!? Punt. */
3889 if (appData.debugMode) {
3890 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3891 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3893 strcpy(parseList[moveNum - 1], move_str);
3894 strcat(parseList[moveNum - 1], " ");
3895 strcat(parseList[moveNum - 1], elapsed_time);
3896 moveList[moveNum - 1][0] = NULLCHAR;
3897 fromX = fromY = toX = toY = -1;
3900 if (appData.debugMode) {
3901 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3902 setbuf(debugFP, NULL);
3906 /* Send move to chess program (BEFORE animating it). */
3907 if (appData.zippyPlay && !newGame && newMove &&
3908 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3910 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3911 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3912 if (moveList[moveNum - 1][0] == NULLCHAR) {
3913 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3915 DisplayError(str, 0);
3917 if (first.sendTime) {
3918 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3920 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3921 if (firstMove && !bookHit) {
3923 if (first.useColors) {
3924 SendToProgram(gameMode == IcsPlayingWhite ?
3926 "black\ngo\n", &first);
3928 SendToProgram("go\n", &first);
3930 first.maybeThinking = TRUE;
3933 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3934 if (moveList[moveNum - 1][0] == NULLCHAR) {
3935 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3936 DisplayError(str, 0);
3938 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3939 SendMoveToProgram(moveNum - 1, &first);
3946 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3947 /* If move comes from a remote source, animate it. If it
3948 isn't remote, it will have already been animated. */
3949 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3950 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3952 if (!pausing && appData.highlightLastMove) {
3953 SetHighlights(fromX, fromY, toX, toY);
3957 /* Start the clocks */
3958 whiteFlag = blackFlag = FALSE;
3959 appData.clockMode = !(basetime == 0 && increment == 0);
3961 ics_clock_paused = TRUE;
3963 } else if (ticking == 1) {
3964 ics_clock_paused = FALSE;
3966 if (gameMode == IcsIdle ||
3967 relation == RELATION_OBSERVING_STATIC ||
3968 relation == RELATION_EXAMINING ||
3970 DisplayBothClocks();
3974 /* Display opponents and material strengths */
3975 if (gameInfo.variant != VariantBughouse &&
3976 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3977 if (tinyLayout || smallLayout) {
3978 if(gameInfo.variant == VariantNormal)
3979 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3980 gameInfo.white, white_stren, gameInfo.black, black_stren,
3981 basetime, increment);
3983 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3984 gameInfo.white, white_stren, gameInfo.black, black_stren,
3985 basetime, increment, (int) gameInfo.variant);
3987 if(gameInfo.variant == VariantNormal)
3988 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3989 gameInfo.white, white_stren, gameInfo.black, black_stren,
3990 basetime, increment);
3992 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3993 gameInfo.white, white_stren, gameInfo.black, black_stren,
3994 basetime, increment, VariantName(gameInfo.variant));
3997 if (appData.debugMode) {
3998 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4003 /* Display the board */
4004 if (!pausing && !appData.noGUI) {
4006 if (appData.premove)
4008 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4009 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4010 ClearPremoveHighlights();
4012 DrawPosition(FALSE, boards[currentMove]);
4013 DisplayMove(moveNum - 1);
4014 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4015 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4016 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4017 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4021 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4023 if(bookHit) { // [HGM] book: simulate book reply
4024 static char bookMove[MSG_SIZ]; // a bit generous?
4026 programStats.nodes = programStats.depth = programStats.time =
4027 programStats.score = programStats.got_only_move = 0;
4028 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4030 strcpy(bookMove, "move ");
4031 strcat(bookMove, bookHit);
4032 HandleMachineMove(bookMove, &first);
4041 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4042 ics_getting_history = H_REQUESTED;
4043 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4049 AnalysisPeriodicEvent(force)
4052 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4053 && !force) || !appData.periodicUpdates)
4056 /* Send . command to Crafty to collect stats */
4057 SendToProgram(".\n", &first);
4059 /* Don't send another until we get a response (this makes
4060 us stop sending to old Crafty's which don't understand
4061 the "." command (sending illegal cmds resets node count & time,
4062 which looks bad)) */
4063 programStats.ok_to_send = 0;
4066 void ics_update_width(new_width)
4069 ics_printf("set width %d\n", new_width);
4073 SendMoveToProgram(moveNum, cps)
4075 ChessProgramState *cps;
4079 if (cps->useUsermove) {
4080 SendToProgram("usermove ", cps);
4084 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4085 int len = space - parseList[moveNum];
4086 memcpy(buf, parseList[moveNum], len);
4088 buf[len] = NULLCHAR;
4090 sprintf(buf, "%s\n", parseList[moveNum]);
4092 SendToProgram(buf, cps);
4094 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4095 AlphaRank(moveList[moveNum], 4);
4096 SendToProgram(moveList[moveNum], cps);
4097 AlphaRank(moveList[moveNum], 4); // and back
4099 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4100 * the engine. It would be nice to have a better way to identify castle
4102 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4103 && cps->useOOCastle) {
4104 int fromX = moveList[moveNum][0] - AAA;
4105 int fromY = moveList[moveNum][1] - ONE;
4106 int toX = moveList[moveNum][2] - AAA;
4107 int toY = moveList[moveNum][3] - ONE;
4108 if((boards[moveNum][fromY][fromX] == WhiteKing
4109 && boards[moveNum][toY][toX] == WhiteRook)
4110 || (boards[moveNum][fromY][fromX] == BlackKing
4111 && boards[moveNum][toY][toX] == BlackRook)) {
4112 if(toX > fromX) SendToProgram("O-O\n", cps);
4113 else SendToProgram("O-O-O\n", cps);
4115 else SendToProgram(moveList[moveNum], cps);
4117 else SendToProgram(moveList[moveNum], cps);
4118 /* End of additions by Tord */
4121 /* [HGM] setting up the opening has brought engine in force mode! */
4122 /* Send 'go' if we are in a mode where machine should play. */
4123 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4124 (gameMode == TwoMachinesPlay ||
4126 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4128 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4129 SendToProgram("go\n", cps);
4130 if (appData.debugMode) {
4131 fprintf(debugFP, "(extra)\n");
4134 setboardSpoiledMachineBlack = 0;
4138 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4140 int fromX, fromY, toX, toY;
4142 char user_move[MSG_SIZ];
4146 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4147 (int)moveType, fromX, fromY, toX, toY);
4148 DisplayError(user_move + strlen("say "), 0);
4150 case WhiteKingSideCastle:
4151 case BlackKingSideCastle:
4152 case WhiteQueenSideCastleWild:
4153 case BlackQueenSideCastleWild:
4155 case WhiteHSideCastleFR:
4156 case BlackHSideCastleFR:
4158 sprintf(user_move, "o-o\n");
4160 case WhiteQueenSideCastle:
4161 case BlackQueenSideCastle:
4162 case WhiteKingSideCastleWild:
4163 case BlackKingSideCastleWild:
4165 case WhiteASideCastleFR:
4166 case BlackASideCastleFR:
4168 sprintf(user_move, "o-o-o\n");
4170 case WhitePromotionQueen:
4171 case BlackPromotionQueen:
4172 case WhitePromotionRook:
4173 case BlackPromotionRook:
4174 case WhitePromotionBishop:
4175 case BlackPromotionBishop:
4176 case WhitePromotionKnight:
4177 case BlackPromotionKnight:
4178 case WhitePromotionKing:
4179 case BlackPromotionKing:
4180 case WhitePromotionChancellor:
4181 case BlackPromotionChancellor:
4182 case WhitePromotionArchbishop:
4183 case BlackPromotionArchbishop:
4184 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4185 sprintf(user_move, "%c%c%c%c=%c\n",
4186 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4187 PieceToChar(WhiteFerz));
4188 else if(gameInfo.variant == VariantGreat)
4189 sprintf(user_move, "%c%c%c%c=%c\n",
4190 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4191 PieceToChar(WhiteMan));
4193 sprintf(user_move, "%c%c%c%c=%c\n",
4194 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4195 PieceToChar(PromoPiece(moveType)));
4199 sprintf(user_move, "%c@%c%c\n",
4200 ToUpper(PieceToChar((ChessSquare) fromX)),
4201 AAA + toX, ONE + toY);
4204 case WhiteCapturesEnPassant:
4205 case BlackCapturesEnPassant:
4206 case IllegalMove: /* could be a variant we don't quite understand */
4207 sprintf(user_move, "%c%c%c%c\n",
4208 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4211 SendToICS(user_move);
4212 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4213 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4217 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4222 if (rf == DROP_RANK) {
4223 sprintf(move, "%c@%c%c\n",
4224 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4226 if (promoChar == 'x' || promoChar == NULLCHAR) {
4227 sprintf(move, "%c%c%c%c\n",
4228 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4230 sprintf(move, "%c%c%c%c%c\n",
4231 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4237 ProcessICSInitScript(f)
4242 while (fgets(buf, MSG_SIZ, f)) {
4243 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4250 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4252 AlphaRank(char *move, int n)
4254 // char *p = move, c; int x, y;
4256 if (appData.debugMode) {
4257 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4261 move[2]>='0' && move[2]<='9' &&
4262 move[3]>='a' && move[3]<='x' ) {
4264 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4265 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4267 if(move[0]>='0' && move[0]<='9' &&
4268 move[1]>='a' && move[1]<='x' &&
4269 move[2]>='0' && move[2]<='9' &&
4270 move[3]>='a' && move[3]<='x' ) {
4271 /* input move, Shogi -> normal */
4272 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4273 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4274 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4275 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4278 move[3]>='0' && move[3]<='9' &&
4279 move[2]>='a' && move[2]<='x' ) {
4281 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4282 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4285 move[0]>='a' && move[0]<='x' &&
4286 move[3]>='0' && move[3]<='9' &&
4287 move[2]>='a' && move[2]<='x' ) {
4288 /* output move, normal -> Shogi */
4289 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4290 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4291 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4292 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4293 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4295 if (appData.debugMode) {
4296 fprintf(debugFP, " out = '%s'\n", move);
4300 /* Parser for moves from gnuchess, ICS, or user typein box */
4302 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4305 ChessMove *moveType;
4306 int *fromX, *fromY, *toX, *toY;
4309 if (appData.debugMode) {
4310 fprintf(debugFP, "move to parse: %s\n", move);
4312 *moveType = yylexstr(moveNum, move);
4314 switch (*moveType) {
4315 case WhitePromotionChancellor:
4316 case BlackPromotionChancellor:
4317 case WhitePromotionArchbishop:
4318 case BlackPromotionArchbishop:
4319 case WhitePromotionQueen:
4320 case BlackPromotionQueen:
4321 case WhitePromotionRook:
4322 case BlackPromotionRook:
4323 case WhitePromotionBishop:
4324 case BlackPromotionBishop:
4325 case WhitePromotionKnight:
4326 case BlackPromotionKnight:
4327 case WhitePromotionKing:
4328 case BlackPromotionKing:
4330 case WhiteCapturesEnPassant:
4331 case BlackCapturesEnPassant:
4332 case WhiteKingSideCastle:
4333 case WhiteQueenSideCastle:
4334 case BlackKingSideCastle:
4335 case BlackQueenSideCastle:
4336 case WhiteKingSideCastleWild:
4337 case WhiteQueenSideCastleWild:
4338 case BlackKingSideCastleWild:
4339 case BlackQueenSideCastleWild:
4340 /* Code added by Tord: */
4341 case WhiteHSideCastleFR:
4342 case WhiteASideCastleFR:
4343 case BlackHSideCastleFR:
4344 case BlackASideCastleFR:
4345 /* End of code added by Tord */
4346 case IllegalMove: /* bug or odd chess variant */
4347 *fromX = currentMoveString[0] - AAA;
4348 *fromY = currentMoveString[1] - ONE;
4349 *toX = currentMoveString[2] - AAA;
4350 *toY = currentMoveString[3] - ONE;
4351 *promoChar = currentMoveString[4];
4352 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4353 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4354 if (appData.debugMode) {
4355 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4357 *fromX = *fromY = *toX = *toY = 0;
4360 if (appData.testLegality) {
4361 return (*moveType != IllegalMove);
4363 return !(fromX == fromY && toX == toY);
4368 *fromX = *moveType == WhiteDrop ?
4369 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4370 (int) CharToPiece(ToLower(currentMoveString[0]));
4372 *toX = currentMoveString[2] - AAA;
4373 *toY = currentMoveString[3] - ONE;
4374 *promoChar = NULLCHAR;
4378 case ImpossibleMove:
4379 case (ChessMove) 0: /* end of file */
4388 if (appData.debugMode) {
4389 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4392 *fromX = *fromY = *toX = *toY = 0;
4393 *promoChar = NULLCHAR;
4398 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4399 // All positions will have equal probability, but the current method will not provide a unique
4400 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4406 int piecesLeft[(int)BlackPawn];
4407 int seed, nrOfShuffles;
4409 void GetPositionNumber()
4410 { // sets global variable seed
4413 seed = appData.defaultFrcPosition;
4414 if(seed < 0) { // randomize based on time for negative FRC position numbers
4415 for(i=0; i<50; i++) seed += random();
4416 seed = random() ^ random() >> 8 ^ random() << 8;
4417 if(seed<0) seed = -seed;
4421 int put(Board board, int pieceType, int rank, int n, int shade)
4422 // put the piece on the (n-1)-th empty squares of the given shade
4426 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4427 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4428 board[rank][i] = (ChessSquare) pieceType;
4429 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4431 piecesLeft[pieceType]--;
4439 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4440 // calculate where the next piece goes, (any empty square), and put it there
4444 i = seed % squaresLeft[shade];
4445 nrOfShuffles *= squaresLeft[shade];
4446 seed /= squaresLeft[shade];
4447 put(board, pieceType, rank, i, shade);
4450 void AddTwoPieces(Board board, int pieceType, int rank)
4451 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4453 int i, n=squaresLeft[ANY], j=n-1, k;
4455 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4456 i = seed % k; // pick one
4459 while(i >= j) i -= j--;
4460 j = n - 1 - j; i += j;
4461 put(board, pieceType, rank, j, ANY);
4462 put(board, pieceType, rank, i, ANY);
4465 void SetUpShuffle(Board board, int number)
4469 GetPositionNumber(); nrOfShuffles = 1;
4471 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4472 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4473 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4475 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4477 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4478 p = (int) board[0][i];
4479 if(p < (int) BlackPawn) piecesLeft[p] ++;
4480 board[0][i] = EmptySquare;
4483 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4484 // shuffles restricted to allow normal castling put KRR first
4485 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4486 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4487 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4488 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4489 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4490 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4491 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4492 put(board, WhiteRook, 0, 0, ANY);
4493 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4496 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4497 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4498 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4499 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4500 while(piecesLeft[p] >= 2) {
4501 AddOnePiece(board, p, 0, LITE);
4502 AddOnePiece(board, p, 0, DARK);
4504 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4507 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4508 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4509 // but we leave King and Rooks for last, to possibly obey FRC restriction
4510 if(p == (int)WhiteRook) continue;
4511 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4512 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4515 // now everything is placed, except perhaps King (Unicorn) and Rooks
4517 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4518 // Last King gets castling rights
4519 while(piecesLeft[(int)WhiteUnicorn]) {
4520 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4521 initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4524 while(piecesLeft[(int)WhiteKing]) {
4525 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4526 initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4531 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4532 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4535 // Only Rooks can be left; simply place them all
4536 while(piecesLeft[(int)WhiteRook]) {
4537 i = put(board, WhiteRook, 0, 0, ANY);
4538 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4541 initialRights[1] = initialRights[4] = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
4543 initialRights[0] = initialRights[3] = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
4546 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4547 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4550 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4553 int SetCharTable( char *table, const char * map )
4554 /* [HGM] moved here from winboard.c because of its general usefulness */
4555 /* Basically a safe strcpy that uses the last character as King */
4557 int result = FALSE; int NrPieces;
4559 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4560 && NrPieces >= 12 && !(NrPieces&1)) {
4561 int i; /* [HGM] Accept even length from 12 to 34 */
4563 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4564 for( i=0; i<NrPieces/2-1; i++ ) {
4566 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4568 table[(int) WhiteKing] = map[NrPieces/2-1];
4569 table[(int) BlackKing] = map[NrPieces-1];
4577 void Prelude(Board board)
4578 { // [HGM] superchess: random selection of exo-pieces
4579 int i, j, k; ChessSquare p;
4580 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4582 GetPositionNumber(); // use FRC position number
4584 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4585 SetCharTable(pieceToChar, appData.pieceToCharTable);
4586 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4587 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4590 j = seed%4; seed /= 4;
4591 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4592 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4593 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4594 j = seed%3 + (seed%3 >= j); seed /= 3;
4595 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4596 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4597 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4598 j = seed%3; seed /= 3;
4599 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4600 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4601 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4602 j = seed%2 + (seed%2 >= j); seed /= 2;
4603 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4604 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4605 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4606 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4607 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4608 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4609 put(board, exoPieces[0], 0, 0, ANY);
4610 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4614 InitPosition(redraw)
4617 ChessSquare (* pieces)[BOARD_FILES];
4618 int i, j, pawnRow, overrule,
4619 oldx = gameInfo.boardWidth,
4620 oldy = gameInfo.boardHeight,
4621 oldh = gameInfo.holdingsWidth,
4622 oldv = gameInfo.variant;
4624 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4626 /* [AS] Initialize pv info list [HGM] and game status */
4628 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4629 pvInfoList[i].depth = 0;
4630 boards[i][EP_STATUS] = EP_NONE;
4631 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4634 initialRulePlies = 0; /* 50-move counter start */
4636 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4637 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4641 /* [HGM] logic here is completely changed. In stead of full positions */
4642 /* the initialized data only consist of the two backranks. The switch */
4643 /* selects which one we will use, which is than copied to the Board */
4644 /* initialPosition, which for the rest is initialized by Pawns and */
4645 /* empty squares. This initial position is then copied to boards[0], */
4646 /* possibly after shuffling, so that it remains available. */
4648 gameInfo.holdingsWidth = 0; /* default board sizes */
4649 gameInfo.boardWidth = 8;
4650 gameInfo.boardHeight = 8;
4651 gameInfo.holdingsSize = 0;
4652 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4653 for(i=0; i<BOARD_FILES-2; i++)
4654 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4655 initialPosition[EP_STATUS] = EP_NONE;
4656 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4658 switch (gameInfo.variant) {
4659 case VariantFischeRandom:
4660 shuffleOpenings = TRUE;
4664 case VariantShatranj:
4665 pieces = ShatranjArray;
4666 nrCastlingRights = 0;
4667 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4669 case VariantTwoKings:
4670 pieces = twoKingsArray;
4672 case VariantCapaRandom:
4673 shuffleOpenings = TRUE;
4674 case VariantCapablanca:
4675 pieces = CapablancaArray;
4676 gameInfo.boardWidth = 10;
4677 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4680 pieces = GothicArray;
4681 gameInfo.boardWidth = 10;
4682 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4685 pieces = JanusArray;
4686 gameInfo.boardWidth = 10;
4687 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4688 nrCastlingRights = 6;
4689 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4690 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4691 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4692 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4693 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4694 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4697 pieces = FalconArray;
4698 gameInfo.boardWidth = 10;
4699 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4701 case VariantXiangqi:
4702 pieces = XiangqiArray;
4703 gameInfo.boardWidth = 9;
4704 gameInfo.boardHeight = 10;
4705 nrCastlingRights = 0;
4706 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4709 pieces = ShogiArray;
4710 gameInfo.boardWidth = 9;
4711 gameInfo.boardHeight = 9;
4712 gameInfo.holdingsSize = 7;
4713 nrCastlingRights = 0;
4714 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4716 case VariantCourier:
4717 pieces = CourierArray;
4718 gameInfo.boardWidth = 12;
4719 nrCastlingRights = 0;
4720 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4722 case VariantKnightmate:
4723 pieces = KnightmateArray;
4724 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4727 pieces = fairyArray;
4728 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4731 pieces = GreatArray;
4732 gameInfo.boardWidth = 10;
4733 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4734 gameInfo.holdingsSize = 8;
4738 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4739 gameInfo.holdingsSize = 8;
4740 startedFromSetupPosition = TRUE;
4742 case VariantCrazyhouse:
4743 case VariantBughouse:
4745 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4746 gameInfo.holdingsSize = 5;
4748 case VariantWildCastle:
4750 /* !!?shuffle with kings guaranteed to be on d or e file */
4751 shuffleOpenings = 1;
4753 case VariantNoCastle:
4755 nrCastlingRights = 0;
4756 /* !!?unconstrained back-rank shuffle */
4757 shuffleOpenings = 1;
4762 if(appData.NrFiles >= 0) {
4763 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4764 gameInfo.boardWidth = appData.NrFiles;
4766 if(appData.NrRanks >= 0) {
4767 gameInfo.boardHeight = appData.NrRanks;
4769 if(appData.holdingsSize >= 0) {
4770 i = appData.holdingsSize;
4771 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4772 gameInfo.holdingsSize = i;
4774 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4775 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4776 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4778 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4779 if(pawnRow < 1) pawnRow = 1;
4781 /* User pieceToChar list overrules defaults */
4782 if(appData.pieceToCharTable != NULL)
4783 SetCharTable(pieceToChar, appData.pieceToCharTable);
4785 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4787 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4788 s = (ChessSquare) 0; /* account holding counts in guard band */
4789 for( i=0; i<BOARD_HEIGHT; i++ )
4790 initialPosition[i][j] = s;
4792 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4793 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4794 initialPosition[pawnRow][j] = WhitePawn;
4795 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4796 if(gameInfo.variant == VariantXiangqi) {
4798 initialPosition[pawnRow][j] =
4799 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4800 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4801 initialPosition[2][j] = WhiteCannon;
4802 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4806 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4808 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4811 initialPosition[1][j] = WhiteBishop;
4812 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4814 initialPosition[1][j] = WhiteRook;
4815 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4818 if( nrCastlingRights == -1) {
4819 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4820 /* This sets default castling rights from none to normal corners */
4821 /* Variants with other castling rights must set them themselves above */
4822 nrCastlingRights = 6;
4824 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4825 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4826 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4827 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4828 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4829 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4832 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4833 if(gameInfo.variant == VariantGreat) { // promotion commoners
4834 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4835 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4836 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4837 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4839 if (appData.debugMode) {
4840 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4842 if(shuffleOpenings) {
4843 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4844 startedFromSetupPosition = TRUE;
4846 if(startedFromPositionFile) {
4847 /* [HGM] loadPos: use PositionFile for every new game */
4848 CopyBoard(initialPosition, filePosition);
4849 for(i=0; i<nrCastlingRights; i++)
4850 initialRights[i] = filePosition[CASTLING][i];
4851 startedFromSetupPosition = TRUE;
4854 CopyBoard(boards[0], initialPosition);
4856 if(oldx != gameInfo.boardWidth ||
4857 oldy != gameInfo.boardHeight ||
4858 oldh != gameInfo.holdingsWidth
4860 || oldv == VariantGothic || // For licensing popups
4861 gameInfo.variant == VariantGothic
4864 || oldv == VariantFalcon ||
4865 gameInfo.variant == VariantFalcon
4868 InitDrawingSizes(-2 ,0);
4871 DrawPosition(TRUE, boards[currentMove]);
4875 SendBoard(cps, moveNum)
4876 ChessProgramState *cps;
4879 char message[MSG_SIZ];
4881 if (cps->useSetboard) {
4882 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4883 sprintf(message, "setboard %s\n", fen);
4884 SendToProgram(message, cps);
4890 /* Kludge to set black to move, avoiding the troublesome and now
4891 * deprecated "black" command.
4893 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4895 SendToProgram("edit\n", cps);
4896 SendToProgram("#\n", cps);
4897 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4898 bp = &boards[moveNum][i][BOARD_LEFT];
4899 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4900 if ((int) *bp < (int) BlackPawn) {
4901 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4903 if(message[0] == '+' || message[0] == '~') {
4904 sprintf(message, "%c%c%c+\n",
4905 PieceToChar((ChessSquare)(DEMOTED *bp)),
4908 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4909 message[1] = BOARD_RGHT - 1 - j + '1';
4910 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4912 SendToProgram(message, cps);
4917 SendToProgram("c\n", cps);
4918 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4919 bp = &boards[moveNum][i][BOARD_LEFT];
4920 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4921 if (((int) *bp != (int) EmptySquare)
4922 && ((int) *bp >= (int) BlackPawn)) {
4923 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4925 if(message[0] == '+' || message[0] == '~') {
4926 sprintf(message, "%c%c%c+\n",
4927 PieceToChar((ChessSquare)(DEMOTED *bp)),
4930 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4931 message[1] = BOARD_RGHT - 1 - j + '1';
4932 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4934 SendToProgram(message, cps);
4939 SendToProgram(".\n", cps);
4941 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4945 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4947 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4948 /* [HGM] add Shogi promotions */
4949 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4954 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4955 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4957 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4958 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4961 piece = boards[currentMove][fromY][fromX];
4962 if(gameInfo.variant == VariantShogi) {
4963 promotionZoneSize = 3;
4964 highestPromotingPiece = (int)WhiteFerz;
4967 // next weed out all moves that do not touch the promotion zone at all
4968 if((int)piece >= BlackPawn) {
4969 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4971 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4973 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4974 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4977 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4979 // weed out mandatory Shogi promotions
4980 if(gameInfo.variant == VariantShogi) {
4981 if(piece >= BlackPawn) {
4982 if(toY == 0 && piece == BlackPawn ||
4983 toY == 0 && piece == BlackQueen ||
4984 toY <= 1 && piece == BlackKnight) {
4989 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4990 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4991 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4998 // weed out obviously illegal Pawn moves
4999 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5000 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5001 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5002 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5003 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5004 // note we are not allowed to test for valid (non-)capture, due to premove
5007 // we either have a choice what to promote to, or (in Shogi) whether to promote
5008 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5009 *promoChoice = PieceToChar(BlackFerz); // no choice
5012 if(appData.alwaysPromoteToQueen) { // predetermined
5013 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5014 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5015 else *promoChoice = PieceToChar(BlackQueen);
5019 // suppress promotion popup on illegal moves that are not premoves
5020 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5021 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5022 if(appData.testLegality && !premove) {
5023 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5024 fromY, fromX, toY, toX, NULLCHAR);
5025 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5026 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5034 InPalace(row, column)
5036 { /* [HGM] for Xiangqi */
5037 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5038 column < (BOARD_WIDTH + 4)/2 &&
5039 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5044 PieceForSquare (x, y)
5048 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5051 return boards[currentMove][y][x];
5055 OKToStartUserMove(x, y)
5058 ChessSquare from_piece;
5061 if (matchMode) return FALSE;
5062 if (gameMode == EditPosition) return TRUE;
5064 if (x >= 0 && y >= 0)
5065 from_piece = boards[currentMove][y][x];
5067 from_piece = EmptySquare;
5069 if (from_piece == EmptySquare) return FALSE;
5071 white_piece = (int)from_piece >= (int)WhitePawn &&
5072 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5075 case PlayFromGameFile:
5077 case TwoMachinesPlay:
5085 case MachinePlaysWhite:
5086 case IcsPlayingBlack:
5087 if (appData.zippyPlay) return FALSE;
5089 DisplayMoveError(_("You are playing Black"));
5094 case MachinePlaysBlack:
5095 case IcsPlayingWhite:
5096 if (appData.zippyPlay) return FALSE;
5098 DisplayMoveError(_("You are playing White"));
5104 if (!white_piece && WhiteOnMove(currentMove)) {
5105 DisplayMoveError(_("It is White's turn"));
5108 if (white_piece && !WhiteOnMove(currentMove)) {
5109 DisplayMoveError(_("It is Black's turn"));
5112 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5113 /* Editing correspondence game history */
5114 /* Could disallow this or prompt for confirmation */
5119 case BeginningOfGame:
5120 if (appData.icsActive) return FALSE;
5121 if (!appData.noChessProgram) {
5123 DisplayMoveError(_("You are playing White"));
5130 if (!white_piece && WhiteOnMove(currentMove)) {
5131 DisplayMoveError(_("It is White's turn"));
5134 if (white_piece && !WhiteOnMove(currentMove)) {
5135 DisplayMoveError(_("It is Black's turn"));
5144 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5145 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5146 && gameMode != AnalyzeFile && gameMode != Training) {
5147 DisplayMoveError(_("Displayed position is not current"));
5153 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5154 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5155 int lastLoadGameUseList = FALSE;
5156 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5157 ChessMove lastLoadGameStart = (ChessMove) 0;
5160 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5161 int fromX, fromY, toX, toY;
5166 ChessSquare pdown, pup;
5168 /* Check if the user is playing in turn. This is complicated because we
5169 let the user "pick up" a piece before it is his turn. So the piece he
5170 tried to pick up may have been captured by the time he puts it down!
5171 Therefore we use the color the user is supposed to be playing in this
5172 test, not the color of the piece that is currently on the starting
5173 square---except in EditGame mode, where the user is playing both
5174 sides; fortunately there the capture race can't happen. (It can
5175 now happen in IcsExamining mode, but that's just too bad. The user
5176 will get a somewhat confusing message in that case.)
5180 case PlayFromGameFile:
5182 case TwoMachinesPlay:
5186 /* We switched into a game mode where moves are not accepted,
5187 perhaps while the mouse button was down. */
5188 return ImpossibleMove;
5190 case MachinePlaysWhite:
5191 /* User is moving for Black */
5192 if (WhiteOnMove(currentMove)) {
5193 DisplayMoveError(_("It is White's turn"));
5194 return ImpossibleMove;
5198 case MachinePlaysBlack:
5199 /* User is moving for White */
5200 if (!WhiteOnMove(currentMove)) {
5201 DisplayMoveError(_("It is Black's turn"));
5202 return ImpossibleMove;
5208 case BeginningOfGame:
5211 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5212 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5213 /* User is moving for Black */
5214 if (WhiteOnMove(currentMove)) {
5215 DisplayMoveError(_("It is White's turn"));
5216 return ImpossibleMove;
5219 /* User is moving for White */
5220 if (!WhiteOnMove(currentMove)) {
5221 DisplayMoveError(_("It is Black's turn"));
5222 return ImpossibleMove;
5227 case IcsPlayingBlack:
5228 /* User is moving for Black */
5229 if (WhiteOnMove(currentMove)) {
5230 if (!appData.premove) {
5231 DisplayMoveError(_("It is White's turn"));
5232 } else if (toX >= 0 && toY >= 0) {
5235 premoveFromX = fromX;
5236 premoveFromY = fromY;
5237 premovePromoChar = promoChar;
5239 if (appData.debugMode)
5240 fprintf(debugFP, "Got premove: fromX %d,"
5241 "fromY %d, toX %d, toY %d\n",
5242 fromX, fromY, toX, toY);
5244 return ImpossibleMove;
5248 case IcsPlayingWhite:
5249 /* User is moving for White */
5250 if (!WhiteOnMove(currentMove)) {
5251 if (!appData.premove) {
5252 DisplayMoveError(_("It is Black's turn"));
5253 } else if (toX >= 0 && toY >= 0) {
5256 premoveFromX = fromX;
5257 premoveFromY = fromY;
5258 premovePromoChar = promoChar;
5260 if (appData.debugMode)
5261 fprintf(debugFP, "Got premove: fromX %d,"
5262 "fromY %d, toX %d, toY %d\n",
5263 fromX, fromY, toX, toY);
5265 return ImpossibleMove;
5273 /* EditPosition, empty square, or different color piece;
5274 click-click move is possible */
5275 if (toX == -2 || toY == -2) {
5276 boards[0][fromY][fromX] = EmptySquare;
5277 return AmbiguousMove;
5278 } else if (toX >= 0 && toY >= 0) {
5279 boards[0][toY][toX] = boards[0][fromY][fromX];
5280 boards[0][fromY][fromX] = EmptySquare;
5281 return AmbiguousMove;
5283 return ImpossibleMove;
5286 if(toX < 0 || toY < 0) return ImpossibleMove;
5287 pdown = boards[currentMove][fromY][fromX];
5288 pup = boards[currentMove][toY][toX];
5290 /* [HGM] If move started in holdings, it means a drop */
5291 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5292 if( pup != EmptySquare ) return ImpossibleMove;
5293 if(appData.testLegality) {
5294 /* it would be more logical if LegalityTest() also figured out
5295 * which drops are legal. For now we forbid pawns on back rank.
5296 * Shogi is on its own here...
5298 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5299 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5300 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5302 return WhiteDrop; /* Not needed to specify white or black yet */
5305 userOfferedDraw = FALSE;
5307 /* [HGM] always test for legality, to get promotion info */
5308 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5309 fromY, fromX, toY, toX, promoChar);
5310 /* [HGM] but possibly ignore an IllegalMove result */
5311 if (appData.testLegality) {
5312 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5313 DisplayMoveError(_("Illegal move"));
5314 return ImpossibleMove;
5317 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5319 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5320 function is made into one that returns an OK move type if FinishMove
5321 should be called. This to give the calling driver routine the
5322 opportunity to finish the userMove input with a promotion popup,
5323 without bothering the user with this for invalid or illegal moves */
5325 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5328 /* Common tail of UserMoveEvent and DropMenuEvent */
5330 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5332 int fromX, fromY, toX, toY;
5333 /*char*/int promoChar;
5336 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5337 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5338 // [HGM] superchess: suppress promotions to non-available piece
5339 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5340 if(WhiteOnMove(currentMove)) {
5341 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5343 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5347 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5348 move type in caller when we know the move is a legal promotion */
5349 if(moveType == NormalMove && promoChar)
5350 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5351 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5352 /* [HGM] convert drag-and-drop piece drops to standard form */
5353 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5354 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5355 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5356 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5357 // fromX = boards[currentMove][fromY][fromX];
5358 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5359 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5360 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5361 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5365 /* [HGM] <popupFix> The following if has been moved here from
5366 UserMoveEvent(). Because it seemed to belon here (why not allow
5367 piece drops in training games?), and because it can only be
5368 performed after it is known to what we promote. */
5369 if (gameMode == Training) {
5370 /* compare the move played on the board to the next move in the
5371 * game. If they match, display the move and the opponent's response.
5372 * If they don't match, display an error message.
5376 CopyBoard(testBoard, boards[currentMove]);
5377 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5379 if (CompareBoards(testBoard, boards[currentMove+1])) {
5380 ForwardInner(currentMove+1);
5382 /* Autoplay the opponent's response.
5383 * if appData.animate was TRUE when Training mode was entered,
5384 * the response will be animated.
5386 saveAnimate = appData.animate;
5387 appData.animate = animateTraining;
5388 ForwardInner(currentMove+1);
5389 appData.animate = saveAnimate;
5391 /* check for the end of the game */
5392 if (currentMove >= forwardMostMove) {
5393 gameMode = PlayFromGameFile;
5395 SetTrainingModeOff();
5396 DisplayInformation(_("End of game"));
5399 DisplayError(_("Incorrect move"), 0);
5404 /* Ok, now we know that the move is good, so we can kill
5405 the previous line in Analysis Mode */
5406 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5407 && currentMove < forwardMostMove) {
5408 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5411 /* If we need the chess program but it's dead, restart it */
5412 ResurrectChessProgram();
5414 /* A user move restarts a paused game*/
5418 thinkOutput[0] = NULLCHAR;
5420 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5422 if (gameMode == BeginningOfGame) {
5423 if (appData.noChessProgram) {
5424 gameMode = EditGame;
5428 gameMode = MachinePlaysBlack;
5431 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5433 if (first.sendName) {
5434 sprintf(buf, "name %s\n", gameInfo.white);
5435 SendToProgram(buf, &first);
5441 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5442 /* Relay move to ICS or chess engine */
5443 if (appData.icsActive) {
5444 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5445 gameMode == IcsExamining) {
5446 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5450 if (first.sendTime && (gameMode == BeginningOfGame ||
5451 gameMode == MachinePlaysWhite ||
5452 gameMode == MachinePlaysBlack)) {
5453 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5455 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5456 // [HGM] book: if program might be playing, let it use book
5457 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5458 first.maybeThinking = TRUE;
5459 } else SendMoveToProgram(forwardMostMove-1, &first);
5460 if (currentMove == cmailOldMove + 1) {
5461 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5465 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5469 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5475 if (WhiteOnMove(currentMove)) {
5476 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5478 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5482 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5487 case MachinePlaysBlack:
5488 case MachinePlaysWhite:
5489 /* disable certain menu options while machine is thinking */
5490 SetMachineThinkingEnables();
5497 if(bookHit) { // [HGM] book: simulate book reply
5498 static char bookMove[MSG_SIZ]; // a bit generous?
5500 programStats.nodes = programStats.depth = programStats.time =
5501 programStats.score = programStats.got_only_move = 0;
5502 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5504 strcpy(bookMove, "move ");
5505 strcat(bookMove, bookHit);
5506 HandleMachineMove(bookMove, &first);
5512 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5513 int fromX, fromY, toX, toY;
5516 /* [HGM] This routine was added to allow calling of its two logical
5517 parts from other modules in the old way. Before, UserMoveEvent()
5518 automatically called FinishMove() if the move was OK, and returned
5519 otherwise. I separated the two, in order to make it possible to
5520 slip a promotion popup in between. But that it always needs two
5521 calls, to the first part, (now called UserMoveTest() ), and to
5522 FinishMove if the first part succeeded. Calls that do not need
5523 to do anything in between, can call this routine the old way.
5525 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5526 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5527 if(moveType == AmbiguousMove)
5528 DrawPosition(FALSE, boards[currentMove]);
5529 else if(moveType != ImpossibleMove && moveType != Comment)
5530 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5533 void LeftClick(ClickType clickType, int xPix, int yPix)
5536 Boolean saveAnimate;
5537 static int second = 0, promotionChoice = 0;
5538 char promoChoice = NULLCHAR;
5540 if (clickType == Press) ErrorPopDown();
5542 x = EventToSquare(xPix, BOARD_WIDTH);
5543 y = EventToSquare(yPix, BOARD_HEIGHT);
5544 if (!flipView && y >= 0) {
5545 y = BOARD_HEIGHT - 1 - y;
5547 if (flipView && x >= 0) {
5548 x = BOARD_WIDTH - 1 - x;
5551 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5552 if(clickType == Release) return; // ignore upclick of click-click destination
5553 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5554 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5555 if(gameInfo.holdingsWidth &&
5556 (WhiteOnMove(currentMove)
5557 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5558 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5559 // click in right holdings, for determining promotion piece
5560 ChessSquare p = boards[currentMove][y][x];
5561 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5562 if(p != EmptySquare) {
5563 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5568 DrawPosition(FALSE, boards[currentMove]);
5572 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5573 if(clickType == Press
5574 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5575 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5576 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5580 if (clickType == Press) {
5582 if (OKToStartUserMove(x, y)) {
5586 DragPieceBegin(xPix, yPix);
5587 if (appData.highlightDragging) {
5588 SetHighlights(x, y, -1, -1);
5596 if (clickType == Press && gameMode != EditPosition) {
5601 // ignore off-board to clicks
5602 if(y < 0 || x < 0) return;
5604 /* Check if clicking again on the same color piece */
5605 fromP = boards[currentMove][fromY][fromX];
5606 toP = boards[currentMove][y][x];
5607 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5608 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5609 WhitePawn <= toP && toP <= WhiteKing &&
5610 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5611 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5612 (BlackPawn <= fromP && fromP <= BlackKing &&
5613 BlackPawn <= toP && toP <= BlackKing &&
5614 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5615 !(fromP == BlackKing && toP == BlackRook && frc))) {
5616 /* Clicked again on same color piece -- changed his mind */
5617 second = (x == fromX && y == fromY);
5618 if (appData.highlightDragging) {
5619 SetHighlights(x, y, -1, -1);
5623 if (OKToStartUserMove(x, y)) {
5626 DragPieceBegin(xPix, yPix);
5630 // ignore clicks on holdings
5631 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5634 if (clickType == Release && x == fromX && y == fromY) {
5635 DragPieceEnd(xPix, yPix);
5636 if (appData.animateDragging) {
5637 /* Undo animation damage if any */
5638 DrawPosition(FALSE, NULL);
5641 /* Second up/down in same square; just abort move */
5646 ClearPremoveHighlights();
5648 /* First upclick in same square; start click-click mode */
5649 SetHighlights(x, y, -1, -1);
5654 /* we now have a different from- and (possibly off-board) to-square */
5655 /* Completed move */
5658 saveAnimate = appData.animate;
5659 if (clickType == Press) {
5660 /* Finish clickclick move */
5661 if (appData.animate || appData.highlightLastMove) {
5662 SetHighlights(fromX, fromY, toX, toY);
5667 /* Finish drag move */
5668 if (appData.highlightLastMove) {
5669 SetHighlights(fromX, fromY, toX, toY);
5673 DragPieceEnd(xPix, yPix);
5674 /* Don't animate move and drag both */
5675 appData.animate = FALSE;
5678 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5679 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5682 DrawPosition(TRUE, NULL);
5686 // off-board moves should not be highlighted
5687 if(x < 0 || x < 0) ClearHighlights();
5689 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5690 SetHighlights(fromX, fromY, toX, toY);
5691 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5692 // [HGM] super: promotion to captured piece selected from holdings
5693 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5694 promotionChoice = TRUE;
5695 // kludge follows to temporarily execute move on display, without promoting yet
5696 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5697 boards[currentMove][toY][toX] = p;
5698 DrawPosition(FALSE, boards[currentMove]);
5699 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5700 boards[currentMove][toY][toX] = q;
5701 DisplayMessage("Click in holdings to choose piece", "");
5706 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5707 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5708 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5711 appData.animate = saveAnimate;
5712 if (appData.animate || appData.animateDragging) {
5713 /* Undo animation damage if needed */
5714 DrawPosition(FALSE, NULL);
5718 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5720 // char * hint = lastHint;
5721 FrontEndProgramStats stats;
5723 stats.which = cps == &first ? 0 : 1;
5724 stats.depth = cpstats->depth;
5725 stats.nodes = cpstats->nodes;
5726 stats.score = cpstats->score;
5727 stats.time = cpstats->time;
5728 stats.pv = cpstats->movelist;
5729 stats.hint = lastHint;
5730 stats.an_move_index = 0;
5731 stats.an_move_count = 0;
5733 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5734 stats.hint = cpstats->move_name;
5735 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5736 stats.an_move_count = cpstats->nr_moves;
5739 SetProgramStats( &stats );
5742 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5743 { // [HGM] book: this routine intercepts moves to simulate book replies
5744 char *bookHit = NULL;
5746 //first determine if the incoming move brings opponent into his book
5747 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5748 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5749 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5750 if(bookHit != NULL && !cps->bookSuspend) {
5751 // make sure opponent is not going to reply after receiving move to book position
5752 SendToProgram("force\n", cps);
5753 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5755 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5756 // now arrange restart after book miss
5758 // after a book hit we never send 'go', and the code after the call to this routine
5759 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5761 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5762 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5763 SendToProgram(buf, cps);
5764 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5765 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5766 SendToProgram("go\n", cps);
5767 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5768 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5769 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5770 SendToProgram("go\n", cps);
5771 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5773 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5777 ChessProgramState *savedState;
5778 void DeferredBookMove(void)
5780 if(savedState->lastPing != savedState->lastPong)
5781 ScheduleDelayedEvent(DeferredBookMove, 10);
5783 HandleMachineMove(savedMessage, savedState);
5787 HandleMachineMove(message, cps)
5789 ChessProgramState *cps;
5791 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5792 char realname[MSG_SIZ];
5793 int fromX, fromY, toX, toY;
5800 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5802 * Kludge to ignore BEL characters
5804 while (*message == '\007') message++;
5807 * [HGM] engine debug message: ignore lines starting with '#' character
5809 if(cps->debug && *message == '#') return;
5812 * Look for book output
5814 if (cps == &first && bookRequested) {
5815 if (message[0] == '\t' || message[0] == ' ') {
5816 /* Part of the book output is here; append it */
5817 strcat(bookOutput, message);
5818 strcat(bookOutput, " \n");
5820 } else if (bookOutput[0] != NULLCHAR) {
5821 /* All of book output has arrived; display it */
5822 char *p = bookOutput;
5823 while (*p != NULLCHAR) {
5824 if (*p == '\t') *p = ' ';
5827 DisplayInformation(bookOutput);
5828 bookRequested = FALSE;
5829 /* Fall through to parse the current output */
5834 * Look for machine move.
5836 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5837 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5839 /* This method is only useful on engines that support ping */
5840 if (cps->lastPing != cps->lastPong) {
5841 if (gameMode == BeginningOfGame) {
5842 /* Extra move from before last new; ignore */
5843 if (appData.debugMode) {
5844 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5847 if (appData.debugMode) {
5848 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5849 cps->which, gameMode);
5852 SendToProgram("undo\n", cps);
5858 case BeginningOfGame:
5859 /* Extra move from before last reset; ignore */
5860 if (appData.debugMode) {
5861 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5868 /* Extra move after we tried to stop. The mode test is
5869 not a reliable way of detecting this problem, but it's
5870 the best we can do on engines that don't support ping.
5872 if (appData.debugMode) {
5873 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5874 cps->which, gameMode);
5876 SendToProgram("undo\n", cps);
5879 case MachinePlaysWhite:
5880 case IcsPlayingWhite:
5881 machineWhite = TRUE;
5884 case MachinePlaysBlack:
5885 case IcsPlayingBlack:
5886 machineWhite = FALSE;
5889 case TwoMachinesPlay:
5890 machineWhite = (cps->twoMachinesColor[0] == 'w');
5893 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5894 if (appData.debugMode) {
5896 "Ignoring move out of turn by %s, gameMode %d"
5897 ", forwardMost %d\n",
5898 cps->which, gameMode, forwardMostMove);
5903 if (appData.debugMode) { int f = forwardMostMove;
5904 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5905 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5906 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5908 if(cps->alphaRank) AlphaRank(machineMove, 4);
5909 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5910 &fromX, &fromY, &toX, &toY, &promoChar)) {
5911 /* Machine move could not be parsed; ignore it. */
5912 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5913 machineMove, cps->which);
5914 DisplayError(buf1, 0);
5915 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5916 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5917 if (gameMode == TwoMachinesPlay) {
5918 GameEnds(machineWhite ? BlackWins : WhiteWins,
5924 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5925 /* So we have to redo legality test with true e.p. status here, */
5926 /* to make sure an illegal e.p. capture does not slip through, */
5927 /* to cause a forfeit on a justified illegal-move complaint */
5928 /* of the opponent. */
5929 if( gameMode==TwoMachinesPlay && appData.testLegality
5930 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5933 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5934 fromY, fromX, toY, toX, promoChar);
5935 if (appData.debugMode) {
5937 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5938 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5939 fprintf(debugFP, "castling rights\n");
5941 if(moveType == IllegalMove) {
5942 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5943 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5944 GameEnds(machineWhite ? BlackWins : WhiteWins,
5947 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5948 /* [HGM] Kludge to handle engines that send FRC-style castling
5949 when they shouldn't (like TSCP-Gothic) */
5951 case WhiteASideCastleFR:
5952 case BlackASideCastleFR:
5954 currentMoveString[2]++;
5956 case WhiteHSideCastleFR:
5957 case BlackHSideCastleFR:
5959 currentMoveString[2]--;
5961 default: ; // nothing to do, but suppresses warning of pedantic compilers
5964 hintRequested = FALSE;
5965 lastHint[0] = NULLCHAR;
5966 bookRequested = FALSE;
5967 /* Program may be pondering now */
5968 cps->maybeThinking = TRUE;
5969 if (cps->sendTime == 2) cps->sendTime = 1;
5970 if (cps->offeredDraw) cps->offeredDraw--;
5973 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5975 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5977 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5978 char buf[3*MSG_SIZ];
5980 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5981 programStats.score / 100.,
5983 programStats.time / 100.,
5984 (unsigned int)programStats.nodes,
5985 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5986 programStats.movelist);
5988 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5992 /* currentMoveString is set as a side-effect of ParseOneMove */
5993 strcpy(machineMove, currentMoveString);
5994 strcat(machineMove, "\n");
5995 strcpy(moveList[forwardMostMove], machineMove);
5997 /* [AS] Save move info and clear stats for next move */
5998 pvInfoList[ forwardMostMove ].score = programStats.score;
5999 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6000 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6001 ClearProgramStats();
6002 thinkOutput[0] = NULLCHAR;
6003 hiddenThinkOutputState = 0;
6005 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6007 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6008 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6011 while( count < adjudicateLossPlies ) {
6012 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6015 score = -score; /* Flip score for winning side */
6018 if( score > adjudicateLossThreshold ) {
6025 if( count >= adjudicateLossPlies ) {
6026 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6028 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6029 "Xboard adjudication",
6036 if( gameMode == TwoMachinesPlay ) {
6037 // [HGM] some adjudications useful with buggy engines
6038 int k, count = 0; static int bare = 1;
6039 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6042 if( appData.testLegality )
6043 { /* [HGM] Some more adjudications for obstinate engines */
6044 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6045 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6046 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6047 static int moveCount = 6;
6049 char *reason = NULL;
6051 /* Count what is on board. */
6052 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6053 { ChessSquare p = boards[forwardMostMove][i][j];
6057 { /* count B,N,R and other of each side */
6060 NrK++; break; // [HGM] atomic: count Kings
6064 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6065 bishopsColor |= 1 << ((i^j)&1);
6070 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6071 bishopsColor |= 1 << ((i^j)&1);
6086 PawnAdvance += m; NrPawns++;
6088 NrPieces += (p != EmptySquare);
6089 NrW += ((int)p < (int)BlackPawn);
6090 if(gameInfo.variant == VariantXiangqi &&
6091 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6092 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6093 NrW -= ((int)p < (int)BlackPawn);
6097 /* Some material-based adjudications that have to be made before stalemate test */
6098 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6099 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6100 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6101 if(appData.checkMates) {
6102 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6103 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6104 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6105 "Xboard adjudication: King destroyed", GE_XBOARD );
6110 /* Bare King in Shatranj (loses) or Losers (wins) */
6111 if( NrW == 1 || NrPieces - NrW == 1) {
6112 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6113 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6114 if(appData.checkMates) {
6115 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6116 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6117 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6118 "Xboard adjudication: Bare king", GE_XBOARD );
6122 if( gameInfo.variant == VariantShatranj && --bare < 0)
6124 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6125 if(appData.checkMates) {
6126 /* but only adjudicate if adjudication enabled */
6127 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6128 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6129 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6130 "Xboard adjudication: Bare king", GE_XBOARD );
6137 // don't wait for engine to announce game end if we can judge ourselves
6138 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6140 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6141 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6142 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6143 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6146 reason = "Xboard adjudication: 3rd check";
6147 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6157 reason = "Xboard adjudication: Stalemate";
6158 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6159 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6160 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6161 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6162 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6163 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6164 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6165 EP_CHECKMATE : EP_WINS);
6166 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6167 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6171 reason = "Xboard adjudication: Checkmate";
6172 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6176 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6178 result = GameIsDrawn; break;
6180 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6182 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6184 result = (ChessMove) 0;
6186 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6187 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6188 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6189 GameEnds( result, reason, GE_XBOARD );
6193 /* Next absolutely insufficient mating material. */
6194 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6195 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6196 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6197 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6198 { /* KBK, KNK, KK of KBKB with like Bishops */
6200 /* always flag draws, for judging claims */
6201 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6203 if(appData.materialDraws) {
6204 /* but only adjudicate them if adjudication enabled */
6205 SendToProgram("force\n", cps->other); // suppress reply
6206 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6207 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6208 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6213 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6215 ( NrWR == 1 && NrBR == 1 /* KRKR */
6216 || NrWQ==1 && NrBQ==1 /* KQKQ */
6217 || NrWN==2 || NrBN==2 /* KNNK */
6218 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6220 if(--moveCount < 0 && appData.trivialDraws)
6221 { /* if the first 3 moves do not show a tactical win, declare draw */
6222 SendToProgram("force\n", cps->other); // suppress reply
6223 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6224 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6225 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6228 } else moveCount = 6;
6232 if (appData.debugMode) { int i;
6233 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6234 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6235 appData.drawRepeats);
6236 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6237 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6241 /* Check for rep-draws */
6243 for(k = forwardMostMove-2;
6244 k>=backwardMostMove && k>=forwardMostMove-100 &&
6245 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6246 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6249 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6250 /* compare castling rights */
6251 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6252 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6253 rights++; /* King lost rights, while rook still had them */
6254 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6255 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6256 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6257 rights++; /* but at least one rook lost them */
6259 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6260 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6262 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6263 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6264 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6267 if( rights == 0 && ++count > appData.drawRepeats-2
6268 && appData.drawRepeats > 1) {
6269 /* adjudicate after user-specified nr of repeats */
6270 SendToProgram("force\n", cps->other); // suppress reply
6271 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6272 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6273 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6274 // [HGM] xiangqi: check for forbidden perpetuals
6275 int m, ourPerpetual = 1, hisPerpetual = 1;
6276 for(m=forwardMostMove; m>k; m-=2) {
6277 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6278 ourPerpetual = 0; // the current mover did not always check
6279 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6280 hisPerpetual = 0; // the opponent did not always check
6282 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6283 ourPerpetual, hisPerpetual);
6284 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6285 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6286 "Xboard adjudication: perpetual checking", GE_XBOARD );
6289 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6290 break; // (or we would have caught him before). Abort repetition-checking loop.
6291 // Now check for perpetual chases
6292 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6293 hisPerpetual = PerpetualChase(k, forwardMostMove);
6294 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6295 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6296 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6297 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6300 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6301 break; // Abort repetition-checking loop.
6303 // if neither of us is checking or chasing all the time, or both are, it is draw
6305 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6308 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6309 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6313 /* Now we test for 50-move draws. Determine ply count */
6314 count = forwardMostMove;
6315 /* look for last irreversble move */
6316 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6318 /* if we hit starting position, add initial plies */
6319 if( count == backwardMostMove )
6320 count -= initialRulePlies;
6321 count = forwardMostMove - count;
6323 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6324 /* this is used to judge if draw claims are legal */
6325 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6326 SendToProgram("force\n", cps->other); // suppress reply
6327 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6328 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6329 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6333 /* if draw offer is pending, treat it as a draw claim
6334 * when draw condition present, to allow engines a way to
6335 * claim draws before making their move to avoid a race
6336 * condition occurring after their move
6338 if( cps->other->offeredDraw || cps->offeredDraw ) {
6340 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6341 p = "Draw claim: 50-move rule";
6342 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6343 p = "Draw claim: 3-fold repetition";
6344 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6345 p = "Draw claim: insufficient mating material";
6347 SendToProgram("force\n", cps->other); // suppress reply
6348 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6349 GameEnds( GameIsDrawn, p, GE_XBOARD );
6350 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6356 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6357 SendToProgram("force\n", cps->other); // suppress reply
6358 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6359 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6361 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6368 if (gameMode == TwoMachinesPlay) {
6369 /* [HGM] relaying draw offers moved to after reception of move */
6370 /* and interpreting offer as claim if it brings draw condition */
6371 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6372 SendToProgram("draw\n", cps->other);
6374 if (cps->other->sendTime) {
6375 SendTimeRemaining(cps->other,
6376 cps->other->twoMachinesColor[0] == 'w');
6378 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6379 if (firstMove && !bookHit) {
6381 if (cps->other->useColors) {
6382 SendToProgram(cps->other->twoMachinesColor, cps->other);
6384 SendToProgram("go\n", cps->other);
6386 cps->other->maybeThinking = TRUE;
6389 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6391 if (!pausing && appData.ringBellAfterMoves) {
6396 * Reenable menu items that were disabled while
6397 * machine was thinking
6399 if (gameMode != TwoMachinesPlay)
6400 SetUserThinkingEnables();
6402 // [HGM] book: after book hit opponent has received move and is now in force mode
6403 // force the book reply into it, and then fake that it outputted this move by jumping
6404 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6406 static char bookMove[MSG_SIZ]; // a bit generous?
6408 strcpy(bookMove, "move ");
6409 strcat(bookMove, bookHit);
6412 programStats.nodes = programStats.depth = programStats.time =
6413 programStats.score = programStats.got_only_move = 0;
6414 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6416 if(cps->lastPing != cps->lastPong) {
6417 savedMessage = message; // args for deferred call
6419 ScheduleDelayedEvent(DeferredBookMove, 10);
6428 /* Set special modes for chess engines. Later something general
6429 * could be added here; for now there is just one kludge feature,
6430 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6431 * when "xboard" is given as an interactive command.
6433 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6434 cps->useSigint = FALSE;
6435 cps->useSigterm = FALSE;
6437 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6438 ParseFeatures(message+8, cps);
6439 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6442 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6443 * want this, I was asked to put it in, and obliged.
6445 if (!strncmp(message, "setboard ", 9)) {
6446 Board initial_position;
6448 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6450 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6451 DisplayError(_("Bad FEN received from engine"), 0);
6455 CopyBoard(boards[0], initial_position);
6456 initialRulePlies = FENrulePlies;
6457 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6458 else gameMode = MachinePlaysBlack;
6459 DrawPosition(FALSE, boards[currentMove]);
6465 * Look for communication commands
6467 if (!strncmp(message, "telluser ", 9)) {
6468 DisplayNote(message + 9);
6471 if (!strncmp(message, "tellusererror ", 14)) {
6472 DisplayError(message + 14, 0);
6475 if (!strncmp(message, "tellopponent ", 13)) {
6476 if (appData.icsActive) {
6478 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6482 DisplayNote(message + 13);
6486 if (!strncmp(message, "tellothers ", 11)) {
6487 if (appData.icsActive) {
6489 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6495 if (!strncmp(message, "tellall ", 8)) {
6496 if (appData.icsActive) {
6498 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6502 DisplayNote(message + 8);
6506 if (strncmp(message, "warning", 7) == 0) {
6507 /* Undocumented feature, use tellusererror in new code */
6508 DisplayError(message, 0);
6511 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6512 strcpy(realname, cps->tidy);
6513 strcat(realname, " query");
6514 AskQuestion(realname, buf2, buf1, cps->pr);
6517 /* Commands from the engine directly to ICS. We don't allow these to be
6518 * sent until we are logged on. Crafty kibitzes have been known to
6519 * interfere with the login process.
6522 if (!strncmp(message, "tellics ", 8)) {
6523 SendToICS(message + 8);
6527 if (!strncmp(message, "tellicsnoalias ", 15)) {
6528 SendToICS(ics_prefix);
6529 SendToICS(message + 15);
6533 /* The following are for backward compatibility only */
6534 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6535 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6536 SendToICS(ics_prefix);
6542 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6546 * If the move is illegal, cancel it and redraw the board.
6547 * Also deal with other error cases. Matching is rather loose
6548 * here to accommodate engines written before the spec.
6550 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6551 strncmp(message, "Error", 5) == 0) {
6552 if (StrStr(message, "name") ||
6553 StrStr(message, "rating") || StrStr(message, "?") ||
6554 StrStr(message, "result") || StrStr(message, "board") ||
6555 StrStr(message, "bk") || StrStr(message, "computer") ||
6556 StrStr(message, "variant") || StrStr(message, "hint") ||
6557 StrStr(message, "random") || StrStr(message, "depth") ||
6558 StrStr(message, "accepted")) {
6561 if (StrStr(message, "protover")) {
6562 /* Program is responding to input, so it's apparently done
6563 initializing, and this error message indicates it is
6564 protocol version 1. So we don't need to wait any longer
6565 for it to initialize and send feature commands. */
6566 FeatureDone(cps, 1);
6567 cps->protocolVersion = 1;
6570 cps->maybeThinking = FALSE;
6572 if (StrStr(message, "draw")) {
6573 /* Program doesn't have "draw" command */
6574 cps->sendDrawOffers = 0;
6577 if (cps->sendTime != 1 &&
6578 (StrStr(message, "time") || StrStr(message, "otim"))) {
6579 /* Program apparently doesn't have "time" or "otim" command */
6583 if (StrStr(message, "analyze")) {
6584 cps->analysisSupport = FALSE;
6585 cps->analyzing = FALSE;
6587 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6588 DisplayError(buf2, 0);
6591 if (StrStr(message, "(no matching move)st")) {
6592 /* Special kludge for GNU Chess 4 only */
6593 cps->stKludge = TRUE;
6594 SendTimeControl(cps, movesPerSession, timeControl,
6595 timeIncrement, appData.searchDepth,
6599 if (StrStr(message, "(no matching move)sd")) {
6600 /* Special kludge for GNU Chess 4 only */
6601 cps->sdKludge = TRUE;
6602 SendTimeControl(cps, movesPerSession, timeControl,
6603 timeIncrement, appData.searchDepth,
6607 if (!StrStr(message, "llegal")) {
6610 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6611 gameMode == IcsIdle) return;
6612 if (forwardMostMove <= backwardMostMove) return;
6613 if (pausing) PauseEvent();
6614 if(appData.forceIllegal) {
6615 // [HGM] illegal: machine refused move; force position after move into it
6616 SendToProgram("force\n", cps);
6617 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6618 // we have a real problem now, as SendBoard will use the a2a3 kludge
6619 // when black is to move, while there might be nothing on a2 or black
6620 // might already have the move. So send the board as if white has the move.
6621 // But first we must change the stm of the engine, as it refused the last move
6622 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6623 if(WhiteOnMove(forwardMostMove)) {
6624 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6625 SendBoard(cps, forwardMostMove); // kludgeless board
6627 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6628 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6629 SendBoard(cps, forwardMostMove+1); // kludgeless board
6631 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6632 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6633 gameMode == TwoMachinesPlay)
6634 SendToProgram("go\n", cps);
6637 if (gameMode == PlayFromGameFile) {
6638 /* Stop reading this game file */
6639 gameMode = EditGame;
6642 currentMove = --forwardMostMove;
6643 DisplayMove(currentMove-1); /* before DisplayMoveError */
6645 DisplayBothClocks();
6646 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6647 parseList[currentMove], cps->which);
6648 DisplayMoveError(buf1);
6649 DrawPosition(FALSE, boards[currentMove]);
6651 /* [HGM] illegal-move claim should forfeit game when Xboard */
6652 /* only passes fully legal moves */
6653 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6654 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6655 "False illegal-move claim", GE_XBOARD );
6659 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6660 /* Program has a broken "time" command that
6661 outputs a string not ending in newline.
6667 * If chess program startup fails, exit with an error message.
6668 * Attempts to recover here are futile.
6670 if ((StrStr(message, "unknown host") != NULL)
6671 || (StrStr(message, "No remote directory") != NULL)
6672 || (StrStr(message, "not found") != NULL)
6673 || (StrStr(message, "No such file") != NULL)
6674 || (StrStr(message, "can't alloc") != NULL)
6675 || (StrStr(message, "Permission denied") != NULL)) {
6677 cps->maybeThinking = FALSE;
6678 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6679 cps->which, cps->program, cps->host, message);
6680 RemoveInputSource(cps->isr);
6681 DisplayFatalError(buf1, 0, 1);
6686 * Look for hint output
6688 if (sscanf(message, "Hint: %s", buf1) == 1) {
6689 if (cps == &first && hintRequested) {
6690 hintRequested = FALSE;
6691 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6692 &fromX, &fromY, &toX, &toY, &promoChar)) {
6693 (void) CoordsToAlgebraic(boards[forwardMostMove],
6694 PosFlags(forwardMostMove),
6695 fromY, fromX, toY, toX, promoChar, buf1);
6696 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6697 DisplayInformation(buf2);
6699 /* Hint move could not be parsed!? */
6700 snprintf(buf2, sizeof(buf2),
6701 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6703 DisplayError(buf2, 0);
6706 strcpy(lastHint, buf1);
6712 * Ignore other messages if game is not in progress
6714 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6715 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6718 * look for win, lose, draw, or draw offer
6720 if (strncmp(message, "1-0", 3) == 0) {
6721 char *p, *q, *r = "";
6722 p = strchr(message, '{');
6730 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6732 } else if (strncmp(message, "0-1", 3) == 0) {
6733 char *p, *q, *r = "";
6734 p = strchr(message, '{');
6742 /* Kludge for Arasan 4.1 bug */
6743 if (strcmp(r, "Black resigns") == 0) {
6744 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6747 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6749 } else if (strncmp(message, "1/2", 3) == 0) {
6750 char *p, *q, *r = "";
6751 p = strchr(message, '{');
6760 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6763 } else if (strncmp(message, "White resign", 12) == 0) {
6764 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6766 } else if (strncmp(message, "Black resign", 12) == 0) {
6767 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6769 } else if (strncmp(message, "White matches", 13) == 0 ||
6770 strncmp(message, "Black matches", 13) == 0 ) {
6771 /* [HGM] ignore GNUShogi noises */
6773 } else if (strncmp(message, "White", 5) == 0 &&
6774 message[5] != '(' &&
6775 StrStr(message, "Black") == NULL) {
6776 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6778 } else if (strncmp(message, "Black", 5) == 0 &&
6779 message[5] != '(') {
6780 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6782 } else if (strcmp(message, "resign") == 0 ||
6783 strcmp(message, "computer resigns") == 0) {
6785 case MachinePlaysBlack:
6786 case IcsPlayingBlack:
6787 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6789 case MachinePlaysWhite:
6790 case IcsPlayingWhite:
6791 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6793 case TwoMachinesPlay:
6794 if (cps->twoMachinesColor[0] == 'w')
6795 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6797 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6804 } else if (strncmp(message, "opponent mates", 14) == 0) {
6806 case MachinePlaysBlack:
6807 case IcsPlayingBlack:
6808 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6810 case MachinePlaysWhite:
6811 case IcsPlayingWhite:
6812 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6814 case TwoMachinesPlay:
6815 if (cps->twoMachinesColor[0] == 'w')
6816 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6818 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6825 } else if (strncmp(message, "computer mates", 14) == 0) {
6827 case MachinePlaysBlack:
6828 case IcsPlayingBlack:
6829 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6831 case MachinePlaysWhite:
6832 case IcsPlayingWhite:
6833 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6835 case TwoMachinesPlay:
6836 if (cps->twoMachinesColor[0] == 'w')
6837 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6839 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6846 } else if (strncmp(message, "checkmate", 9) == 0) {
6847 if (WhiteOnMove(forwardMostMove)) {
6848 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6850 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6853 } else if (strstr(message, "Draw") != NULL ||
6854 strstr(message, "game is a draw") != NULL) {
6855 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6857 } else if (strstr(message, "offer") != NULL &&
6858 strstr(message, "draw") != NULL) {
6860 if (appData.zippyPlay && first.initDone) {
6861 /* Relay offer to ICS */
6862 SendToICS(ics_prefix);
6863 SendToICS("draw\n");
6866 cps->offeredDraw = 2; /* valid until this engine moves twice */
6867 if (gameMode == TwoMachinesPlay) {
6868 if (cps->other->offeredDraw) {
6869 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6870 /* [HGM] in two-machine mode we delay relaying draw offer */
6871 /* until after we also have move, to see if it is really claim */
6873 } else if (gameMode == MachinePlaysWhite ||
6874 gameMode == MachinePlaysBlack) {
6875 if (userOfferedDraw) {
6876 DisplayInformation(_("Machine accepts your draw offer"));
6877 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6879 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6886 * Look for thinking output
6888 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6889 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6891 int plylev, mvleft, mvtot, curscore, time;
6892 char mvname[MOVE_LEN];
6896 int prefixHint = FALSE;
6897 mvname[0] = NULLCHAR;
6900 case MachinePlaysBlack:
6901 case IcsPlayingBlack:
6902 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6904 case MachinePlaysWhite:
6905 case IcsPlayingWhite:
6906 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6911 case IcsObserving: /* [DM] icsEngineAnalyze */
6912 if (!appData.icsEngineAnalyze) ignore = TRUE;
6914 case TwoMachinesPlay:
6915 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6926 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6927 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6929 if (plyext != ' ' && plyext != '\t') {
6933 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6934 if( cps->scoreIsAbsolute &&
6935 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6937 curscore = -curscore;
6941 programStats.depth = plylev;
6942 programStats.nodes = nodes;
6943 programStats.time = time;
6944 programStats.score = curscore;
6945 programStats.got_only_move = 0;
6947 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6950 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6951 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6952 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6953 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6954 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6955 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6956 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6957 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6960 /* Buffer overflow protection */
6961 if (buf1[0] != NULLCHAR) {
6962 if (strlen(buf1) >= sizeof(programStats.movelist)
6963 && appData.debugMode) {
6965 "PV is too long; using the first %u bytes.\n",
6966 (unsigned) sizeof(programStats.movelist) - 1);
6969 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6971 sprintf(programStats.movelist, " no PV\n");
6974 if (programStats.seen_stat) {
6975 programStats.ok_to_send = 1;
6978 if (strchr(programStats.movelist, '(') != NULL) {
6979 programStats.line_is_book = 1;
6980 programStats.nr_moves = 0;
6981 programStats.moves_left = 0;
6983 programStats.line_is_book = 0;
6986 SendProgramStatsToFrontend( cps, &programStats );
6989 [AS] Protect the thinkOutput buffer from overflow... this
6990 is only useful if buf1 hasn't overflowed first!
6992 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6994 (gameMode == TwoMachinesPlay ?
6995 ToUpper(cps->twoMachinesColor[0]) : ' '),
6996 ((double) curscore) / 100.0,
6997 prefixHint ? lastHint : "",
6998 prefixHint ? " " : "" );
7000 if( buf1[0] != NULLCHAR ) {
7001 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7003 if( strlen(buf1) > max_len ) {
7004 if( appData.debugMode) {
7005 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7007 buf1[max_len+1] = '\0';
7010 strcat( thinkOutput, buf1 );
7013 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7014 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7015 DisplayMove(currentMove - 1);
7019 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7020 /* crafty (9.25+) says "(only move) <move>"
7021 * if there is only 1 legal move
7023 sscanf(p, "(only move) %s", buf1);
7024 sprintf(thinkOutput, "%s (only move)", buf1);
7025 sprintf(programStats.movelist, "%s (only move)", buf1);
7026 programStats.depth = 1;
7027 programStats.nr_moves = 1;
7028 programStats.moves_left = 1;
7029 programStats.nodes = 1;
7030 programStats.time = 1;
7031 programStats.got_only_move = 1;
7033 /* Not really, but we also use this member to
7034 mean "line isn't going to change" (Crafty
7035 isn't searching, so stats won't change) */
7036 programStats.line_is_book = 1;
7038 SendProgramStatsToFrontend( cps, &programStats );
7040 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7041 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7042 DisplayMove(currentMove - 1);
7045 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7046 &time, &nodes, &plylev, &mvleft,
7047 &mvtot, mvname) >= 5) {
7048 /* The stat01: line is from Crafty (9.29+) in response
7049 to the "." command */
7050 programStats.seen_stat = 1;
7051 cps->maybeThinking = TRUE;
7053 if (programStats.got_only_move || !appData.periodicUpdates)
7056 programStats.depth = plylev;
7057 programStats.time = time;
7058 programStats.nodes = nodes;
7059 programStats.moves_left = mvleft;
7060 programStats.nr_moves = mvtot;
7061 strcpy(programStats.move_name, mvname);
7062 programStats.ok_to_send = 1;
7063 programStats.movelist[0] = '\0';
7065 SendProgramStatsToFrontend( cps, &programStats );
7069 } else if (strncmp(message,"++",2) == 0) {
7070 /* Crafty 9.29+ outputs this */
7071 programStats.got_fail = 2;
7074 } else if (strncmp(message,"--",2) == 0) {
7075 /* Crafty 9.29+ outputs this */
7076 programStats.got_fail = 1;
7079 } else if (thinkOutput[0] != NULLCHAR &&
7080 strncmp(message, " ", 4) == 0) {
7081 unsigned message_len;
7084 while (*p && *p == ' ') p++;
7086 message_len = strlen( p );
7088 /* [AS] Avoid buffer overflow */
7089 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7090 strcat(thinkOutput, " ");
7091 strcat(thinkOutput, p);
7094 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7095 strcat(programStats.movelist, " ");
7096 strcat(programStats.movelist, p);
7099 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7100 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7101 DisplayMove(currentMove - 1);
7109 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7110 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7112 ChessProgramStats cpstats;
7114 if (plyext != ' ' && plyext != '\t') {
7118 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7119 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7120 curscore = -curscore;
7123 cpstats.depth = plylev;
7124 cpstats.nodes = nodes;
7125 cpstats.time = time;
7126 cpstats.score = curscore;
7127 cpstats.got_only_move = 0;
7128 cpstats.movelist[0] = '\0';
7130 if (buf1[0] != NULLCHAR) {
7131 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7134 cpstats.ok_to_send = 0;
7135 cpstats.line_is_book = 0;
7136 cpstats.nr_moves = 0;
7137 cpstats.moves_left = 0;
7139 SendProgramStatsToFrontend( cps, &cpstats );
7146 /* Parse a game score from the character string "game", and
7147 record it as the history of the current game. The game
7148 score is NOT assumed to start from the standard position.
7149 The display is not updated in any way.
7152 ParseGameHistory(game)
7156 int fromX, fromY, toX, toY, boardIndex;
7161 if (appData.debugMode)
7162 fprintf(debugFP, "Parsing game history: %s\n", game);
7164 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7165 gameInfo.site = StrSave(appData.icsHost);
7166 gameInfo.date = PGNDate();
7167 gameInfo.round = StrSave("-");
7169 /* Parse out names of players */
7170 while (*game == ' ') game++;
7172 while (*game != ' ') *p++ = *game++;
7174 gameInfo.white = StrSave(buf);
7175 while (*game == ' ') game++;
7177 while (*game != ' ' && *game != '\n') *p++ = *game++;
7179 gameInfo.black = StrSave(buf);
7182 boardIndex = blackPlaysFirst ? 1 : 0;
7185 yyboardindex = boardIndex;
7186 moveType = (ChessMove) yylex();
7188 case IllegalMove: /* maybe suicide chess, etc. */
7189 if (appData.debugMode) {
7190 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7191 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7192 setbuf(debugFP, NULL);
7194 case WhitePromotionChancellor:
7195 case BlackPromotionChancellor:
7196 case WhitePromotionArchbishop:
7197 case BlackPromotionArchbishop:
7198 case WhitePromotionQueen:
7199 case BlackPromotionQueen:
7200 case WhitePromotionRook:
7201 case BlackPromotionRook:
7202 case WhitePromotionBishop:
7203 case BlackPromotionBishop:
7204 case WhitePromotionKnight:
7205 case BlackPromotionKnight:
7206 case WhitePromotionKing:
7207 case BlackPromotionKing:
7209 case WhiteCapturesEnPassant:
7210 case BlackCapturesEnPassant:
7211 case WhiteKingSideCastle:
7212 case WhiteQueenSideCastle:
7213 case BlackKingSideCastle:
7214 case BlackQueenSideCastle:
7215 case WhiteKingSideCastleWild:
7216 case WhiteQueenSideCastleWild:
7217 case BlackKingSideCastleWild:
7218 case BlackQueenSideCastleWild:
7220 case WhiteHSideCastleFR:
7221 case WhiteASideCastleFR:
7222 case BlackHSideCastleFR:
7223 case BlackASideCastleFR:
7225 fromX = currentMoveString[0] - AAA;
7226 fromY = currentMoveString[1] - ONE;
7227 toX = currentMoveString[2] - AAA;
7228 toY = currentMoveString[3] - ONE;
7229 promoChar = currentMoveString[4];
7233 fromX = moveType == WhiteDrop ?
7234 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7235 (int) CharToPiece(ToLower(currentMoveString[0]));
7237 toX = currentMoveString[2] - AAA;
7238 toY = currentMoveString[3] - ONE;
7239 promoChar = NULLCHAR;
7243 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7244 if (appData.debugMode) {
7245 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7246 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7247 setbuf(debugFP, NULL);
7249 DisplayError(buf, 0);
7251 case ImpossibleMove:
7253 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7254 if (appData.debugMode) {
7255 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7256 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7257 setbuf(debugFP, NULL);
7259 DisplayError(buf, 0);
7261 case (ChessMove) 0: /* end of file */
7262 if (boardIndex < backwardMostMove) {
7263 /* Oops, gap. How did that happen? */
7264 DisplayError(_("Gap in move list"), 0);
7267 backwardMostMove = blackPlaysFirst ? 1 : 0;
7268 if (boardIndex > forwardMostMove) {
7269 forwardMostMove = boardIndex;
7273 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7274 strcat(parseList[boardIndex-1], " ");
7275 strcat(parseList[boardIndex-1], yy_text);
7287 case GameUnfinished:
7288 if (gameMode == IcsExamining) {
7289 if (boardIndex < backwardMostMove) {
7290 /* Oops, gap. How did that happen? */
7293 backwardMostMove = blackPlaysFirst ? 1 : 0;
7296 gameInfo.result = moveType;
7297 p = strchr(yy_text, '{');
7298 if (p == NULL) p = strchr(yy_text, '(');
7301 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7303 q = strchr(p, *p == '{' ? '}' : ')');
7304 if (q != NULL) *q = NULLCHAR;
7307 gameInfo.resultDetails = StrSave(p);
7310 if (boardIndex >= forwardMostMove &&
7311 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7312 backwardMostMove = blackPlaysFirst ? 1 : 0;
7315 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7316 fromY, fromX, toY, toX, promoChar,
7317 parseList[boardIndex]);
7318 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7319 /* currentMoveString is set as a side-effect of yylex */
7320 strcpy(moveList[boardIndex], currentMoveString);
7321 strcat(moveList[boardIndex], "\n");
7323 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7324 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7330 if(gameInfo.variant != VariantShogi)
7331 strcat(parseList[boardIndex - 1], "+");
7335 strcat(parseList[boardIndex - 1], "#");
7342 /* Apply a move to the given board */
7344 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7345 int fromX, fromY, toX, toY;
7349 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7351 /* [HGM] compute & store e.p. status and castling rights for new position */
7352 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7355 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7356 oldEP = (signed char)board[EP_STATUS];
7357 board[EP_STATUS] = EP_NONE;
7359 if( board[toY][toX] != EmptySquare )
7360 board[EP_STATUS] = EP_CAPTURE;
7362 if( board[fromY][fromX] == WhitePawn ) {
7363 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7364 board[EP_STATUS] = EP_PAWN_MOVE;
7366 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7367 gameInfo.variant != VariantBerolina || toX < fromX)
7368 board[EP_STATUS] = toX | berolina;
7369 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7370 gameInfo.variant != VariantBerolina || toX > fromX)
7371 board[EP_STATUS] = toX;
7374 if( board[fromY][fromX] == BlackPawn ) {
7375 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7376 board[EP_STATUS] = EP_PAWN_MOVE;
7377 if( toY-fromY== -2) {
7378 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7379 gameInfo.variant != VariantBerolina || toX < fromX)
7380 board[EP_STATUS] = toX | berolina;
7381 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7382 gameInfo.variant != VariantBerolina || toX > fromX)
7383 board[EP_STATUS] = toX;
7387 for(i=0; i<nrCastlingRights; i++) {
7388 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7389 board[CASTLING][i] == toX && castlingRank[i] == toY
7390 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7395 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7396 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7397 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7399 if (fromX == toX && fromY == toY) return;
7401 if (fromY == DROP_RANK) {
7403 piece = board[toY][toX] = (ChessSquare) fromX;
7405 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7406 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7407 if(gameInfo.variant == VariantKnightmate)
7408 king += (int) WhiteUnicorn - (int) WhiteKing;
7410 /* Code added by Tord: */
7411 /* FRC castling assumed when king captures friendly rook. */
7412 if (board[fromY][fromX] == WhiteKing &&
7413 board[toY][toX] == WhiteRook) {
7414 board[fromY][fromX] = EmptySquare;
7415 board[toY][toX] = EmptySquare;
7417 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7419 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7421 } else if (board[fromY][fromX] == BlackKing &&
7422 board[toY][toX] == BlackRook) {
7423 board[fromY][fromX] = EmptySquare;
7424 board[toY][toX] = EmptySquare;
7426 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7428 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7430 /* End of code added by Tord */
7432 } else if (board[fromY][fromX] == king
7433 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7434 && toY == fromY && toX > fromX+1) {
7435 board[fromY][fromX] = EmptySquare;
7436 board[toY][toX] = king;
7437 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7438 board[fromY][BOARD_RGHT-1] = EmptySquare;
7439 } else if (board[fromY][fromX] == king
7440 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7441 && toY == fromY && toX < fromX-1) {
7442 board[fromY][fromX] = EmptySquare;
7443 board[toY][toX] = king;
7444 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7445 board[fromY][BOARD_LEFT] = EmptySquare;
7446 } else if (board[fromY][fromX] == WhitePawn
7447 && toY == BOARD_HEIGHT-1
7448 && gameInfo.variant != VariantXiangqi
7450 /* white pawn promotion */
7451 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7452 if (board[toY][toX] == EmptySquare) {
7453 board[toY][toX] = WhiteQueen;
7455 if(gameInfo.variant==VariantBughouse ||
7456 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7457 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7458 board[fromY][fromX] = EmptySquare;
7459 } else if ((fromY == BOARD_HEIGHT-4)
7461 && gameInfo.variant != VariantXiangqi
7462 && gameInfo.variant != VariantBerolina
7463 && (board[fromY][fromX] == WhitePawn)
7464 && (board[toY][toX] == EmptySquare)) {
7465 board[fromY][fromX] = EmptySquare;
7466 board[toY][toX] = WhitePawn;
7467 captured = board[toY - 1][toX];
7468 board[toY - 1][toX] = EmptySquare;
7469 } else if ((fromY == BOARD_HEIGHT-4)
7471 && gameInfo.variant == VariantBerolina
7472 && (board[fromY][fromX] == WhitePawn)
7473 && (board[toY][toX] == EmptySquare)) {
7474 board[fromY][fromX] = EmptySquare;
7475 board[toY][toX] = WhitePawn;
7476 if(oldEP & EP_BEROLIN_A) {
7477 captured = board[fromY][fromX-1];
7478 board[fromY][fromX-1] = EmptySquare;
7479 }else{ captured = board[fromY][fromX+1];
7480 board[fromY][fromX+1] = EmptySquare;
7482 } else if (board[fromY][fromX] == king
7483 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7484 && toY == fromY && toX > fromX+1) {
7485 board[fromY][fromX] = EmptySquare;
7486 board[toY][toX] = king;
7487 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7488 board[fromY][BOARD_RGHT-1] = EmptySquare;
7489 } else if (board[fromY][fromX] == king
7490 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7491 && toY == fromY && toX < fromX-1) {
7492 board[fromY][fromX] = EmptySquare;
7493 board[toY][toX] = king;
7494 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7495 board[fromY][BOARD_LEFT] = EmptySquare;
7496 } else if (fromY == 7 && fromX == 3
7497 && board[fromY][fromX] == BlackKing
7498 && toY == 7 && toX == 5) {
7499 board[fromY][fromX] = EmptySquare;
7500 board[toY][toX] = BlackKing;
7501 board[fromY][7] = EmptySquare;
7502 board[toY][4] = BlackRook;
7503 } else if (fromY == 7 && fromX == 3
7504 && board[fromY][fromX] == BlackKing
7505 && toY == 7 && toX == 1) {
7506 board[fromY][fromX] = EmptySquare;
7507 board[toY][toX] = BlackKing;
7508 board[fromY][0] = EmptySquare;
7509 board[toY][2] = BlackRook;
7510 } else if (board[fromY][fromX] == BlackPawn
7512 && gameInfo.variant != VariantXiangqi
7514 /* black pawn promotion */
7515 board[0][toX] = CharToPiece(ToLower(promoChar));
7516 if (board[0][toX] == EmptySquare) {
7517 board[0][toX] = BlackQueen;
7519 if(gameInfo.variant==VariantBughouse ||
7520 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7521 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7522 board[fromY][fromX] = EmptySquare;
7523 } else if ((fromY == 3)
7525 && gameInfo.variant != VariantXiangqi
7526 && gameInfo.variant != VariantBerolina
7527 && (board[fromY][fromX] == BlackPawn)
7528 && (board[toY][toX] == EmptySquare)) {
7529 board[fromY][fromX] = EmptySquare;
7530 board[toY][toX] = BlackPawn;
7531 captured = board[toY + 1][toX];
7532 board[toY + 1][toX] = EmptySquare;
7533 } else if ((fromY == 3)
7535 && gameInfo.variant == VariantBerolina
7536 && (board[fromY][fromX] == BlackPawn)
7537 && (board[toY][toX] == EmptySquare)) {
7538 board[fromY][fromX] = EmptySquare;
7539 board[toY][toX] = BlackPawn;
7540 if(oldEP & EP_BEROLIN_A) {
7541 captured = board[fromY][fromX-1];
7542 board[fromY][fromX-1] = EmptySquare;
7543 }else{ captured = board[fromY][fromX+1];
7544 board[fromY][fromX+1] = EmptySquare;
7547 board[toY][toX] = board[fromY][fromX];
7548 board[fromY][fromX] = EmptySquare;
7551 /* [HGM] now we promote for Shogi, if needed */
7552 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7553 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7556 if (gameInfo.holdingsWidth != 0) {
7558 /* !!A lot more code needs to be written to support holdings */
7559 /* [HGM] OK, so I have written it. Holdings are stored in the */
7560 /* penultimate board files, so they are automaticlly stored */
7561 /* in the game history. */
7562 if (fromY == DROP_RANK) {
7563 /* Delete from holdings, by decreasing count */
7564 /* and erasing image if necessary */
7566 if(p < (int) BlackPawn) { /* white drop */
7567 p -= (int)WhitePawn;
7568 p = PieceToNumber((ChessSquare)p);
7569 if(p >= gameInfo.holdingsSize) p = 0;
7570 if(--board[p][BOARD_WIDTH-2] <= 0)
7571 board[p][BOARD_WIDTH-1] = EmptySquare;
7572 if((int)board[p][BOARD_WIDTH-2] < 0)
7573 board[p][BOARD_WIDTH-2] = 0;
7574 } else { /* black drop */
7575 p -= (int)BlackPawn;
7576 p = PieceToNumber((ChessSquare)p);
7577 if(p >= gameInfo.holdingsSize) p = 0;
7578 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7579 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7580 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7581 board[BOARD_HEIGHT-1-p][1] = 0;
7584 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7585 && gameInfo.variant != VariantBughouse ) {
7586 /* [HGM] holdings: Add to holdings, if holdings exist */
7587 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7588 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7589 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7592 if (p >= (int) BlackPawn) {
7593 p -= (int)BlackPawn;
7594 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7595 /* in Shogi restore piece to its original first */
7596 captured = (ChessSquare) (DEMOTED captured);
7599 p = PieceToNumber((ChessSquare)p);
7600 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7601 board[p][BOARD_WIDTH-2]++;
7602 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7604 p -= (int)WhitePawn;
7605 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7606 captured = (ChessSquare) (DEMOTED captured);
7609 p = PieceToNumber((ChessSquare)p);
7610 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7611 board[BOARD_HEIGHT-1-p][1]++;
7612 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7615 } else if (gameInfo.variant == VariantAtomic) {
7616 if (captured != EmptySquare) {
7618 for (y = toY-1; y <= toY+1; y++) {
7619 for (x = toX-1; x <= toX+1; x++) {
7620 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7621 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7622 board[y][x] = EmptySquare;
7626 board[toY][toX] = EmptySquare;
7629 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7630 /* [HGM] Shogi promotions */
7631 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7634 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7635 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7636 // [HGM] superchess: take promotion piece out of holdings
7637 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7638 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7639 if(!--board[k][BOARD_WIDTH-2])
7640 board[k][BOARD_WIDTH-1] = EmptySquare;
7642 if(!--board[BOARD_HEIGHT-1-k][1])
7643 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7649 /* Updates forwardMostMove */
7651 MakeMove(fromX, fromY, toX, toY, promoChar)
7652 int fromX, fromY, toX, toY;
7655 // forwardMostMove++; // [HGM] bare: moved downstream
7657 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7658 int timeLeft; static int lastLoadFlag=0; int king, piece;
7659 piece = boards[forwardMostMove][fromY][fromX];
7660 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7661 if(gameInfo.variant == VariantKnightmate)
7662 king += (int) WhiteUnicorn - (int) WhiteKing;
7663 if(forwardMostMove == 0) {
7665 fprintf(serverMoves, "%s;", second.tidy);
7666 fprintf(serverMoves, "%s;", first.tidy);
7667 if(!blackPlaysFirst)
7668 fprintf(serverMoves, "%s;", second.tidy);
7669 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7670 lastLoadFlag = loadFlag;
7672 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7673 // print castling suffix
7674 if( toY == fromY && piece == king ) {
7676 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7678 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7681 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7682 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7683 boards[forwardMostMove][toY][toX] == EmptySquare
7685 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7687 if(promoChar != NULLCHAR)
7688 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7690 fprintf(serverMoves, "/%d/%d",
7691 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7692 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7693 else timeLeft = blackTimeRemaining/1000;
7694 fprintf(serverMoves, "/%d", timeLeft);
7696 fflush(serverMoves);
7699 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7700 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7704 if (commentList[forwardMostMove+1] != NULL) {
7705 free(commentList[forwardMostMove+1]);
7706 commentList[forwardMostMove+1] = NULL;
7708 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7709 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7710 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7711 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7712 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7713 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7714 gameInfo.result = GameUnfinished;
7715 if (gameInfo.resultDetails != NULL) {
7716 free(gameInfo.resultDetails);
7717 gameInfo.resultDetails = NULL;
7719 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7720 moveList[forwardMostMove - 1]);
7721 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7722 PosFlags(forwardMostMove - 1),
7723 fromY, fromX, toY, toX, promoChar,
7724 parseList[forwardMostMove - 1]);
7725 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7731 if(gameInfo.variant != VariantShogi)
7732 strcat(parseList[forwardMostMove - 1], "+");
7736 strcat(parseList[forwardMostMove - 1], "#");
7739 if (appData.debugMode) {
7740 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7745 /* Updates currentMove if not pausing */
7747 ShowMove(fromX, fromY, toX, toY)
7749 int instant = (gameMode == PlayFromGameFile) ?
7750 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7751 if(appData.noGUI) return;
7752 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7754 if (forwardMostMove == currentMove + 1) {
7755 AnimateMove(boards[forwardMostMove - 1],
7756 fromX, fromY, toX, toY);
7758 if (appData.highlightLastMove) {
7759 SetHighlights(fromX, fromY, toX, toY);
7762 currentMove = forwardMostMove;
7765 if (instant) return;
7767 DisplayMove(currentMove - 1);
7768 DrawPosition(FALSE, boards[currentMove]);
7769 DisplayBothClocks();
7770 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7773 void SendEgtPath(ChessProgramState *cps)
7774 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7775 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7777 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7780 char c, *q = name+1, *r, *s;
7782 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7783 while(*p && *p != ',') *q++ = *p++;
7785 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7786 strcmp(name, ",nalimov:") == 0 ) {
7787 // take nalimov path from the menu-changeable option first, if it is defined
7788 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7789 SendToProgram(buf,cps); // send egtbpath command for nalimov
7791 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7792 (s = StrStr(appData.egtFormats, name)) != NULL) {
7793 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7794 s = r = StrStr(s, ":") + 1; // beginning of path info
7795 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7796 c = *r; *r = 0; // temporarily null-terminate path info
7797 *--q = 0; // strip of trailig ':' from name
7798 sprintf(buf, "egtpath %s %s\n", name+1, s);
7800 SendToProgram(buf,cps); // send egtbpath command for this format
7802 if(*p == ',') p++; // read away comma to position for next format name
7807 InitChessProgram(cps, setup)
7808 ChessProgramState *cps;
7809 int setup; /* [HGM] needed to setup FRC opening position */
7811 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7812 if (appData.noChessProgram) return;
7813 hintRequested = FALSE;
7814 bookRequested = FALSE;
7816 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7817 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7818 if(cps->memSize) { /* [HGM] memory */
7819 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7820 SendToProgram(buf, cps);
7822 SendEgtPath(cps); /* [HGM] EGT */
7823 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7824 sprintf(buf, "cores %d\n", appData.smpCores);
7825 SendToProgram(buf, cps);
7828 SendToProgram(cps->initString, cps);
7829 if (gameInfo.variant != VariantNormal &&
7830 gameInfo.variant != VariantLoadable
7831 /* [HGM] also send variant if board size non-standard */
7832 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7834 char *v = VariantName(gameInfo.variant);
7835 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7836 /* [HGM] in protocol 1 we have to assume all variants valid */
7837 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7838 DisplayFatalError(buf, 0, 1);
7842 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7843 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7844 if( gameInfo.variant == VariantXiangqi )
7845 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7846 if( gameInfo.variant == VariantShogi )
7847 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7848 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7849 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7850 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7851 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7852 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7853 if( gameInfo.variant == VariantCourier )
7854 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7855 if( gameInfo.variant == VariantSuper )
7856 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7857 if( gameInfo.variant == VariantGreat )
7858 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7861 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7862 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7863 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7864 if(StrStr(cps->variants, b) == NULL) {
7865 // specific sized variant not known, check if general sizing allowed
7866 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7867 if(StrStr(cps->variants, "boardsize") == NULL) {
7868 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7869 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7870 DisplayFatalError(buf, 0, 1);
7873 /* [HGM] here we really should compare with the maximum supported board size */
7876 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7877 sprintf(buf, "variant %s\n", b);
7878 SendToProgram(buf, cps);
7880 currentlyInitializedVariant = gameInfo.variant;
7882 /* [HGM] send opening position in FRC to first engine */
7884 SendToProgram("force\n", cps);
7886 /* engine is now in force mode! Set flag to wake it up after first move. */
7887 setboardSpoiledMachineBlack = 1;
7891 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7892 SendToProgram(buf, cps);
7894 cps->maybeThinking = FALSE;
7895 cps->offeredDraw = 0;
7896 if (!appData.icsActive) {
7897 SendTimeControl(cps, movesPerSession, timeControl,
7898 timeIncrement, appData.searchDepth,
7901 if (appData.showThinking
7902 // [HGM] thinking: four options require thinking output to be sent
7903 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7905 SendToProgram("post\n", cps);
7907 SendToProgram("hard\n", cps);
7908 if (!appData.ponderNextMove) {
7909 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7910 it without being sure what state we are in first. "hard"
7911 is not a toggle, so that one is OK.
7913 SendToProgram("easy\n", cps);
7916 sprintf(buf, "ping %d\n", ++cps->lastPing);
7917 SendToProgram(buf, cps);
7919 cps->initDone = TRUE;
7924 StartChessProgram(cps)
7925 ChessProgramState *cps;
7930 if (appData.noChessProgram) return;
7931 cps->initDone = FALSE;
7933 if (strcmp(cps->host, "localhost") == 0) {
7934 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7935 } else if (*appData.remoteShell == NULLCHAR) {
7936 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7938 if (*appData.remoteUser == NULLCHAR) {
7939 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7942 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7943 cps->host, appData.remoteUser, cps->program);
7945 err = StartChildProcess(buf, "", &cps->pr);
7949 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7950 DisplayFatalError(buf, err, 1);
7956 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7957 if (cps->protocolVersion > 1) {
7958 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7959 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7960 cps->comboCnt = 0; // and values of combo boxes
7961 SendToProgram(buf, cps);
7963 SendToProgram("xboard\n", cps);
7969 TwoMachinesEventIfReady P((void))
7971 if (first.lastPing != first.lastPong) {
7972 DisplayMessage("", _("Waiting for first chess program"));
7973 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7976 if (second.lastPing != second.lastPong) {
7977 DisplayMessage("", _("Waiting for second chess program"));
7978 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7986 NextMatchGame P((void))
7988 int index; /* [HGM] autoinc: step load index during match */
7990 if (*appData.loadGameFile != NULLCHAR) {
7991 index = appData.loadGameIndex;
7992 if(index < 0) { // [HGM] autoinc
7993 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7994 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7996 LoadGameFromFile(appData.loadGameFile,
7998 appData.loadGameFile, FALSE);
7999 } else if (*appData.loadPositionFile != NULLCHAR) {
8000 index = appData.loadPositionIndex;
8001 if(index < 0) { // [HGM] autoinc
8002 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8003 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8005 LoadPositionFromFile(appData.loadPositionFile,
8007 appData.loadPositionFile);
8009 TwoMachinesEventIfReady();
8012 void UserAdjudicationEvent( int result )
8014 ChessMove gameResult = GameIsDrawn;
8017 gameResult = WhiteWins;
8019 else if( result < 0 ) {
8020 gameResult = BlackWins;
8023 if( gameMode == TwoMachinesPlay ) {
8024 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8029 // [HGM] save: calculate checksum of game to make games easily identifiable
8030 int StringCheckSum(char *s)
8033 if(s==NULL) return 0;
8034 while(*s) i = i*259 + *s++;
8041 for(i=backwardMostMove; i<forwardMostMove; i++) {
8042 sum += pvInfoList[i].depth;
8043 sum += StringCheckSum(parseList[i]);
8044 sum += StringCheckSum(commentList[i]);
8047 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8048 return sum + StringCheckSum(commentList[i]);
8049 } // end of save patch
8052 GameEnds(result, resultDetails, whosays)
8054 char *resultDetails;
8057 GameMode nextGameMode;
8061 if(endingGame) return; /* [HGM] crash: forbid recursion */
8064 if (appData.debugMode) {
8065 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8066 result, resultDetails ? resultDetails : "(null)", whosays);
8069 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8070 /* If we are playing on ICS, the server decides when the
8071 game is over, but the engine can offer to draw, claim
8075 if (appData.zippyPlay && first.initDone) {
8076 if (result == GameIsDrawn) {
8077 /* In case draw still needs to be claimed */
8078 SendToICS(ics_prefix);
8079 SendToICS("draw\n");
8080 } else if (StrCaseStr(resultDetails, "resign")) {
8081 SendToICS(ics_prefix);
8082 SendToICS("resign\n");
8086 endingGame = 0; /* [HGM] crash */
8090 /* If we're loading the game from a file, stop */
8091 if (whosays == GE_FILE) {
8092 (void) StopLoadGameTimer();
8096 /* Cancel draw offers */
8097 first.offeredDraw = second.offeredDraw = 0;
8099 /* If this is an ICS game, only ICS can really say it's done;
8100 if not, anyone can. */
8101 isIcsGame = (gameMode == IcsPlayingWhite ||
8102 gameMode == IcsPlayingBlack ||
8103 gameMode == IcsObserving ||
8104 gameMode == IcsExamining);
8106 if (!isIcsGame || whosays == GE_ICS) {
8107 /* OK -- not an ICS game, or ICS said it was done */
8109 if (!isIcsGame && !appData.noChessProgram)
8110 SetUserThinkingEnables();
8112 /* [HGM] if a machine claims the game end we verify this claim */
8113 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8114 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8116 ChessMove trueResult = (ChessMove) -1;
8118 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8119 first.twoMachinesColor[0] :
8120 second.twoMachinesColor[0] ;
8122 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8123 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8124 /* [HGM] verify: engine mate claims accepted if they were flagged */
8125 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8127 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8128 /* [HGM] verify: engine mate claims accepted if they were flagged */
8129 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8131 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8132 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8135 // now verify win claims, but not in drop games, as we don't understand those yet
8136 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8137 || gameInfo.variant == VariantGreat) &&
8138 (result == WhiteWins && claimer == 'w' ||
8139 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8140 if (appData.debugMode) {
8141 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8142 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8144 if(result != trueResult) {
8145 sprintf(buf, "False win claim: '%s'", resultDetails);
8146 result = claimer == 'w' ? BlackWins : WhiteWins;
8147 resultDetails = buf;
8150 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8151 && (forwardMostMove <= backwardMostMove ||
8152 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8153 (claimer=='b')==(forwardMostMove&1))
8155 /* [HGM] verify: draws that were not flagged are false claims */
8156 sprintf(buf, "False draw claim: '%s'", resultDetails);
8157 result = claimer == 'w' ? BlackWins : WhiteWins;
8158 resultDetails = buf;
8160 /* (Claiming a loss is accepted no questions asked!) */
8162 /* [HGM] bare: don't allow bare King to win */
8163 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8164 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8165 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8166 && result != GameIsDrawn)
8167 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8168 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8169 int p = (signed char)boards[forwardMostMove][i][j] - color;
8170 if(p >= 0 && p <= (int)WhiteKing) k++;
8172 if (appData.debugMode) {
8173 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8174 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8177 result = GameIsDrawn;
8178 sprintf(buf, "%s but bare king", resultDetails);
8179 resultDetails = buf;
8185 if(serverMoves != NULL && !loadFlag) { char c = '=';
8186 if(result==WhiteWins) c = '+';
8187 if(result==BlackWins) c = '-';
8188 if(resultDetails != NULL)
8189 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8191 if (resultDetails != NULL) {
8192 gameInfo.result = result;
8193 gameInfo.resultDetails = StrSave(resultDetails);
8195 /* display last move only if game was not loaded from file */
8196 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8197 DisplayMove(currentMove - 1);
8199 if (forwardMostMove != 0) {
8200 if (gameMode != PlayFromGameFile && gameMode != EditGame
8201 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8203 if (*appData.saveGameFile != NULLCHAR) {
8204 SaveGameToFile(appData.saveGameFile, TRUE);
8205 } else if (appData.autoSaveGames) {
8208 if (*appData.savePositionFile != NULLCHAR) {
8209 SavePositionToFile(appData.savePositionFile);
8214 /* Tell program how game ended in case it is learning */
8215 /* [HGM] Moved this to after saving the PGN, just in case */
8216 /* engine died and we got here through time loss. In that */
8217 /* case we will get a fatal error writing the pipe, which */
8218 /* would otherwise lose us the PGN. */
8219 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8220 /* output during GameEnds should never be fatal anymore */
8221 if (gameMode == MachinePlaysWhite ||
8222 gameMode == MachinePlaysBlack ||
8223 gameMode == TwoMachinesPlay ||
8224 gameMode == IcsPlayingWhite ||
8225 gameMode == IcsPlayingBlack ||
8226 gameMode == BeginningOfGame) {
8228 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8230 if (first.pr != NoProc) {
8231 SendToProgram(buf, &first);
8233 if (second.pr != NoProc &&
8234 gameMode == TwoMachinesPlay) {
8235 SendToProgram(buf, &second);
8240 if (appData.icsActive) {
8241 if (appData.quietPlay &&
8242 (gameMode == IcsPlayingWhite ||
8243 gameMode == IcsPlayingBlack)) {
8244 SendToICS(ics_prefix);
8245 SendToICS("set shout 1\n");
8247 nextGameMode = IcsIdle;
8248 ics_user_moved = FALSE;
8249 /* clean up premove. It's ugly when the game has ended and the
8250 * premove highlights are still on the board.
8254 ClearPremoveHighlights();
8255 DrawPosition(FALSE, boards[currentMove]);
8257 if (whosays == GE_ICS) {
8260 if (gameMode == IcsPlayingWhite)
8262 else if(gameMode == IcsPlayingBlack)
8266 if (gameMode == IcsPlayingBlack)
8268 else if(gameMode == IcsPlayingWhite)
8275 PlayIcsUnfinishedSound();
8278 } else if (gameMode == EditGame ||
8279 gameMode == PlayFromGameFile ||
8280 gameMode == AnalyzeMode ||
8281 gameMode == AnalyzeFile) {
8282 nextGameMode = gameMode;
8284 nextGameMode = EndOfGame;
8289 nextGameMode = gameMode;
8292 if (appData.noChessProgram) {
8293 gameMode = nextGameMode;
8295 endingGame = 0; /* [HGM] crash */
8300 /* Put first chess program into idle state */
8301 if (first.pr != NoProc &&
8302 (gameMode == MachinePlaysWhite ||
8303 gameMode == MachinePlaysBlack ||
8304 gameMode == TwoMachinesPlay ||
8305 gameMode == IcsPlayingWhite ||
8306 gameMode == IcsPlayingBlack ||
8307 gameMode == BeginningOfGame)) {
8308 SendToProgram("force\n", &first);
8309 if (first.usePing) {
8311 sprintf(buf, "ping %d\n", ++first.lastPing);
8312 SendToProgram(buf, &first);
8315 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8316 /* Kill off first chess program */
8317 if (first.isr != NULL)
8318 RemoveInputSource(first.isr);
8321 if (first.pr != NoProc) {
8323 DoSleep( appData.delayBeforeQuit );
8324 SendToProgram("quit\n", &first);
8325 DoSleep( appData.delayAfterQuit );
8326 DestroyChildProcess(first.pr, first.useSigterm);
8331 /* Put second chess program into idle state */
8332 if (second.pr != NoProc &&
8333 gameMode == TwoMachinesPlay) {
8334 SendToProgram("force\n", &second);
8335 if (second.usePing) {
8337 sprintf(buf, "ping %d\n", ++second.lastPing);
8338 SendToProgram(buf, &second);
8341 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8342 /* Kill off second chess program */
8343 if (second.isr != NULL)
8344 RemoveInputSource(second.isr);
8347 if (second.pr != NoProc) {
8348 DoSleep( appData.delayBeforeQuit );
8349 SendToProgram("quit\n", &second);
8350 DoSleep( appData.delayAfterQuit );
8351 DestroyChildProcess(second.pr, second.useSigterm);
8356 if (matchMode && gameMode == TwoMachinesPlay) {
8359 if (first.twoMachinesColor[0] == 'w') {
8366 if (first.twoMachinesColor[0] == 'b') {
8375 if (matchGame < appData.matchGames) {
8377 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8378 tmp = first.twoMachinesColor;
8379 first.twoMachinesColor = second.twoMachinesColor;
8380 second.twoMachinesColor = tmp;
8382 gameMode = nextGameMode;
8384 if(appData.matchPause>10000 || appData.matchPause<10)
8385 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8386 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8387 endingGame = 0; /* [HGM] crash */
8391 gameMode = nextGameMode;
8392 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8393 first.tidy, second.tidy,
8394 first.matchWins, second.matchWins,
8395 appData.matchGames - (first.matchWins + second.matchWins));
8396 DisplayFatalError(buf, 0, 0);
8399 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8400 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8402 gameMode = nextGameMode;
8404 endingGame = 0; /* [HGM] crash */
8407 /* Assumes program was just initialized (initString sent).
8408 Leaves program in force mode. */
8410 FeedMovesToProgram(cps, upto)
8411 ChessProgramState *cps;
8416 if (appData.debugMode)
8417 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8418 startedFromSetupPosition ? "position and " : "",
8419 backwardMostMove, upto, cps->which);
8420 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8421 // [HGM] variantswitch: make engine aware of new variant
8422 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8423 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8424 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8425 SendToProgram(buf, cps);
8426 currentlyInitializedVariant = gameInfo.variant;
8428 SendToProgram("force\n", cps);
8429 if (startedFromSetupPosition) {
8430 SendBoard(cps, backwardMostMove);
8431 if (appData.debugMode) {
8432 fprintf(debugFP, "feedMoves\n");
8435 for (i = backwardMostMove; i < upto; i++) {
8436 SendMoveToProgram(i, cps);
8442 ResurrectChessProgram()
8444 /* The chess program may have exited.
8445 If so, restart it and feed it all the moves made so far. */
8447 if (appData.noChessProgram || first.pr != NoProc) return;
8449 StartChessProgram(&first);
8450 InitChessProgram(&first, FALSE);
8451 FeedMovesToProgram(&first, currentMove);
8453 if (!first.sendTime) {
8454 /* can't tell gnuchess what its clock should read,
8455 so we bow to its notion. */
8457 timeRemaining[0][currentMove] = whiteTimeRemaining;
8458 timeRemaining[1][currentMove] = blackTimeRemaining;
8461 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8462 appData.icsEngineAnalyze) && first.analysisSupport) {
8463 SendToProgram("analyze\n", &first);
8464 first.analyzing = TRUE;
8477 if (appData.debugMode) {
8478 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8479 redraw, init, gameMode);
8481 CleanupTail(); // [HGM] vari: delete any stored variations
8482 pausing = pauseExamInvalid = FALSE;
8483 startedFromSetupPosition = blackPlaysFirst = FALSE;
8485 whiteFlag = blackFlag = FALSE;
8486 userOfferedDraw = FALSE;
8487 hintRequested = bookRequested = FALSE;
8488 first.maybeThinking = FALSE;
8489 second.maybeThinking = FALSE;
8490 first.bookSuspend = FALSE; // [HGM] book
8491 second.bookSuspend = FALSE;
8492 thinkOutput[0] = NULLCHAR;
8493 lastHint[0] = NULLCHAR;
8494 ClearGameInfo(&gameInfo);
8495 gameInfo.variant = StringToVariant(appData.variant);
8496 ics_user_moved = ics_clock_paused = FALSE;
8497 ics_getting_history = H_FALSE;
8499 white_holding[0] = black_holding[0] = NULLCHAR;
8500 ClearProgramStats();
8501 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8505 flipView = appData.flipView;
8506 ClearPremoveHighlights();
8508 alarmSounded = FALSE;
8510 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8511 if(appData.serverMovesName != NULL) {
8512 /* [HGM] prepare to make moves file for broadcasting */
8513 clock_t t = clock();
8514 if(serverMoves != NULL) fclose(serverMoves);
8515 serverMoves = fopen(appData.serverMovesName, "r");
8516 if(serverMoves != NULL) {
8517 fclose(serverMoves);
8518 /* delay 15 sec before overwriting, so all clients can see end */
8519 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8521 serverMoves = fopen(appData.serverMovesName, "w");
8525 gameMode = BeginningOfGame;
8527 if(appData.icsActive) gameInfo.variant = VariantNormal;
8528 currentMove = forwardMostMove = backwardMostMove = 0;
8529 InitPosition(redraw);
8530 for (i = 0; i < MAX_MOVES; i++) {
8531 if (commentList[i] != NULL) {
8532 free(commentList[i]);
8533 commentList[i] = NULL;
8537 timeRemaining[0][0] = whiteTimeRemaining;
8538 timeRemaining[1][0] = blackTimeRemaining;
8539 if (first.pr == NULL) {
8540 StartChessProgram(&first);
8543 InitChessProgram(&first, startedFromSetupPosition);
8546 DisplayMessage("", "");
8547 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8548 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8555 if (!AutoPlayOneMove())
8557 if (matchMode || appData.timeDelay == 0)
8559 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8561 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8570 int fromX, fromY, toX, toY;
8572 if (appData.debugMode) {
8573 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8576 if (gameMode != PlayFromGameFile)
8579 if (currentMove >= forwardMostMove) {
8580 gameMode = EditGame;
8583 /* [AS] Clear current move marker at the end of a game */
8584 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8589 toX = moveList[currentMove][2] - AAA;
8590 toY = moveList[currentMove][3] - ONE;
8592 if (moveList[currentMove][1] == '@') {
8593 if (appData.highlightLastMove) {
8594 SetHighlights(-1, -1, toX, toY);
8597 fromX = moveList[currentMove][0] - AAA;
8598 fromY = moveList[currentMove][1] - ONE;
8600 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8602 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8604 if (appData.highlightLastMove) {
8605 SetHighlights(fromX, fromY, toX, toY);
8608 DisplayMove(currentMove);
8609 SendMoveToProgram(currentMove++, &first);
8610 DisplayBothClocks();
8611 DrawPosition(FALSE, boards[currentMove]);
8612 // [HGM] PV info: always display, routine tests if empty
8613 DisplayComment(currentMove - 1, commentList[currentMove]);
8619 LoadGameOneMove(readAhead)
8620 ChessMove readAhead;
8622 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8623 char promoChar = NULLCHAR;
8628 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8629 gameMode != AnalyzeMode && gameMode != Training) {
8634 yyboardindex = forwardMostMove;
8635 if (readAhead != (ChessMove)0) {
8636 moveType = readAhead;
8638 if (gameFileFP == NULL)
8640 moveType = (ChessMove) yylex();
8646 if (appData.debugMode)
8647 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8650 /* append the comment but don't display it */
8651 AppendComment(currentMove, p, FALSE);
8654 case WhiteCapturesEnPassant:
8655 case BlackCapturesEnPassant:
8656 case WhitePromotionChancellor:
8657 case BlackPromotionChancellor:
8658 case WhitePromotionArchbishop:
8659 case BlackPromotionArchbishop:
8660 case WhitePromotionCentaur:
8661 case BlackPromotionCentaur:
8662 case WhitePromotionQueen:
8663 case BlackPromotionQueen:
8664 case WhitePromotionRook:
8665 case BlackPromotionRook:
8666 case WhitePromotionBishop:
8667 case BlackPromotionBishop:
8668 case WhitePromotionKnight:
8669 case BlackPromotionKnight:
8670 case WhitePromotionKing:
8671 case BlackPromotionKing:
8673 case WhiteKingSideCastle:
8674 case WhiteQueenSideCastle:
8675 case BlackKingSideCastle:
8676 case BlackQueenSideCastle:
8677 case WhiteKingSideCastleWild:
8678 case WhiteQueenSideCastleWild:
8679 case BlackKingSideCastleWild:
8680 case BlackQueenSideCastleWild:
8682 case WhiteHSideCastleFR:
8683 case WhiteASideCastleFR:
8684 case BlackHSideCastleFR:
8685 case BlackASideCastleFR:
8687 if (appData.debugMode)
8688 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8689 fromX = currentMoveString[0] - AAA;
8690 fromY = currentMoveString[1] - ONE;
8691 toX = currentMoveString[2] - AAA;
8692 toY = currentMoveString[3] - ONE;
8693 promoChar = currentMoveString[4];
8698 if (appData.debugMode)
8699 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8700 fromX = moveType == WhiteDrop ?
8701 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8702 (int) CharToPiece(ToLower(currentMoveString[0]));
8704 toX = currentMoveString[2] - AAA;
8705 toY = currentMoveString[3] - ONE;
8711 case GameUnfinished:
8712 if (appData.debugMode)
8713 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8714 p = strchr(yy_text, '{');
8715 if (p == NULL) p = strchr(yy_text, '(');
8718 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8720 q = strchr(p, *p == '{' ? '}' : ')');
8721 if (q != NULL) *q = NULLCHAR;
8724 GameEnds(moveType, p, GE_FILE);
8726 if (cmailMsgLoaded) {
8728 flipView = WhiteOnMove(currentMove);
8729 if (moveType == GameUnfinished) flipView = !flipView;
8730 if (appData.debugMode)
8731 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8735 case (ChessMove) 0: /* end of file */
8736 if (appData.debugMode)
8737 fprintf(debugFP, "Parser hit end of file\n");
8738 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8744 if (WhiteOnMove(currentMove)) {
8745 GameEnds(BlackWins, "Black mates", GE_FILE);
8747 GameEnds(WhiteWins, "White mates", GE_FILE);
8751 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8758 if (lastLoadGameStart == GNUChessGame) {
8759 /* GNUChessGames have numbers, but they aren't move numbers */
8760 if (appData.debugMode)
8761 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8762 yy_text, (int) moveType);
8763 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8765 /* else fall thru */
8770 /* Reached start of next game in file */
8771 if (appData.debugMode)
8772 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8773 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8779 if (WhiteOnMove(currentMove)) {
8780 GameEnds(BlackWins, "Black mates", GE_FILE);
8782 GameEnds(WhiteWins, "White mates", GE_FILE);
8786 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8792 case PositionDiagram: /* should not happen; ignore */
8793 case ElapsedTime: /* ignore */
8794 case NAG: /* ignore */
8795 if (appData.debugMode)
8796 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8797 yy_text, (int) moveType);
8798 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8801 if (appData.testLegality) {
8802 if (appData.debugMode)
8803 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8804 sprintf(move, _("Illegal move: %d.%s%s"),
8805 (forwardMostMove / 2) + 1,
8806 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8807 DisplayError(move, 0);
8810 if (appData.debugMode)
8811 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8812 yy_text, currentMoveString);
8813 fromX = currentMoveString[0] - AAA;
8814 fromY = currentMoveString[1] - ONE;
8815 toX = currentMoveString[2] - AAA;
8816 toY = currentMoveString[3] - ONE;
8817 promoChar = currentMoveString[4];
8822 if (appData.debugMode)
8823 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8824 sprintf(move, _("Ambiguous move: %d.%s%s"),
8825 (forwardMostMove / 2) + 1,
8826 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8827 DisplayError(move, 0);
8832 case ImpossibleMove:
8833 if (appData.debugMode)
8834 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8835 sprintf(move, _("Illegal move: %d.%s%s"),
8836 (forwardMostMove / 2) + 1,
8837 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8838 DisplayError(move, 0);
8844 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8845 DrawPosition(FALSE, boards[currentMove]);
8846 DisplayBothClocks();
8847 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8848 DisplayComment(currentMove - 1, commentList[currentMove]);
8850 (void) StopLoadGameTimer();
8852 cmailOldMove = forwardMostMove;
8855 /* currentMoveString is set as a side-effect of yylex */
8856 strcat(currentMoveString, "\n");
8857 strcpy(moveList[forwardMostMove], currentMoveString);
8859 thinkOutput[0] = NULLCHAR;
8860 MakeMove(fromX, fromY, toX, toY, promoChar);
8861 currentMove = forwardMostMove;
8866 /* Load the nth game from the given file */
8868 LoadGameFromFile(filename, n, title, useList)
8872 /*Boolean*/ int useList;
8877 if (strcmp(filename, "-") == 0) {
8881 f = fopen(filename, "rb");
8883 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8884 DisplayError(buf, errno);
8888 if (fseek(f, 0, 0) == -1) {
8889 /* f is not seekable; probably a pipe */
8892 if (useList && n == 0) {
8893 int error = GameListBuild(f);
8895 DisplayError(_("Cannot build game list"), error);
8896 } else if (!ListEmpty(&gameList) &&
8897 ((ListGame *) gameList.tailPred)->number > 1) {
8898 GameListPopUp(f, title);
8905 return LoadGame(f, n, title, FALSE);
8910 MakeRegisteredMove()
8912 int fromX, fromY, toX, toY;
8914 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8915 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8918 if (appData.debugMode)
8919 fprintf(debugFP, "Restoring %s for game %d\n",
8920 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8922 thinkOutput[0] = NULLCHAR;
8923 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8924 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8925 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8926 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8927 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8928 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8929 MakeMove(fromX, fromY, toX, toY, promoChar);
8930 ShowMove(fromX, fromY, toX, toY);
8932 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8939 if (WhiteOnMove(currentMove)) {
8940 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8942 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8947 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8954 if (WhiteOnMove(currentMove)) {
8955 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8957 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8962 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8973 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8975 CmailLoadGame(f, gameNumber, title, useList)
8983 if (gameNumber > nCmailGames) {
8984 DisplayError(_("No more games in this message"), 0);
8987 if (f == lastLoadGameFP) {
8988 int offset = gameNumber - lastLoadGameNumber;
8990 cmailMsg[0] = NULLCHAR;
8991 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8992 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8993 nCmailMovesRegistered--;
8995 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8996 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8997 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9000 if (! RegisterMove()) return FALSE;
9004 retVal = LoadGame(f, gameNumber, title, useList);
9006 /* Make move registered during previous look at this game, if any */
9007 MakeRegisteredMove();
9009 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9010 commentList[currentMove]
9011 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9012 DisplayComment(currentMove - 1, commentList[currentMove]);
9018 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9023 int gameNumber = lastLoadGameNumber + offset;
9024 if (lastLoadGameFP == NULL) {
9025 DisplayError(_("No game has been loaded yet"), 0);
9028 if (gameNumber <= 0) {
9029 DisplayError(_("Can't back up any further"), 0);
9032 if (cmailMsgLoaded) {
9033 return CmailLoadGame(lastLoadGameFP, gameNumber,
9034 lastLoadGameTitle, lastLoadGameUseList);
9036 return LoadGame(lastLoadGameFP, gameNumber,
9037 lastLoadGameTitle, lastLoadGameUseList);
9043 /* Load the nth game from open file f */
9045 LoadGame(f, gameNumber, title, useList)
9053 int gn = gameNumber;
9054 ListGame *lg = NULL;
9057 GameMode oldGameMode;
9058 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9060 if (appData.debugMode)
9061 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9063 if (gameMode == Training )
9064 SetTrainingModeOff();
9066 oldGameMode = gameMode;
9067 if (gameMode != BeginningOfGame) {
9072 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9073 fclose(lastLoadGameFP);
9077 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9080 fseek(f, lg->offset, 0);
9081 GameListHighlight(gameNumber);
9085 DisplayError(_("Game number out of range"), 0);
9090 if (fseek(f, 0, 0) == -1) {
9091 if (f == lastLoadGameFP ?
9092 gameNumber == lastLoadGameNumber + 1 :
9096 DisplayError(_("Can't seek on game file"), 0);
9102 lastLoadGameNumber = gameNumber;
9103 strcpy(lastLoadGameTitle, title);
9104 lastLoadGameUseList = useList;
9108 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9109 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9110 lg->gameInfo.black);
9112 } else if (*title != NULLCHAR) {
9113 if (gameNumber > 1) {
9114 sprintf(buf, "%s %d", title, gameNumber);
9117 DisplayTitle(title);
9121 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9122 gameMode = PlayFromGameFile;
9126 currentMove = forwardMostMove = backwardMostMove = 0;
9127 CopyBoard(boards[0], initialPosition);
9131 * Skip the first gn-1 games in the file.
9132 * Also skip over anything that precedes an identifiable
9133 * start of game marker, to avoid being confused by
9134 * garbage at the start of the file. Currently
9135 * recognized start of game markers are the move number "1",
9136 * the pattern "gnuchess .* game", the pattern
9137 * "^[#;%] [^ ]* game file", and a PGN tag block.
9138 * A game that starts with one of the latter two patterns
9139 * will also have a move number 1, possibly
9140 * following a position diagram.
9141 * 5-4-02: Let's try being more lenient and allowing a game to
9142 * start with an unnumbered move. Does that break anything?
9144 cm = lastLoadGameStart = (ChessMove) 0;
9146 yyboardindex = forwardMostMove;
9147 cm = (ChessMove) yylex();
9150 if (cmailMsgLoaded) {
9151 nCmailGames = CMAIL_MAX_GAMES - gn;
9154 DisplayError(_("Game not found in file"), 0);
9161 lastLoadGameStart = cm;
9165 switch (lastLoadGameStart) {
9172 gn--; /* count this game */
9173 lastLoadGameStart = cm;
9182 switch (lastLoadGameStart) {
9187 gn--; /* count this game */
9188 lastLoadGameStart = cm;
9191 lastLoadGameStart = cm; /* game counted already */
9199 yyboardindex = forwardMostMove;
9200 cm = (ChessMove) yylex();
9201 } while (cm == PGNTag || cm == Comment);
9208 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9209 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9210 != CMAIL_OLD_RESULT) {
9212 cmailResult[ CMAIL_MAX_GAMES
9213 - gn - 1] = CMAIL_OLD_RESULT;
9219 /* Only a NormalMove can be at the start of a game
9220 * without a position diagram. */
9221 if (lastLoadGameStart == (ChessMove) 0) {
9223 lastLoadGameStart = MoveNumberOne;
9232 if (appData.debugMode)
9233 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9235 if (cm == XBoardGame) {
9236 /* Skip any header junk before position diagram and/or move 1 */
9238 yyboardindex = forwardMostMove;
9239 cm = (ChessMove) yylex();
9241 if (cm == (ChessMove) 0 ||
9242 cm == GNUChessGame || cm == XBoardGame) {
9243 /* Empty game; pretend end-of-file and handle later */
9248 if (cm == MoveNumberOne || cm == PositionDiagram ||
9249 cm == PGNTag || cm == Comment)
9252 } else if (cm == GNUChessGame) {
9253 if (gameInfo.event != NULL) {
9254 free(gameInfo.event);
9256 gameInfo.event = StrSave(yy_text);
9259 startedFromSetupPosition = FALSE;
9260 while (cm == PGNTag) {
9261 if (appData.debugMode)
9262 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9263 err = ParsePGNTag(yy_text, &gameInfo);
9264 if (!err) numPGNTags++;
9266 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9267 if(gameInfo.variant != oldVariant) {
9268 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9270 oldVariant = gameInfo.variant;
9271 if (appData.debugMode)
9272 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9276 if (gameInfo.fen != NULL) {
9277 Board initial_position;
9278 startedFromSetupPosition = TRUE;
9279 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9281 DisplayError(_("Bad FEN position in file"), 0);
9284 CopyBoard(boards[0], initial_position);
9285 if (blackPlaysFirst) {
9286 currentMove = forwardMostMove = backwardMostMove = 1;
9287 CopyBoard(boards[1], initial_position);
9288 strcpy(moveList[0], "");
9289 strcpy(parseList[0], "");
9290 timeRemaining[0][1] = whiteTimeRemaining;
9291 timeRemaining[1][1] = blackTimeRemaining;
9292 if (commentList[0] != NULL) {
9293 commentList[1] = commentList[0];
9294 commentList[0] = NULL;
9297 currentMove = forwardMostMove = backwardMostMove = 0;
9299 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9301 initialRulePlies = FENrulePlies;
9302 for( i=0; i< nrCastlingRights; i++ )
9303 initialRights[i] = initial_position[CASTLING][i];
9305 yyboardindex = forwardMostMove;
9307 gameInfo.fen = NULL;
9310 yyboardindex = forwardMostMove;
9311 cm = (ChessMove) yylex();
9313 /* Handle comments interspersed among the tags */
9314 while (cm == Comment) {
9316 if (appData.debugMode)
9317 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9319 AppendComment(currentMove, p, FALSE);
9320 yyboardindex = forwardMostMove;
9321 cm = (ChessMove) yylex();
9325 /* don't rely on existence of Event tag since if game was
9326 * pasted from clipboard the Event tag may not exist
9328 if (numPGNTags > 0){
9330 if (gameInfo.variant == VariantNormal) {
9331 gameInfo.variant = StringToVariant(gameInfo.event);
9334 if( appData.autoDisplayTags ) {
9335 tags = PGNTags(&gameInfo);
9336 TagsPopUp(tags, CmailMsg());
9341 /* Make something up, but don't display it now */
9346 if (cm == PositionDiagram) {
9349 Board initial_position;
9351 if (appData.debugMode)
9352 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9354 if (!startedFromSetupPosition) {
9356 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9357 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9367 initial_position[i][j++] = CharToPiece(*p);
9370 while (*p == ' ' || *p == '\t' ||
9371 *p == '\n' || *p == '\r') p++;
9373 if (strncmp(p, "black", strlen("black"))==0)
9374 blackPlaysFirst = TRUE;
9376 blackPlaysFirst = FALSE;
9377 startedFromSetupPosition = TRUE;
9379 CopyBoard(boards[0], initial_position);
9380 if (blackPlaysFirst) {
9381 currentMove = forwardMostMove = backwardMostMove = 1;
9382 CopyBoard(boards[1], initial_position);
9383 strcpy(moveList[0], "");
9384 strcpy(parseList[0], "");
9385 timeRemaining[0][1] = whiteTimeRemaining;
9386 timeRemaining[1][1] = blackTimeRemaining;
9387 if (commentList[0] != NULL) {
9388 commentList[1] = commentList[0];
9389 commentList[0] = NULL;
9392 currentMove = forwardMostMove = backwardMostMove = 0;
9395 yyboardindex = forwardMostMove;
9396 cm = (ChessMove) yylex();
9399 if (first.pr == NoProc) {
9400 StartChessProgram(&first);
9402 InitChessProgram(&first, FALSE);
9403 SendToProgram("force\n", &first);
9404 if (startedFromSetupPosition) {
9405 SendBoard(&first, forwardMostMove);
9406 if (appData.debugMode) {
9407 fprintf(debugFP, "Load Game\n");
9409 DisplayBothClocks();
9412 /* [HGM] server: flag to write setup moves in broadcast file as one */
9413 loadFlag = appData.suppressLoadMoves;
9415 while (cm == Comment) {
9417 if (appData.debugMode)
9418 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9420 AppendComment(currentMove, p, FALSE);
9421 yyboardindex = forwardMostMove;
9422 cm = (ChessMove) yylex();
9425 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9426 cm == WhiteWins || cm == BlackWins ||
9427 cm == GameIsDrawn || cm == GameUnfinished) {
9428 DisplayMessage("", _("No moves in game"));
9429 if (cmailMsgLoaded) {
9430 if (appData.debugMode)
9431 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9435 DrawPosition(FALSE, boards[currentMove]);
9436 DisplayBothClocks();
9437 gameMode = EditGame;
9444 // [HGM] PV info: routine tests if comment empty
9445 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9446 DisplayComment(currentMove - 1, commentList[currentMove]);
9448 if (!matchMode && appData.timeDelay != 0)
9449 DrawPosition(FALSE, boards[currentMove]);
9451 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9452 programStats.ok_to_send = 1;
9455 /* if the first token after the PGN tags is a move
9456 * and not move number 1, retrieve it from the parser
9458 if (cm != MoveNumberOne)
9459 LoadGameOneMove(cm);
9461 /* load the remaining moves from the file */
9462 while (LoadGameOneMove((ChessMove)0)) {
9463 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9464 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9467 /* rewind to the start of the game */
9468 currentMove = backwardMostMove;
9470 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9472 if (oldGameMode == AnalyzeFile ||
9473 oldGameMode == AnalyzeMode) {
9477 if (matchMode || appData.timeDelay == 0) {
9479 gameMode = EditGame;
9481 } else if (appData.timeDelay > 0) {
9485 if (appData.debugMode)
9486 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9488 loadFlag = 0; /* [HGM] true game starts */
9492 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9494 ReloadPosition(offset)
9497 int positionNumber = lastLoadPositionNumber + offset;
9498 if (lastLoadPositionFP == NULL) {
9499 DisplayError(_("No position has been loaded yet"), 0);
9502 if (positionNumber <= 0) {
9503 DisplayError(_("Can't back up any further"), 0);
9506 return LoadPosition(lastLoadPositionFP, positionNumber,
9507 lastLoadPositionTitle);
9510 /* Load the nth position from the given file */
9512 LoadPositionFromFile(filename, n, title)
9520 if (strcmp(filename, "-") == 0) {
9521 return LoadPosition(stdin, n, "stdin");
9523 f = fopen(filename, "rb");
9525 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9526 DisplayError(buf, errno);
9529 return LoadPosition(f, n, title);
9534 /* Load the nth position from the given open file, and close it */
9536 LoadPosition(f, positionNumber, title)
9541 char *p, line[MSG_SIZ];
9542 Board initial_position;
9543 int i, j, fenMode, pn;
9545 if (gameMode == Training )
9546 SetTrainingModeOff();
9548 if (gameMode != BeginningOfGame) {
9551 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9552 fclose(lastLoadPositionFP);
9554 if (positionNumber == 0) positionNumber = 1;
9555 lastLoadPositionFP = f;
9556 lastLoadPositionNumber = positionNumber;
9557 strcpy(lastLoadPositionTitle, title);
9558 if (first.pr == NoProc) {
9559 StartChessProgram(&first);
9560 InitChessProgram(&first, FALSE);
9562 pn = positionNumber;
9563 if (positionNumber < 0) {
9564 /* Negative position number means to seek to that byte offset */
9565 if (fseek(f, -positionNumber, 0) == -1) {
9566 DisplayError(_("Can't seek on position file"), 0);
9571 if (fseek(f, 0, 0) == -1) {
9572 if (f == lastLoadPositionFP ?
9573 positionNumber == lastLoadPositionNumber + 1 :
9574 positionNumber == 1) {
9577 DisplayError(_("Can't seek on position file"), 0);
9582 /* See if this file is FEN or old-style xboard */
9583 if (fgets(line, MSG_SIZ, f) == NULL) {
9584 DisplayError(_("Position not found in file"), 0);
9587 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9588 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9591 if (fenMode || line[0] == '#') pn--;
9593 /* skip positions before number pn */
9594 if (fgets(line, MSG_SIZ, f) == NULL) {
9596 DisplayError(_("Position not found in file"), 0);
9599 if (fenMode || line[0] == '#') pn--;
9604 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9605 DisplayError(_("Bad FEN position in file"), 0);
9609 (void) fgets(line, MSG_SIZ, f);
9610 (void) fgets(line, MSG_SIZ, f);
9612 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9613 (void) fgets(line, MSG_SIZ, f);
9614 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9617 initial_position[i][j++] = CharToPiece(*p);
9621 blackPlaysFirst = FALSE;
9623 (void) fgets(line, MSG_SIZ, f);
9624 if (strncmp(line, "black", strlen("black"))==0)
9625 blackPlaysFirst = TRUE;
9628 startedFromSetupPosition = TRUE;
9630 SendToProgram("force\n", &first);
9631 CopyBoard(boards[0], initial_position);
9632 if (blackPlaysFirst) {
9633 currentMove = forwardMostMove = backwardMostMove = 1;
9634 strcpy(moveList[0], "");
9635 strcpy(parseList[0], "");
9636 CopyBoard(boards[1], initial_position);
9637 DisplayMessage("", _("Black to play"));
9639 currentMove = forwardMostMove = backwardMostMove = 0;
9640 DisplayMessage("", _("White to play"));
9642 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9643 SendBoard(&first, forwardMostMove);
9644 if (appData.debugMode) {
9646 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9647 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9648 fprintf(debugFP, "Load Position\n");
9651 if (positionNumber > 1) {
9652 sprintf(line, "%s %d", title, positionNumber);
9655 DisplayTitle(title);
9657 gameMode = EditGame;
9660 timeRemaining[0][1] = whiteTimeRemaining;
9661 timeRemaining[1][1] = blackTimeRemaining;
9662 DrawPosition(FALSE, boards[currentMove]);
9669 CopyPlayerNameIntoFileName(dest, src)
9672 while (*src != NULLCHAR && *src != ',') {
9677 *(*dest)++ = *src++;
9682 char *DefaultFileName(ext)
9685 static char def[MSG_SIZ];
9688 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9690 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9692 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9701 /* Save the current game to the given file */
9703 SaveGameToFile(filename, append)
9710 if (strcmp(filename, "-") == 0) {
9711 return SaveGame(stdout, 0, NULL);
9713 f = fopen(filename, append ? "a" : "w");
9715 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9716 DisplayError(buf, errno);
9719 return SaveGame(f, 0, NULL);
9728 static char buf[MSG_SIZ];
9731 p = strchr(str, ' ');
9732 if (p == NULL) return str;
9733 strncpy(buf, str, p - str);
9734 buf[p - str] = NULLCHAR;
9738 #define PGN_MAX_LINE 75
9740 #define PGN_SIDE_WHITE 0
9741 #define PGN_SIDE_BLACK 1
9744 static int FindFirstMoveOutOfBook( int side )
9748 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9749 int index = backwardMostMove;
9750 int has_book_hit = 0;
9752 if( (index % 2) != side ) {
9756 while( index < forwardMostMove ) {
9757 /* Check to see if engine is in book */
9758 int depth = pvInfoList[index].depth;
9759 int score = pvInfoList[index].score;
9765 else if( score == 0 && depth == 63 ) {
9766 in_book = 1; /* Zappa */
9768 else if( score == 2 && depth == 99 ) {
9769 in_book = 1; /* Abrok */
9772 has_book_hit += in_book;
9788 void GetOutOfBookInfo( char * buf )
9792 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9794 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9795 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9799 if( oob[0] >= 0 || oob[1] >= 0 ) {
9800 for( i=0; i<2; i++ ) {
9804 if( i > 0 && oob[0] >= 0 ) {
9808 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9809 sprintf( buf+strlen(buf), "%s%.2f",
9810 pvInfoList[idx].score >= 0 ? "+" : "",
9811 pvInfoList[idx].score / 100.0 );
9817 /* Save game in PGN style and close the file */
9822 int i, offset, linelen, newblock;
9826 int movelen, numlen, blank;
9827 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9829 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9831 tm = time((time_t *) NULL);
9833 PrintPGNTags(f, &gameInfo);
9835 if (backwardMostMove > 0 || startedFromSetupPosition) {
9836 char *fen = PositionToFEN(backwardMostMove, NULL);
9837 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9838 fprintf(f, "\n{--------------\n");
9839 PrintPosition(f, backwardMostMove);
9840 fprintf(f, "--------------}\n");
9844 /* [AS] Out of book annotation */
9845 if( appData.saveOutOfBookInfo ) {
9848 GetOutOfBookInfo( buf );
9850 if( buf[0] != '\0' ) {
9851 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9858 i = backwardMostMove;
9862 while (i < forwardMostMove) {
9863 /* Print comments preceding this move */
9864 if (commentList[i] != NULL) {
9865 if (linelen > 0) fprintf(f, "\n");
9866 fprintf(f, "%s\n", commentList[i]);
9871 /* Format move number */
9873 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9876 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9878 numtext[0] = NULLCHAR;
9881 numlen = strlen(numtext);
9884 /* Print move number */
9885 blank = linelen > 0 && numlen > 0;
9886 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9895 fprintf(f, "%s", numtext);
9899 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9900 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9903 blank = linelen > 0 && movelen > 0;
9904 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9913 fprintf(f, "%s", move_buffer);
9916 /* [AS] Add PV info if present */
9917 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9918 /* [HGM] add time */
9919 char buf[MSG_SIZ]; int seconds = 0;
9921 if(i >= backwardMostMove) {
9923 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9924 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9926 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9927 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9929 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9931 if( seconds <= 0) buf[0] = 0; else
9932 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9933 seconds = (seconds + 4)/10; // round to full seconds
9934 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9935 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9938 sprintf( move_buffer, "{%s%.2f/%d%s}",
9939 pvInfoList[i].score >= 0 ? "+" : "",
9940 pvInfoList[i].score / 100.0,
9941 pvInfoList[i].depth,
9944 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9946 /* Print score/depth */
9947 blank = linelen > 0 && movelen > 0;
9948 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9957 fprintf(f, "%s", move_buffer);
9964 /* Start a new line */
9965 if (linelen > 0) fprintf(f, "\n");
9967 /* Print comments after last move */
9968 if (commentList[i] != NULL) {
9969 fprintf(f, "%s\n", commentList[i]);
9973 if (gameInfo.resultDetails != NULL &&
9974 gameInfo.resultDetails[0] != NULLCHAR) {
9975 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9976 PGNResult(gameInfo.result));
9978 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9982 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9986 /* Save game in old style and close the file */
9994 tm = time((time_t *) NULL);
9996 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9999 if (backwardMostMove > 0 || startedFromSetupPosition) {
10000 fprintf(f, "\n[--------------\n");
10001 PrintPosition(f, backwardMostMove);
10002 fprintf(f, "--------------]\n");
10007 i = backwardMostMove;
10008 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10010 while (i < forwardMostMove) {
10011 if (commentList[i] != NULL) {
10012 fprintf(f, "[%s]\n", commentList[i]);
10015 if ((i % 2) == 1) {
10016 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10019 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10021 if (commentList[i] != NULL) {
10025 if (i >= forwardMostMove) {
10029 fprintf(f, "%s\n", parseList[i]);
10034 if (commentList[i] != NULL) {
10035 fprintf(f, "[%s]\n", commentList[i]);
10038 /* This isn't really the old style, but it's close enough */
10039 if (gameInfo.resultDetails != NULL &&
10040 gameInfo.resultDetails[0] != NULLCHAR) {
10041 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10042 gameInfo.resultDetails);
10044 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10051 /* Save the current game to open file f and close the file */
10053 SaveGame(f, dummy, dummy2)
10058 if (gameMode == EditPosition) EditPositionDone(TRUE);
10059 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10060 if (appData.oldSaveStyle)
10061 return SaveGameOldStyle(f);
10063 return SaveGamePGN(f);
10066 /* Save the current position to the given file */
10068 SavePositionToFile(filename)
10074 if (strcmp(filename, "-") == 0) {
10075 return SavePosition(stdout, 0, NULL);
10077 f = fopen(filename, "a");
10079 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10080 DisplayError(buf, errno);
10083 SavePosition(f, 0, NULL);
10089 /* Save the current position to the given open file and close the file */
10091 SavePosition(f, dummy, dummy2)
10099 if (appData.oldSaveStyle) {
10100 tm = time((time_t *) NULL);
10102 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10104 fprintf(f, "[--------------\n");
10105 PrintPosition(f, currentMove);
10106 fprintf(f, "--------------]\n");
10108 fen = PositionToFEN(currentMove, NULL);
10109 fprintf(f, "%s\n", fen);
10117 ReloadCmailMsgEvent(unregister)
10121 static char *inFilename = NULL;
10122 static char *outFilename;
10124 struct stat inbuf, outbuf;
10127 /* Any registered moves are unregistered if unregister is set, */
10128 /* i.e. invoked by the signal handler */
10130 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10131 cmailMoveRegistered[i] = FALSE;
10132 if (cmailCommentList[i] != NULL) {
10133 free(cmailCommentList[i]);
10134 cmailCommentList[i] = NULL;
10137 nCmailMovesRegistered = 0;
10140 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10141 cmailResult[i] = CMAIL_NOT_RESULT;
10145 if (inFilename == NULL) {
10146 /* Because the filenames are static they only get malloced once */
10147 /* and they never get freed */
10148 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10149 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10151 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10152 sprintf(outFilename, "%s.out", appData.cmailGameName);
10155 status = stat(outFilename, &outbuf);
10157 cmailMailedMove = FALSE;
10159 status = stat(inFilename, &inbuf);
10160 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10163 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10164 counts the games, notes how each one terminated, etc.
10166 It would be nice to remove this kludge and instead gather all
10167 the information while building the game list. (And to keep it
10168 in the game list nodes instead of having a bunch of fixed-size
10169 parallel arrays.) Note this will require getting each game's
10170 termination from the PGN tags, as the game list builder does
10171 not process the game moves. --mann
10173 cmailMsgLoaded = TRUE;
10174 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10176 /* Load first game in the file or popup game menu */
10177 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10179 #endif /* !WIN32 */
10187 char string[MSG_SIZ];
10189 if ( cmailMailedMove
10190 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10191 return TRUE; /* Allow free viewing */
10194 /* Unregister move to ensure that we don't leave RegisterMove */
10195 /* with the move registered when the conditions for registering no */
10197 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10198 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10199 nCmailMovesRegistered --;
10201 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10203 free(cmailCommentList[lastLoadGameNumber - 1]);
10204 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10208 if (cmailOldMove == -1) {
10209 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10213 if (currentMove > cmailOldMove + 1) {
10214 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10218 if (currentMove < cmailOldMove) {
10219 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10223 if (forwardMostMove > currentMove) {
10224 /* Silently truncate extra moves */
10228 if ( (currentMove == cmailOldMove + 1)
10229 || ( (currentMove == cmailOldMove)
10230 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10231 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10232 if (gameInfo.result != GameUnfinished) {
10233 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10236 if (commentList[currentMove] != NULL) {
10237 cmailCommentList[lastLoadGameNumber - 1]
10238 = StrSave(commentList[currentMove]);
10240 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10242 if (appData.debugMode)
10243 fprintf(debugFP, "Saving %s for game %d\n",
10244 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10247 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10249 f = fopen(string, "w");
10250 if (appData.oldSaveStyle) {
10251 SaveGameOldStyle(f); /* also closes the file */
10253 sprintf(string, "%s.pos.out", appData.cmailGameName);
10254 f = fopen(string, "w");
10255 SavePosition(f, 0, NULL); /* also closes the file */
10257 fprintf(f, "{--------------\n");
10258 PrintPosition(f, currentMove);
10259 fprintf(f, "--------------}\n\n");
10261 SaveGame(f, 0, NULL); /* also closes the file*/
10264 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10265 nCmailMovesRegistered ++;
10266 } else if (nCmailGames == 1) {
10267 DisplayError(_("You have not made a move yet"), 0);
10278 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10279 FILE *commandOutput;
10280 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10281 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10287 if (! cmailMsgLoaded) {
10288 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10292 if (nCmailGames == nCmailResults) {
10293 DisplayError(_("No unfinished games"), 0);
10297 #if CMAIL_PROHIBIT_REMAIL
10298 if (cmailMailedMove) {
10299 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);
10300 DisplayError(msg, 0);
10305 if (! (cmailMailedMove || RegisterMove())) return;
10307 if ( cmailMailedMove
10308 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10309 sprintf(string, partCommandString,
10310 appData.debugMode ? " -v" : "", appData.cmailGameName);
10311 commandOutput = popen(string, "r");
10313 if (commandOutput == NULL) {
10314 DisplayError(_("Failed to invoke cmail"), 0);
10316 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10317 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10319 if (nBuffers > 1) {
10320 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10321 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10322 nBytes = MSG_SIZ - 1;
10324 (void) memcpy(msg, buffer, nBytes);
10326 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10328 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10329 cmailMailedMove = TRUE; /* Prevent >1 moves */
10332 for (i = 0; i < nCmailGames; i ++) {
10333 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10338 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10340 sprintf(buffer, "%s/%s.%s.archive",
10342 appData.cmailGameName,
10344 LoadGameFromFile(buffer, 1, buffer, FALSE);
10345 cmailMsgLoaded = FALSE;
10349 DisplayInformation(msg);
10350 pclose(commandOutput);
10353 if ((*cmailMsg) != '\0') {
10354 DisplayInformation(cmailMsg);
10359 #endif /* !WIN32 */
10368 int prependComma = 0;
10370 char string[MSG_SIZ]; /* Space for game-list */
10373 if (!cmailMsgLoaded) return "";
10375 if (cmailMailedMove) {
10376 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10378 /* Create a list of games left */
10379 sprintf(string, "[");
10380 for (i = 0; i < nCmailGames; i ++) {
10381 if (! ( cmailMoveRegistered[i]
10382 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10383 if (prependComma) {
10384 sprintf(number, ",%d", i + 1);
10386 sprintf(number, "%d", i + 1);
10390 strcat(string, number);
10393 strcat(string, "]");
10395 if (nCmailMovesRegistered + nCmailResults == 0) {
10396 switch (nCmailGames) {
10399 _("Still need to make move for game\n"));
10404 _("Still need to make moves for both games\n"));
10409 _("Still need to make moves for all %d games\n"),
10414 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10417 _("Still need to make a move for game %s\n"),
10422 if (nCmailResults == nCmailGames) {
10423 sprintf(cmailMsg, _("No unfinished games\n"));
10425 sprintf(cmailMsg, _("Ready to send mail\n"));
10431 _("Still need to make moves for games %s\n"),
10443 if (gameMode == Training)
10444 SetTrainingModeOff();
10447 cmailMsgLoaded = FALSE;
10448 if (appData.icsActive) {
10449 SendToICS(ics_prefix);
10450 SendToICS("refresh\n");
10460 /* Give up on clean exit */
10464 /* Keep trying for clean exit */
10468 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10470 if (telnetISR != NULL) {
10471 RemoveInputSource(telnetISR);
10473 if (icsPR != NoProc) {
10474 DestroyChildProcess(icsPR, TRUE);
10477 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10478 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10480 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10481 /* make sure this other one finishes before killing it! */
10482 if(endingGame) { int count = 0;
10483 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10484 while(endingGame && count++ < 10) DoSleep(1);
10485 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10488 /* Kill off chess programs */
10489 if (first.pr != NoProc) {
10492 DoSleep( appData.delayBeforeQuit );
10493 SendToProgram("quit\n", &first);
10494 DoSleep( appData.delayAfterQuit );
10495 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10497 if (second.pr != NoProc) {
10498 DoSleep( appData.delayBeforeQuit );
10499 SendToProgram("quit\n", &second);
10500 DoSleep( appData.delayAfterQuit );
10501 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10503 if (first.isr != NULL) {
10504 RemoveInputSource(first.isr);
10506 if (second.isr != NULL) {
10507 RemoveInputSource(second.isr);
10510 ShutDownFrontEnd();
10517 if (appData.debugMode)
10518 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10522 if (gameMode == MachinePlaysWhite ||
10523 gameMode == MachinePlaysBlack) {
10526 DisplayBothClocks();
10528 if (gameMode == PlayFromGameFile) {
10529 if (appData.timeDelay >= 0)
10530 AutoPlayGameLoop();
10531 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10532 Reset(FALSE, TRUE);
10533 SendToICS(ics_prefix);
10534 SendToICS("refresh\n");
10535 } else if (currentMove < forwardMostMove) {
10536 ForwardInner(forwardMostMove);
10538 pauseExamInvalid = FALSE;
10540 switch (gameMode) {
10544 pauseExamForwardMostMove = forwardMostMove;
10545 pauseExamInvalid = FALSE;
10548 case IcsPlayingWhite:
10549 case IcsPlayingBlack:
10553 case PlayFromGameFile:
10554 (void) StopLoadGameTimer();
10558 case BeginningOfGame:
10559 if (appData.icsActive) return;
10560 /* else fall through */
10561 case MachinePlaysWhite:
10562 case MachinePlaysBlack:
10563 case TwoMachinesPlay:
10564 if (forwardMostMove == 0)
10565 return; /* don't pause if no one has moved */
10566 if ((gameMode == MachinePlaysWhite &&
10567 !WhiteOnMove(forwardMostMove)) ||
10568 (gameMode == MachinePlaysBlack &&
10569 WhiteOnMove(forwardMostMove))) {
10582 char title[MSG_SIZ];
10584 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10585 strcpy(title, _("Edit comment"));
10587 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10588 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10589 parseList[currentMove - 1]);
10592 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10599 char *tags = PGNTags(&gameInfo);
10600 EditTagsPopUp(tags);
10607 if (appData.noChessProgram || gameMode == AnalyzeMode)
10610 if (gameMode != AnalyzeFile) {
10611 if (!appData.icsEngineAnalyze) {
10613 if (gameMode != EditGame) return;
10615 ResurrectChessProgram();
10616 SendToProgram("analyze\n", &first);
10617 first.analyzing = TRUE;
10618 /*first.maybeThinking = TRUE;*/
10619 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10620 EngineOutputPopUp();
10622 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10627 StartAnalysisClock();
10628 GetTimeMark(&lastNodeCountTime);
10635 if (appData.noChessProgram || gameMode == AnalyzeFile)
10638 if (gameMode != AnalyzeMode) {
10640 if (gameMode != EditGame) return;
10641 ResurrectChessProgram();
10642 SendToProgram("analyze\n", &first);
10643 first.analyzing = TRUE;
10644 /*first.maybeThinking = TRUE;*/
10645 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10646 EngineOutputPopUp();
10648 gameMode = AnalyzeFile;
10653 StartAnalysisClock();
10654 GetTimeMark(&lastNodeCountTime);
10659 MachineWhiteEvent()
10662 char *bookHit = NULL;
10664 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10668 if (gameMode == PlayFromGameFile ||
10669 gameMode == TwoMachinesPlay ||
10670 gameMode == Training ||
10671 gameMode == AnalyzeMode ||
10672 gameMode == EndOfGame)
10675 if (gameMode == EditPosition)
10676 EditPositionDone(TRUE);
10678 if (!WhiteOnMove(currentMove)) {
10679 DisplayError(_("It is not White's turn"), 0);
10683 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10686 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10687 gameMode == AnalyzeFile)
10690 ResurrectChessProgram(); /* in case it isn't running */
10691 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10692 gameMode = MachinePlaysWhite;
10695 gameMode = MachinePlaysWhite;
10699 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10701 if (first.sendName) {
10702 sprintf(buf, "name %s\n", gameInfo.black);
10703 SendToProgram(buf, &first);
10705 if (first.sendTime) {
10706 if (first.useColors) {
10707 SendToProgram("black\n", &first); /*gnu kludge*/
10709 SendTimeRemaining(&first, TRUE);
10711 if (first.useColors) {
10712 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10714 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10715 SetMachineThinkingEnables();
10716 first.maybeThinking = TRUE;
10720 if (appData.autoFlipView && !flipView) {
10721 flipView = !flipView;
10722 DrawPosition(FALSE, NULL);
10723 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10726 if(bookHit) { // [HGM] book: simulate book reply
10727 static char bookMove[MSG_SIZ]; // a bit generous?
10729 programStats.nodes = programStats.depth = programStats.time =
10730 programStats.score = programStats.got_only_move = 0;
10731 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10733 strcpy(bookMove, "move ");
10734 strcat(bookMove, bookHit);
10735 HandleMachineMove(bookMove, &first);
10740 MachineBlackEvent()
10743 char *bookHit = NULL;
10745 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10749 if (gameMode == PlayFromGameFile ||
10750 gameMode == TwoMachinesPlay ||
10751 gameMode == Training ||
10752 gameMode == AnalyzeMode ||
10753 gameMode == EndOfGame)
10756 if (gameMode == EditPosition)
10757 EditPositionDone(TRUE);
10759 if (WhiteOnMove(currentMove)) {
10760 DisplayError(_("It is not Black's turn"), 0);
10764 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10767 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10768 gameMode == AnalyzeFile)
10771 ResurrectChessProgram(); /* in case it isn't running */
10772 gameMode = MachinePlaysBlack;
10776 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10778 if (first.sendName) {
10779 sprintf(buf, "name %s\n", gameInfo.white);
10780 SendToProgram(buf, &first);
10782 if (first.sendTime) {
10783 if (first.useColors) {
10784 SendToProgram("white\n", &first); /*gnu kludge*/
10786 SendTimeRemaining(&first, FALSE);
10788 if (first.useColors) {
10789 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10791 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10792 SetMachineThinkingEnables();
10793 first.maybeThinking = TRUE;
10796 if (appData.autoFlipView && flipView) {
10797 flipView = !flipView;
10798 DrawPosition(FALSE, NULL);
10799 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10801 if(bookHit) { // [HGM] book: simulate book reply
10802 static char bookMove[MSG_SIZ]; // a bit generous?
10804 programStats.nodes = programStats.depth = programStats.time =
10805 programStats.score = programStats.got_only_move = 0;
10806 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10808 strcpy(bookMove, "move ");
10809 strcat(bookMove, bookHit);
10810 HandleMachineMove(bookMove, &first);
10816 DisplayTwoMachinesTitle()
10819 if (appData.matchGames > 0) {
10820 if (first.twoMachinesColor[0] == 'w') {
10821 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10822 gameInfo.white, gameInfo.black,
10823 first.matchWins, second.matchWins,
10824 matchGame - 1 - (first.matchWins + second.matchWins));
10826 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10827 gameInfo.white, gameInfo.black,
10828 second.matchWins, first.matchWins,
10829 matchGame - 1 - (first.matchWins + second.matchWins));
10832 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10838 TwoMachinesEvent P((void))
10842 ChessProgramState *onmove;
10843 char *bookHit = NULL;
10845 if (appData.noChessProgram) return;
10847 switch (gameMode) {
10848 case TwoMachinesPlay:
10850 case MachinePlaysWhite:
10851 case MachinePlaysBlack:
10852 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10853 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10857 case BeginningOfGame:
10858 case PlayFromGameFile:
10861 if (gameMode != EditGame) return;
10864 EditPositionDone(TRUE);
10875 // forwardMostMove = currentMove;
10876 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10877 ResurrectChessProgram(); /* in case first program isn't running */
10879 if (second.pr == NULL) {
10880 StartChessProgram(&second);
10881 if (second.protocolVersion == 1) {
10882 TwoMachinesEventIfReady();
10884 /* kludge: allow timeout for initial "feature" command */
10886 DisplayMessage("", _("Starting second chess program"));
10887 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10891 DisplayMessage("", "");
10892 InitChessProgram(&second, FALSE);
10893 SendToProgram("force\n", &second);
10894 if (startedFromSetupPosition) {
10895 SendBoard(&second, backwardMostMove);
10896 if (appData.debugMode) {
10897 fprintf(debugFP, "Two Machines\n");
10900 for (i = backwardMostMove; i < forwardMostMove; i++) {
10901 SendMoveToProgram(i, &second);
10904 gameMode = TwoMachinesPlay;
10908 DisplayTwoMachinesTitle();
10910 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10916 SendToProgram(first.computerString, &first);
10917 if (first.sendName) {
10918 sprintf(buf, "name %s\n", second.tidy);
10919 SendToProgram(buf, &first);
10921 SendToProgram(second.computerString, &second);
10922 if (second.sendName) {
10923 sprintf(buf, "name %s\n", first.tidy);
10924 SendToProgram(buf, &second);
10928 if (!first.sendTime || !second.sendTime) {
10929 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10930 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10932 if (onmove->sendTime) {
10933 if (onmove->useColors) {
10934 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10936 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10938 if (onmove->useColors) {
10939 SendToProgram(onmove->twoMachinesColor, onmove);
10941 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10942 // SendToProgram("go\n", onmove);
10943 onmove->maybeThinking = TRUE;
10944 SetMachineThinkingEnables();
10948 if(bookHit) { // [HGM] book: simulate book reply
10949 static char bookMove[MSG_SIZ]; // a bit generous?
10951 programStats.nodes = programStats.depth = programStats.time =
10952 programStats.score = programStats.got_only_move = 0;
10953 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10955 strcpy(bookMove, "move ");
10956 strcat(bookMove, bookHit);
10957 savedMessage = bookMove; // args for deferred call
10958 savedState = onmove;
10959 ScheduleDelayedEvent(DeferredBookMove, 1);
10966 if (gameMode == Training) {
10967 SetTrainingModeOff();
10968 gameMode = PlayFromGameFile;
10969 DisplayMessage("", _("Training mode off"));
10971 gameMode = Training;
10972 animateTraining = appData.animate;
10974 /* make sure we are not already at the end of the game */
10975 if (currentMove < forwardMostMove) {
10976 SetTrainingModeOn();
10977 DisplayMessage("", _("Training mode on"));
10979 gameMode = PlayFromGameFile;
10980 DisplayError(_("Already at end of game"), 0);
10989 if (!appData.icsActive) return;
10990 switch (gameMode) {
10991 case IcsPlayingWhite:
10992 case IcsPlayingBlack:
10995 case BeginningOfGame:
11003 EditPositionDone(TRUE);
11016 gameMode = IcsIdle;
11027 switch (gameMode) {
11029 SetTrainingModeOff();
11031 case MachinePlaysWhite:
11032 case MachinePlaysBlack:
11033 case BeginningOfGame:
11034 SendToProgram("force\n", &first);
11035 SetUserThinkingEnables();
11037 case PlayFromGameFile:
11038 (void) StopLoadGameTimer();
11039 if (gameFileFP != NULL) {
11044 EditPositionDone(TRUE);
11049 SendToProgram("force\n", &first);
11051 case TwoMachinesPlay:
11052 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11053 ResurrectChessProgram();
11054 SetUserThinkingEnables();
11057 ResurrectChessProgram();
11059 case IcsPlayingBlack:
11060 case IcsPlayingWhite:
11061 DisplayError(_("Warning: You are still playing a game"), 0);
11064 DisplayError(_("Warning: You are still observing a game"), 0);
11067 DisplayError(_("Warning: You are still examining a game"), 0);
11078 first.offeredDraw = second.offeredDraw = 0;
11080 if (gameMode == PlayFromGameFile) {
11081 whiteTimeRemaining = timeRemaining[0][currentMove];
11082 blackTimeRemaining = timeRemaining[1][currentMove];
11086 if (gameMode == MachinePlaysWhite ||
11087 gameMode == MachinePlaysBlack ||
11088 gameMode == TwoMachinesPlay ||
11089 gameMode == EndOfGame) {
11090 i = forwardMostMove;
11091 while (i > currentMove) {
11092 SendToProgram("undo\n", &first);
11095 whiteTimeRemaining = timeRemaining[0][currentMove];
11096 blackTimeRemaining = timeRemaining[1][currentMove];
11097 DisplayBothClocks();
11098 if (whiteFlag || blackFlag) {
11099 whiteFlag = blackFlag = 0;
11104 gameMode = EditGame;
11111 EditPositionEvent()
11113 if (gameMode == EditPosition) {
11119 if (gameMode != EditGame) return;
11121 gameMode = EditPosition;
11124 if (currentMove > 0)
11125 CopyBoard(boards[0], boards[currentMove]);
11127 blackPlaysFirst = !WhiteOnMove(currentMove);
11129 currentMove = forwardMostMove = backwardMostMove = 0;
11130 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11137 /* [DM] icsEngineAnalyze - possible call from other functions */
11138 if (appData.icsEngineAnalyze) {
11139 appData.icsEngineAnalyze = FALSE;
11141 DisplayMessage("",_("Close ICS engine analyze..."));
11143 if (first.analysisSupport && first.analyzing) {
11144 SendToProgram("exit\n", &first);
11145 first.analyzing = FALSE;
11147 thinkOutput[0] = NULLCHAR;
11151 EditPositionDone(Boolean fakeRights)
11153 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11155 startedFromSetupPosition = TRUE;
11156 InitChessProgram(&first, FALSE);
11157 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11158 boards[0][EP_STATUS] = EP_NONE;
11159 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11160 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11161 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11162 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11163 } else boards[0][CASTLING][2] = NoRights;
11164 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11165 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11166 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11167 } else boards[0][CASTLING][5] = NoRights;
11169 SendToProgram("force\n", &first);
11170 if (blackPlaysFirst) {
11171 strcpy(moveList[0], "");
11172 strcpy(parseList[0], "");
11173 currentMove = forwardMostMove = backwardMostMove = 1;
11174 CopyBoard(boards[1], boards[0]);
11176 currentMove = forwardMostMove = backwardMostMove = 0;
11178 SendBoard(&first, forwardMostMove);
11179 if (appData.debugMode) {
11180 fprintf(debugFP, "EditPosDone\n");
11183 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11184 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11185 gameMode = EditGame;
11187 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11188 ClearHighlights(); /* [AS] */
11191 /* Pause for `ms' milliseconds */
11192 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11202 } while (SubtractTimeMarks(&m2, &m1) < ms);
11205 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11207 SendMultiLineToICS(buf)
11210 char temp[MSG_SIZ+1], *p;
11217 strncpy(temp, buf, len);
11222 if (*p == '\n' || *p == '\r')
11227 strcat(temp, "\n");
11229 SendToPlayer(temp, strlen(temp));
11233 SetWhiteToPlayEvent()
11235 if (gameMode == EditPosition) {
11236 blackPlaysFirst = FALSE;
11237 DisplayBothClocks(); /* works because currentMove is 0 */
11238 } else if (gameMode == IcsExamining) {
11239 SendToICS(ics_prefix);
11240 SendToICS("tomove white\n");
11245 SetBlackToPlayEvent()
11247 if (gameMode == EditPosition) {
11248 blackPlaysFirst = TRUE;
11249 currentMove = 1; /* kludge */
11250 DisplayBothClocks();
11252 } else if (gameMode == IcsExamining) {
11253 SendToICS(ics_prefix);
11254 SendToICS("tomove black\n");
11259 EditPositionMenuEvent(selection, x, y)
11260 ChessSquare selection;
11264 ChessSquare piece = boards[0][y][x];
11266 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11268 switch (selection) {
11270 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11271 SendToICS(ics_prefix);
11272 SendToICS("bsetup clear\n");
11273 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11274 SendToICS(ics_prefix);
11275 SendToICS("clearboard\n");
11277 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11278 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11279 for (y = 0; y < BOARD_HEIGHT; y++) {
11280 if (gameMode == IcsExamining) {
11281 if (boards[currentMove][y][x] != EmptySquare) {
11282 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11287 boards[0][y][x] = p;
11292 if (gameMode == EditPosition) {
11293 DrawPosition(FALSE, boards[0]);
11298 SetWhiteToPlayEvent();
11302 SetBlackToPlayEvent();
11306 if (gameMode == IcsExamining) {
11307 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11310 boards[0][y][x] = EmptySquare;
11311 DrawPosition(FALSE, boards[0]);
11316 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11317 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11318 selection = (ChessSquare) (PROMOTED piece);
11319 } else if(piece == EmptySquare) selection = WhiteSilver;
11320 else selection = (ChessSquare)((int)piece - 1);
11324 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11325 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11326 selection = (ChessSquare) (DEMOTED piece);
11327 } else if(piece == EmptySquare) selection = BlackSilver;
11328 else selection = (ChessSquare)((int)piece + 1);
11333 if(gameInfo.variant == VariantShatranj ||
11334 gameInfo.variant == VariantXiangqi ||
11335 gameInfo.variant == VariantCourier )
11336 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11341 if(gameInfo.variant == VariantXiangqi)
11342 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11343 if(gameInfo.variant == VariantKnightmate)
11344 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11347 if (gameMode == IcsExamining) {
11348 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11349 PieceToChar(selection), AAA + x, ONE + y);
11352 boards[0][y][x] = selection;
11353 DrawPosition(FALSE, boards[0]);
11361 DropMenuEvent(selection, x, y)
11362 ChessSquare selection;
11365 ChessMove moveType;
11367 switch (gameMode) {
11368 case IcsPlayingWhite:
11369 case MachinePlaysBlack:
11370 if (!WhiteOnMove(currentMove)) {
11371 DisplayMoveError(_("It is Black's turn"));
11374 moveType = WhiteDrop;
11376 case IcsPlayingBlack:
11377 case MachinePlaysWhite:
11378 if (WhiteOnMove(currentMove)) {
11379 DisplayMoveError(_("It is White's turn"));
11382 moveType = BlackDrop;
11385 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11391 if (moveType == BlackDrop && selection < BlackPawn) {
11392 selection = (ChessSquare) ((int) selection
11393 + (int) BlackPawn - (int) WhitePawn);
11395 if (boards[currentMove][y][x] != EmptySquare) {
11396 DisplayMoveError(_("That square is occupied"));
11400 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11406 /* Accept a pending offer of any kind from opponent */
11408 if (appData.icsActive) {
11409 SendToICS(ics_prefix);
11410 SendToICS("accept\n");
11411 } else if (cmailMsgLoaded) {
11412 if (currentMove == cmailOldMove &&
11413 commentList[cmailOldMove] != NULL &&
11414 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11415 "Black offers a draw" : "White offers a draw")) {
11417 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11418 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11420 DisplayError(_("There is no pending offer on this move"), 0);
11421 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11424 /* Not used for offers from chess program */
11431 /* Decline a pending offer of any kind from opponent */
11433 if (appData.icsActive) {
11434 SendToICS(ics_prefix);
11435 SendToICS("decline\n");
11436 } else if (cmailMsgLoaded) {
11437 if (currentMove == cmailOldMove &&
11438 commentList[cmailOldMove] != NULL &&
11439 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11440 "Black offers a draw" : "White offers a draw")) {
11442 AppendComment(cmailOldMove, "Draw declined", TRUE);
11443 DisplayComment(cmailOldMove - 1, "Draw declined");
11446 DisplayError(_("There is no pending offer on this move"), 0);
11449 /* Not used for offers from chess program */
11456 /* Issue ICS rematch command */
11457 if (appData.icsActive) {
11458 SendToICS(ics_prefix);
11459 SendToICS("rematch\n");
11466 /* Call your opponent's flag (claim a win on time) */
11467 if (appData.icsActive) {
11468 SendToICS(ics_prefix);
11469 SendToICS("flag\n");
11471 switch (gameMode) {
11474 case MachinePlaysWhite:
11477 GameEnds(GameIsDrawn, "Both players ran out of time",
11480 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11482 DisplayError(_("Your opponent is not out of time"), 0);
11485 case MachinePlaysBlack:
11488 GameEnds(GameIsDrawn, "Both players ran out of time",
11491 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11493 DisplayError(_("Your opponent is not out of time"), 0);
11503 /* Offer draw or accept pending draw offer from opponent */
11505 if (appData.icsActive) {
11506 /* Note: tournament rules require draw offers to be
11507 made after you make your move but before you punch
11508 your clock. Currently ICS doesn't let you do that;
11509 instead, you immediately punch your clock after making
11510 a move, but you can offer a draw at any time. */
11512 SendToICS(ics_prefix);
11513 SendToICS("draw\n");
11514 } else if (cmailMsgLoaded) {
11515 if (currentMove == cmailOldMove &&
11516 commentList[cmailOldMove] != NULL &&
11517 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11518 "Black offers a draw" : "White offers a draw")) {
11519 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11520 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11521 } else if (currentMove == cmailOldMove + 1) {
11522 char *offer = WhiteOnMove(cmailOldMove) ?
11523 "White offers a draw" : "Black offers a draw";
11524 AppendComment(currentMove, offer, TRUE);
11525 DisplayComment(currentMove - 1, offer);
11526 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11528 DisplayError(_("You must make your move before offering a draw"), 0);
11529 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11531 } else if (first.offeredDraw) {
11532 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11534 if (first.sendDrawOffers) {
11535 SendToProgram("draw\n", &first);
11536 userOfferedDraw = TRUE;
11544 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11546 if (appData.icsActive) {
11547 SendToICS(ics_prefix);
11548 SendToICS("adjourn\n");
11550 /* Currently GNU Chess doesn't offer or accept Adjourns */
11558 /* Offer Abort or accept pending Abort offer from opponent */
11560 if (appData.icsActive) {
11561 SendToICS(ics_prefix);
11562 SendToICS("abort\n");
11564 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11571 /* Resign. You can do this even if it's not your turn. */
11573 if (appData.icsActive) {
11574 SendToICS(ics_prefix);
11575 SendToICS("resign\n");
11577 switch (gameMode) {
11578 case MachinePlaysWhite:
11579 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11581 case MachinePlaysBlack:
11582 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11585 if (cmailMsgLoaded) {
11587 if (WhiteOnMove(cmailOldMove)) {
11588 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11590 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11592 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11603 StopObservingEvent()
11605 /* Stop observing current games */
11606 SendToICS(ics_prefix);
11607 SendToICS("unobserve\n");
11611 StopExaminingEvent()
11613 /* Stop observing current game */
11614 SendToICS(ics_prefix);
11615 SendToICS("unexamine\n");
11619 ForwardInner(target)
11624 if (appData.debugMode)
11625 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11626 target, currentMove, forwardMostMove);
11628 if (gameMode == EditPosition)
11631 if (gameMode == PlayFromGameFile && !pausing)
11634 if (gameMode == IcsExamining && pausing)
11635 limit = pauseExamForwardMostMove;
11637 limit = forwardMostMove;
11639 if (target > limit) target = limit;
11641 if (target > 0 && moveList[target - 1][0]) {
11642 int fromX, fromY, toX, toY;
11643 toX = moveList[target - 1][2] - AAA;
11644 toY = moveList[target - 1][3] - ONE;
11645 if (moveList[target - 1][1] == '@') {
11646 if (appData.highlightLastMove) {
11647 SetHighlights(-1, -1, toX, toY);
11650 fromX = moveList[target - 1][0] - AAA;
11651 fromY = moveList[target - 1][1] - ONE;
11652 if (target == currentMove + 1) {
11653 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11655 if (appData.highlightLastMove) {
11656 SetHighlights(fromX, fromY, toX, toY);
11660 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11661 gameMode == Training || gameMode == PlayFromGameFile ||
11662 gameMode == AnalyzeFile) {
11663 while (currentMove < target) {
11664 SendMoveToProgram(currentMove++, &first);
11667 currentMove = target;
11670 if (gameMode == EditGame || gameMode == EndOfGame) {
11671 whiteTimeRemaining = timeRemaining[0][currentMove];
11672 blackTimeRemaining = timeRemaining[1][currentMove];
11674 DisplayBothClocks();
11675 DisplayMove(currentMove - 1);
11676 DrawPosition(FALSE, boards[currentMove]);
11677 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11678 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11679 DisplayComment(currentMove - 1, commentList[currentMove]);
11687 if (gameMode == IcsExamining && !pausing) {
11688 SendToICS(ics_prefix);
11689 SendToICS("forward\n");
11691 ForwardInner(currentMove + 1);
11698 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11699 /* to optimze, we temporarily turn off analysis mode while we feed
11700 * the remaining moves to the engine. Otherwise we get analysis output
11703 if (first.analysisSupport) {
11704 SendToProgram("exit\nforce\n", &first);
11705 first.analyzing = FALSE;
11709 if (gameMode == IcsExamining && !pausing) {
11710 SendToICS(ics_prefix);
11711 SendToICS("forward 999999\n");
11713 ForwardInner(forwardMostMove);
11716 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11717 /* we have fed all the moves, so reactivate analysis mode */
11718 SendToProgram("analyze\n", &first);
11719 first.analyzing = TRUE;
11720 /*first.maybeThinking = TRUE;*/
11721 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11726 BackwardInner(target)
11729 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11731 if (appData.debugMode)
11732 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11733 target, currentMove, forwardMostMove);
11735 if (gameMode == EditPosition) return;
11736 if (currentMove <= backwardMostMove) {
11738 DrawPosition(full_redraw, boards[currentMove]);
11741 if (gameMode == PlayFromGameFile && !pausing)
11744 if (moveList[target][0]) {
11745 int fromX, fromY, toX, toY;
11746 toX = moveList[target][2] - AAA;
11747 toY = moveList[target][3] - ONE;
11748 if (moveList[target][1] == '@') {
11749 if (appData.highlightLastMove) {
11750 SetHighlights(-1, -1, toX, toY);
11753 fromX = moveList[target][0] - AAA;
11754 fromY = moveList[target][1] - ONE;
11755 if (target == currentMove - 1) {
11756 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11758 if (appData.highlightLastMove) {
11759 SetHighlights(fromX, fromY, toX, toY);
11763 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11764 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11765 while (currentMove > target) {
11766 SendToProgram("undo\n", &first);
11770 currentMove = target;
11773 if (gameMode == EditGame || gameMode == EndOfGame) {
11774 whiteTimeRemaining = timeRemaining[0][currentMove];
11775 blackTimeRemaining = timeRemaining[1][currentMove];
11777 DisplayBothClocks();
11778 DisplayMove(currentMove - 1);
11779 DrawPosition(full_redraw, boards[currentMove]);
11780 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11781 // [HGM] PV info: routine tests if comment empty
11782 DisplayComment(currentMove - 1, commentList[currentMove]);
11788 if (gameMode == IcsExamining && !pausing) {
11789 SendToICS(ics_prefix);
11790 SendToICS("backward\n");
11792 BackwardInner(currentMove - 1);
11799 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11800 /* to optimze, we temporarily turn off analysis mode while we undo
11801 * all the moves. Otherwise we get analysis output after each undo.
11803 if (first.analysisSupport) {
11804 SendToProgram("exit\nforce\n", &first);
11805 first.analyzing = FALSE;
11809 if (gameMode == IcsExamining && !pausing) {
11810 SendToICS(ics_prefix);
11811 SendToICS("backward 999999\n");
11813 BackwardInner(backwardMostMove);
11816 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11817 /* we have fed all the moves, so reactivate analysis mode */
11818 SendToProgram("analyze\n", &first);
11819 first.analyzing = TRUE;
11820 /*first.maybeThinking = TRUE;*/
11821 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11828 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11829 if (to >= forwardMostMove) to = forwardMostMove;
11830 if (to <= backwardMostMove) to = backwardMostMove;
11831 if (to < currentMove) {
11841 if(PopTail()) { // [HGM] vari: restore old game tail
11844 if (gameMode != IcsExamining) {
11845 DisplayError(_("You are not examining a game"), 0);
11849 DisplayError(_("You can't revert while pausing"), 0);
11852 SendToICS(ics_prefix);
11853 SendToICS("revert\n");
11859 switch (gameMode) {
11860 case MachinePlaysWhite:
11861 case MachinePlaysBlack:
11862 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11863 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11866 if (forwardMostMove < 2) return;
11867 currentMove = forwardMostMove = forwardMostMove - 2;
11868 whiteTimeRemaining = timeRemaining[0][currentMove];
11869 blackTimeRemaining = timeRemaining[1][currentMove];
11870 DisplayBothClocks();
11871 DisplayMove(currentMove - 1);
11872 ClearHighlights();/*!! could figure this out*/
11873 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11874 SendToProgram("remove\n", &first);
11875 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11878 case BeginningOfGame:
11882 case IcsPlayingWhite:
11883 case IcsPlayingBlack:
11884 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11885 SendToICS(ics_prefix);
11886 SendToICS("takeback 2\n");
11888 SendToICS(ics_prefix);
11889 SendToICS("takeback 1\n");
11898 ChessProgramState *cps;
11900 switch (gameMode) {
11901 case MachinePlaysWhite:
11902 if (!WhiteOnMove(forwardMostMove)) {
11903 DisplayError(_("It is your turn"), 0);
11908 case MachinePlaysBlack:
11909 if (WhiteOnMove(forwardMostMove)) {
11910 DisplayError(_("It is your turn"), 0);
11915 case TwoMachinesPlay:
11916 if (WhiteOnMove(forwardMostMove) ==
11917 (first.twoMachinesColor[0] == 'w')) {
11923 case BeginningOfGame:
11927 SendToProgram("?\n", cps);
11931 TruncateGameEvent()
11934 if (gameMode != EditGame) return;
11941 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
11942 if (forwardMostMove > currentMove) {
11943 if (gameInfo.resultDetails != NULL) {
11944 free(gameInfo.resultDetails);
11945 gameInfo.resultDetails = NULL;
11946 gameInfo.result = GameUnfinished;
11948 forwardMostMove = currentMove;
11949 HistorySet(parseList, backwardMostMove, forwardMostMove,
11957 if (appData.noChessProgram) return;
11958 switch (gameMode) {
11959 case MachinePlaysWhite:
11960 if (WhiteOnMove(forwardMostMove)) {
11961 DisplayError(_("Wait until your turn"), 0);
11965 case BeginningOfGame:
11966 case MachinePlaysBlack:
11967 if (!WhiteOnMove(forwardMostMove)) {
11968 DisplayError(_("Wait until your turn"), 0);
11973 DisplayError(_("No hint available"), 0);
11976 SendToProgram("hint\n", &first);
11977 hintRequested = TRUE;
11983 if (appData.noChessProgram) return;
11984 switch (gameMode) {
11985 case MachinePlaysWhite:
11986 if (WhiteOnMove(forwardMostMove)) {
11987 DisplayError(_("Wait until your turn"), 0);
11991 case BeginningOfGame:
11992 case MachinePlaysBlack:
11993 if (!WhiteOnMove(forwardMostMove)) {
11994 DisplayError(_("Wait until your turn"), 0);
11999 EditPositionDone(TRUE);
12001 case TwoMachinesPlay:
12006 SendToProgram("bk\n", &first);
12007 bookOutput[0] = NULLCHAR;
12008 bookRequested = TRUE;
12014 char *tags = PGNTags(&gameInfo);
12015 TagsPopUp(tags, CmailMsg());
12019 /* end button procedures */
12022 PrintPosition(fp, move)
12028 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12029 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12030 char c = PieceToChar(boards[move][i][j]);
12031 fputc(c == 'x' ? '.' : c, fp);
12032 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12035 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12036 fprintf(fp, "white to play\n");
12038 fprintf(fp, "black to play\n");
12045 if (gameInfo.white != NULL) {
12046 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12052 /* Find last component of program's own name, using some heuristics */
12054 TidyProgramName(prog, host, buf)
12055 char *prog, *host, buf[MSG_SIZ];
12058 int local = (strcmp(host, "localhost") == 0);
12059 while (!local && (p = strchr(prog, ';')) != NULL) {
12061 while (*p == ' ') p++;
12064 if (*prog == '"' || *prog == '\'') {
12065 q = strchr(prog + 1, *prog);
12067 q = strchr(prog, ' ');
12069 if (q == NULL) q = prog + strlen(prog);
12071 while (p >= prog && *p != '/' && *p != '\\') p--;
12073 if(p == prog && *p == '"') p++;
12074 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12075 memcpy(buf, p, q - p);
12076 buf[q - p] = NULLCHAR;
12084 TimeControlTagValue()
12087 if (!appData.clockMode) {
12089 } else if (movesPerSession > 0) {
12090 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12091 } else if (timeIncrement == 0) {
12092 sprintf(buf, "%ld", timeControl/1000);
12094 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12096 return StrSave(buf);
12102 /* This routine is used only for certain modes */
12103 VariantClass v = gameInfo.variant;
12104 ClearGameInfo(&gameInfo);
12105 gameInfo.variant = v;
12107 switch (gameMode) {
12108 case MachinePlaysWhite:
12109 gameInfo.event = StrSave( appData.pgnEventHeader );
12110 gameInfo.site = StrSave(HostName());
12111 gameInfo.date = PGNDate();
12112 gameInfo.round = StrSave("-");
12113 gameInfo.white = StrSave(first.tidy);
12114 gameInfo.black = StrSave(UserName());
12115 gameInfo.timeControl = TimeControlTagValue();
12118 case MachinePlaysBlack:
12119 gameInfo.event = StrSave( appData.pgnEventHeader );
12120 gameInfo.site = StrSave(HostName());
12121 gameInfo.date = PGNDate();
12122 gameInfo.round = StrSave("-");
12123 gameInfo.white = StrSave(UserName());
12124 gameInfo.black = StrSave(first.tidy);
12125 gameInfo.timeControl = TimeControlTagValue();
12128 case TwoMachinesPlay:
12129 gameInfo.event = StrSave( appData.pgnEventHeader );
12130 gameInfo.site = StrSave(HostName());
12131 gameInfo.date = PGNDate();
12132 if (matchGame > 0) {
12134 sprintf(buf, "%d", matchGame);
12135 gameInfo.round = StrSave(buf);
12137 gameInfo.round = StrSave("-");
12139 if (first.twoMachinesColor[0] == 'w') {
12140 gameInfo.white = StrSave(first.tidy);
12141 gameInfo.black = StrSave(second.tidy);
12143 gameInfo.white = StrSave(second.tidy);
12144 gameInfo.black = StrSave(first.tidy);
12146 gameInfo.timeControl = TimeControlTagValue();
12150 gameInfo.event = StrSave("Edited game");
12151 gameInfo.site = StrSave(HostName());
12152 gameInfo.date = PGNDate();
12153 gameInfo.round = StrSave("-");
12154 gameInfo.white = StrSave("-");
12155 gameInfo.black = StrSave("-");
12159 gameInfo.event = StrSave("Edited position");
12160 gameInfo.site = StrSave(HostName());
12161 gameInfo.date = PGNDate();
12162 gameInfo.round = StrSave("-");
12163 gameInfo.white = StrSave("-");
12164 gameInfo.black = StrSave("-");
12167 case IcsPlayingWhite:
12168 case IcsPlayingBlack:
12173 case PlayFromGameFile:
12174 gameInfo.event = StrSave("Game from non-PGN file");
12175 gameInfo.site = StrSave(HostName());
12176 gameInfo.date = PGNDate();
12177 gameInfo.round = StrSave("-");
12178 gameInfo.white = StrSave("?");
12179 gameInfo.black = StrSave("?");
12188 ReplaceComment(index, text)
12194 while (*text == '\n') text++;
12195 len = strlen(text);
12196 while (len > 0 && text[len - 1] == '\n') len--;
12198 if (commentList[index] != NULL)
12199 free(commentList[index]);
12202 commentList[index] = NULL;
12205 if(*text == '{' || *text == '(' || *text == '[') {
12206 commentList[index] = (char *) malloc(len + 2);
12207 strncpy(commentList[index], text, len);
12208 commentList[index][len] = '\n';
12209 commentList[index][len + 1] = NULLCHAR;
12211 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12213 commentList[index] = (char *) malloc(len + 6);
12214 strcpy(commentList[index], "{\n");
12215 strcat(commentList[index], text);
12216 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12217 strcat(commentList[index], "\n}");
12231 if (ch == '\r') continue;
12233 } while (ch != '\0');
12237 AppendComment(index, text, addBraces)
12240 Boolean addBraces; // [HGM] braces: tells if we should add {}
12245 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12246 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12249 while (*text == '\n') text++;
12250 len = strlen(text);
12251 while (len > 0 && text[len - 1] == '\n') len--;
12253 if (len == 0) return;
12255 if (commentList[index] != NULL) {
12256 old = commentList[index];
12257 oldlen = strlen(old);
12258 commentList[index] = (char *) malloc(oldlen + len + 4); // might waste 2
12259 strcpy(commentList[index], old);
12261 // [HGM] braces: join "{A\n}" + "{B}" as "{A\nB\n}"
12262 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12263 if(addBraces) addBraces = FALSE; else { text++; len--; }
12264 while (*text == '\n') { text++; len--; }
12265 commentList[index][oldlen-1] = NULLCHAR;
12268 strncpy(&commentList[index][oldlen], text, len);
12269 if(addBraces) strcpy(&commentList[index][oldlen + len], "\n}");
12270 else strcpy(&commentList[index][oldlen + len], "\n");
12272 commentList[index] = (char *) malloc(len + 4); // perhaps wastes 2...
12273 if(addBraces) commentList[index][0] = '{';
12274 strcpy(commentList[index] + addBraces, text);
12275 strcat(commentList[index], "\n");
12276 if(addBraces) strcat(commentList[index], "}");
12280 static char * FindStr( char * text, char * sub_text )
12282 char * result = strstr( text, sub_text );
12284 if( result != NULL ) {
12285 result += strlen( sub_text );
12291 /* [AS] Try to extract PV info from PGN comment */
12292 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12293 char *GetInfoFromComment( int index, char * text )
12297 if( text != NULL && index > 0 ) {
12300 int time = -1, sec = 0, deci;
12301 char * s_eval = FindStr( text, "[%eval " );
12302 char * s_emt = FindStr( text, "[%emt " );
12304 if( s_eval != NULL || s_emt != NULL ) {
12308 if( s_eval != NULL ) {
12309 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12313 if( delim != ']' ) {
12318 if( s_emt != NULL ) {
12323 /* We expect something like: [+|-]nnn.nn/dd */
12326 if(*text != '{') return text; // [HGM] braces: must be normal comment
12328 sep = strchr( text, '/' );
12329 if( sep == NULL || sep < (text+4) ) {
12333 time = -1; sec = -1; deci = -1;
12334 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12335 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12336 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12337 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12341 if( score_lo < 0 || score_lo >= 100 ) {
12345 if(sec >= 0) time = 600*time + 10*sec; else
12346 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12348 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12350 /* [HGM] PV time: now locate end of PV info */
12351 while( *++sep >= '0' && *sep <= '9'); // strip depth
12353 while( *++sep >= '0' && *sep <= '9'); // strip time
12355 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12357 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12358 while(*sep == ' ') sep++;
12369 pvInfoList[index-1].depth = depth;
12370 pvInfoList[index-1].score = score;
12371 pvInfoList[index-1].time = 10*time; // centi-sec
12372 if(*sep == '}') *sep = 0; else *--sep = '{';
12378 SendToProgram(message, cps)
12380 ChessProgramState *cps;
12382 int count, outCount, error;
12385 if (cps->pr == NULL) return;
12388 if (appData.debugMode) {
12391 fprintf(debugFP, "%ld >%-6s: %s",
12392 SubtractTimeMarks(&now, &programStartTime),
12393 cps->which, message);
12396 count = strlen(message);
12397 outCount = OutputToProcess(cps->pr, message, count, &error);
12398 if (outCount < count && !exiting
12399 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12400 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12401 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12402 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12403 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12404 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12406 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12408 gameInfo.resultDetails = buf;
12410 DisplayFatalError(buf, error, 1);
12415 ReceiveFromProgram(isr, closure, message, count, error)
12416 InputSourceRef isr;
12424 ChessProgramState *cps = (ChessProgramState *)closure;
12426 if (isr != cps->isr) return; /* Killed intentionally */
12430 _("Error: %s chess program (%s) exited unexpectedly"),
12431 cps->which, cps->program);
12432 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12433 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12434 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12435 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12437 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12439 gameInfo.resultDetails = buf;
12441 RemoveInputSource(cps->isr);
12442 DisplayFatalError(buf, 0, 1);
12445 _("Error reading from %s chess program (%s)"),
12446 cps->which, cps->program);
12447 RemoveInputSource(cps->isr);
12449 /* [AS] Program is misbehaving badly... kill it */
12450 if( count == -2 ) {
12451 DestroyChildProcess( cps->pr, 9 );
12455 DisplayFatalError(buf, error, 1);
12460 if ((end_str = strchr(message, '\r')) != NULL)
12461 *end_str = NULLCHAR;
12462 if ((end_str = strchr(message, '\n')) != NULL)
12463 *end_str = NULLCHAR;
12465 if (appData.debugMode) {
12466 TimeMark now; int print = 1;
12467 char *quote = ""; char c; int i;
12469 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12470 char start = message[0];
12471 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12472 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12473 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12474 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12475 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12476 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12477 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12478 sscanf(message, "pong %c", &c)!=1 && start != '#')
12479 { quote = "# "; print = (appData.engineComments == 2); }
12480 message[0] = start; // restore original message
12484 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12485 SubtractTimeMarks(&now, &programStartTime), cps->which,
12491 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12492 if (appData.icsEngineAnalyze) {
12493 if (strstr(message, "whisper") != NULL ||
12494 strstr(message, "kibitz") != NULL ||
12495 strstr(message, "tellics") != NULL) return;
12498 HandleMachineMove(message, cps);
12503 SendTimeControl(cps, mps, tc, inc, sd, st)
12504 ChessProgramState *cps;
12505 int mps, inc, sd, st;
12511 if( timeControl_2 > 0 ) {
12512 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12513 tc = timeControl_2;
12516 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12517 inc /= cps->timeOdds;
12518 st /= cps->timeOdds;
12520 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12523 /* Set exact time per move, normally using st command */
12524 if (cps->stKludge) {
12525 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12527 if (seconds == 0) {
12528 sprintf(buf, "level 1 %d\n", st/60);
12530 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12533 sprintf(buf, "st %d\n", st);
12536 /* Set conventional or incremental time control, using level command */
12537 if (seconds == 0) {
12538 /* Note old gnuchess bug -- minutes:seconds used to not work.
12539 Fixed in later versions, but still avoid :seconds
12540 when seconds is 0. */
12541 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12543 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12544 seconds, inc/1000);
12547 SendToProgram(buf, cps);
12549 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12550 /* Orthogonally, limit search to given depth */
12552 if (cps->sdKludge) {
12553 sprintf(buf, "depth\n%d\n", sd);
12555 sprintf(buf, "sd %d\n", sd);
12557 SendToProgram(buf, cps);
12560 if(cps->nps > 0) { /* [HGM] nps */
12561 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12563 sprintf(buf, "nps %d\n", cps->nps);
12564 SendToProgram(buf, cps);
12569 ChessProgramState *WhitePlayer()
12570 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12572 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12573 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12579 SendTimeRemaining(cps, machineWhite)
12580 ChessProgramState *cps;
12581 int /*boolean*/ machineWhite;
12583 char message[MSG_SIZ];
12586 /* Note: this routine must be called when the clocks are stopped
12587 or when they have *just* been set or switched; otherwise
12588 it will be off by the time since the current tick started.
12590 if (machineWhite) {
12591 time = whiteTimeRemaining / 10;
12592 otime = blackTimeRemaining / 10;
12594 time = blackTimeRemaining / 10;
12595 otime = whiteTimeRemaining / 10;
12597 /* [HGM] translate opponent's time by time-odds factor */
12598 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12599 if (appData.debugMode) {
12600 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12603 if (time <= 0) time = 1;
12604 if (otime <= 0) otime = 1;
12606 sprintf(message, "time %ld\n", time);
12607 SendToProgram(message, cps);
12609 sprintf(message, "otim %ld\n", otime);
12610 SendToProgram(message, cps);
12614 BoolFeature(p, name, loc, cps)
12618 ChessProgramState *cps;
12621 int len = strlen(name);
12623 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12625 sscanf(*p, "%d", &val);
12627 while (**p && **p != ' ') (*p)++;
12628 sprintf(buf, "accepted %s\n", name);
12629 SendToProgram(buf, cps);
12636 IntFeature(p, name, loc, cps)
12640 ChessProgramState *cps;
12643 int len = strlen(name);
12644 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12646 sscanf(*p, "%d", loc);
12647 while (**p && **p != ' ') (*p)++;
12648 sprintf(buf, "accepted %s\n", name);
12649 SendToProgram(buf, cps);
12656 StringFeature(p, name, loc, cps)
12660 ChessProgramState *cps;
12663 int len = strlen(name);
12664 if (strncmp((*p), name, len) == 0
12665 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12667 sscanf(*p, "%[^\"]", loc);
12668 while (**p && **p != '\"') (*p)++;
12669 if (**p == '\"') (*p)++;
12670 sprintf(buf, "accepted %s\n", name);
12671 SendToProgram(buf, cps);
12678 ParseOption(Option *opt, ChessProgramState *cps)
12679 // [HGM] options: process the string that defines an engine option, and determine
12680 // name, type, default value, and allowed value range
12682 char *p, *q, buf[MSG_SIZ];
12683 int n, min = (-1)<<31, max = 1<<31, def;
12685 if(p = strstr(opt->name, " -spin ")) {
12686 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12687 if(max < min) max = min; // enforce consistency
12688 if(def < min) def = min;
12689 if(def > max) def = max;
12694 } else if((p = strstr(opt->name, " -slider "))) {
12695 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12696 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12697 if(max < min) max = min; // enforce consistency
12698 if(def < min) def = min;
12699 if(def > max) def = max;
12703 opt->type = Spin; // Slider;
12704 } else if((p = strstr(opt->name, " -string "))) {
12705 opt->textValue = p+9;
12706 opt->type = TextBox;
12707 } else if((p = strstr(opt->name, " -file "))) {
12708 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12709 opt->textValue = p+7;
12710 opt->type = TextBox; // FileName;
12711 } else if((p = strstr(opt->name, " -path "))) {
12712 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12713 opt->textValue = p+7;
12714 opt->type = TextBox; // PathName;
12715 } else if(p = strstr(opt->name, " -check ")) {
12716 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12717 opt->value = (def != 0);
12718 opt->type = CheckBox;
12719 } else if(p = strstr(opt->name, " -combo ")) {
12720 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12721 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12722 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12723 opt->value = n = 0;
12724 while(q = StrStr(q, " /// ")) {
12725 n++; *q = 0; // count choices, and null-terminate each of them
12727 if(*q == '*') { // remember default, which is marked with * prefix
12731 cps->comboList[cps->comboCnt++] = q;
12733 cps->comboList[cps->comboCnt++] = NULL;
12735 opt->type = ComboBox;
12736 } else if(p = strstr(opt->name, " -button")) {
12737 opt->type = Button;
12738 } else if(p = strstr(opt->name, " -save")) {
12739 opt->type = SaveButton;
12740 } else return FALSE;
12741 *p = 0; // terminate option name
12742 // now look if the command-line options define a setting for this engine option.
12743 if(cps->optionSettings && cps->optionSettings[0])
12744 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12745 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12746 sprintf(buf, "option %s", p);
12747 if(p = strstr(buf, ",")) *p = 0;
12749 SendToProgram(buf, cps);
12755 FeatureDone(cps, val)
12756 ChessProgramState* cps;
12759 DelayedEventCallback cb = GetDelayedEvent();
12760 if ((cb == InitBackEnd3 && cps == &first) ||
12761 (cb == TwoMachinesEventIfReady && cps == &second)) {
12762 CancelDelayedEvent();
12763 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12765 cps->initDone = val;
12768 /* Parse feature command from engine */
12770 ParseFeatures(args, cps)
12772 ChessProgramState *cps;
12780 while (*p == ' ') p++;
12781 if (*p == NULLCHAR) return;
12783 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12784 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12785 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12786 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12787 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12788 if (BoolFeature(&p, "reuse", &val, cps)) {
12789 /* Engine can disable reuse, but can't enable it if user said no */
12790 if (!val) cps->reuse = FALSE;
12793 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12794 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12795 if (gameMode == TwoMachinesPlay) {
12796 DisplayTwoMachinesTitle();
12802 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12803 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12804 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12805 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12806 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12807 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12808 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12809 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12810 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12811 if (IntFeature(&p, "done", &val, cps)) {
12812 FeatureDone(cps, val);
12815 /* Added by Tord: */
12816 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12817 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12818 /* End of additions by Tord */
12820 /* [HGM] added features: */
12821 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12822 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12823 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12824 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12825 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12826 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12827 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12828 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12829 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12830 SendToProgram(buf, cps);
12833 if(cps->nrOptions >= MAX_OPTIONS) {
12835 sprintf(buf, "%s engine has too many options\n", cps->which);
12836 DisplayError(buf, 0);
12840 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12841 /* End of additions by HGM */
12843 /* unknown feature: complain and skip */
12845 while (*q && *q != '=') q++;
12846 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12847 SendToProgram(buf, cps);
12853 while (*p && *p != '\"') p++;
12854 if (*p == '\"') p++;
12856 while (*p && *p != ' ') p++;
12864 PeriodicUpdatesEvent(newState)
12867 if (newState == appData.periodicUpdates)
12870 appData.periodicUpdates=newState;
12872 /* Display type changes, so update it now */
12873 // DisplayAnalysis();
12875 /* Get the ball rolling again... */
12877 AnalysisPeriodicEvent(1);
12878 StartAnalysisClock();
12883 PonderNextMoveEvent(newState)
12886 if (newState == appData.ponderNextMove) return;
12887 if (gameMode == EditPosition) EditPositionDone(TRUE);
12889 SendToProgram("hard\n", &first);
12890 if (gameMode == TwoMachinesPlay) {
12891 SendToProgram("hard\n", &second);
12894 SendToProgram("easy\n", &first);
12895 thinkOutput[0] = NULLCHAR;
12896 if (gameMode == TwoMachinesPlay) {
12897 SendToProgram("easy\n", &second);
12900 appData.ponderNextMove = newState;
12904 NewSettingEvent(option, command, value)
12910 if (gameMode == EditPosition) EditPositionDone(TRUE);
12911 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12912 SendToProgram(buf, &first);
12913 if (gameMode == TwoMachinesPlay) {
12914 SendToProgram(buf, &second);
12919 ShowThinkingEvent()
12920 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12922 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12923 int newState = appData.showThinking
12924 // [HGM] thinking: other features now need thinking output as well
12925 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12927 if (oldState == newState) return;
12928 oldState = newState;
12929 if (gameMode == EditPosition) EditPositionDone(TRUE);
12931 SendToProgram("post\n", &first);
12932 if (gameMode == TwoMachinesPlay) {
12933 SendToProgram("post\n", &second);
12936 SendToProgram("nopost\n", &first);
12937 thinkOutput[0] = NULLCHAR;
12938 if (gameMode == TwoMachinesPlay) {
12939 SendToProgram("nopost\n", &second);
12942 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12946 AskQuestionEvent(title, question, replyPrefix, which)
12947 char *title; char *question; char *replyPrefix; char *which;
12949 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12950 if (pr == NoProc) return;
12951 AskQuestion(title, question, replyPrefix, pr);
12955 DisplayMove(moveNumber)
12958 char message[MSG_SIZ];
12960 char cpThinkOutput[MSG_SIZ];
12962 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12964 if (moveNumber == forwardMostMove - 1 ||
12965 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12967 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12969 if (strchr(cpThinkOutput, '\n')) {
12970 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12973 *cpThinkOutput = NULLCHAR;
12976 /* [AS] Hide thinking from human user */
12977 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12978 *cpThinkOutput = NULLCHAR;
12979 if( thinkOutput[0] != NULLCHAR ) {
12982 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12983 cpThinkOutput[i] = '.';
12985 cpThinkOutput[i] = NULLCHAR;
12986 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12990 if (moveNumber == forwardMostMove - 1 &&
12991 gameInfo.resultDetails != NULL) {
12992 if (gameInfo.resultDetails[0] == NULLCHAR) {
12993 sprintf(res, " %s", PGNResult(gameInfo.result));
12995 sprintf(res, " {%s} %s",
12996 gameInfo.resultDetails, PGNResult(gameInfo.result));
13002 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13003 DisplayMessage(res, cpThinkOutput);
13005 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13006 WhiteOnMove(moveNumber) ? " " : ".. ",
13007 parseList[moveNumber], res);
13008 DisplayMessage(message, cpThinkOutput);
13013 DisplayComment(moveNumber, text)
13017 char title[MSG_SIZ];
13018 char buf[8000]; // comment can be long!
13021 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13022 strcpy(title, "Comment");
13024 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13025 WhiteOnMove(moveNumber) ? " " : ".. ",
13026 parseList[moveNumber]);
13028 // [HGM] PV info: display PV info together with (or as) comment
13029 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13030 if(text == NULL) text = "";
13031 score = pvInfoList[moveNumber].score;
13032 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13033 depth, (pvInfoList[moveNumber].time+50)/100, text);
13036 if (text != NULL && (appData.autoDisplayComment || commentUp))
13037 CommentPopUp(title, text);
13040 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13041 * might be busy thinking or pondering. It can be omitted if your
13042 * gnuchess is configured to stop thinking immediately on any user
13043 * input. However, that gnuchess feature depends on the FIONREAD
13044 * ioctl, which does not work properly on some flavors of Unix.
13048 ChessProgramState *cps;
13051 if (!cps->useSigint) return;
13052 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13053 switch (gameMode) {
13054 case MachinePlaysWhite:
13055 case MachinePlaysBlack:
13056 case TwoMachinesPlay:
13057 case IcsPlayingWhite:
13058 case IcsPlayingBlack:
13061 /* Skip if we know it isn't thinking */
13062 if (!cps->maybeThinking) return;
13063 if (appData.debugMode)
13064 fprintf(debugFP, "Interrupting %s\n", cps->which);
13065 InterruptChildProcess(cps->pr);
13066 cps->maybeThinking = FALSE;
13071 #endif /*ATTENTION*/
13077 if (whiteTimeRemaining <= 0) {
13080 if (appData.icsActive) {
13081 if (appData.autoCallFlag &&
13082 gameMode == IcsPlayingBlack && !blackFlag) {
13083 SendToICS(ics_prefix);
13084 SendToICS("flag\n");
13088 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13090 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13091 if (appData.autoCallFlag) {
13092 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13099 if (blackTimeRemaining <= 0) {
13102 if (appData.icsActive) {
13103 if (appData.autoCallFlag &&
13104 gameMode == IcsPlayingWhite && !whiteFlag) {
13105 SendToICS(ics_prefix);
13106 SendToICS("flag\n");
13110 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13112 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13113 if (appData.autoCallFlag) {
13114 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13127 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13128 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13131 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13133 if ( !WhiteOnMove(forwardMostMove) )
13134 /* White made time control */
13135 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13136 /* [HGM] time odds: correct new time quota for time odds! */
13137 / WhitePlayer()->timeOdds;
13139 /* Black made time control */
13140 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13141 / WhitePlayer()->other->timeOdds;
13145 DisplayBothClocks()
13147 int wom = gameMode == EditPosition ?
13148 !blackPlaysFirst : WhiteOnMove(currentMove);
13149 DisplayWhiteClock(whiteTimeRemaining, wom);
13150 DisplayBlackClock(blackTimeRemaining, !wom);
13154 /* Timekeeping seems to be a portability nightmare. I think everyone
13155 has ftime(), but I'm really not sure, so I'm including some ifdefs
13156 to use other calls if you don't. Clocks will be less accurate if
13157 you have neither ftime nor gettimeofday.
13160 /* VS 2008 requires the #include outside of the function */
13161 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13162 #include <sys/timeb.h>
13165 /* Get the current time as a TimeMark */
13170 #if HAVE_GETTIMEOFDAY
13172 struct timeval timeVal;
13173 struct timezone timeZone;
13175 gettimeofday(&timeVal, &timeZone);
13176 tm->sec = (long) timeVal.tv_sec;
13177 tm->ms = (int) (timeVal.tv_usec / 1000L);
13179 #else /*!HAVE_GETTIMEOFDAY*/
13182 // include <sys/timeb.h> / moved to just above start of function
13183 struct timeb timeB;
13186 tm->sec = (long) timeB.time;
13187 tm->ms = (int) timeB.millitm;
13189 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13190 tm->sec = (long) time(NULL);
13196 /* Return the difference in milliseconds between two
13197 time marks. We assume the difference will fit in a long!
13200 SubtractTimeMarks(tm2, tm1)
13201 TimeMark *tm2, *tm1;
13203 return 1000L*(tm2->sec - tm1->sec) +
13204 (long) (tm2->ms - tm1->ms);
13209 * Code to manage the game clocks.
13211 * In tournament play, black starts the clock and then white makes a move.
13212 * We give the human user a slight advantage if he is playing white---the
13213 * clocks don't run until he makes his first move, so it takes zero time.
13214 * Also, we don't account for network lag, so we could get out of sync
13215 * with GNU Chess's clock -- but then, referees are always right.
13218 static TimeMark tickStartTM;
13219 static long intendedTickLength;
13222 NextTickLength(timeRemaining)
13223 long timeRemaining;
13225 long nominalTickLength, nextTickLength;
13227 if (timeRemaining > 0L && timeRemaining <= 10000L)
13228 nominalTickLength = 100L;
13230 nominalTickLength = 1000L;
13231 nextTickLength = timeRemaining % nominalTickLength;
13232 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13234 return nextTickLength;
13237 /* Adjust clock one minute up or down */
13239 AdjustClock(Boolean which, int dir)
13241 if(which) blackTimeRemaining += 60000*dir;
13242 else whiteTimeRemaining += 60000*dir;
13243 DisplayBothClocks();
13246 /* Stop clocks and reset to a fresh time control */
13250 (void) StopClockTimer();
13251 if (appData.icsActive) {
13252 whiteTimeRemaining = blackTimeRemaining = 0;
13253 } else if (searchTime) {
13254 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13255 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13256 } else { /* [HGM] correct new time quote for time odds */
13257 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13258 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13260 if (whiteFlag || blackFlag) {
13262 whiteFlag = blackFlag = FALSE;
13264 DisplayBothClocks();
13267 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13269 /* Decrement running clock by amount of time that has passed */
13273 long timeRemaining;
13274 long lastTickLength, fudge;
13277 if (!appData.clockMode) return;
13278 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13282 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13284 /* Fudge if we woke up a little too soon */
13285 fudge = intendedTickLength - lastTickLength;
13286 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13288 if (WhiteOnMove(forwardMostMove)) {
13289 if(whiteNPS >= 0) lastTickLength = 0;
13290 timeRemaining = whiteTimeRemaining -= lastTickLength;
13291 DisplayWhiteClock(whiteTimeRemaining - fudge,
13292 WhiteOnMove(currentMove));
13294 if(blackNPS >= 0) lastTickLength = 0;
13295 timeRemaining = blackTimeRemaining -= lastTickLength;
13296 DisplayBlackClock(blackTimeRemaining - fudge,
13297 !WhiteOnMove(currentMove));
13300 if (CheckFlags()) return;
13303 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13304 StartClockTimer(intendedTickLength);
13306 /* if the time remaining has fallen below the alarm threshold, sound the
13307 * alarm. if the alarm has sounded and (due to a takeback or time control
13308 * with increment) the time remaining has increased to a level above the
13309 * threshold, reset the alarm so it can sound again.
13312 if (appData.icsActive && appData.icsAlarm) {
13314 /* make sure we are dealing with the user's clock */
13315 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13316 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13319 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13320 alarmSounded = FALSE;
13321 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13323 alarmSounded = TRUE;
13329 /* A player has just moved, so stop the previously running
13330 clock and (if in clock mode) start the other one.
13331 We redisplay both clocks in case we're in ICS mode, because
13332 ICS gives us an update to both clocks after every move.
13333 Note that this routine is called *after* forwardMostMove
13334 is updated, so the last fractional tick must be subtracted
13335 from the color that is *not* on move now.
13340 long lastTickLength;
13342 int flagged = FALSE;
13346 if (StopClockTimer() && appData.clockMode) {
13347 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13348 if (WhiteOnMove(forwardMostMove)) {
13349 if(blackNPS >= 0) lastTickLength = 0;
13350 blackTimeRemaining -= lastTickLength;
13351 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13352 // if(pvInfoList[forwardMostMove-1].time == -1)
13353 pvInfoList[forwardMostMove-1].time = // use GUI time
13354 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13356 if(whiteNPS >= 0) lastTickLength = 0;
13357 whiteTimeRemaining -= lastTickLength;
13358 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13359 // if(pvInfoList[forwardMostMove-1].time == -1)
13360 pvInfoList[forwardMostMove-1].time =
13361 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13363 flagged = CheckFlags();
13365 CheckTimeControl();
13367 if (flagged || !appData.clockMode) return;
13369 switch (gameMode) {
13370 case MachinePlaysBlack:
13371 case MachinePlaysWhite:
13372 case BeginningOfGame:
13373 if (pausing) return;
13377 case PlayFromGameFile:
13385 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13386 if(WhiteOnMove(forwardMostMove))
13387 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13388 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13392 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13393 whiteTimeRemaining : blackTimeRemaining);
13394 StartClockTimer(intendedTickLength);
13398 /* Stop both clocks */
13402 long lastTickLength;
13405 if (!StopClockTimer()) return;
13406 if (!appData.clockMode) return;
13410 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13411 if (WhiteOnMove(forwardMostMove)) {
13412 if(whiteNPS >= 0) lastTickLength = 0;
13413 whiteTimeRemaining -= lastTickLength;
13414 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13416 if(blackNPS >= 0) lastTickLength = 0;
13417 blackTimeRemaining -= lastTickLength;
13418 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13423 /* Start clock of player on move. Time may have been reset, so
13424 if clock is already running, stop and restart it. */
13428 (void) StopClockTimer(); /* in case it was running already */
13429 DisplayBothClocks();
13430 if (CheckFlags()) return;
13432 if (!appData.clockMode) return;
13433 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13435 GetTimeMark(&tickStartTM);
13436 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13437 whiteTimeRemaining : blackTimeRemaining);
13439 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13440 whiteNPS = blackNPS = -1;
13441 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13442 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13443 whiteNPS = first.nps;
13444 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13445 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13446 blackNPS = first.nps;
13447 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13448 whiteNPS = second.nps;
13449 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13450 blackNPS = second.nps;
13451 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13453 StartClockTimer(intendedTickLength);
13460 long second, minute, hour, day;
13462 static char buf[32];
13464 if (ms > 0 && ms <= 9900) {
13465 /* convert milliseconds to tenths, rounding up */
13466 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13468 sprintf(buf, " %03.1f ", tenths/10.0);
13472 /* convert milliseconds to seconds, rounding up */
13473 /* use floating point to avoid strangeness of integer division
13474 with negative dividends on many machines */
13475 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13482 day = second / (60 * 60 * 24);
13483 second = second % (60 * 60 * 24);
13484 hour = second / (60 * 60);
13485 second = second % (60 * 60);
13486 minute = second / 60;
13487 second = second % 60;
13490 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13491 sign, day, hour, minute, second);
13493 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13495 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13502 * This is necessary because some C libraries aren't ANSI C compliant yet.
13505 StrStr(string, match)
13506 char *string, *match;
13510 length = strlen(match);
13512 for (i = strlen(string) - length; i >= 0; i--, string++)
13513 if (!strncmp(match, string, length))
13520 StrCaseStr(string, match)
13521 char *string, *match;
13525 length = strlen(match);
13527 for (i = strlen(string) - length; i >= 0; i--, string++) {
13528 for (j = 0; j < length; j++) {
13529 if (ToLower(match[j]) != ToLower(string[j]))
13532 if (j == length) return string;
13546 c1 = ToLower(*s1++);
13547 c2 = ToLower(*s2++);
13548 if (c1 > c2) return 1;
13549 if (c1 < c2) return -1;
13550 if (c1 == NULLCHAR) return 0;
13559 return isupper(c) ? tolower(c) : c;
13567 return islower(c) ? toupper(c) : c;
13569 #endif /* !_amigados */
13577 if ((ret = (char *) malloc(strlen(s) + 1))) {
13584 StrSavePtr(s, savePtr)
13585 char *s, **savePtr;
13590 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13591 strcpy(*savePtr, s);
13603 clock = time((time_t *)NULL);
13604 tm = localtime(&clock);
13605 sprintf(buf, "%04d.%02d.%02d",
13606 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13607 return StrSave(buf);
13612 PositionToFEN(move, overrideCastling)
13614 char *overrideCastling;
13616 int i, j, fromX, fromY, toX, toY;
13623 whiteToPlay = (gameMode == EditPosition) ?
13624 !blackPlaysFirst : (move % 2 == 0);
13627 /* Piece placement data */
13628 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13630 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13631 if (boards[move][i][j] == EmptySquare) {
13633 } else { ChessSquare piece = boards[move][i][j];
13634 if (emptycount > 0) {
13635 if(emptycount<10) /* [HGM] can be >= 10 */
13636 *p++ = '0' + emptycount;
13637 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13640 if(PieceToChar(piece) == '+') {
13641 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13643 piece = (ChessSquare)(DEMOTED piece);
13645 *p++ = PieceToChar(piece);
13647 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13648 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13653 if (emptycount > 0) {
13654 if(emptycount<10) /* [HGM] can be >= 10 */
13655 *p++ = '0' + emptycount;
13656 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13663 /* [HGM] print Crazyhouse or Shogi holdings */
13664 if( gameInfo.holdingsWidth ) {
13665 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13667 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13668 piece = boards[move][i][BOARD_WIDTH-1];
13669 if( piece != EmptySquare )
13670 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13671 *p++ = PieceToChar(piece);
13673 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13674 piece = boards[move][BOARD_HEIGHT-i-1][0];
13675 if( piece != EmptySquare )
13676 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13677 *p++ = PieceToChar(piece);
13680 if( q == p ) *p++ = '-';
13686 *p++ = whiteToPlay ? 'w' : 'b';
13689 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13690 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13692 if(nrCastlingRights) {
13694 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13695 /* [HGM] write directly from rights */
13696 if(boards[move][CASTLING][2] != NoRights &&
13697 boards[move][CASTLING][0] != NoRights )
13698 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13699 if(boards[move][CASTLING][2] != NoRights &&
13700 boards[move][CASTLING][1] != NoRights )
13701 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13702 if(boards[move][CASTLING][5] != NoRights &&
13703 boards[move][CASTLING][3] != NoRights )
13704 *p++ = boards[move][CASTLING][3] + AAA;
13705 if(boards[move][CASTLING][5] != NoRights &&
13706 boards[move][CASTLING][4] != NoRights )
13707 *p++ = boards[move][CASTLING][4] + AAA;
13710 /* [HGM] write true castling rights */
13711 if( nrCastlingRights == 6 ) {
13712 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13713 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13714 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13715 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13716 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13717 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13718 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13719 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13722 if (q == p) *p++ = '-'; /* No castling rights */
13726 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13727 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13728 /* En passant target square */
13729 if (move > backwardMostMove) {
13730 fromX = moveList[move - 1][0] - AAA;
13731 fromY = moveList[move - 1][1] - ONE;
13732 toX = moveList[move - 1][2] - AAA;
13733 toY = moveList[move - 1][3] - ONE;
13734 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13735 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13736 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13738 /* 2-square pawn move just happened */
13740 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13744 } else if(move == backwardMostMove) {
13745 // [HGM] perhaps we should always do it like this, and forget the above?
13746 if((signed char)boards[move][EP_STATUS] >= 0) {
13747 *p++ = boards[move][EP_STATUS] + AAA;
13748 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13759 /* [HGM] find reversible plies */
13760 { int i = 0, j=move;
13762 if (appData.debugMode) { int k;
13763 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13764 for(k=backwardMostMove; k<=forwardMostMove; k++)
13765 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13769 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13770 if( j == backwardMostMove ) i += initialRulePlies;
13771 sprintf(p, "%d ", i);
13772 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13774 /* Fullmove number */
13775 sprintf(p, "%d", (move / 2) + 1);
13777 return StrSave(buf);
13781 ParseFEN(board, blackPlaysFirst, fen)
13783 int *blackPlaysFirst;
13793 /* [HGM] by default clear Crazyhouse holdings, if present */
13794 if(gameInfo.holdingsWidth) {
13795 for(i=0; i<BOARD_HEIGHT; i++) {
13796 board[i][0] = EmptySquare; /* black holdings */
13797 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13798 board[i][1] = (ChessSquare) 0; /* black counts */
13799 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13803 /* Piece placement data */
13804 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13807 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13808 if (*p == '/') p++;
13809 emptycount = gameInfo.boardWidth - j;
13810 while (emptycount--)
13811 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13813 #if(BOARD_FILES >= 10)
13814 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13815 p++; emptycount=10;
13816 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13817 while (emptycount--)
13818 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13820 } else if (isdigit(*p)) {
13821 emptycount = *p++ - '0';
13822 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13823 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13824 while (emptycount--)
13825 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13826 } else if (*p == '+' || isalpha(*p)) {
13827 if (j >= gameInfo.boardWidth) return FALSE;
13829 piece = CharToPiece(*++p);
13830 if(piece == EmptySquare) return FALSE; /* unknown piece */
13831 piece = (ChessSquare) (PROMOTED piece ); p++;
13832 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13833 } else piece = CharToPiece(*p++);
13835 if(piece==EmptySquare) return FALSE; /* unknown piece */
13836 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13837 piece = (ChessSquare) (PROMOTED piece);
13838 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13841 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13847 while (*p == '/' || *p == ' ') p++;
13849 /* [HGM] look for Crazyhouse holdings here */
13850 while(*p==' ') p++;
13851 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13853 if(*p == '-' ) *p++; /* empty holdings */ else {
13854 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13855 /* if we would allow FEN reading to set board size, we would */
13856 /* have to add holdings and shift the board read so far here */
13857 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13859 if((int) piece >= (int) BlackPawn ) {
13860 i = (int)piece - (int)BlackPawn;
13861 i = PieceToNumber((ChessSquare)i);
13862 if( i >= gameInfo.holdingsSize ) return FALSE;
13863 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13864 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13866 i = (int)piece - (int)WhitePawn;
13867 i = PieceToNumber((ChessSquare)i);
13868 if( i >= gameInfo.holdingsSize ) return FALSE;
13869 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13870 board[i][BOARD_WIDTH-2]++; /* black holdings */
13874 if(*p == ']') *p++;
13877 while(*p == ' ') p++;
13882 *blackPlaysFirst = FALSE;
13885 *blackPlaysFirst = TRUE;
13891 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13892 /* return the extra info in global variiables */
13894 /* set defaults in case FEN is incomplete */
13895 board[EP_STATUS] = EP_UNKNOWN;
13896 for(i=0; i<nrCastlingRights; i++ ) {
13897 board[CASTLING][i] =
13898 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13899 } /* assume possible unless obviously impossible */
13900 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13901 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13902 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13903 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13904 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13905 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13908 while(*p==' ') p++;
13909 if(nrCastlingRights) {
13910 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13911 /* castling indicator present, so default becomes no castlings */
13912 for(i=0; i<nrCastlingRights; i++ ) {
13913 board[CASTLING][i] = NoRights;
13916 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13917 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13918 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13919 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13920 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13922 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13923 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13924 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13928 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13929 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
13930 board[CASTLING][2] = whiteKingFile;
13933 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13934 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
13935 board[CASTLING][2] = whiteKingFile;
13938 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13939 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
13940 board[CASTLING][5] = blackKingFile;
13943 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13944 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
13945 board[CASTLING][5] = blackKingFile;
13948 default: /* FRC castlings */
13949 if(c >= 'a') { /* black rights */
13950 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13951 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13952 if(i == BOARD_RGHT) break;
13953 board[CASTLING][5] = i;
13955 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13956 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13958 board[CASTLING][3] = c;
13960 board[CASTLING][4] = c;
13961 } else { /* white rights */
13962 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13963 if(board[0][i] == WhiteKing) break;
13964 if(i == BOARD_RGHT) break;
13965 board[CASTLING][2] = i;
13966 c -= AAA - 'a' + 'A';
13967 if(board[0][c] >= WhiteKing) break;
13969 board[CASTLING][0] = c;
13971 board[CASTLING][1] = c;
13975 if (appData.debugMode) {
13976 fprintf(debugFP, "FEN castling rights:");
13977 for(i=0; i<nrCastlingRights; i++)
13978 fprintf(debugFP, " %d", board[CASTLING][i]);
13979 fprintf(debugFP, "\n");
13982 while(*p==' ') p++;
13985 /* read e.p. field in games that know e.p. capture */
13986 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13987 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13989 p++; board[EP_STATUS] = EP_NONE;
13991 char c = *p++ - AAA;
13993 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13994 if(*p >= '0' && *p <='9') *p++;
13995 board[EP_STATUS] = c;
14000 if(sscanf(p, "%d", &i) == 1) {
14001 FENrulePlies = i; /* 50-move ply counter */
14002 /* (The move number is still ignored) */
14009 EditPositionPasteFEN(char *fen)
14012 Board initial_position;
14014 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14015 DisplayError(_("Bad FEN position in clipboard"), 0);
14018 int savedBlackPlaysFirst = blackPlaysFirst;
14019 EditPositionEvent();
14020 blackPlaysFirst = savedBlackPlaysFirst;
14021 CopyBoard(boards[0], initial_position);
14022 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14023 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14024 DisplayBothClocks();
14025 DrawPosition(FALSE, boards[currentMove]);
14030 static char cseq[12] = "\\ ";
14032 Boolean set_cont_sequence(char *new_seq)
14037 // handle bad attempts to set the sequence
14039 return 0; // acceptable error - no debug
14041 len = strlen(new_seq);
14042 ret = (len > 0) && (len < sizeof(cseq));
14044 strcpy(cseq, new_seq);
14045 else if (appData.debugMode)
14046 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14051 reformat a source message so words don't cross the width boundary. internal
14052 newlines are not removed. returns the wrapped size (no null character unless
14053 included in source message). If dest is NULL, only calculate the size required
14054 for the dest buffer. lp argument indicats line position upon entry, and it's
14055 passed back upon exit.
14057 int wrap(char *dest, char *src, int count, int width, int *lp)
14059 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14061 cseq_len = strlen(cseq);
14062 old_line = line = *lp;
14063 ansi = len = clen = 0;
14065 for (i=0; i < count; i++)
14067 if (src[i] == '\033')
14070 // if we hit the width, back up
14071 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14073 // store i & len in case the word is too long
14074 old_i = i, old_len = len;
14076 // find the end of the last word
14077 while (i && src[i] != ' ' && src[i] != '\n')
14083 // word too long? restore i & len before splitting it
14084 if ((old_i-i+clen) >= width)
14091 if (i && src[i-1] == ' ')
14094 if (src[i] != ' ' && src[i] != '\n')
14101 // now append the newline and continuation sequence
14106 strncpy(dest+len, cseq, cseq_len);
14114 dest[len] = src[i];
14118 if (src[i] == '\n')
14123 if (dest && appData.debugMode)
14125 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14126 count, width, line, len, *lp);
14127 show_bytes(debugFP, src, count);
14128 fprintf(debugFP, "\ndest: ");
14129 show_bytes(debugFP, dest, len);
14130 fprintf(debugFP, "\n");
14132 *lp = dest ? line : old_line;
14137 // [HGM] vari: routines for shelving variations
14140 PushTail(int firstMove, int lastMove)
14142 int i, j, nrMoves = lastMove - firstMove;
14144 if(appData.icsActive) { // only in local mode
14145 forwardMostMove = currentMove; // mimic old ICS behavior
14148 if(storedGames >= MAX_VARIATIONS-1) return;
14150 // push current tail of game on stack
14151 savedResult[storedGames] = gameInfo.result;
14152 savedDetails[storedGames] = gameInfo.resultDetails;
14153 gameInfo.resultDetails = NULL;
14154 savedFirst[storedGames] = firstMove;
14155 savedLast [storedGames] = lastMove;
14156 savedFramePtr[storedGames] = framePtr;
14157 framePtr -= nrMoves; // reserve space for the boards
14158 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14159 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14160 for(j=0; j<MOVE_LEN; j++)
14161 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14162 for(j=0; j<2*MOVE_LEN; j++)
14163 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14164 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14165 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14166 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14167 pvInfoList[firstMove+i-1].depth = 0;
14168 commentList[framePtr+i] = commentList[firstMove+i];
14169 commentList[firstMove+i] = NULL;
14173 forwardMostMove = currentMove; // truncte game so we can start variation
14174 if(storedGames == 1) GreyRevert(FALSE);
14182 if(appData.icsActive) return FALSE; // only in local mode
14183 if(!storedGames) return FALSE; // sanity
14186 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14187 nrMoves = savedLast[storedGames] - currentMove;
14188 for(i=1; i<nrMoves; i++) { // copy last variation back
14189 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14190 for(j=0; j<MOVE_LEN; j++)
14191 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14192 for(j=0; j<2*MOVE_LEN; j++)
14193 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14194 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14195 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14196 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14197 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14198 commentList[currentMove+i] = commentList[framePtr+i];
14199 commentList[framePtr+i] = NULL;
14201 framePtr = savedFramePtr[storedGames];
14202 gameInfo.result = savedResult[storedGames];
14203 if(gameInfo.resultDetails != NULL) {
14204 free(gameInfo.resultDetails);
14206 gameInfo.resultDetails = savedDetails[storedGames];
14207 forwardMostMove = currentMove + nrMoves;
14208 if(storedGames == 0) GreyRevert(TRUE);
14214 { // remove all shelved variations
14216 for(i=0; i<storedGames; i++) {
14217 if(savedDetails[i])
14218 free(savedDetails[i]);
14219 savedDetails[i] = NULL;
14221 for(i=framePtr; i<MAX_MOVES; i++) {
14222 if(commentList[i]) free(commentList[i]);
14223 commentList[i] = NULL;
14225 framePtr = MAX_MOVES-1;