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/>.
19 #include <QStringList>
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
);
44 void UciEngine::startProtocol()
46 // Tell the engine to turn on UCI mode
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
;
60 if (!m_moveStrings
.isEmpty())
61 str
+= QString(" moves") + m_moveStrings
;
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
:
90 case Chess::Variant::Caparandom
:
91 return "UCI_CapaRandom";
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
);
107 m_startFen
= board()->fenString();
109 if (variant
!= Chess::Variant::Standard
)
110 setOption(variantString(variant
), "true");
113 if (getOption("UCI_Opponent") != 0)
114 setOption("UCI_Opponent", opponent()->name());
119 void UciEngine::endGame(Chess::Result result
)
122 ChessEngine::endGame(result
);
125 void UciEngine::makeMove(const Chess::Move
& move
)
127 m_moveStrings
+= " " + board()->moveString(move
, Chess::UciLongAlgebraic
);
131 void UciEngine::startThinking()
133 const TimeControl
* whiteTc
= 0;
134 const TimeControl
* blackTc
= 0;
135 const TimeControl
* myTc
= timeControl();
136 if (side() == Chess::White
)
139 blackTc
= opponent()->timeControl();
141 else if (side() == Chess::Black
)
143 whiteTc
= opponent()->timeControl();
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());
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());
170 void UciEngine::stopThinking()
172 if (state() == Thinking
)
176 ChessEngine::Protocol
UciEngine::protocol() const
178 return ChessEngine::Uci
;
181 bool UciEngine::sendPing()
187 void UciEngine::sendQuit()
192 void UciEngine::addVariants()
194 foreach (const EngineOption
* option
, m_options
)
196 Chess::Variant
v(variantCode(option
->name()));
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
210 while ((pos
= rx
.indexIn(str
, pos
)) != -1)
214 QString val
= str
.mid(prevPos
+ 1, pos
- prevPos
- 2);
215 attrs
.append((QStringList() << item
<< val
.trimmed()));
218 pos
+= rx
.matchedLength();
221 if (prevPos
>= str
.length() - 1)
222 return attrs
; // No value for the last attribute
224 // Add the last attribute to the vector
227 QString val
= str
.right(str
.length() - prevPos
- 1);
228 attrs
.append((QStringList() << item
<< val
.trimmed()));
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
);
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
)
267 else if (attr2
[0] == "lowerbound"
268 || attr2
[0] == "upperbound")
273 else if (attr2
[0] == "mate")
275 score
= attr2
[1].toInt();
277 score
= 30001 - score
* 2;
279 score
= -30000 - score
* 2;
284 m_eval
.setScore(score
);
289 EngineOption
* UciEngine::parseOption(const QString
& str
)
298 QRegExp
rx("\\b(name|type|default|min|max|var)\\b");
299 QVector
<QStringList
> attrs
= parseCommand(str
, rx
);
301 foreach (const QStringList
& attr
, attrs
)
306 if (attr
[0] == "name")
308 else if (attr
[0] == "type")
310 else if (attr
[0] == "default")
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")
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
);
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
)
348 qDebug() << "Unexpected move from" << name();
352 QString moveString
= args
.section(' ', 0, 0);
353 if (moveString
.isEmpty())
356 m_moveStrings
+= " " + moveString
;
357 Chess::Move move
= board()->moveFromString(moveString
);
362 emitForfeit(Chess::Result::WinByIllegalMove
, moveString
);
364 else if (command
== "uciok")
366 if (state() == Starting
)
373 else if (command
== "readyok")
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")
385 else if (command
== "registration")
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() << ":"
401 m_options
.append(option
);
403 else if (command
== "info")
409 void UciEngine::sendOption(const QString
& name
, const QString
& value
)
411 write(QString("setoption name %1 value %2").arg(name
).arg(value
));