Const correctness
[sloppygui.git] / projects / lib / src / uciengine.cpp
blob9c5df5dd56aed41cb277220df919ec5e98c24cf2
1 /*
2 This file is part of Cute Chess.
4 Cute Chess is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 Cute Chess is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with Cute Chess. If not, see <http://www.gnu.org/licenses/>.
18 #include <QString>
19 #include <QStringList>
20 #include <QRegExp>
21 #include <QDebug>
23 #include "uciengine.h"
24 #include "chessboard/chessboard.h"
25 #include "chessboard/chessmove.h"
26 #include "timecontrol.h"
27 #include "enginesettings.h"
29 #include "enginebuttonoption.h"
30 #include "enginecheckoption.h"
31 #include "enginecombooption.h"
32 #include "enginespinoption.h"
33 #include "enginetextoption.h"
36 UciEngine::UciEngine(QIODevice* ioDevice, QObject* parent)
37 : ChessEngine(ioDevice, parent)
39 m_variants.append(Chess::Variant::Standard);
41 setName("UciEngine");
44 void UciEngine::startProtocol()
46 // Tell the engine to turn on UCI mode
47 write("uci");
50 void UciEngine::sendPosition()
52 QString str("position");
54 const Chess::Variant& variant = board()->variant();
55 if (variant.isRandom() || m_startFen != variant.startingFen())
56 str += QString(" fen ") + m_startFen;
57 else
58 str += " startpos";
60 if (!m_moveStrings.isEmpty())
61 str += QString(" moves") + m_moveStrings;
63 write(str);
66 static Chess::Variant variantCode(const QString& str)
68 if (str == "UCI_Chess960")
69 return Chess::Variant::Fischerandom;
70 else if (str == "UCI_Capablanca")
71 return Chess::Variant::Capablanca;
72 else if (str == "UCI_Gothic")
73 return Chess::Variant::Gothic;
74 else if (str == "UCI_CapaRandom")
75 return Chess::Variant::Caparandom;
77 return Chess::Variant::NoVariant;
80 static QString variantString(Chess::Variant variant)
82 switch (variant.code())
84 case Chess::Variant::Fischerandom:
85 return "UCI_Chess960";
86 case Chess::Variant::Capablanca:
87 return "UCI_Capablanca";
88 case Chess::Variant::Gothic:
89 return "UCI_Gothic";
90 case Chess::Variant::Caparandom:
91 return "UCI_CapaRandom";
92 default:
93 return QString();
97 void UciEngine::startGame()
99 m_moveStrings.clear();
101 const Chess::Variant& variant = board()->variant();
102 Q_ASSERT(supportsVariant(variant));
104 if (variant.isRandom())
105 m_startFen = board()->fenString(Chess::ShredderFen);
106 else
107 m_startFen = board()->fenString();
109 if (variant != Chess::Variant::Standard)
110 setOption(variantString(variant), "true");
111 write("ucinewgame");
113 if (getOption("UCI_Opponent") != 0)
114 setOption("UCI_Opponent", opponent()->name());
116 sendPosition();
119 void UciEngine::endGame(Chess::Result result)
121 stopThinking();
122 ChessEngine::endGame(result);
125 void UciEngine::makeMove(const Chess::Move& move)
127 m_moveStrings += " " + board()->moveString(move, Chess::UciLongAlgebraic);
128 sendPosition();
131 void UciEngine::startThinking()
133 const TimeControl* whiteTc = 0;
134 const TimeControl* blackTc = 0;
135 const TimeControl* myTc = timeControl();
136 if (side() == Chess::White)
138 whiteTc = myTc;
139 blackTc = opponent()->timeControl();
141 else if (side() == Chess::Black)
143 whiteTc = opponent()->timeControl();
144 blackTc = myTc;
146 else
147 qFatal("Player %s doesn't have a side", qPrintable(name()));
149 QString command = "go";
150 if (myTc->timePerMove() > 0)
151 command += QString(" movetime %1").arg(myTc->timePerMove());
152 else
154 command += QString(" wtime %1").arg(whiteTc->timeLeft());
155 command += QString(" btime %1").arg(blackTc->timeLeft());
156 if (whiteTc->timeIncrement() > 0)
157 command += QString(" winc %1").arg(whiteTc->timeIncrement());
158 if (blackTc->timeIncrement() > 0)
159 command += QString(" binc %1").arg(blackTc->timeIncrement());
160 if (myTc->movesLeft() > 0)
161 command += QString(" movestogo %1").arg(myTc->movesLeft());
162 if (myTc->maxDepth() > 0)
163 command += QString(" depth %1").arg(myTc->maxDepth());
164 if (myTc->nodeLimit() > 0)
165 command += QString(" nodes %1").arg(myTc->nodeLimit());
167 write(command);
170 void UciEngine::stopThinking()
172 if (state() == Thinking)
173 write("stop");
176 ChessEngine::Protocol UciEngine::protocol() const
178 return ChessEngine::Uci;
181 bool UciEngine::sendPing()
183 write("isready");
184 return true;
187 void UciEngine::sendQuit()
189 write("quit");
192 void UciEngine::addVariants()
194 foreach (const EngineOption* option, m_options)
196 Chess::Variant v(variantCode(option->name()));
197 if (!v.isNone())
198 m_variants.append(v);
202 static QVector<QStringList> parseCommand(const QString& str, const QRegExp& rx)
204 QVector<QStringList> attrs;
206 // Put the attributes' names and values in a vector
207 QString item;
208 int pos = 0;
209 int prevPos= 0;
210 while ((pos = rx.indexIn(str, pos)) != -1)
212 if (!item.isEmpty())
214 QString val = str.mid(prevPos + 1, pos - prevPos - 2);
215 attrs.append((QStringList() << item << val.trimmed()));
217 item = rx.cap();
218 pos += rx.matchedLength();
219 prevPos = pos;
221 if (prevPos >= str.length() - 1)
222 return attrs; // No value for the last attribute
224 // Add the last attribute to the vector
225 if (!item.isEmpty())
227 QString val = str.right(str.length() - prevPos - 1);
228 attrs.append((QStringList() << item << val.trimmed()));
231 return attrs;
234 void UciEngine::parseInfo(const QString& line)
236 QRegExp rx("\\b(depth|seldepth|time|nodes|pv|multipv|score|currmove|"
237 "currmovenumber|hashfull|nps|tbhits|cpuload|string|"
238 "refutation|currline)\\b");
239 QVector<QStringList> attrs = parseCommand(line, rx);
241 foreach (const QStringList& attr, attrs)
243 if (attr[0] == "depth")
244 m_eval.setDepth(attr[1].toInt());
245 else if (attr[0] == "time")
246 m_eval.setTime(attr[1].toInt());
247 else if (attr[0] == "nodes")
248 m_eval.setNodeCount(attr[1].toInt());
249 else if (attr[0] == "pv")
250 m_eval.setPv(attr[1]);
251 else if (attr[0] == "score")
253 QRegExp rx2("\\b(cp|mate|lowerbound|upperbound)\\b");
254 QVector<QStringList> attrs2 = parseCommand(attr[1], rx2);
255 int score = 0;
256 bool hasScore = false;
258 foreach (const QStringList& attr2, attrs2)
260 if (attr2[0] == "cp")
262 score = attr2[1].toInt();
263 if (m_whiteEvalPov && side() == Chess::Black)
264 score = -score;
265 hasScore = true;
267 else if (attr2[0] == "lowerbound"
268 || attr2[0] == "upperbound")
270 hasScore = false;
271 break;
273 else if (attr2[0] == "mate")
275 score = attr2[1].toInt();
276 if (score > 0)
277 score = 30001 - score * 2;
278 else if (score < 0)
279 score = -30000 - score * 2;
280 hasScore = true;
283 if (hasScore)
284 m_eval.setScore(score);
289 EngineOption* UciEngine::parseOption(const QString& str)
291 QString name;
292 QString type;
293 QString value;
294 QStringList choices;
295 int min = 0;
296 int max = 0;
298 QRegExp rx("\\b(name|type|default|min|max|var)\\b");
299 QVector<QStringList> attrs = parseCommand(str, rx);
301 foreach (const QStringList& attr, attrs)
303 if (attr.size() < 2)
304 continue;
306 if (attr[0] == "name")
307 name = attr[1];
308 else if (attr[0] == "type")
309 type = attr[1];
310 else if (attr[0] == "default")
311 value = attr[1];
312 else if (attr[0] == "min")
313 min = attr[1].toInt();
314 else if (attr[0] == "max")
315 max = attr[1].toInt();
316 else if (attr[0] == "var")
317 choices << attr[1];
319 if (name.isEmpty())
320 return 0;
322 if (type == "button")
323 return new EngineButtonOption(name);
324 else if (type == "check")
325 return new EngineCheckOption(name, value, value);
326 else if (type == "combo")
327 return new EngineComboOption(name, value, value, choices);
328 else if (type == "spin")
329 return new EngineSpinOption(name, value, value, min, max);
330 else if (type == "string")
331 return new EngineTextOption(name, value, value);
333 return 0;
336 void UciEngine::parseLine(const QString& line)
338 const QString command = line.section(' ', 0, 0);
339 const QString args = line.right(line.length() - command.length() - 1);
341 if (command == "bestmove")
343 if (state() != Thinking)
345 if (state() == FinishingGame)
346 pong();
347 else
348 qDebug() << "Unexpected move from" << name();
349 return;
352 QString moveString = args.section(' ', 0, 0);
353 if (moveString.isEmpty())
354 moveString = args;
356 m_moveStrings += " " + moveString;
357 Chess::Move move = board()->moveFromString(moveString);
359 if (!move.isNull())
360 emitMove(move);
361 else
362 emitForfeit(Chess::Result::WinByIllegalMove, moveString);
364 else if (command == "uciok")
366 if (state() == Starting)
368 onProtocolStart();
369 addVariants();
370 ping();
373 else if (command == "readyok")
375 pong();
377 else if (command == "id")
379 const QString tag = args.section(' ', 0, 0);
380 const QString tagVal = args.section(' ', 1);
382 if (tag == "name" && name() == "UciEngine")
383 setName(tagVal);
385 else if (command == "registration")
387 if (args == "error")
389 qDebug() << "Failed to register UCI engine" << name();
390 write("register later");
393 else if (command == "option")
395 EngineOption* option = parseOption(args);
397 if (option == 0 || !option->isValid())
398 qDebug() << "Invalid UCI option from" << name() << ":"
399 << args;
400 else
401 m_options.append(option);
403 else if (command == "info")
405 parseInfo(args);
409 void UciEngine::sendOption(const QString& name, const QString& value)
411 write(QString("setoption name %1 value %2").arg(name).arg(value));