/* * backend.c -- Common back end for X and Windows NT versions of * XBoard $Id: backend.c,v 1.134 2001/02/20 03:54:45 mann Exp $ * * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts. * Enhancements Copyright 1992-96 Free Software Foundation, Inc. * * The following terms apply to Digital Equipment Corporation's copyright * interest in XBoard: * ------------------------------------------------------------------------ * All Rights Reserved * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose and without fee is hereby granted, * provided that the above copyright notice appear in all copies and that * both that copyright notice and this permission notice appear in * supporting documentation, and that the name of Digital not be * used in advertising or publicity pertaining to distribution of the * software without specific, written prior permission. * * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. * ------------------------------------------------------------------------ * * The following terms apply to the enhanced version of XBoard distributed * by the Free Software Foundation: * ------------------------------------------------------------------------ * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ------------------------------------------------------------------------ * * See the file ChangeLog for a revision history. */ #include "config.h" #include #include #include #include #include #include #if STDC_HEADERS # include # include #else /* not STDC_HEADERS */ # if HAVE_STRING_H # include # else /* not HAVE_STRING_H */ # include # endif /* not HAVE_STRING_H */ #endif /* not STDC_HEADERS */ #if HAVE_SYS_FCNTL_H # include #else /* not HAVE_SYS_FCNTL_H */ # if HAVE_FCNTL_H # include # endif /* HAVE_FCNTL_H */ #endif /* not HAVE_SYS_FCNTL_H */ #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #if defined(_amigados) && !defined(__GNUC__) struct timezone { int tz_minuteswest; int tz_dsttime; }; extern int gettimeofday(struct timeval *, struct timezone *); #endif #if HAVE_UNISTD_H # include #endif #include "common.h" #include "frontend.h" #include "backend.h" #include "parser.h" #include "moves.h" #if ZIPPY # include "zippy.h" #endif #include "backendz.h" #include "analyst.h" /* A point in time */ typedef struct { long sec; /* Assuming this is >= 32 bits */ int ms; /* Assuming this is >= 16 bits */ } TimeMark; int establish P((void)); void read_from_player P((InputSourceRef isr, VOIDSTAR closure, char *buf, int count, int error)); void read_from_ics P((InputSourceRef isr, VOIDSTAR closure, char *buf, int count, int error)); void SendToICS P((char *s)); void SendToICSDelayed P((char *s, long msdelay)); void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY)); void InitPosition P((int redraw)); void HandleMachineMove P((char *message, ChessProgramState *cps)); int AutoPlayOneMove P((void)); int LoadGameOneMove P((ChessMove readAhead)); int LoadGameFromFile P((char *filename, int n, char *title, int useList)); int LoadPositionFromFile P((char *filename, int n, char *title)); int SavePositionToFile P((char *filename)); void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar, Board board)); void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar)); void ShowMove P((int fromX, int fromY, int toX, int toY)); void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY, /*char*/int promoChar)); void BackwardInner P((int target)); void ForwardInner P((int target)); void GameEnds P((ChessMove result, char *resultDetails, int whosays)); void EditPositionDone P((void)); void PrintOpponents P((FILE *fp)); void PrintPosition P((FILE *fp, int move)); void StartChessProgram P((ChessProgramState *cps)); void SendToProgram P((char *message, ChessProgramState *cps)); void SendMoveToProgram P((int moveNum, ChessProgramState *cps)); void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure, char *buf, int count, int error)); void SendTimeControl P((ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)); char *TimeControlTagValue P((void)); void Attention P((ChessProgramState *cps)); void FeedMovesToProgram P((ChessProgramState *cps, int upto)); void ResurrectChessProgram P((void)); void DisplayComment P((int moveNumber, char *text)); void DisplayMove P((int moveNumber)); void DisplayAnalysis P((void)); void ParseGameHistory P((char *game)); void ParseBoard12 P((char *string)); void StartClocks P((void)); void SwitchClocks P((void)); void StopClocks P((void)); void ResetClocks P((void)); char *PGNDate P((void)); void SetGameInfo P((void)); Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen)); int RegisterMove P((void)); void MakeRegisteredMove P((void)); void TruncateGame P((void)); int looking_at P((char *, int *, char *)); void CopyPlayerNameIntoFileName P((char **, char *)); char *SavePart P((char *)); int SaveGameOldStyle P((FILE *)); int SaveGamePGN P((FILE *)); void GetTimeMark P((TimeMark *)); long SubtractTimeMarks P((TimeMark *, TimeMark *)); int CheckFlags P((void)); long NextTickLength P((long)); void CheckTimeControl P((void)); void show_bytes P((FILE *, char *, int)); int string_to_rating P((char *str)); void ParseFeatures P((char* args, ChessProgramState *cps)); void InitBackEnd3 P((void)); extern int tinyLayout, smallLayout; ChessProgramStats programStats; /* States for ics_getting_history */ #define H_FALSE 0 #define H_REQUESTED 1 #define H_GOT_REQ_HEADER 2 #define H_GOT_UNREQ_HEADER 3 #define H_GETTING_MOVES 4 #define H_GOT_UNWANTED_HEADER 5 /* whosays values for GameEnds */ #define GE_ICS 0 #define GE_ENGINE 1 #define GE_PLAYER 2 #define GE_FILE 3 #define GE_XBOARD 4 /* Maximum number of games in a cmail message */ #define CMAIL_MAX_GAMES 20 /* Different types of move when calling RegisterMove */ #define CMAIL_MOVE 0 #define CMAIL_RESIGN 1 #define CMAIL_DRAW 2 #define CMAIL_ACCEPT 3 /* Different types of result to remember for each game */ #define CMAIL_NOT_RESULT 0 #define CMAIL_OLD_RESULT 1 #define CMAIL_NEW_RESULT 2 /* Telnet protocol constants */ #define TN_WILL 0373 #define TN_WONT 0374 #define TN_DO 0375 #define TN_DONT 0376 #define TN_IAC 0377 #define TN_ECHO 0001 #define TN_SGA 0003 #define TN_PORT 23 /* Fake up flags for now, as we aren't keeping track of castling availability yet */ int PosFlags(index) { int flags = F_ALL_CASTLE_OK; if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE; switch (gameInfo.variant) { case VariantSuicide: case VariantGiveaway: flags |= F_IGNORE_CHECK; flags &= ~F_ALL_CASTLE_OK; break; case VariantAtomic: flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE; break; case VariantKriegspiel: flags |= F_KRIEGSPIEL_CAPTURE; break; case VariantNoCastle: flags &= ~F_ALL_CASTLE_OK; break; default: break; } return flags; } FILE *gameFileFP, *debugFP; char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ]; char bookOutput[MSG_SIZ], thinkOutput[MSG_SIZ], lastHint[MSG_SIZ]; char thinkOutput1[MSG_SIZ]; ChessProgramState first, second; /* premove variables */ int premoveToX = 0; int premoveToY = 0; int premoveFromX = 0; int premoveFromY = 0; int premovePromoChar = 0; int gotPremove = 0; Boolean alarmSounded; /* end premove variables */ #define ICS_GENERIC 0 #define ICS_ICC 1 #define ICS_FICS 2 #define ICS_CHESSNET 3 /* not really supported */ int ics_type = ICS_GENERIC; char *ics_prefix = "$"; int coachMode = 0; int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0; int pauseExamForwardMostMove = 0; int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0; int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES]; int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE; int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE; int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE; int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE; int whiteFlag = FALSE, blackFlag = FALSE; int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE; int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE; int cmailMoveType[CMAIL_MAX_GAMES]; long ics_clock_paused = 0; ProcRef icsPR = NoProc, cmailPR = NoProc; InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL; GameMode gameMode = BeginningOfGame; char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2]; char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES]; char white_holding[64], black_holding[64]; TimeMark lastNodeCountTime; long lastNodeCount=0; int have_sent_ICS_logon = 0; int movesPerSession; long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement; long timeRemaining[2][MAX_MOVES]; int matchGame = 0; TimeMark programStartTime; char ics_handle[MSG_SIZ]; int have_set_title = 0; /* animateTraining preserves the state of appData.animate * when Training mode is activated. This allows the * response to be animated when appData.animate == TRUE and * appData.animateDragging == TRUE. */ Boolean animateTraining; GameInfo gameInfo; AppData appData; Board boards[MAX_MOVES]; Board initialPosition = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing, WhiteBishop, WhiteKnight, WhiteRook }, { WhitePawn, WhitePawn, WhitePawn, WhitePawn, WhitePawn, WhitePawn, WhitePawn, WhitePawn }, { EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare }, { EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare }, { EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare }, { EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare }, { BlackPawn, BlackPawn, BlackPawn, BlackPawn, BlackPawn, BlackPawn, BlackPawn, BlackPawn }, { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackKing, BlackBishop, BlackKnight, BlackRook } }; Board twoKingsPosition = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing, WhiteKing, WhiteKnight, WhiteRook }, { WhitePawn, WhitePawn, WhitePawn, WhitePawn, WhitePawn, WhitePawn, WhitePawn, WhitePawn }, { EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare }, { EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare }, { EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare }, { EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare, EmptySquare }, { BlackPawn, BlackPawn, BlackPawn, BlackPawn, BlackPawn, BlackPawn, BlackPawn, BlackPawn }, { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackKing, BlackKing, BlackKnight, BlackRook } }; /* Convert str to a rating. Checks for special cases of "----", "++++", etc. Also strips ()'s */ int string_to_rating(str) char *str; { while(*str && !isdigit(*str)) ++str; if (!*str) return 0; /* One of the special "no rating" cases */ else return atoi(str); } void ClearProgramStats() { /* Init programStats */ programStats.movelist[0] = 0; programStats.depth = 0; programStats.nr_moves = 0; programStats.moves_left = 0; programStats.nodes = 0; programStats.time = 100; programStats.score = 0; programStats.got_only_move = 0; programStats.got_fail = 0; programStats.line_is_book = 0; } void InitBackEnd1() { int matched, min, sec; GetTimeMark(&programStartTime); ClearProgramStats(); programStats.ok_to_send = 1; programStats.seen_stat = 0; /* * Initialize game list */ ListNew(&gameList); /* * Internet chess server status */ if (appData.icsActive) { appData.matchMode = FALSE; appData.matchGames = 0; #if ZIPPY appData.noChessProgram = !appData.zippyPlay; #else appData.zippyPlay = FALSE; appData.zippyTalk = FALSE; appData.noChessProgram = TRUE; #endif if (*appData.icsHelper != NULLCHAR) { appData.useTelnet = TRUE; appData.telnetProgram = appData.icsHelper; } } else { appData.zippyTalk = appData.zippyPlay = FALSE; } /* * Parse timeControl resource */ if (!ParseTimeControl(appData.timeControl, appData.timeIncrement, appData.movesPerSession)) { char buf[MSG_SIZ]; sprintf(buf, "bad timeControl option %s", appData.timeControl); DisplayFatalError(buf, 0, 2); } /* * Parse searchTime resource */ if (*appData.searchTime != NULLCHAR) { matched = sscanf(appData.searchTime, "%d:%d", &min, &sec); if (matched == 1) { searchTime = min * 60; } else if (matched == 2) { searchTime = min * 60 + sec; } else { char buf[MSG_SIZ]; sprintf(buf, "bad searchTime option %s", appData.searchTime); DisplayFatalError(buf, 0, 2); } } first.which = "first"; second.which = "second"; first.maybeThinking = second.maybeThinking = FALSE; first.pr = second.pr = NoProc; first.isr = second.isr = NULL; first.sendTime = second.sendTime = 2; first.sendDrawOffers = 1; if (appData.firstPlaysBlack) { first.twoMachinesColor = "black\n"; second.twoMachinesColor = "white\n"; } else { first.twoMachinesColor = "white\n"; second.twoMachinesColor = "black\n"; } first.program = appData.firstChessProgram; second.program = appData.secondChessProgram; first.host = appData.firstHost; second.host = appData.secondHost; first.dir = appData.firstDirectory; second.dir = appData.secondDirectory; first.other = &second; second.other = &first; first.initString = appData.initString; second.initString = appData.secondInitString; first.computerString = appData.firstComputerString; second.computerString = appData.secondComputerString; first.useSigint = second.useSigint = TRUE; first.useSigterm = second.useSigterm = TRUE; first.reuse = appData.reuseFirst; second.reuse = appData.reuseSecond; first.useSetboard = second.useSetboard = FALSE; first.useSAN = second.useSAN = FALSE; first.usePing = second.usePing = FALSE; first.lastPing = second.lastPing = 0; first.lastPong = second.lastPong = 0; first.usePlayother = second.usePlayother = FALSE; first.useColors = second.useColors = TRUE; first.useUsermove = second.useUsermove = FALSE; first.sendICS = second.sendICS = FALSE; first.sendName = second.sendName = appData.icsActive; first.sdKludge = second.sdKludge = FALSE; first.stKludge = second.stKludge = FALSE; TidyProgramName(first.program, first.host, first.tidy); TidyProgramName(second.program, second.host, second.tidy); first.matchWins = second.matchWins = 0; strcpy(first.variants, appData.variant); strcpy(second.variants, appData.variant); first.analysisSupport = second.analysisSupport = 2; /* detect */ if (appData.firstProtocolVersion > PROTOVER || appData.firstProtocolVersion < 1) { char buf[MSG_SIZ]; sprintf(buf, "protocol version %d not supported", appData.firstProtocolVersion); DisplayFatalError(buf, 0, 2); } else { first.protocolVersion = appData.firstProtocolVersion; } if (appData.secondProtocolVersion > PROTOVER || appData.secondProtocolVersion < 1) { char buf[MSG_SIZ]; sprintf(buf, "protocol version %d not supported", appData.secondProtocolVersion); DisplayFatalError(buf, 0, 2); } else { second.protocolVersion = appData.secondProtocolVersion; } if (appData.icsActive) { appData.clockMode = TRUE; } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) { appData.clockMode = FALSE; first.sendTime = second.sendTime = 0; } #if ZIPPY /* Override some settings from environment variables, for backward compatibility. Unfortunately it's not feasible to have the env vars just set defaults, at least in xboard. Ugh. */ if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) { ZippyInit(); } #endif if (appData.noChessProgram) { programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION) + strlen(PATCHLEVEL)); sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL); } else { char *p, *q; q = first.program; while (*q != ' ' && *q != NULLCHAR) q++; p = q; while (p > first.program && *(p-1) != '/') p--; programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION) + strlen(PATCHLEVEL) + (q - p)); sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL); strncat(programVersion, p, q - p); } if (!appData.icsActive) { char buf[MSG_SIZ]; /* Check for variants that are supported only in ICS mode, or not at all. Some that are accepted here nevertheless have bugs; see comments below. */ VariantClass variant = StringToVariant(appData.variant); switch (variant) { case VariantBughouse: /* need four players and two boards */ case VariantKriegspiel: /* need to hide pieces and move details */ case VariantFischeRandom: /* castling doesn't work, shuffle not done */ sprintf(buf, "Variant %s supported only in ICS mode", appData.variant); DisplayFatalError(buf, 0, 2); return; case VariantUnknown: case VariantLoadable: case Variant28: case Variant29: case Variant30: case Variant31: case Variant32: case Variant33: case Variant34: case Variant35: case Variant36: default: sprintf(buf, "Unknown variant name %s", appData.variant); DisplayFatalError(buf, 0, 2); return; case VariantNormal: /* definitely works! */ case VariantWildCastle: /* pieces not automatically shuffled */ case VariantNoCastle: /* pieces not automatically shuffled */ case VariantCrazyhouse: /* holdings not shown, offboard interposition not understood */ case VariantLosers: /* should work except for win condition, and doesn't know captures are mandatory */ case VariantSuicide: /* should work except for win condition, and doesn't know captures are mandatory */ case VariantGiveaway: /* should work except for win condition, and doesn't know captures are mandatory */ case VariantTwoKings: /* should work */ case VariantAtomic: /* should work except for win condition */ case Variant3Check: /* should work except for win condition */ break; } } } int ParseTimeControl(tc, ti, mps) char *tc; int ti; int mps; { int matched, min, sec; matched = sscanf(tc, "%d:%d", &min, &sec); if (matched == 1) { timeControl = min * 60 * 1000; } else if (matched == 2) { timeControl = (min * 60 + sec) * 1000; } else { return FALSE; } if (ti >= 0) { timeIncrement = ti * 1000; /* convert to ms */ movesPerSession = 0; } else { timeIncrement = 0; movesPerSession = mps; } return TRUE; } void InitBackEnd2() { char buf[MSG_SIZ]; int err; if (appData.debugMode) { fprintf(debugFP, "%s\n", programVersion); } if (appData.icsActive) { err = establish(); if (err != 0) { if (*appData.icsCommPort != NULLCHAR) { sprintf(buf, "Could not open comm port %s", appData.icsCommPort); } else { sprintf(buf, "Could not connect to host %s, port %s", appData.icsHost, appData.icsPort); } DisplayFatalError(buf, err, 1); return; } SetICSMode(); telnetISR = AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR); fromUserISR = AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR); } else if (appData.noChessProgram) { SetNCPMode(); } else { SetGNUMode(); } if (*appData.cmailGameName != NULLCHAR) { SetCmailMode(); OpenLoopback(&cmailPR); cmailISR = AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR); } if (appData.matchGames > 0) { appData.matchMode = TRUE; } else if (appData.matchMode) { appData.matchGames = 1; } Reset(TRUE); if (appData.noChessProgram) { InitBackEnd3(); } else { /* kludge: allow time for "feature" command */ FreezeUI(); DisplayMessage("", "Starting chess program"); ScheduleDelayedEvent(InitBackEnd3, 2000); } } void InitBackEnd3 P((void)) { GameMode initialMode; char buf[MSG_SIZ]; ThawUI(); DisplayMessage("", ""); if (StrCaseCmp(appData.initialMode, "") == 0) { initialMode = BeginningOfGame; } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) { initialMode = TwoMachinesPlay; } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) { initialMode = AnalyzeFile; } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) { initialMode = AnalyzeMode; } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) { initialMode = MachinePlaysWhite; } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) { initialMode = MachinePlaysBlack; } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) { initialMode = EditGame; } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) { initialMode = EditPosition; } else if (StrCaseCmp(appData.initialMode, "Training") == 0) { initialMode = Training; } else { sprintf(buf, "Unknown initialMode %s", appData.initialMode); DisplayFatalError(buf, 0, 2); return; } if (appData.matchMode) { /* Set up machine vs. machine match */ if (appData.noChessProgram) { DisplayFatalError("Can't have a match with no chess programs", 0, 2); return; } matchMode = TRUE; matchGame = 1; if (*appData.loadGameFile != NULLCHAR) { if (!LoadGameFromFile(appData.loadGameFile, appData.loadGameIndex, appData.loadGameFile, FALSE)) { DisplayFatalError("Bad game file", 0, 1); return; } } else if (*appData.loadPositionFile != NULLCHAR) { if (!LoadPositionFromFile(appData.loadPositionFile, appData.loadPositionIndex, appData.loadPositionFile)) { DisplayFatalError("Bad position file", 0, 1); return; } } TwoMachinesEvent(); } else if (*appData.cmailGameName != NULLCHAR) { /* Set up cmail mode */ ReloadCmailMsgEvent(TRUE); } else { /* Set up other modes */ if (initialMode == AnalyzeFile) { if (*appData.loadGameFile == NULLCHAR) { DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1); return; } } if (*appData.loadGameFile != NULLCHAR) { (void) LoadGameFromFile(appData.loadGameFile, appData.loadGameIndex, appData.loadGameFile, TRUE); } else if (*appData.loadPositionFile != NULLCHAR) { (void) LoadPositionFromFile(appData.loadPositionFile, appData.loadPositionIndex, appData.loadPositionFile); } if (initialMode == AnalyzeMode) { if (appData.noChessProgram) { DisplayFatalError("Analysis mode requires a chess engine", 0, 2); return; } if (appData.icsActive) { DisplayFatalError("Analysis mode does not work with ICS mode",0,2); return; } AnalyzeModeEvent(); } else if (initialMode == AnalyzeFile) { ShowThinkingEvent(TRUE); AnalyzeFileEvent(); AnalysisPeriodicEvent(1); } else if (initialMode == MachinePlaysWhite) { if (appData.noChessProgram) { DisplayFatalError("MachineWhite mode requires a chess engine", 0, 2); return; } if (appData.icsActive) { DisplayFatalError("MachineWhite mode does not work with ICS mode", 0, 2); return; } MachineWhiteEvent(); } else if (initialMode == MachinePlaysBlack) { if (appData.noChessProgram) { DisplayFatalError("MachineBlack mode requires a chess engine", 0, 2); return; } if (appData.icsActive) { DisplayFatalError("MachineBlack mode does not work with ICS mode", 0, 2); return; } MachineBlackEvent(); } else if (initialMode == TwoMachinesPlay) { if (appData.noChessProgram) { DisplayFatalError("TwoMachines mode requires a chess engine", 0, 2); return; } if (appData.icsActive) { DisplayFatalError("TwoMachines mode does not work with ICS mode", 0, 2); return; } TwoMachinesEvent(); } else if (initialMode == EditGame) { EditGameEvent(); } else if (initialMode == EditPosition) { EditPositionEvent(); } else if (initialMode == Training) { if (*appData.loadGameFile == NULLCHAR) { DisplayFatalError("Training mode requires a game file", 0, 2); return; } TrainingEvent(); } } } /* * Establish will establish a contact to a remote host.port. * Sets icsPR to a ProcRef for a process (or pseudo-process) * used to talk to the host. * Returns 0 if okay, error code if not. */ int establish() { char buf[MSG_SIZ]; if (*appData.icsCommPort != NULLCHAR) { /* Talk to the host through a serial comm port */ return OpenCommPort(appData.icsCommPort, &icsPR); } else if (*appData.gateway != NULLCHAR) { if (*appData.remoteShell == NULLCHAR) { /* Use the rcmd protocol to run telnet program on a gateway host */ sprintf(buf, "%s %s %s", appData.telnetProgram, appData.icsHost, appData.icsPort); return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR); } else { /* Use the rsh program to run telnet program on a gateway host */ if (*appData.remoteUser == NULLCHAR) { sprintf(buf, "%s %s %s %s %s", appData.remoteShell, appData.gateway, appData.telnetProgram, appData.icsHost, appData.icsPort); } else { sprintf(buf, "%s -l %s %s %s %s %s", appData.remoteShell, appData.remoteUser, appData.gateway, appData.telnetProgram, appData.icsHost, appData.icsPort); } return StartChildProcess(buf, "", &icsPR); } } else if (appData.useTelnet) { return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR); } else { /* TCP socket interface differs somewhat between Unix and NT; handle details in the front end. */ return OpenTCP(appData.icsHost, appData.icsPort, &icsPR); } } void show_bytes(fp, buf, count) FILE *fp; char *buf; int count; { while (count--) { if (*buf < 040 || *(unsigned char *) buf > 0177) { fprintf(fp, "\\%03o", *buf & 0xff); } else { putc(*buf, fp); } buf++; } fflush(fp); } /* Returns an errno value */ int OutputMaybeTelnet(pr, message, count, outError) ProcRef pr; char *message; int count; int *outError; { char buf[8192], *p, *q, *buflim; int left, newcount, outcount; if (*appData.icsCommPort != NULLCHAR || appData.useTelnet || *appData.gateway != NULLCHAR) { if (appData.debugMode) { fprintf(debugFP, ">ICS: "); show_bytes(debugFP, message, count); fprintf(debugFP, "\n"); } return OutputToProcess(pr, message, count, outError); } buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */ p = message; q = buf; left = count; newcount = 0; while (left) { if (q >= buflim) { if (appData.debugMode) { fprintf(debugFP, ">ICS: "); show_bytes(debugFP, buf, newcount); fprintf(debugFP, "\n"); } outcount = OutputToProcess(pr, buf, newcount, outError); if (outcount < newcount) return -1; /* to be sure */ q = buf; newcount = 0; } if (*p == '\n') { *q++ = '\r'; newcount++; } else if (((unsigned char) *p) == TN_IAC) { *q++ = (char) TN_IAC; newcount ++; } *q++ = *p++; newcount++; left--; } if (appData.debugMode) { fprintf(debugFP, ">ICS: "); show_bytes(debugFP, buf, newcount); fprintf(debugFP, "\n"); } outcount = OutputToProcess(pr, buf, newcount, outError); if (outcount < newcount) return -1; /* to be sure */ return count; } void read_from_player(isr, closure, message, count, error) InputSourceRef isr; VOIDSTAR closure; char *message; int count; int error; { int outError, outCount; static int gotEof = 0; /* Pass data read from player on to ICS */ if (count > 0) { gotEof = 0; outCount = OutputMaybeTelnet(icsPR, message, count, &outError); if (outCount < count) { DisplayFatalError("Error writing to ICS", outError, 1); } } else if (count < 0) { RemoveInputSource(isr); DisplayFatalError("Error reading from keyboard", error, 1); } else if (gotEof++ > 0) { RemoveInputSource(isr); DisplayFatalError("Got end of file from keyboard", 0, 0); } } void SendToICS(s) char *s; { int count, outCount, outError; if (icsPR == NULL) return; count = strlen(s); outCount = OutputMaybeTelnet(icsPR, s, count, &outError); if (outCount < count) { DisplayFatalError("Error writing to ICS", outError, 1); } } /* This is used for sending logon scripts to the ICS. Sending without a delay causes problems when using timestamp on ICC (at least on my machine). */ void SendToICSDelayed(s,msdelay) char *s; long msdelay; { int count, outCount, outError; if (icsPR == NULL) return; count = strlen(s); if (appData.debugMode) { fprintf(debugFP, ">ICS: "); show_bytes(debugFP, s, count); fprintf(debugFP, "\n"); } outCount = OutputToProcessDelayed(icsPR, s, count, &outError, msdelay); if (outCount < count) { DisplayFatalError("Error writing to ICS", outError, 1); } } /* Remove all highlighting escape sequences in s Also deletes any suffix starting with '(' */ char * StripHighlightAndTitle(s) char *s; { static char retbuf[MSG_SIZ]; char *p = retbuf; while (*s != NULLCHAR) { while (*s == '\033') { while (*s != NULLCHAR && !isalpha(*s)) s++; if (*s != NULLCHAR) s++; } while (*s != NULLCHAR && *s != '\033') { if (*s == '(' || *s == '[') { *p = NULLCHAR; return retbuf; } *p++ = *s++; } } *p = NULLCHAR; return retbuf; } /* Remove all highlighting escape sequences in s */ char * StripHighlight(s) char *s; { static char retbuf[MSG_SIZ]; char *p = retbuf; while (*s != NULLCHAR) { while (*s == '\033') { while (*s != NULLCHAR && !isalpha(*s)) s++; if (*s != NULLCHAR) s++; } while (*s != NULLCHAR && *s != '\033') { *p++ = *s++; } } *p = NULLCHAR; return retbuf; } char *variantNames[] = VARIANT_NAMES; char * VariantName(v) VariantClass v; { return variantNames[v]; } /* Identify a variant from the strings the chess servers use or the PGN Variant tag names we use. */ VariantClass StringToVariant(e) char *e; { char *p; int wnum = -1; VariantClass v = VariantNormal; int i, found = FALSE; char buf[MSG_SIZ]; if (!e) return v; for (i=0; i%s %s ", ddwwStr, optionStr); } msg[0] = TN_IAC; msg[1] = ddww; msg[2] = option; outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError); if (outCount < 3) { DisplayFatalError("Error writing to ICS", outError, 1); } } void DoEcho() { if (!appData.icsActive) return; TelnetRequest(TN_DO, TN_ECHO); } void DontEcho() { if (!appData.icsActive) return; TelnetRequest(TN_DONT, TN_ECHO); } static int loggedOn = FALSE; /*-- Game start info cache: --*/ int gs_gamenum; char gs_kind[MSG_SIZ]; static char player1Name[128] = ""; static char player2Name[128] = ""; static int player1Rating = -1; static int player2Rating = -1; /*----------------------------*/ void read_from_ics(isr, closure, data, count, error) InputSourceRef isr; VOIDSTAR closure; char *data; int count; int error; { #define BUF_SIZE 8192 #define STARTED_NONE 0 #define STARTED_MOVES 1 #define STARTED_BOARD 2 #define STARTED_OBSERVE 3 #define STARTED_HOLDINGS 4 #define STARTED_CHATTER 5 #define STARTED_COMMENT 6 #define STARTED_MOVES_NOHIDE 7 static int started = STARTED_NONE; static char parse[20000]; static int parse_pos = 0; static char buf[BUF_SIZE + 1]; static int firstTime = TRUE, intfSet = FALSE; static ColorClass curColor = ColorNormal; static ColorClass prevColor = ColorNormal; static int savingComment = FALSE; char str[500]; int i, oldi; int buf_len; int next_out; int tkind; char *p; if (count > 0) { /* If last read ended with a partial line that we couldn't parse, prepend it to the new read and try again. */ if (leftover_len > 0) { for (i=0; i= 3 && (unsigned char) buf[i] == TN_IAC) { static int remoteEchoOption = FALSE; /* telnet ECHO option */ unsigned char option; oldi = i; switch ((unsigned char) buf[++i]) { case TN_WILL: if (appData.debugMode) fprintf(debugFP, "\n next_out) SendToPlayer(&buf[next_out], oldi - next_out); if (++i > next_out) next_out = i; continue; } /* OK, this at least will *usually* work */ if (!loggedOn && looking_at(buf, &i, "ics%")) { loggedOn = TRUE; } if (loggedOn && !intfSet) { if (ics_type == ICS_ICC) { sprintf(str, "/set-quietly interface %s\n/set-quietly style 12\n", programVersion); } else if (ics_type == ICS_CHESSNET) { sprintf(str, "/style 12\n"); } else { strcpy(str, "alias $ @\n$set interface "); strcat(str, programVersion); strcat(str, "\n$iset startpos 1\n"); #ifdef WIN32 strcat(str, "$iset nohighlight 1\n"); #endif strcat(str, "$iset lock 1\n$style 12\n"); } SendToICS(str); intfSet = TRUE; } if (started == STARTED_COMMENT) { /* Accumulate characters in comment */ parse[parse_pos++] = buf[i]; if (buf[i] == '\n') { parse[parse_pos] = NULLCHAR; AppendComment(forwardMostMove, StripHighlight(parse)); started = STARTED_NONE; } else { /* Don't match patterns against characters in chatter */ i++; continue; } } if (started == STARTED_CHATTER) { if (buf[i] != '\n') { /* Don't match patterns against characters in chatter */ i++; continue; } started = STARTED_NONE; } /* Kludge to deal with rcmd protocol */ if (firstTime && looking_at(buf, &i, "\001*")) { DisplayFatalError(&buf[1], 0, 1); continue; } else { firstTime = FALSE; } if (!loggedOn && looking_at(buf, &i, "chessclub.com")) { ics_type = ICS_ICC; ics_prefix = "/"; if (appData.debugMode) fprintf(debugFP, "ics_type %d\n", ics_type); continue; } if (!loggedOn && looking_at(buf, &i, "freechess.org")) { ics_type = ICS_FICS; ics_prefix = "$"; if (appData.debugMode) fprintf(debugFP, "ics_type %d\n", ics_type); continue; } if (!loggedOn && looking_at(buf, &i, "chess.net")) { ics_type = ICS_CHESSNET; ics_prefix = "/"; if (appData.debugMode) fprintf(debugFP, "ics_type %d\n", ics_type); continue; } if (!loggedOn && (looking_at(buf, &i, "\"*\" is *a registered name") || looking_at(buf, &i, "Logging you in as \"*\"") || looking_at(buf, &i, "will be \"*\""))) { strcpy(ics_handle, star_match[0]); continue; } if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) { char buf[MSG_SIZ]; sprintf(buf, "%s@%s", ics_handle, appData.icsHost); DisplayIcsInteractionTitle(buf); have_set_title = TRUE; } /* skip finger notes */ if (started == STARTED_NONE && ((buf[i] == ' ' && isdigit(buf[i+1])) || (buf[i] == '1' && buf[i+1] == '0')) && buf[i+2] == ':' && buf[i+3] == ' ') { started = STARTED_CHATTER; i += 3; continue; } /* skip formula vars */ if (started == STARTED_NONE && buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') { started = STARTED_CHATTER; i += 3; continue; } oldi = i; if (appData.zippyTalk || appData.zippyPlay) { #if ZIPPY if (ZippyControl(buf, &i) || ZippyConverse(buf, &i) || (appData.zippyPlay && ZippyMatch(buf, &i))) { loggedOn = TRUE; continue; } #endif } else { if (/* Don't color "message" or "messages" output */ (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) || looking_at(buf, &i, "*. * at *:*: ") || looking_at(buf, &i, "--* (*:*): ") || /* Regular tells and says */ (tkind = 1, looking_at(buf, &i, "* tells you: ")) || looking_at(buf, &i, "* (your partner) tells you: ") || looking_at(buf, &i, "* says: ") || /* Message notifications (same color as tells) */ looking_at(buf, &i, "* has left a message ") || looking_at(buf, &i, "* just sent you a message:\n") || /* Whispers and kibitzes */ (tkind = 2, looking_at(buf, &i, "* whispers: ")) || looking_at(buf, &i, "* kibitzes: ") || /* Channel tells */ (tkind = 3, looking_at(buf, &i, "*(*: "))) { if (tkind == 1 && strchr(star_match[0], ':')) { /* Avoid "tells you:" spoofs in channels */ tkind = 3; } if (star_match[0][0] == NULLCHAR || strchr(star_match[0], ' ') || (tkind == 3 && strchr(star_match[1], ' '))) { /* Reject bogus matches */ i = oldi; } else { if (appData.colorize) { if (oldi > next_out) { SendToPlayer(&buf[next_out], oldi - next_out); next_out = oldi; } switch (tkind) { case 1: Colorize(ColorTell, FALSE); curColor = ColorTell; break; case 2: Colorize(ColorKibitz, FALSE); curColor = ColorKibitz; break; case 3: p = strrchr(star_match[1], '('); if (p == NULL) { p = star_match[1]; } else { p++; } if (atoi(p) == 1) { Colorize(ColorChannel1, FALSE); curColor = ColorChannel1; } else { Colorize(ColorChannel, FALSE); curColor = ColorChannel; } break; case 5: curColor = ColorNormal; break; } } if (started == STARTED_NONE && appData.autoComment && (gameMode == IcsObserving || gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) { parse_pos = i - oldi; memcpy(parse, &buf[oldi], parse_pos); parse[parse_pos] = NULLCHAR; started = STARTED_COMMENT; savingComment = TRUE; } else { started = STARTED_CHATTER; savingComment = FALSE; } loggedOn = TRUE; continue; } } if (looking_at(buf, &i, "* s-shouts: ") || looking_at(buf, &i, "* c-shouts: ")) { if (appData.colorize) { if (oldi > next_out) { SendToPlayer(&buf[next_out], oldi - next_out); next_out = oldi; } Colorize(ColorSShout, FALSE); curColor = ColorSShout; } loggedOn = TRUE; started = STARTED_CHATTER; continue; } if (looking_at(buf, &i, "--->")) { loggedOn = TRUE; continue; } if (looking_at(buf, &i, "* shouts: ") || looking_at(buf, &i, "--> ")) { if (appData.colorize) { if (oldi > next_out) { SendToPlayer(&buf[next_out], oldi - next_out); next_out = oldi; } Colorize(ColorShout, FALSE); curColor = ColorShout; } loggedOn = TRUE; started = STARTED_CHATTER; continue; } if (looking_at( buf, &i, "Challenge:")) { if (appData.colorize) { if (oldi > next_out) { SendToPlayer(&buf[next_out], oldi - next_out); next_out = oldi; } Colorize(ColorChallenge, FALSE); curColor = ColorChallenge; } loggedOn = TRUE; continue; } if (looking_at(buf, &i, "* offers you") || looking_at(buf, &i, "* offers to be") || looking_at(buf, &i, "* would like to") || looking_at(buf, &i, "* requests to") || looking_at(buf, &i, "Your opponent offers") || looking_at(buf, &i, "Your opponent requests")) { if (appData.colorize) { if (oldi > next_out) { SendToPlayer(&buf[next_out], oldi - next_out); next_out = oldi; } Colorize(ColorRequest, FALSE); curColor = ColorRequest; } continue; } if (looking_at(buf, &i, "* (*) seeking")) { if (appData.colorize) { if (oldi > next_out) { SendToPlayer(&buf[next_out], oldi - next_out); next_out = oldi; } Colorize(ColorSeek, FALSE); curColor = ColorSeek; } continue; } } if (looking_at(buf, &i, "\\ ")) { if (prevColor != ColorNormal) { if (oldi > next_out) { SendToPlayer(&buf[next_out], oldi - next_out); next_out = oldi; } Colorize(prevColor, TRUE); curColor = prevColor; } if (savingComment) { parse_pos = i - oldi; memcpy(parse, &buf[oldi], parse_pos); parse[parse_pos] = NULLCHAR; started = STARTED_COMMENT; } else { started = STARTED_CHATTER; } continue; } if (looking_at(buf, &i, "Black Strength :") || looking_at(buf, &i, "<<< style 10 board >>>") || looking_at(buf, &i, "<10>") || looking_at(buf, &i, "#@#")) { /* Wrong board style */ loggedOn = TRUE; SendToICS(ics_prefix); SendToICS("set style 12\n"); SendToICS(ics_prefix); SendToICS("refresh\n"); continue; } if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) { ICSInitScript(); have_sent_ICS_logon = 1; continue; } if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && (looking_at(buf, &i, "\n<12> ") || looking_at(buf, &i, "<12> "))) { loggedOn = TRUE; if (oldi > next_out) { SendToPlayer(&buf[next_out], oldi - next_out); } next_out = i; started = STARTED_BOARD; parse_pos = 0; continue; } if ((started == STARTED_NONE && looking_at(buf, &i, "\n ")) || looking_at(buf, &i, " ")) { if (oldi > next_out) { SendToPlayer(&buf[next_out], oldi - next_out); } next_out = i; started = STARTED_HOLDINGS; parse_pos = 0; continue; } if (looking_at(buf, &i, "* *vs. * *--- *")) { loggedOn = TRUE; /* Header for a move list -- first line */ switch (ics_getting_history) { case H_FALSE: switch (gameMode) { case IcsIdle: case BeginningOfGame: /* User typed "moves" or "oldmoves" while we were idle. Pretend we asked for these moves and soak them up so user can step through them and/or save them. */ Reset(FALSE); gameMode = IcsObserving; ModeHighlight(); ics_gamenum = -1; ics_getting_history = H_GOT_UNREQ_HEADER; break; case EditGame: /*?*/ case EditPosition: /*?*/ /* Should above feature work in these modes too? */ /* For now it doesn't */ ics_getting_history = H_GOT_UNWANTED_HEADER; break; default: ics_getting_history = H_GOT_UNWANTED_HEADER; break; } break; case H_REQUESTED: /* Is this the right one? */ if (gameInfo.white && gameInfo.black && strcmp(gameInfo.white, star_match[0]) == 0 && strcmp(gameInfo.black, star_match[2]) == 0) { /* All is well */ ics_getting_history = H_GOT_REQ_HEADER; } break; case H_GOT_REQ_HEADER: case H_GOT_UNREQ_HEADER: case H_GOT_UNWANTED_HEADER: case H_GETTING_MOVES: /* Should not happen */ DisplayError("Error gathering move list: two headers", 0); ics_getting_history = H_FALSE; break; } /* Save player ratings into gameInfo if needed */ if ((ics_getting_history == H_GOT_REQ_HEADER || ics_getting_history == H_GOT_UNREQ_HEADER) && (gameInfo.whiteRating == -1 || gameInfo.blackRating == -1)) { gameInfo.whiteRating = string_to_rating(star_match[1]); gameInfo.blackRating = string_to_rating(star_match[3]); if (appData.debugMode) fprintf(debugFP, "Ratings from header: W %d, B %d\n", gameInfo.whiteRating, gameInfo.blackRating); } continue; } if (looking_at(buf, &i, "* * match, initial time: * minute*, increment: * second")) { /* Header for a move list -- second line */ /* Initial board will follow if this is a wild game */ if (gameInfo.event != NULL) free(gameInfo.event); sprintf(str, "ICS %s %s match", star_match[0], star_match[1]); gameInfo.event = StrSave(str); gameInfo.variant = StringToVariant(gameInfo.event); continue; } if (looking_at(buf, &i, "Move ")) { /* Beginning of a move list */ switch (ics_getting_history) { case H_FALSE: /* Normally should not happen */ /* Maybe user hit reset while we were parsing */ break; case H_REQUESTED: /* Happens if we are ignoring a move list that is not * the one we just requested. Common if the user * tries to observe two games without turning off * getMoveList */ break; case H_GETTING_MOVES: /* Should not happen */ DisplayError("Error gathering move list: nested", 0); ics_getting_history = H_FALSE; break; case H_GOT_REQ_HEADER: ics_getting_history = H_GETTING_MOVES; started = STARTED_MOVES; parse_pos = 0; if (oldi > next_out) { SendToPlayer(&buf[next_out], oldi - next_out); } break; case H_GOT_UNREQ_HEADER: ics_getting_history = H_GETTING_MOVES; started = STARTED_MOVES_NOHIDE; parse_pos = 0; break; case H_GOT_UNWANTED_HEADER: ics_getting_history = H_FALSE; break; } continue; } if (looking_at(buf, &i, "% ") || ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE) && looking_at(buf, &i, "}*"))) { savingComment = FALSE; switch (started) { case STARTED_MOVES: case STARTED_MOVES_NOHIDE: memcpy(&parse[parse_pos], &buf[oldi], i - oldi); parse[parse_pos + i - oldi] = NULLCHAR; ParseGameHistory(parse); #if ZIPPY if (appData.zippyPlay) { FeedMovesToProgram(&first, forwardMostMove); if (gameMode == IcsPlayingWhite) { if (WhiteOnMove(forwardMostMove)) { if (first.sendTime) { if (first.useColors) { SendToProgram("black\n", &first); } SendTimeRemaining(&first, TRUE); } if (first.useColors) { SendToProgram("white\ngo\n", &first); } else { SendToProgram("go\n", &first); } first.maybeThinking = TRUE; } else { if (first.usePlayother) { SendToProgram("playother\n", &first); firstMove = FALSE; } else { firstMove = TRUE; } } } else if (gameMode == IcsPlayingBlack) { if (!WhiteOnMove(forwardMostMove)) { if (first.sendTime) { if (first.useColors) { SendToProgram("white\n", &first); } SendTimeRemaining(&first, FALSE); } if (first.useColors) { SendToProgram("black\ngo\n", &first); } else { SendToProgram("go\n", &first); } first.maybeThinking = TRUE; } else { if (first.usePlayother) { SendToProgram("playother\n", &first); firstMove = FALSE; } else { firstMove = TRUE; } } } } #endif if (gameMode == IcsObserving && ics_gamenum == -1) { /* Moves came from oldmoves or moves command while we weren't doing anything else. */ currentMove = forwardMostMove; ClearHighlights();/*!!could figure this out*/ flipView = appData.flipView; DrawPosition(FALSE, boards[currentMove]); DisplayBothClocks(); sprintf(str, "%s vs. %s", gameInfo.white, gameInfo.black); DisplayTitle(str); gameMode = IcsIdle; } else { /* Moves were history of an active game */ if (gameInfo.resultDetails != NULL) { free(gameInfo.resultDetails); gameInfo.resultDetails = NULL; } } HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); DisplayMove(currentMove - 1); if (started == STARTED_MOVES) next_out = i; started = STARTED_NONE; ics_getting_history = H_FALSE; break; case STARTED_OBSERVE: started = STARTED_NONE; SendToICS(ics_prefix); SendToICS("refresh\n"); break; default: break; } continue; } if ((started == STARTED_MOVES || started == STARTED_BOARD || started == STARTED_HOLDINGS || started == STARTED_MOVES_NOHIDE) && i >= leftover_len) { /* Accumulate characters in move list or board */ parse[parse_pos++] = buf[i]; } /* Start of game messages. Mostly we detect start of game when the first board image arrives. On some versions of the ICS, though, we need to do a "refresh" after starting to observe in order to get the current board right away. */ if (looking_at(buf, &i, "Adding game * to observation list")) { started = STARTED_OBSERVE; continue; } /* Handle auto-observe */ if (appData.autoObserve && (gameMode == IcsIdle || gameMode == BeginningOfGame) && looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) { char *player; /* Choose the player that was highlighted, if any. */ if (star_match[0][0] == '\033' || star_match[1][0] != '\033') { player = star_match[0]; } else { player = star_match[2]; } sprintf(str, "%sobserve %s\n", ics_prefix, StripHighlightAndTitle(player)); SendToICS(str); /* Save ratings from notify string */ strcpy(player1Name, star_match[0]); player1Rating = string_to_rating(star_match[1]); strcpy(player2Name, star_match[2]); player2Rating = string_to_rating(star_match[3]); if (appData.debugMode) fprintf(debugFP, "Ratings from 'Game notification:' %s %d, %s %d\n", player1Name, player1Rating, player2Name, player2Rating); continue; } /* Deal with automatic examine mode after a game, and with IcsObserving -> IcsExamining transition */ if (looking_at(buf, &i, "Entering examine mode for game *") || looking_at(buf, &i, "has made you an examiner of game *")) { int gamenum = atoi(star_match[0]); if ((gameMode == IcsIdle || gameMode == IcsObserving) && gamenum == ics_gamenum) { /* We were already playing or observing this game; no need to refetch history */ gameMode = IcsExamining; if (pausing) { pauseExamForwardMostMove = forwardMostMove; } else if (currentMove < forwardMostMove) { ForwardInner(forwardMostMove); } } else { /* I don't think this case really can happen */ SendToICS(ics_prefix); SendToICS("refresh\n"); } continue; } /* Error messages */ if (ics_user_moved) { if (looking_at(buf, &i, "Illegal move") || looking_at(buf, &i, "Not a legal move") || looking_at(buf, &i, "Your king is in check") || looking_at(buf, &i, "It isn't your turn") || looking_at(buf, &i, "It is not your move")) { /* Illegal move */ ics_user_moved = 0; if (forwardMostMove > backwardMostMove) { currentMove = --forwardMostMove; DisplayMove(currentMove - 1); /* before DMError */ DisplayMoveError("Illegal move (rejected by ICS)"); DrawPosition(FALSE, boards[currentMove]); SwitchClocks(); } continue; } } if (looking_at(buf, &i, "still have time") || looking_at(buf, &i, "not out of time") || looking_at(buf, &i, "either player is out of time") || looking_at(buf, &i, "has timeseal; checking")) { /* We must have called his flag a little too soon */ whiteFlag = blackFlag = FALSE; continue; } if (looking_at(buf, &i, "added * seconds to") || looking_at(buf, &i, "seconds were added to")) { /* Update the clocks */ SendToICS(ics_prefix); SendToICS("refresh\n"); continue; } if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) { ics_clock_paused = TRUE; StopClocks(); continue; } if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) { ics_clock_paused = FALSE; StartClocks(); continue; } /* Grab player ratings from the Creating: message. Note we have to check for the special case when the ICS inserts things like [white] or [black]. */ if (looking_at(buf, &i, "Creating: * (*)* * (*)") || looking_at(buf, &i, "Creating: * (*) [*] * (*)")) { /* star_matches: 0 player 1 name (not necessarily white) 1 player 1 rating 2 empty, white, or black (IGNORED) 3 player 2 name (not necessarily black) 4 player 2 rating The names/ratings are sorted out when the game actually starts (below). */ strcpy(player1Name, StripHighlightAndTitle(star_match[0])); player1Rating = string_to_rating(star_match[1]); strcpy(player2Name, StripHighlightAndTitle(star_match[3])); player2Rating = string_to_rating(star_match[4]); if (appData.debugMode) fprintf(debugFP, "Ratings from 'Creating:' %s %d, %s %d\n", player1Name, player1Rating, player2Name, player2Rating); continue; } /* Improved generic start/end-of-game messages */ if (looking_at(buf, &i, "{Game * (* vs. *) *}*")) { /* star_match[0] is the game number */ /* [1] is the white player's name */ /* [2] is the black player's name */ /* For end-of-game: */ /* [3] is the reason for the game end */ /* [4] is a PGN end game-token, preceded by " " */ /* For start-of-game: */ /* [3] begins with "Creating" or "Continuing" */ /* [4] is " *" or empty (don't care). */ int gamenum = atoi(star_match[0]); char *why = star_match[3]; char *endtoken = star_match[4]; ChessMove endtype = (ChessMove) 0; /* Game start messages */ if (strncmp(why, "Creating ", 9) == 0 || strncmp(why, "Continuing ", 11) == 0) { gs_gamenum = gamenum; strcpy(gs_kind, strchr(why, ' ') + 1); #if ZIPPY if (appData.zippyPlay) { ZippyGameStart(star_match[1], star_match[2]); } #endif /*ZIPPY*/ continue; } /* Game end messages */ if (gameMode == IcsIdle || gameMode == BeginningOfGame || ics_gamenum != gamenum) { continue; } while (endtoken[0] == ' ') endtoken++; switch (endtoken[0]) { case '*': default: endtype = GameUnfinished; break; case '0': endtype = BlackWins; break; case '1': if (endtoken[1] == '/') endtype = GameIsDrawn; else endtype = WhiteWins; break; } GameEnds(endtype, why, GE_ICS); #if ZIPPY if (appData.zippyPlay) { ZippyGameEnd(endtype, why); if (first.pr == NULL) { /* Start the next process early so that we'll be ready for the next challenge */ StartChessProgram(&first); } /* Send "new" early, in case this command takes a long time to finish, so that we'll be ready for the next challenge. */ Reset(TRUE); } #endif /*ZIPPY*/ continue; } if (looking_at(buf, &i, "Removing game * from observation") || looking_at(buf, &i, "no longer observing game *") || looking_at(buf, &i, "Game * (*) has no examiners")) { if (gameMode == IcsObserving && atoi(star_match[0]) == ics_gamenum) { StopClocks(); gameMode = IcsIdle; ics_gamenum = -1; ics_user_moved = FALSE; } continue; } if (looking_at(buf, &i, "no longer examining game *")) { if (gameMode == IcsExamining && atoi(star_match[0]) == ics_gamenum) { gameMode = IcsIdle; ics_gamenum = -1; ics_user_moved = FALSE; } continue; } /* Advance leftover_start past any newlines we find, so only partial lines can get reparsed */ if (looking_at(buf, &i, "\n")) { prevColor = curColor; if (curColor != ColorNormal) { if (oldi > next_out) { SendToPlayer(&buf[next_out], oldi - next_out); next_out = oldi; } Colorize(ColorNormal, FALSE); curColor = ColorNormal; } if (started == STARTED_BOARD) { started = STARTED_NONE; parse[parse_pos] = NULLCHAR; ParseBoard12(parse); ics_user_moved = 0; /* Send premove here */ if (appData.premove) { char str[MSG_SIZ]; if (currentMove == 0 && gameMode == IcsPlayingWhite && appData.premoveWhite) { sprintf(str, "%s%s\n", ics_prefix, appData.premoveWhiteText); if (appData.debugMode) fprintf(debugFP, "Sending premove:\n"); SendToICS(str); } else if (currentMove == 1 && gameMode == IcsPlayingBlack && appData.premoveBlack) { sprintf(str, "%s%s\n", ics_prefix, appData.premoveBlackText); if (appData.debugMode) fprintf(debugFP, "Sending premove:\n"); SendToICS(str); } else if (gotPremove) { gotPremove = 0; ClearPremoveHighlights(); if (appData.debugMode) fprintf(debugFP, "Sending premove:\n"); UserMoveEvent(premoveFromX, premoveFromY, premoveToX, premoveToY, premovePromoChar); } } /* Usually suppress following prompt */ if (!(forwardMostMove == 0 && gameMode == IcsExamining)) { if (looking_at(buf, &i, "*% ")) { savingComment = FALSE; } } next_out = i; } else if (started == STARTED_HOLDINGS) { int gamenum; char new_piece[MSG_SIZ]; started = STARTED_NONE; parse[parse_pos] = NULLCHAR; if (appData.debugMode) fprintf(debugFP, "Parsing holdings: %s\n", parse); if (sscanf(parse, " game %d", &gamenum) == 1 && gamenum == ics_gamenum) { if (gameInfo.variant == VariantNormal) { gameInfo.variant = VariantBughouse; if (appData.debugMode) { fprintf(debugFP, "guessing this is bughouse\n"); } } new_piece[0] = NULLCHAR; sscanf(parse, "game %d white [%s black [%s <- %s", &gamenum, white_holding, black_holding, new_piece); white_holding[strlen(white_holding)-1] = NULLCHAR; black_holding[strlen(black_holding)-1] = NULLCHAR; #if ZIPPY if (appData.zippyPlay) { ZippyHoldings(white_holding, black_holding, new_piece); } #endif /*ZIPPY*/ if (tinyLayout || smallLayout) { char wh[16], bh[16]; PackHolding(wh, white_holding); PackHolding(bh, black_holding); sprintf(str, "[%s-%s] %s-%s", wh, bh, gameInfo.white, gameInfo.black); } else { sprintf(str, "%s [%s] vs. %s [%s]", gameInfo.white, white_holding, gameInfo.black, black_holding); } DrawPosition(FALSE, NULL); DisplayTitle(str); } /* Suppress following prompt */ if (looking_at(buf, &i, "*% ")) { savingComment = FALSE; } next_out = i; } continue; } i++; /* skip unparsed character and loop back */ } if (started != STARTED_MOVES && started != STARTED_BOARD && started != STARTED_HOLDINGS && i > next_out) { SendToPlayer(&buf[next_out], i - next_out); next_out = i; } leftover_len = buf_len - leftover_start; /* if buffer ends with something we couldn't parse, reparse it after appending the next read */ } else if (count == 0) { RemoveInputSource(isr); DisplayFatalError("Connection closed by ICS", 0, 0); } else { DisplayFatalError("Error reading from ICS", error, 1); } } /* Board style 12 looks like this: <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 * The "<12> " is stripped before it gets to this routine. The two * trailing 0's (flip state and clock ticking) are later addition, and * some chess servers may not have them, or may have only the first. * Additional trailing fields may be added in the future. */ #define PATTERN "%72c%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d" #define RELATION_OBSERVING_PLAYED 0 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */ #define RELATION_PLAYING_MYMOVE 1 #define RELATION_PLAYING_NOTMYMOVE -1 #define RELATION_EXAMINING 2 #define RELATION_ISOLATED_BOARD -3 #define RELATION_STARTING_POSITION -4 /* FICS only */ void ParseBoard12(string) char *string; { GameMode newGameMode; int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0; int j, k, n, moveNum, white_stren, black_stren, white_time, black_time; int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count; char to_play, board_chars[72]; char move_str[500], str[500], elapsed_time[500]; char black[32], white[32]; Board board; int prevMove = currentMove; int ticking = 2; ChessMove moveType; int fromX, fromY, toX, toY; char promoChar; fromX = fromY = toX = toY = -1; newGame = FALSE; if (appData.debugMode) fprintf(debugFP, "Parsing board: %s\n", string); move_str[0] = NULLCHAR; elapsed_time[0] = NULLCHAR; n = sscanf(string, PATTERN, board_chars, &to_play, &double_push, &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count, &gamenum, white, black, &relation, &basetime, &increment, &white_stren, &black_stren, &white_time, &black_time, &moveNum, str, elapsed_time, move_str, &ics_flip, &ticking); if (n < 22) { sprintf(str, "Failed to parse board string:\n\"%s\"", string); DisplayError(str, 0); return; } /* Convert the move number to internal form */ moveNum = (moveNum - 1) * 2; if (to_play == 'B') moveNum++; if (moveNum >= MAX_MOVES) { DisplayFatalError("Game too long; increase MAX_MOVES and recompile", 0, 1); return; } switch (relation) { case RELATION_OBSERVING_PLAYED: case RELATION_OBSERVING_STATIC: if (gamenum == -1) { /* Old ICC buglet */ relation = RELATION_OBSERVING_STATIC; } newGameMode = IcsObserving; break; case RELATION_PLAYING_MYMOVE: case RELATION_PLAYING_NOTMYMOVE: newGameMode = ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ? IcsPlayingWhite : IcsPlayingBlack; break; case RELATION_EXAMINING: newGameMode = IcsExamining; break; case RELATION_ISOLATED_BOARD: default: /* Just display this board. If user was doing something else, we will forget about it until the next board comes. */ newGameMode = IcsIdle; break; case RELATION_STARTING_POSITION: newGameMode = gameMode; break; } /* Modify behavior for initial board display on move listing of wild games. */ switch (ics_getting_history) { case H_FALSE: case H_REQUESTED: break; case H_GOT_REQ_HEADER: case H_GOT_UNREQ_HEADER: /* This is the initial position of the current game */ gamenum = ics_gamenum; moveNum = 0; /* old ICS bug workaround */ if (to_play == 'B') { startedFromSetupPosition = TRUE; blackPlaysFirst = TRUE; moveNum = 1; if (forwardMostMove == 0) forwardMostMove = 1; if (backwardMostMove == 0) backwardMostMove = 1; if (currentMove == 0) currentMove = 1; } newGameMode = gameMode; relation = RELATION_STARTING_POSITION; /* ICC needs this */ break; case H_GOT_UNWANTED_HEADER: /* This is an initial board that we don't want */ return; case H_GETTING_MOVES: /* Should not happen */ DisplayError("Error gathering move list: extra board", 0); ics_getting_history = H_FALSE; return; } /* Take action if this is the first board of a new game, or of a different game than is currently being displayed. */ if (gamenum != ics_gamenum || newGameMode != gameMode || relation == RELATION_ISOLATED_BOARD) { /* Forget the old game and get the history (if any) of the new one */ if (gameMode != BeginningOfGame) { Reset(FALSE); } newGame = TRUE; if (appData.autoRaiseBoard) BoardToTop(); prevMove = -3; if (gamenum == -1) { newGameMode = IcsIdle; } else if (moveNum > 0 && newGameMode != IcsIdle && appData.getMoveList) { /* Need to get game history */ ics_getting_history = H_REQUESTED; sprintf(str, "%smoves %d\n", ics_prefix, gamenum); SendToICS(str); } /* Initially flip the board to have black on the bottom if playing black or if the ICS flip flag is set, but let the user change it with the Flip View button. */ flipView = appData.autoFlipView ? (newGameMode == IcsPlayingBlack) || ics_flip : appData.flipView; /* Done with values from previous mode; copy in new ones */ gameMode = newGameMode; ModeHighlight(); ics_gamenum = gamenum; if (gamenum == gs_gamenum) { int klen = strlen(gs_kind); if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR; sprintf(str, "ICS %s", gs_kind); gameInfo.event = StrSave(str); } else { gameInfo.event = StrSave("ICS game"); } gameInfo.site = StrSave(appData.icsHost); gameInfo.date = PGNDate(); gameInfo.round = StrSave("-"); gameInfo.white = StrSave(white); gameInfo.black = StrSave(black); timeControl = basetime * 60 * 1000; timeIncrement = increment * 1000; movesPerSession = 0; gameInfo.timeControl = TimeControlTagValue(); gameInfo.variant = StringToVariant(gameInfo.event); /* Do we have the ratings? */ if (strcmp(player1Name, white) == 0 && strcmp(player2Name, black) == 0) { if (appData.debugMode) fprintf(debugFP, "Remembered ratings: W %d, B %d\n", player1Rating, player2Rating); gameInfo.whiteRating = player1Rating; gameInfo.blackRating = player2Rating; } else if (strcmp(player2Name, white) == 0 && strcmp(player1Name, black) == 0) { if (appData.debugMode) fprintf(debugFP, "Remembered ratings: W %d, B %d\n", player2Rating, player1Rating); gameInfo.whiteRating = player2Rating; gameInfo.blackRating = player1Rating; } player1Name[0] = player2Name[0] = NULLCHAR; /* Silence shouts if requested */ if (appData.quietPlay && (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) { SendToICS(ics_prefix); SendToICS("set shout 0\n"); } } /* Deal with midgame name changes */ if (!newGame) { if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) { if (gameInfo.white) free(gameInfo.white); gameInfo.white = StrSave(white); } if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) { if (gameInfo.black) free(gameInfo.black); gameInfo.black = StrSave(black); } } /* Throw away game result if anything actually changes in examine mode */ if (gameMode == IcsExamining && !newGame) { gameInfo.result = GameUnfinished; if (gameInfo.resultDetails != NULL) { free(gameInfo.resultDetails); gameInfo.resultDetails = NULL; } } /* In pausing && IcsExamining mode, we ignore boards coming in if they are in a different variation than we are. */ if (pauseExamInvalid) return; if (pausing && gameMode == IcsExamining) { if (moveNum <= pauseExamForwardMostMove) { pauseExamInvalid = TRUE; forwardMostMove = pauseExamForwardMostMove; return; } } /* Parse the board */ for (k = 0; k < 8; k++) for (j = 0; j < 8; j++) board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]); CopyBoard(boards[moveNum], board); if (moveNum == 0) { startedFromSetupPosition = !CompareBoards(board, initialPosition); } if (ics_getting_history == H_GOT_REQ_HEADER || ics_getting_history == H_GOT_UNREQ_HEADER) { /* This was an initial position from a move list, not the current position */ return; } /* Update currentMove and known move number limits */ newMove = newGame || moveNum > forwardMostMove; if (newGame) { forwardMostMove = backwardMostMove = currentMove = moveNum; if (gameMode == IcsExamining && moveNum == 0) { /* Workaround for ICS limitation: we are not told the wild type when starting to examine a game. But if we ask for the move list, the move list header will tell us */ ics_getting_history = H_REQUESTED; sprintf(str, "%smoves %d\n", ics_prefix, gamenum); SendToICS(str); } } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) { forwardMostMove = moveNum; if (!pausing || currentMove > forwardMostMove) currentMove = forwardMostMove; } else { /* New part of history that is not contiguous with old part */ if (pausing && gameMode == IcsExamining) { pauseExamInvalid = TRUE; forwardMostMove = pauseExamForwardMostMove; return; } forwardMostMove = backwardMostMove = currentMove = moveNum; if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) { ics_getting_history = H_REQUESTED; sprintf(str, "%smoves %d\n", ics_prefix, gamenum); SendToICS(str); } } /* Update the clocks */ timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000; timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000; #if ZIPPY if (appData.zippyPlay && newGame && gameMode != IcsObserving && gameMode != IcsIdle && gameMode != IcsExamining) ZippyFirstBoard(moveNum, basetime, increment); #endif /* Put the move on the move list, first converting to canonical algebraic form. */ if (moveNum > 0) { if (moveNum <= backwardMostMove) { /* We don't know what the board looked like before this move. Punt. */ strcpy(parseList[moveNum - 1], move_str); strcat(parseList[moveNum - 1], " "); strcat(parseList[moveNum - 1], elapsed_time); moveList[moveNum - 1][0] = NULLCHAR; } else if (ParseOneMove(move_str, moveNum - 1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { (void) CoordsToAlgebraic(boards[moveNum - 1], PosFlags(moveNum - 1), EP_UNKNOWN, fromY, fromX, toY, toX, promoChar, parseList[moveNum-1]); switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){ case MT_NONE: case MT_STALEMATE: default: break; case MT_CHECK: strcat(parseList[moveNum - 1], "+"); break; case MT_CHECKMATE: strcat(parseList[moveNum - 1], "#"); break; } strcat(parseList[moveNum - 1], " "); strcat(parseList[moveNum - 1], elapsed_time); /* currentMoveString is set as a side-effect of ParseOneMove */ strcpy(moveList[moveNum - 1], currentMoveString); strcat(moveList[moveNum - 1], "\n"); } else if (strcmp(move_str, "none") == 0) { /* Again, we don't know what the board looked like; this is really the start of the game. */ parseList[moveNum - 1][0] = NULLCHAR; moveList[moveNum - 1][0] = NULLCHAR; backwardMostMove = moveNum; startedFromSetupPosition = TRUE; fromX = fromY = toX = toY = -1; } else { /* Move from ICS was illegal!? Punt. */ #if 0 if (appData.testLegality && appData.debugMode) { sprintf(str, "Illegal move \"%s\" from ICS", move_str); DisplayError(str, 0); } #endif strcpy(parseList[moveNum - 1], move_str); strcat(parseList[moveNum - 1], " "); strcat(parseList[moveNum - 1], elapsed_time); moveList[moveNum - 1][0] = NULLCHAR; fromX = fromY = toX = toY = -1; } #if ZIPPY /* Send move to chess program (BEFORE animating it). */ if (appData.zippyPlay && !newGame && newMove && (!appData.getMoveList || backwardMostMove == 0)) { if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) || (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) { if (moveList[moveNum - 1][0] == NULLCHAR) { sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str); DisplayError(str, 0); } else { if (first.sendTime) { SendTimeRemaining(&first, gameMode == IcsPlayingWhite); } SendMoveToProgram(moveNum - 1, &first); if (firstMove) { firstMove = FALSE; if (first.useColors) { SendToProgram(gameMode == IcsPlayingWhite ? "white\ngo\n" : "black\ngo\n", &first); } else { SendToProgram("go\n", &first); } first.maybeThinking = TRUE; } } } else if (gameMode == IcsObserving || gameMode == IcsExamining) { if (moveList[moveNum - 1][0] == NULLCHAR) { sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str); DisplayError(str, 0); } else { SendMoveToProgram(moveNum - 1, &first); } } } #endif } if (moveNum > 0 && !gotPremove) { /* If move comes from a remote source, animate it. If it isn't remote, it will have already been animated. */ if (!pausing && !ics_user_moved && prevMove == moveNum - 1) { AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY); } if (!pausing && appData.highlightLastMove) { SetHighlights(fromX, fromY, toX, toY); } } /* Start the clocks */ whiteFlag = blackFlag = FALSE; appData.clockMode = !(basetime == 0 && increment == 0); if (ticking == 0) { ics_clock_paused = TRUE; StopClocks(); } else if (ticking == 1) { ics_clock_paused = FALSE; } if (gameMode == IcsIdle || relation == RELATION_OBSERVING_STATIC || relation == RELATION_EXAMINING || ics_clock_paused) DisplayBothClocks(); else StartClocks(); /* Display opponents and material strengths */ if (gameInfo.variant != VariantBughouse && gameInfo.variant != VariantCrazyhouse) { if (tinyLayout || smallLayout) { sprintf(str, "%s(%d) %s(%d) {%d %d}", gameInfo.white, white_stren, gameInfo.black, black_stren, basetime, increment); } else { sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", gameInfo.white, white_stren, gameInfo.black, black_stren, basetime, increment); } DisplayTitle(str); } /* Display the board */ if (!pausing) { if (appData.premove) if (!gotPremove || ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) || ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove)))) ClearPremoveHighlights(); DrawPosition(FALSE, boards[currentMove]); DisplayMove(moveNum - 1); if (appData.ringBellAfterMoves && !ics_user_moved) RingBell(); } HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); } void GetMoveListEvent() { char buf[MSG_SIZ]; if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) { ics_getting_history = H_REQUESTED; sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum); SendToICS(buf); } } void AnalysisPeriodicEvent(force) int force; { if (((programStats.ok_to_send == 0 || programStats.line_is_book) && !force) || !appData.periodicUpdates) return; /* Send . command to Crafty to collect stats */ SendToProgram(".\n", &first); /* Don't send another until we get a response (this makes us stop sending to old Crafty's which don't understand the "." command (sending illegal cmds resets node count & time, which looks bad)) */ programStats.ok_to_send = 0; } void SendMoveToProgram(moveNum, cps) int moveNum; ChessProgramState *cps; { char buf[MSG_SIZ]; if (cps->useUsermove) { SendToProgram("usermove ", cps); } if (cps->useSAN) { char *space; if ((space = strchr(parseList[moveNum], ' ')) != NULL) { int len = space - parseList[moveNum]; memcpy(buf, parseList[moveNum], len); buf[len++] = '\n'; buf[len] = NULLCHAR; } else { sprintf(buf, "%s\n", parseList[moveNum]); } SendToProgram(buf, cps); } else { SendToProgram(moveList[moveNum], cps); } } void SendMoveToICS(moveType, fromX, fromY, toX, toY) ChessMove moveType; int fromX, fromY, toX, toY; { char user_move[MSG_SIZ]; switch (moveType) { default: sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)", (int)moveType, fromX, fromY, toX, toY); DisplayError(user_move + strlen("say "), 0); break; case WhiteKingSideCastle: case BlackKingSideCastle: case WhiteQueenSideCastleWild: case BlackQueenSideCastleWild: sprintf(user_move, "o-o\n"); break; case WhiteQueenSideCastle: case BlackQueenSideCastle: case WhiteKingSideCastleWild: case BlackKingSideCastleWild: sprintf(user_move, "o-o-o\n"); break; case WhitePromotionQueen: case BlackPromotionQueen: case WhitePromotionRook: case BlackPromotionRook: case WhitePromotionBishop: case BlackPromotionBishop: case WhitePromotionKnight: case BlackPromotionKnight: case WhitePromotionKing: case BlackPromotionKing: sprintf(user_move, "%c%c%c%c=%c\n", 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY, PieceToChar(PromoPiece(moveType))); break; case WhiteDrop: case BlackDrop: sprintf(user_move, "%c@%c%c\n", ToUpper(PieceToChar((ChessSquare) fromX)), 'a' + toX, '1' + toY); break; case NormalMove: case WhiteCapturesEnPassant: case BlackCapturesEnPassant: case IllegalMove: /* could be a variant we don't quite understand */ sprintf(user_move, "%c%c%c%c\n", 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY); break; } SendToICS(user_move); } void CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move) int rf, ff, rt, ft; char promoChar; char move[7]; { if (rf == DROP_RANK) { sprintf(move, "%c@%c%c\n", ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt); } else { if (promoChar == 'x' || promoChar == NULLCHAR) { sprintf(move, "%c%c%c%c\n", 'a' + ff, '1' + rf, 'a' + ft, '1' + rt); } else { sprintf(move, "%c%c%c%c%c\n", 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar); } } } void ProcessICSInitScript(f) FILE *f; { char buf[MSG_SIZ]; while (fgets(buf, MSG_SIZ, f)) { SendToICSDelayed(buf,(long)appData.msLoginDelay); } fclose(f); } /* Parser for moves from gnuchess, ICS, or user typein box */ Boolean ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) char *move; int moveNum; ChessMove *moveType; int *fromX, *fromY, *toX, *toY; char *promoChar; { *moveType = yylexstr(moveNum, move); switch (*moveType) { case WhitePromotionQueen: case BlackPromotionQueen: case WhitePromotionRook: case BlackPromotionRook: case WhitePromotionBishop: case BlackPromotionBishop: case WhitePromotionKnight: case BlackPromotionKnight: case WhitePromotionKing: case BlackPromotionKing: case NormalMove: case WhiteCapturesEnPassant: case BlackCapturesEnPassant: case WhiteKingSideCastle: case WhiteQueenSideCastle: case BlackKingSideCastle: case BlackQueenSideCastle: case WhiteKingSideCastleWild: case WhiteQueenSideCastleWild: case BlackKingSideCastleWild: case BlackQueenSideCastleWild: case IllegalMove: /* bug or odd chess variant */ *fromX = currentMoveString[0] - 'a'; *fromY = currentMoveString[1] - '1'; *toX = currentMoveString[2] - 'a'; *toY = currentMoveString[3] - '1'; *promoChar = currentMoveString[4]; if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 || *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) { *fromX = *fromY = *toX = *toY = 0; return FALSE; } if (appData.testLegality) { return (*moveType != IllegalMove); } else { return !(fromX == fromY && toX == toY); } case WhiteDrop: case BlackDrop: *fromX = *moveType == WhiteDrop ? (int) CharToPiece(ToUpper(currentMoveString[0])) : (int) CharToPiece(ToLower(currentMoveString[0])); *fromY = DROP_RANK; *toX = currentMoveString[2] - 'a'; *toY = currentMoveString[3] - '1'; *promoChar = NULLCHAR; return TRUE; case AmbiguousMove: case ImpossibleMove: case (ChessMove) 0: /* end of file */ case ElapsedTime: case Comment: case PGNTag: case NAG: case WhiteWins: case BlackWins: case GameIsDrawn: default: /* bug? */ *fromX = *fromY = *toX = *toY = 0; *promoChar = NULLCHAR; return FALSE; } } void InitPosition(redraw) int redraw; { currentMove = forwardMostMove = backwardMostMove = 0; switch (gameInfo.variant) { default: CopyBoard(boards[0], initialPosition); break; case VariantTwoKings: CopyBoard(boards[0], twoKingsPosition); startedFromSetupPosition = TRUE; break; case VariantWildCastle: CopyBoard(boards[0], initialPosition); /* !!?shuffle with kings guaranteed to be on d or e file */ break; case VariantNoCastle: CopyBoard(boards[0], initialPosition); /* !!?unconstrained back-rank shuffle */ break; case VariantFischeRandom: CopyBoard(boards[0], initialPosition); /* !!shuffle according to FR rules */ break; } if (redraw) DrawPosition(FALSE, boards[currentMove]); } void SendBoard(cps, moveNum) ChessProgramState *cps; int moveNum; { char message[MSG_SIZ]; if (cps->useSetboard) { char* fen = PositionToFEN(moveNum); sprintf(message, "setboard %s\n", fen); SendToProgram(message, cps); free(fen); } else { ChessSquare *bp; int i, j; /* Kludge to set black to move, avoiding the troublesome and now * deprecated "black" command. */ if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps); SendToProgram("edit\n", cps); SendToProgram("#\n", cps); for (i = BOARD_SIZE - 1; i >= 0; i--) { bp = &boards[moveNum][i][0]; for (j = 0; j < BOARD_SIZE; j++, bp++) { if ((int) *bp < (int) BlackPawn) { sprintf(message, "%c%c%c\n", PieceToChar(*bp), 'a' + j, '1' + i); SendToProgram(message, cps); } } } SendToProgram("c\n", cps); for (i = BOARD_SIZE - 1; i >= 0; i--) { bp = &boards[moveNum][i][0]; for (j = 0; j < BOARD_SIZE; j++, bp++) { if (((int) *bp != (int) EmptySquare) && ((int) *bp >= (int) BlackPawn)) { sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)), 'a' + j, '1' + i); SendToProgram(message, cps); } } } SendToProgram(".\n", cps); } } int IsPromotion(fromX, fromY, toX, toY) int fromX, fromY, toX, toY; { return gameMode != EditPosition && fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 && ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) || (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0)); } int PieceForSquare (x, y) int x; int y; { if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE) return -1; else return boards[currentMove][y][x]; } int OKToStartUserMove(x, y) int x, y; { ChessSquare from_piece; int white_piece; if (matchMode) return FALSE; if (gameMode == EditPosition) return TRUE; if (x >= 0 && y >= 0) from_piece = boards[currentMove][y][x]; else from_piece = EmptySquare; if (from_piece == EmptySquare) return FALSE; white_piece = (int)from_piece >= (int)WhitePawn && (int)from_piece <= (int)WhiteKing; switch (gameMode) { case PlayFromGameFile: case AnalyzeFile: case TwoMachinesPlay: case EndOfGame: return FALSE; case IcsObserving: case IcsIdle: return FALSE; case MachinePlaysWhite: case IcsPlayingBlack: if (appData.zippyPlay) return FALSE; if (white_piece) { DisplayMoveError("You are playing Black"); return FALSE; } break; case MachinePlaysBlack: case IcsPlayingWhite: if (appData.zippyPlay) return FALSE; if (!white_piece) { DisplayMoveError("You are playing White"); return FALSE; } break; case EditGame: if (!white_piece && WhiteOnMove(currentMove)) { DisplayMoveError("It is White's turn"); return FALSE; } if (white_piece && !WhiteOnMove(currentMove)) { DisplayMoveError("It is Black's turn"); return FALSE; } if (cmailMsgLoaded && (currentMove < cmailOldMove)) { /* Editing correspondence game history */ /* Could disallow this or prompt for confirmation */ cmailOldMove = -1; } if (currentMove < forwardMostMove) { /* Discarding moves */ /* Could prompt for confirmation here, but I don't think that's such a good idea */ forwardMostMove = currentMove; } break; case BeginningOfGame: if (appData.icsActive) return FALSE; if (!appData.noChessProgram) { if (!white_piece) { DisplayMoveError("You are playing White"); return FALSE; } } break; case Training: if (!white_piece && WhiteOnMove(currentMove)) { DisplayMoveError("It is White's turn"); return FALSE; } if (white_piece && !WhiteOnMove(currentMove)) { DisplayMoveError("It is Black's turn"); return FALSE; } break; default: case IcsExamining: break; } if (currentMove != forwardMostMove && gameMode != AnalyzeMode && gameMode != AnalyzeFile && gameMode != Training) { DisplayMoveError("Displayed position is not current"); return FALSE; } return TRUE; } FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL; int lastLoadGameNumber = 0, lastLoadPositionNumber = 0; int lastLoadGameUseList = FALSE; char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ]; ChessMove lastLoadGameStart = (ChessMove) 0; void UserMoveEvent(fromX, fromY, toX, toY, promoChar) int fromX, fromY, toX, toY; int promoChar; { ChessMove moveType; if (fromX < 0 || fromY < 0) return; if ((fromX == toX) && (fromY == toY)) { return; } /* Check if the user is playing in turn. This is complicated because we let the user "pick up" a piece before it is his turn. So the piece he tried to pick up may have been captured by the time he puts it down! Therefore we use the color the user is supposed to be playing in this test, not the color of the piece that is currently on the starting square---except in EditGame mode, where the user is playing both sides; fortunately there the capture race can't happen. (It can now happen in IcsExamining mode, but that's just too bad. The user will get a somewhat confusing message in that case.) */ switch (gameMode) { case PlayFromGameFile: case AnalyzeFile: case TwoMachinesPlay: case EndOfGame: case IcsObserving: case IcsIdle: /* We switched into a game mode where moves are not accepted, perhaps while the mouse button was down. */ return; case MachinePlaysWhite: /* User is moving for Black */ if (WhiteOnMove(currentMove)) { DisplayMoveError("It is White's turn"); return; } break; case MachinePlaysBlack: /* User is moving for White */ if (!WhiteOnMove(currentMove)) { DisplayMoveError("It is Black's turn"); return; } break; case EditGame: case IcsExamining: case BeginningOfGame: case AnalyzeMode: case Training: if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn && (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) { /* User is moving for Black */ if (WhiteOnMove(currentMove)) { DisplayMoveError("It is White's turn"); return; } } else { /* User is moving for White */ if (!WhiteOnMove(currentMove)) { DisplayMoveError("It is Black's turn"); return; } } break; case IcsPlayingBlack: /* User is moving for Black */ if (WhiteOnMove(currentMove)) { if (!appData.premove) { DisplayMoveError("It is White's turn"); } else { premoveToX = toX; premoveToY = toY; premoveFromX = fromX; premoveFromY = fromY; premovePromoChar = promoChar; gotPremove = 1; if (appData.debugMode) fprintf(debugFP, "Got premove: fromX %d," "fromY %d, toX %d, toY %d\n", fromX, fromY, toX, toY); } return; } break; case IcsPlayingWhite: /* User is moving for White */ if (!WhiteOnMove(currentMove)) { if (!appData.premove) { DisplayMoveError("It is Black's turn"); } else { premoveToX = toX; premoveToY = toY; premoveFromX = fromX; premoveFromY = fromY; premovePromoChar = promoChar; gotPremove = 1; if (appData.debugMode) fprintf(debugFP, "Got premove: fromX %d," "fromY %d, toX %d, toY %d\n", fromX, fromY, toX, toY); } return; } break; default: break; case EditPosition: if (toX == -2 || toY == -2) { boards[0][fromY][fromX] = EmptySquare; DrawPosition(FALSE, boards[currentMove]); } else if (toX >= 0 && toY >= 0) { boards[0][toY][toX] = boards[0][fromY][fromX]; boards[0][fromY][fromX] = EmptySquare; DrawPosition(FALSE, boards[currentMove]); } return; } if (toX < 0 || toY < 0) return; if (appData.testLegality) { moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), EP_UNKNOWN, fromY, fromX, toY, toX, promoChar); if (moveType == IllegalMove || moveType == ImpossibleMove) { DisplayMoveError("Illegal move"); return; } } else { moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar); } if (gameMode == Training) { /* compare the move played on the board to the next move in the * game. If they match, display the move and the opponent's response. * If they don't match, display an error message. */ int saveAnimate; Board testBoard; CopyBoard(testBoard, boards[currentMove]); ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard); if (CompareBoards(testBoard, boards[currentMove+1])) { ForwardInner(currentMove+1); /* Autoplay the opponent's response. * if appData.animate was TRUE when Training mode was entered, * the response will be animated. */ saveAnimate = appData.animate; appData.animate = animateTraining; ForwardInner(currentMove+1); appData.animate = saveAnimate; /* check for the end of the game */ if (currentMove >= forwardMostMove) { gameMode = PlayFromGameFile; ModeHighlight(); SetTrainingModeOff(); DisplayInformation("End of game"); } } else { DisplayError("Incorrect move", 0); } return; } FinishMove(moveType, fromX, fromY, toX, toY, promoChar); } /* Common tail of UserMoveEvent and DropMenuEvent */ void FinishMove(moveType, fromX, fromY, toX, toY, promoChar) ChessMove moveType; int fromX, fromY, toX, toY; /*char*/int promoChar; { /* Ok, now we know that the move is good, so we can kill the previous line in Analysis Mode */ if (gameMode == AnalyzeMode && currentMove < forwardMostMove) { forwardMostMove = currentMove; } /* If we need the chess program but it's dead, restart it */ ResurrectChessProgram(); /* A user move restarts a paused game*/ if (pausing) PauseEvent(); thinkOutput[0] = NULLCHAR; MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/ if (gameMode == BeginningOfGame) { if (appData.noChessProgram) { gameMode = EditGame; SetGameInfo(); } else { char buf[MSG_SIZ]; gameMode = MachinePlaysBlack; SetGameInfo(); sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black); DisplayTitle(buf); if (first.sendName) { sprintf(buf, "name %s\n", gameInfo.white); SendToProgram(buf, &first); } } ModeHighlight(); } /* Relay move to ICS or chess engine */ if (appData.icsActive) { if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsExamining) { SendMoveToICS(moveType, fromX, fromY, toX, toY); ics_user_moved = 1; } } else { if (first.sendTime && (gameMode == BeginningOfGame || gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack)) { SendTimeRemaining(&first, gameMode != MachinePlaysBlack); } if (coachMode) /* we are playing against second machine */ { if (appData.debugMode) fprintf(debugFP, "Sending User Move to second (coach mode)\n"); SendMoveToProgram(forwardMostMove-1, &second); } SendMoveToProgram(forwardMostMove-1, &first); if (gameMode != EditGame && gameMode != PlayFromGameFile) { first.maybeThinking = TRUE; } if (currentMove == cmailOldMove + 1) { cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE; } } ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ switch (gameMode) { case EditGame: switch (MateTest(boards[currentMove], PosFlags(currentMove), EP_UNKNOWN)) { case MT_NONE: case MT_CHECK: break; case MT_CHECKMATE: if (WhiteOnMove(currentMove)) { GameEnds(BlackWins, "Black mates", GE_PLAYER); } else { GameEnds(WhiteWins, "White mates", GE_PLAYER); } break; case MT_STALEMATE: GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER); break; } break; case MachinePlaysBlack: case MachinePlaysWhite: /* disable certain menu options while machine is thinking */ SetMachineThinkingEnables(); break; default: break; } } void HandleMachineMove(message, cps) char *message; ChessProgramState *cps; { char machineMove[MSG_SIZ], buf1[MSG_SIZ*4], buf2[MSG_SIZ]; char realname[MSG_SIZ]; int fromX, fromY, toX, toY; ChessMove moveType; char promoChar; char *p; int machineWhite; /* * Kludge to ignore BEL characters */ while (*message == '\007') message++; /* * Look for book output */ if (cps == &first && bookRequested) { if (message[0] == '\t' || message[0] == ' ') { /* Part of the book output is here; append it */ strcat(bookOutput, message); strcat(bookOutput, " \n"); return; } else if (bookOutput[0] != NULLCHAR) { /* All of book output has arrived; display it */ char *p = bookOutput; while (*p != NULLCHAR) { if (*p == '\t') *p = ' '; p++; } DisplayInformation(bookOutput); bookRequested = FALSE; /* Fall through to parse the current output */ } } /* * Look for machine move. */ if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) || (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) { /* This method is only useful on engines that support ping */ if (cps->lastPing != cps->lastPong) { if (gameMode == BeginningOfGame) { /* Extra move from before last new; ignore */ if (appData.debugMode) { fprintf(debugFP, "Ignoring extra move from %s\n", cps->which); } } else { if (appData.debugMode) { fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n", cps->which, gameMode); } SendToProgram("undo\n", cps); } return; } switch (gameMode) { case BeginningOfGame: /* Extra move from before last reset; ignore */ if (appData.debugMode) { fprintf(debugFP, "Ignoring extra move from %s\n", cps->which); } return; case EndOfGame: case IcsIdle: default: /* Extra move after we tried to stop. The mode test is not a reliable way of detecting this problem, but it's the best we can do on engines that don't support ping. */ if (appData.debugMode) { fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n", cps->which, gameMode); } SendToProgram("undo\n", cps); return; case MachinePlaysWhite: case IcsPlayingWhite: machineWhite = TRUE; break; case MachinePlaysBlack: case IcsPlayingBlack: machineWhite = FALSE; break; case TwoMachinesPlay: machineWhite = (cps->twoMachinesColor[0] == 'w'); break; } if (WhiteOnMove(forwardMostMove) != machineWhite) { if (appData.debugMode) { fprintf(debugFP, "Ignoring move out of turn by %s, gameMode %d" ", forwardMost %d\n", cps->which, gameMode, forwardMostMove); } return; } if (!ParseOneMove(machineMove, forwardMostMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { /* Machine move could not be parsed; ignore it. */ sprintf(buf1, "Illegal move \"%s\" from %s machine", machineMove, cps->which); /*!!if (appData.debugMode)*/ DisplayError(buf1, 0); return; } hintRequested = FALSE; lastHint[0] = NULLCHAR; bookRequested = FALSE; /* Program may be pondering now */ cps->maybeThinking = TRUE; if (cps->sendTime == 2) cps->sendTime = 1; if (cps->offeredDraw) cps->offeredDraw--; #if ZIPPY if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) { SendMoveToICS(moveType, fromX, fromY, toX, toY); ics_user_moved = 1; } #endif /* currentMoveString is set as a side-effect of ParseOneMove */ strcpy(machineMove, currentMoveString); strcat(machineMove, "\n"); strcpy(moveList[forwardMostMove], machineMove); MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/ if (gameMode == TwoMachinesPlay) { if (cps->other->sendTime) { SendTimeRemaining(cps->other, cps->other->twoMachinesColor[0] == 'w'); } SendMoveToProgram(forwardMostMove-1, cps->other); if (firstMove) { firstMove = FALSE; if (cps->other->useColors) { SendToProgram(cps->other->twoMachinesColor, cps->other); } SendToProgram("go\n", cps->other); } cps->other->maybeThinking = TRUE; } ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ if (!pausing && appData.ringBellAfterMoves) { RingBell(); } /* * Reenable menu items that were disabled while * machine was thinking */ if (gameMode != TwoMachinesPlay) SetUserThinkingEnables(); if (coachMode) { if (appData.debugMode) fprintf(debugFP, "Sending Machine Move to first (coach mode)\n"); /* send 2nd machine move to first machine to analyze */ SendMoveToProgram(forwardMostMove-1, &first); } return; } /* Set special modes for chess engines. Later something general * could be added here; for now there is just one kludge feature, * needed because Crafty 15.10 and earlier don't ignore SIGINT * when "xboard" is given as an interactive command. */ if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) { cps->useSigint = FALSE; cps->useSigterm = FALSE; } /* * Look for communication commands */ if (!strncmp(message, "telluser ", 9)) { DisplayInformation(message + 9); return; } if (!strncmp(message, "tellusererror ", 14)) { DisplayError(message + 14, 0); return; } if (!strncmp(message, "tellopponent ", 13)) { if (appData.icsActive) { if (loggedOn) { sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13); SendToICS(buf1); } } else { DisplayInformation(message + 13); } return; } if (!strncmp(message, "tellothers ", 11)) { if (appData.icsActive) { if (loggedOn) { sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11); SendToICS(buf1); } } return; } if (!strncmp(message, "tellall ", 8)) { if (appData.icsActive) { if (loggedOn) { sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8); SendToICS(buf1); } } else { DisplayInformation(message + 8); } return; } if (strncmp(message, "warning", 7) == 0) { /* Undocumented feature, use tellusererror in new code */ DisplayError(message, 0); return; } if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) { strcpy(realname, cps->tidy); strcat(realname, " query"); AskQuestion(realname, buf2, buf1, cps->pr); return; } /* Commands from the engine directly to ICS. We don't allow these to be * sent until we are logged on. Crafty kibitzes have been known to * interfere with the login process. */ if (loggedOn) { if (!strncmp(message, "tellics ", 8)) { SendToICS(message + 8); SendToICS("\n"); return; } if (!strncmp(message, "tellicsnoalias ", 15)) { SendToICS(ics_prefix); SendToICS(message + 15); SendToICS("\n"); return; } /* The following are for backward compatibility only */ if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) || !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) { SendToICS(ics_prefix); SendToICS(message); SendToICS("\n"); return; } } if (strncmp(message, "feature ", 8) == 0) { ParseFeatures(message+8, cps); } if (sscanf(message, "pong %d", &cps->lastPong) == 1) { return; } if (sscanf(message, "ping %d", &cps->lastPong) == 1) { return; } /* * If the move is illegal, cancel it and redraw the board. * Also deal with other error cases. Matching is rather loose * here to accommodate engines written before the spec. */ if (strncmp(message + 1, "llegal move", 11) == 0 || strncmp(message, "Error", 5) == 0) { if (StrStr(message, "name") || StrStr(message, "rating") || StrStr(message, "?") || StrStr(message, "result") || StrStr(message, "board") || StrStr(message, "bk") || StrStr(message, "computer") || StrStr(message, "variant") || StrStr(message, "hint") || StrStr(message, "random") || StrStr(message, "depth") || StrStr(message, "protover") || StrStr(message, "accepted")) { return; } cps->maybeThinking = FALSE; if (StrStr(message, "draw")) { /* Program doesn't have "draw" command */ cps->sendDrawOffers = 0; return; } if (StrStr(message, "time") || StrStr(message, "otim")) { /* Program doesn't have "time" or "otim" command */ cps->sendTime = 0; return; } if (StrStr(message, "analyze")) { cps->analysisSupport = FALSE; Reset(FALSE); sprintf(buf2, "%s does not support analysis", cps->tidy); DisplayError(buf2, 0); return; } if (StrStr(message, "st")) { cps->stKludge = TRUE; SendTimeControl(cps, movesPerSession, timeControl, timeIncrement, appData.searchDepth, searchTime); return; } if (StrStr(message, "sd")) { cps->sdKludge = TRUE; SendTimeControl(cps, movesPerSession, timeControl, timeIncrement, appData.searchDepth, searchTime); return; } if (!StrStr(message, "llegal")) return; if (gameMode == BeginningOfGame || gameMode == EndOfGame || gameMode == IcsIdle) return; if (forwardMostMove <= backwardMostMove) return; if (cps == &first && programStats.ok_to_send == 0) { /* Bogus message from Crafty responding to "." This filtering can miss some of the bad messages, but fortunately the bug is fixed in current Crafty versions, so it doesn't matter. */ return; } if (pausing) PauseEvent(); if (gameMode == PlayFromGameFile) { /* Stop reading this game file */ gameMode = EditGame; ModeHighlight(); } currentMove = --forwardMostMove; DisplayMove(currentMove-1); /* before DisplayMoveError */ SwitchClocks(); sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)", parseList[currentMove], cps->which); DisplayMoveError(buf1); DrawPosition(FALSE, boards[currentMove]); return; } if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) { /* Program has a broken "time" command that outputs a string not ending in newline. Don't use it. */ cps->sendTime = 0; } /* * If chess program startup fails, exit with an error message. * Attempts to recover here are futile. */ if ((StrStr(message, "unknown host") != NULL) || (StrStr(message, "No remote directory") != NULL) || (StrStr(message, "not found") != NULL) || (StrStr(message, "No such file") != NULL) || (StrStr(message, "can't alloc") != NULL) || (StrStr(message, "Permission denied") != NULL)) { cps->maybeThinking = FALSE; sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n", cps->which, cps->program, cps->host, message); RemoveInputSource(cps->isr); DisplayFatalError(buf1, 0, 1); return; } /* * Look for hint output */ if (sscanf(message, "Hint: %s", buf1) == 1) { if (cps == &first && hintRequested) { hintRequested = FALSE; if (ParseOneMove(buf1, forwardMostMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { (void) CoordsToAlgebraic(boards[forwardMostMove], PosFlags(forwardMostMove), EP_UNKNOWN, fromY, fromX, toY, toX, promoChar, buf1); sprintf(buf2, "Hint: %s", buf1); DisplayInformation(buf2); } else { /* Hint move could not be parsed!? */ sprintf(buf2, "Illegal hint move \"%s\"\nfrom %s chess program", buf1, cps->which); DisplayError(buf2, 0); } } else { strcpy(lastHint, buf1); } return; } /* * Ignore other messages if game is not in progress */ if (gameMode == BeginningOfGame || gameMode == EndOfGame || gameMode == IcsIdle || cps->lastPing != cps->lastPong) return; /* * look for win, lose, draw, or draw offer */ if (strncmp(message, "1-0", 3) == 0) { char *p, *q, *r = ""; p = strchr(message, '{'); if (p) { q = strchr(p, '}'); if (q) { *q = NULLCHAR; r = p + 1; } } GameEnds(WhiteWins, r, GE_ENGINE); return; } else if (strncmp(message, "0-1", 3) == 0) { char *p, *q, *r = ""; p = strchr(message, '{'); if (p) { q = strchr(p, '}'); if (q) { *q = NULLCHAR; r = p + 1; } } /* Kludge for Arasan 4.1 bug */ if (strcmp(r, "Black resigns") == 0) { GameEnds(WhiteWins, r, GE_ENGINE); return; } GameEnds(BlackWins, r, GE_ENGINE); return; } else if (strncmp(message, "1/2", 3) == 0) { char *p, *q, *r = ""; p = strchr(message, '{'); if (p) { q = strchr(p, '}'); if (q) { *q = NULLCHAR; r = p + 1; } } GameEnds(GameIsDrawn, r, GE_ENGINE); return; } else if (strncmp(message, "White resign", 12) == 0) { GameEnds(BlackWins, "White resigns", GE_ENGINE); return; } else if (strncmp(message, "Black resign", 12) == 0) { GameEnds(WhiteWins, "Black resigns", GE_ENGINE); return; } else if (strncmp(message, "White", 5) == 0 && message[5] != '(' && StrStr(message, "Black") == NULL) { GameEnds(WhiteWins, "White mates", GE_ENGINE); return; } else if (strncmp(message, "Black", 5) == 0 && message[5] != '(') { GameEnds(BlackWins, "Black mates", GE_ENGINE); return; } else if (strcmp(message, "resign") == 0 || strcmp(message, "computer resigns") == 0) { switch (gameMode) { case MachinePlaysBlack: GameEnds(WhiteWins, "Black resigns", GE_ENGINE); break; case MachinePlaysWhite: GameEnds(BlackWins, "White resigns", GE_ENGINE); break; case TwoMachinesPlay: if (cps->twoMachinesColor[0] == 'w') GameEnds(BlackWins, "White resigns", GE_ENGINE); else GameEnds(WhiteWins, "Black resigns", GE_ENGINE); break; default: /* can't happen */ break; } return; } else if (strncmp(message, "opponent mates", 14) == 0) { switch (gameMode) { case MachinePlaysBlack: GameEnds(WhiteWins, "White mates", GE_ENGINE); break; case MachinePlaysWhite: GameEnds(BlackWins, "Black mates", GE_ENGINE); break; case TwoMachinesPlay: if (cps->twoMachinesColor[0] == 'w') GameEnds(BlackWins, "Black mates", GE_ENGINE); else GameEnds(WhiteWins, "White mates", GE_ENGINE); break; default: /* can't happen */ break; } return; } else if (strncmp(message, "computer mates", 14) == 0) { switch (gameMode) { case MachinePlaysBlack: GameEnds(BlackWins, "Black mates", GE_ENGINE); break; case MachinePlaysWhite: GameEnds(WhiteWins, "White mates", GE_ENGINE); break; case TwoMachinesPlay: if (cps->twoMachinesColor[0] == 'w') GameEnds(WhiteWins, "White mates", GE_ENGINE); else GameEnds(BlackWins, "Black mates", GE_ENGINE); break; default: /* can't happen */ break; } return; } else if (strncmp(message, "checkmate", 9) == 0) { if (WhiteOnMove(forwardMostMove)) { GameEnds(BlackWins, "Black mates", GE_ENGINE); } else { GameEnds(WhiteWins, "White mates", GE_ENGINE); } return; } else if (strstr(message, "Draw") != NULL || strstr(message, "game is a draw") != NULL) { GameEnds(GameIsDrawn, "Draw", GE_ENGINE); return; } else if (strstr(message, "offer") != NULL && strstr(message, "draw") != NULL) { #if ZIPPY if (appData.zippyPlay) { /* Relay offer to ICS */ SendToICS(ics_prefix); SendToICS("draw\n"); } #endif cps->offeredDraw = 2; /* valid until this engine moves twice */ if (gameMode == TwoMachinesPlay) { if (cps->other->offeredDraw) { GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD); } else { if (cps->other->sendDrawOffers) { SendToProgram("draw\n", cps->other); } } } else if (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack) { DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree"); } } /* * Look for thinking output */ if (appData.showThinking) { int plylev, mvleft, mvtot, curscore, time; char mvname[MOVE_LEN]; unsigned long nodes; char plyext; int ignore = FALSE; int prefixHint = FALSE; mvname[0] = NULLCHAR; switch (gameMode) { case MachinePlaysBlack: case IcsPlayingBlack: if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE; break; case MachinePlaysWhite: case IcsPlayingWhite: if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE; break; case AnalyzeMode: case AnalyzeFile: break; case TwoMachinesPlay: if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) { ignore = TRUE; } break; default: ignore = TRUE; break; } if (coachMode || !ignore) { buf1[0] = NULLCHAR; if (sscanf(message, "%d%c %d %d %lu %[^\n]\n", &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) { if (plyext != ' ' && plyext != '\t') { time *= 100; } programStats.depth = plylev; programStats.nodes = nodes; programStats.time = time; programStats.score = curscore; strcpy(programStats.movelist, buf1); programStats.got_only_move = 0; if (programStats.seen_stat) { programStats.ok_to_send = 1; } if (strchr(programStats.movelist, '(') != NULL) { programStats.line_is_book = 1; programStats.nr_moves = 0; programStats.moves_left = 0; } else { programStats.line_is_book = 0; } sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s", plylev, (gameMode == TwoMachinesPlay ? ToUpper(cps->twoMachinesColor[0]) : ' '), ((double) curscore) / 100.0, prefixHint ? lastHint : "", prefixHint ? " " : "", buf1); if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) { DisplayMove(currentMove - 1); if (cps == &first) // only care output from first machine DisplayAnalysis(); } return; } else if ((p=StrStr(message, "(only move)")) != NULL) { /* crafty (9.25+) says "(only move) " * if there is only 1 legal move */ sscanf(p, "(only move) %s", buf1); sprintf(thinkOutput, "%s (only move)", buf1); sprintf(programStats.movelist, "%s (only move)", buf1); programStats.depth = 1; programStats.nr_moves = 1; programStats.moves_left = 1; programStats.nodes = 1; programStats.time = 1; programStats.got_only_move = 1; /* Not really, but we also use this member to mean "line isn't going to change" (Crafty isn't searching, so stats won't change) */ programStats.line_is_book = 1; if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) { DisplayMove(currentMove - 1); DisplayAnalysis(); } return; } else if (sscanf(message,"stat01: %d %lu %d %d %d %s", &time, &nodes, &plylev, &mvleft, &mvtot, mvname) >= 5) { /* The stat01: line is from Crafty (9.29+) in response to the "." command */ programStats.seen_stat = 1; cps->maybeThinking = TRUE; if (programStats.got_only_move || !appData.periodicUpdates) return; programStats.depth = plylev; programStats.time = time; programStats.nodes = nodes; programStats.moves_left = mvleft; programStats.nr_moves = mvtot; strcpy(programStats.move_name, mvname); programStats.ok_to_send = 1; DisplayAnalysis(); return; } else if (strncmp(message,"++",2) == 0) { /* Crafty 9.29+ outputs this */ programStats.got_fail = 2; return; } else if (strncmp(message,"--",2) == 0) { /* Crafty 9.29+ outputs this */ programStats.got_fail = 1; return; } else if (thinkOutput[0] != NULLCHAR && strncmp(message, " ", 4) == 0) { p = message; while (*p && *p == ' ') p++; if (strlen(thinkOutput) < 100 && strlen(programStats.movelist) < 100) { strcat(thinkOutput, " "); strcat(thinkOutput, p); strcat(programStats.movelist, " "); strcat(programStats.movelist, p); } if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) { DisplayMove(currentMove - 1); DisplayAnalysis(); } return; } } } } /* Parse a game score from the character string "game", and record it as the history of the current game. The game score is NOT assumed to start from the standard position. The display is not updated in any way. */ void ParseGameHistory(game) char *game; { ChessMove moveType; int fromX, fromY, toX, toY, boardIndex; char promoChar; char *p, *q; char buf[MSG_SIZ]; if (appData.debugMode) fprintf(debugFP, "Parsing game history: %s\n", game); if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game"); gameInfo.site = StrSave(appData.icsHost); gameInfo.date = PGNDate(); gameInfo.round = StrSave("-"); /* Parse out names of players */ while (*game == ' ') game++; p = buf; while (*game != ' ') *p++ = *game++; *p = NULLCHAR; gameInfo.white = StrSave(buf); while (*game == ' ') game++; p = buf; while (*game != ' ' && *game != '\n') *p++ = *game++; *p = NULLCHAR; gameInfo.black = StrSave(buf); /* Parse moves */ boardIndex = blackPlaysFirst ? 1 : 0; yynewstr(game); for (;;) { yyboardindex = boardIndex; moveType = (ChessMove) yylex(); switch (moveType) { case WhitePromotionQueen: case BlackPromotionQueen: case WhitePromotionRook: case BlackPromotionRook: case WhitePromotionBishop: case BlackPromotionBishop: case WhitePromotionKnight: case BlackPromotionKnight: case WhitePromotionKing: case BlackPromotionKing: case NormalMove: case WhiteCapturesEnPassant: case BlackCapturesEnPassant: case WhiteKingSideCastle: case WhiteQueenSideCastle: case BlackKingSideCastle: case BlackQueenSideCastle: case WhiteKingSideCastleWild: case WhiteQueenSideCastleWild: case BlackKingSideCastleWild: case BlackQueenSideCastleWild: case IllegalMove: /* maybe suicide chess, etc. */ fromX = currentMoveString[0] - 'a'; fromY = currentMoveString[1] - '1'; toX = currentMoveString[2] - 'a'; toY = currentMoveString[3] - '1'; promoChar = currentMoveString[4]; break; case WhiteDrop: case BlackDrop: fromX = moveType == WhiteDrop ? (int) CharToPiece(ToUpper(currentMoveString[0])) : (int) CharToPiece(ToLower(currentMoveString[0])); fromY = DROP_RANK; toX = currentMoveString[2] - 'a'; toY = currentMoveString[3] - '1'; promoChar = NULLCHAR; break; case AmbiguousMove: /* bug? */ sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text); DisplayError(buf, 0); return; case ImpossibleMove: /* bug? */ sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text); DisplayError(buf, 0); return; case (ChessMove) 0: /* end of file */ if (boardIndex < backwardMostMove) { /* Oops, gap. How did that happen? */ DisplayError("Gap in move list", 0); return; } backwardMostMove = blackPlaysFirst ? 1 : 0; if (boardIndex > forwardMostMove) { forwardMostMove = boardIndex; } return; case ElapsedTime: if (boardIndex > 0) { strcat(parseList[boardIndex-1], " "); strcat(parseList[boardIndex-1], yy_text); } continue; case Comment: case PGNTag: case NAG: default: /* ignore */ continue; case WhiteWins: case BlackWins: case GameIsDrawn: case GameUnfinished: if (gameMode == IcsExamining) { if (boardIndex < backwardMostMove) { /* Oops, gap. How did that happen? */ return; } backwardMostMove = blackPlaysFirst ? 1 : 0; return; } gameInfo.result = moveType; p = strchr(yy_text, '{'); if (p == NULL) p = strchr(yy_text, '('); if (p == NULL) { p = yy_text; if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = ""; } else { q = strchr(p, *p == '{' ? '}' : ')'); if (q != NULL) *q = NULLCHAR; p++; } gameInfo.resultDetails = StrSave(p); continue; } if (boardIndex >= forwardMostMove && !(gameMode == IcsObserving && ics_gamenum == -1)) { backwardMostMove = blackPlaysFirst ? 1 : 0; return; } (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex), EP_UNKNOWN, fromY, fromX, toY, toX, promoChar, parseList[boardIndex]); CopyBoard(boards[boardIndex + 1], boards[boardIndex]); /* currentMoveString is set as a side-effect of yylex */ strcpy(moveList[boardIndex], currentMoveString); strcat(moveList[boardIndex], "\n"); boardIndex++; ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]); switch (MateTest(boards[boardIndex], PosFlags(boardIndex), EP_UNKNOWN)) { case MT_NONE: case MT_STALEMATE: default: break; case MT_CHECK: strcat(parseList[boardIndex - 1], "+"); break; case MT_CHECKMATE: strcat(parseList[boardIndex - 1], "#"); break; } } } /* Apply a move to the given board */ void ApplyMove(fromX, fromY, toX, toY, promoChar, board) int fromX, fromY, toX, toY; int promoChar; Board board; { ChessSquare captured = board[toY][toX]; if (fromY == DROP_RANK) { /* must be first */ board[toY][toX] = (ChessSquare) fromX; } else if (fromX == toX && fromY == toY) { return; } else if (fromY == 0 && fromX == 4 && board[fromY][fromX] == WhiteKing && toY == 0 && toX == 6) { board[fromY][fromX] = EmptySquare; board[toY][toX] = WhiteKing; board[fromY][7] = EmptySquare; board[toY][5] = WhiteRook; } else if (fromY == 0 && fromX == 4 && board[fromY][fromX] == WhiteKing && toY == 0 && toX == 2) { board[fromY][fromX] = EmptySquare; board[toY][toX] = WhiteKing; board[fromY][0] = EmptySquare; board[toY][3] = WhiteRook; } else if (fromY == 0 && fromX == 3 && board[fromY][fromX] == WhiteKing && toY == 0 && toX == 5) { board[fromY][fromX] = EmptySquare; board[toY][toX] = WhiteKing; board[fromY][7] = EmptySquare; board[toY][4] = WhiteRook; } else if (fromY == 0 && fromX == 3 && board[fromY][fromX] == WhiteKing && toY == 0 && toX == 1) { board[fromY][fromX] = EmptySquare; board[toY][toX] = WhiteKing; board[fromY][0] = EmptySquare; board[toY][2] = WhiteRook; } else if (board[fromY][fromX] == WhitePawn && toY == 7) { /* white pawn promotion */ board[7][toX] = CharToPiece(ToUpper(promoChar)); if (board[7][toX] == EmptySquare) { board[7][toX] = WhiteQueen; } board[fromY][fromX] = EmptySquare; } else if ((fromY == 4) && (toX != fromX) && (board[fromY][fromX] == WhitePawn) && (board[toY][toX] == EmptySquare)) { board[fromY][fromX] = EmptySquare; board[toY][toX] = WhitePawn; captured = board[toY - 1][toX]; board[toY - 1][toX] = EmptySquare; } else if (fromY == 7 && fromX == 4 && board[fromY][fromX] == BlackKing && toY == 7 && toX == 6) { board[fromY][fromX] = EmptySquare; board[toY][toX] = BlackKing; board[fromY][7] = EmptySquar