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"