File indexing completed on 2024-05-12 04:04:16

0001 /***************************************************************************
0002     File                 : xboardprotocol.cpp
0003     Project              : Knights
0004     Description          : Wrapper for the XBoard protocol
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2016 Alexander Semke (alexander.semke@web.de)
0007     SPDX-FileCopyrightText: 2009-2011 Miha Čančula (miha@noughmad.eu)
0008 
0009  ***************************************************************************/
0010 
0011 /***************************************************************************
0012  *                                                                         *
0013  *  SPDX-License-Identifier: GPL-2.0-or-later
0014  *                                                                         *
0015  *  This program is distributed in the hope that it will be useful,        *
0016  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
0017  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
0018  *  GNU General Public License for more details.                           *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program; if not, write to the Free Software           *
0022  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
0023  *   Boston, MA  02110-1301  USA                                           *
0024  *                                                                         *
0025  ***************************************************************************/
0026 #include "proto/xboardprotocol.h"
0027 #include "gamemanager.h"
0028 #include "knightsdebug.h"
0029 
0030 #include <KProcess>
0031 #include <QFileDialog>
0032 #include <QRegExp>
0033 
0034 using namespace Knights;
0035 
0036 XBoardProtocol::XBoardProtocol(QObject* parent) : ComputerProtocol(parent),
0037     m_resumePending(false),
0038     m_moves(0),
0039     m_increment(0),
0040     m_baseTime(0),
0041     m_timeLimit(0) {
0042 
0043 }
0044 
0045 Protocol::Features XBoardProtocol::supportedFeatures() {
0046     // FIXME: This shouldn't be hardcoded. For instance, a chess engine which
0047     // supports the XBoard protocol may or may not support the pause action; we
0048     // should find this out using the 'protover' command which will reply with
0049     // the 'feature' command. See the XBoard protocol specification:
0050     // https://home.hccnet.nl/h.g.muller/engine-intf.html
0051 
0052     //return GameOver | Pause | Draw | Adjourn | Resign | Undo | SetDifficulty | AdjustDifficulty;
0053     return GameOver | Pause | Draw | Resign | Undo | SetDifficulty | AdjustDifficulty;
0054 }
0055 
0056 XBoardProtocol::~XBoardProtocol() {
0057     if ( mProcess && mProcess->isOpen() ) {
0058         write("quit");
0059         if ( !mProcess->waitForFinished ( 500 ) )
0060             mProcess->kill();
0061     }
0062 }
0063 
0064 void XBoardProtocol::startGame() {
0065     qCDebug(LOG_KNIGHTS) << colorName(color());
0066     TimeControl c = Manager::self()->timeControl(White);
0067     if ( c.baseTime != QTime() )
0068         write(QStringLiteral("level %1 %2 %3").arg(c.moves).arg(QTime().secsTo(c.baseTime)/60).arg(c.increment));
0069 
0070     if (color() == White)
0071         write("go");
0072 
0073     m_resumePending = false;
0074 }
0075 
0076 void XBoardProtocol::move ( const Move& m ) {
0077     QString str =  m.from().string() + m.to().string();
0078     if (m.promotedType())
0079         str += Piece::charFromType ( m.promotedType() ).toLower();
0080 
0081     qCDebug(LOG_KNIGHTS) << "Player's move:" << str;
0082     write(str);
0083 
0084     lastMoveString.clear();
0085     Q_EMIT undoPossible ( false );
0086     if ( m_resumePending ) {
0087         write("go");
0088         m_resumePending = false;
0089     }
0090 }
0091 
0092 void XBoardProtocol::init (  ) {
0093     startProgram();
0094     write("xboard");
0095     initComplete();
0096 }
0097 
0098 QList< Protocol::ToolWidgetData > XBoardProtocol::toolWidgets() {
0099     return ComputerProtocol::toolWidgets();
0100 }
0101 
0102 bool XBoardProtocol::parseStub(const QString& line) {
0103     parseLine(line);
0104     return true;
0105 }
0106 
0107 bool XBoardProtocol::parseLine(const QString& line) {
0108     if ( line.isEmpty() )
0109         return true;
0110 
0111     //suppress "Invalid move" replies coming from GNU Chess.
0112     //TODO: why do we have them?
0113     if (line.contains(QLatin1String("Invalid move")))
0114         return true;
0115 
0116     //we start with GreetMessage and set the type to a different value depending on the content of the line to be parsed.
0117     //with this easily identify the actual greet message and to highlight it accordingly in the chat widget.
0118     //However, output like "TimeControl[]" is also recognized as greet message. This is not a problem from the point of view
0119     //of the different highlighting in the chat widget, but maybe we want to set such messages to StatusMessage and to
0120     //highlight differently in future.
0121     ChatWidget::MessageType type = ChatWidget::GreetMessage;
0122 
0123     bool display = true;
0124     const QRegExp position(QLatin1String("[a-h][1-8]"));
0125     if ( line.contains ( QLatin1String ( "Illegal move" ) ) ) {
0126         type = ChatWidget::ErrorMessage;
0127         Q_EMIT illegalMove();
0128     } else if ( position.indexIn(line) > -1 || line.contains ( QLatin1String ( "..." ) ) || line.contains(QLatin1String("move")) ) {
0129         type = ChatWidget::MoveMessage;
0130         QString moveString = line.split ( QLatin1Char ( ' ' ) ).last();
0131         if ( moveString == lastMoveString )
0132             return true;
0133         lastMoveString = moveString;
0134         Move m;
0135         if ( position.indexIn(line) > -1 )
0136             m.setString(moveString);
0137         else if ( moveString.contains(QLatin1String("O-O-O"))
0138                   || moveString.contains(QLatin1String("o-o-o"))
0139                   || moveString.contains(QLatin1String("0-0-0")) )
0140             m = Move::castling(Move::QueenSide, Manager::self()->activePlayer());
0141         else if ( moveString.contains(QLatin1String("O-O"))
0142                   || moveString.contains(QLatin1String("o-o"))
0143                   || moveString.contains(QLatin1String("0-0")) )
0144             m = Move::castling(Move::KingSide, Manager::self()->activePlayer());
0145         else
0146             type = ChatWidget::GeneralMessage;
0147         if ( m.isValid() ) {
0148             qCDebug(LOG_KNIGHTS) << "Move by" << attribute("program").toString() << ":" << moveString << "=>" << m;
0149             Q_EMIT pieceMoved ( m );
0150             Q_EMIT undoPossible ( true );
0151         }
0152     } else if ( line.contains ( QLatin1String ( "wins" ) ) ) {
0153         type = ChatWidget::StatusMessage;
0154         Color winner;
0155         if ( line.split ( QLatin1Char ( ' ' ) ).last().contains ( QLatin1String ( "white" ) ) )
0156             winner = White;
0157         else
0158             winner = Black;
0159         Q_EMIT gameOver ( winner );
0160         return true;
0161     } else if ( line.contains ( QLatin1String("offer") ) && line.contains ( QLatin1String("draw") ) ) {
0162         display = false;
0163         Offer o;
0164         o.action = ActionDraw;
0165         o.id = 0;
0166         o.player = color();
0167         Manager::self()->sendOffer(o);
0168     } else if ( line.startsWith ( QLatin1String("1-0") ) )
0169         Q_EMIT gameOver ( White );
0170     else if ( line.startsWith ( QLatin1String("0-1") ) )
0171         Q_EMIT gameOver ( Black );
0172     else if ( line.startsWith ( QLatin1String("1/2-1/2") ) )
0173         Q_EMIT gameOver ( NoColor );
0174 
0175     if ( display )
0176         writeToConsole ( line, type );
0177 
0178     return true;
0179 }
0180 
0181 void XBoardProtocol::acceptOffer(const Offer& offer) {
0182     qCDebug(LOG_KNIGHTS) << "Accepting offer" << offer.text;
0183     switch ( offer.action ) {
0184     case ActionDraw:
0185         setWinner(NoColor);
0186         break;
0187 
0188     case ActionAdjourn:
0189         write( QLatin1String("save ") + QFileDialog::getSaveFileName() );
0190         break;
0191 
0192     case ActionUndo:
0193         for ( int i = 0; i < offer.numberOfMoves/2; ++i )
0194             write ( "remove" );
0195         if (offer.numberOfMoves % 2) {
0196             write ( "force" );
0197             write ( "undo" );
0198 
0199             if ( Manager::self()->activePlayer() != color() )
0200                 write ( "go" );
0201             else
0202                 m_resumePending = true;
0203         }
0204         break;
0205 
0206     case ActionPause:
0207         write ( "force" );
0208         break;
0209 
0210     case ActionResume:
0211         if ( Manager::self()->activePlayer() == color() )
0212             write ( "go" );
0213         else
0214             m_resumePending = true;
0215         break;
0216 
0217     default:
0218         qCCritical(LOG_KNIGHTS) << "XBoard should not send this kind offers";
0219         break;
0220     }
0221 }
0222 
0223 void XBoardProtocol::declineOffer(const Offer& offer) {
0224     // No special action to do here, ignoring an offer is the same as declining.
0225     Q_UNUSED(offer);
0226 }
0227 
0228 void XBoardProtocol::setWinner(Color winner) {
0229     QByteArray result = "result ";
0230     switch ( winner ) {
0231     case White:
0232         result += "1-0";
0233         break;
0234     case Black:
0235         result += "0-1";
0236         break;
0237     case NoColor:
0238         result += "1/2-1/2";
0239         break;
0240     }
0241     write(QLatin1String(result));
0242 }
0243 
0244 void XBoardProtocol::makeOffer(const Offer& offer) {
0245     switch ( offer.action ) {
0246     case ActionDraw:
0247         write("draw");
0248         break;
0249 
0250     case ActionAdjourn:
0251         write( QLatin1String("save ") + QFileDialog::getSaveFileName() );
0252         offer.accept();
0253         break;
0254 
0255     case ActionUndo:
0256         for ( int i = 0; i < offer.numberOfMoves/2; ++i )
0257             write ( "remove" );
0258         if (offer.numberOfMoves % 2) {
0259             write ( "force" );
0260             write ( "undo" );
0261 
0262             if ( Manager::self()->activePlayer() != color() )
0263                 write ( "go" );
0264             else
0265                 m_resumePending = true;
0266         }
0267         offer.accept();
0268         break;
0269 
0270     case ActionPause:
0271         write ( "force" );
0272         offer.accept();
0273         break;
0274 
0275     case ActionResume:
0276         if ( Manager::self()->activePlayer() == color() )
0277             write ( "go" );
0278         else
0279             m_resumePending = true;
0280         offer.accept();
0281         break;
0282 
0283     default:
0284         break;
0285     }
0286 }
0287 
0288 void XBoardProtocol::setDifficulty(int depth, int memory) {
0289     // Gnuchess only supports 'depth', while Crafty (and the XBoard protocol) wants 'sd'.
0290     // So we give both.
0291     write ( QLatin1String("depth ") + QString::number ( depth ) );
0292     write ( QLatin1String("sd ") + QString::number ( depth ) );
0293     write ( QLatin1String("memory ") + QString::number ( memory ) );
0294 }
0295 
0296 #include "moc_xboardprotocol.cpp"