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/>.
20 #if QT_VERSION < 0x040400
21 #error "Qt version 4.4.0 or later is required"
25 #include <QTextStream>
26 #include <QStringList>
29 #include <enginemanager.h>
30 #include "enginematch.h"
31 #include "cutechesscoreapp.h"
32 #include "matchparser.h"
34 static EngineMatch
* match
= 0;
36 void sigintHandler(int param
)
48 EngineConfiguration config
;
49 EngineSettings settings
;
52 static bool readEngineConfig(const QString
& name
, EngineConfiguration
& config
)
54 const QList
<EngineConfiguration
> engines
=
55 CuteChessCoreApplication::engineManager()->engines();
57 foreach (const EngineConfiguration
& engine
, engines
)
59 if (engine
.name() == name
)
68 static bool parseEngine(const QStringList
& args
, EngineData
& data
)
70 foreach (const QString
& arg
, args
)
72 QString name
= arg
.section('=', 0, 0);
73 QString val
= arg
.section('=', 1);
79 if (!readEngineConfig(val
, data
.config
))
81 qWarning() << "Unknown engine configuration:" << val
;
85 else if (name
== "name")
86 data
.config
.setName(val
);
87 else if (name
== "cmd")
89 if (val
.contains(' '))
94 data
.config
.setCommand(val
);
96 else if (name
== "dir")
97 data
.config
.setWorkingDirectory(val
);
98 else if (name
== "arg")
99 data
.config
.addArgument(val
);
100 else if (name
== "proto")
103 data
.config
.setProtocol(ChessEngine::Uci
);
104 else if (val
== "xboard")
105 data
.config
.setProtocol(ChessEngine::Xboard
);
108 qWarning()<< "Usupported chess protocol:" << val
;
112 else if (name
== "initstr")
113 data
.config
.addInitString(val
);
115 // Time control (moves/time+increment)
116 else if (name
== "tc")
121 qWarning() << "Invalid time control:" << val
;
124 tc
.setMaxDepth(data
.settings
.timeControl().maxDepth());
125 tc
.setNodeLimit(data
.settings
.timeControl().nodeLimit());
126 data
.settings
.setTimeControl(tc
);
128 else if (name
== "invertscores")
130 data
.config
.setWhiteEvalPov(true);
132 else if (name
== "depth")
134 if (val
.toInt() <= 0)
136 qWarning() << "Invalid depth limit:" << val
;
139 data
.settings
.timeControl().setMaxDepth(val
.toInt());
141 else if (name
== "nodes")
143 if (val
.toInt() <= 0)
145 qWarning() << "Invalid node limit:" << val
;
148 data
.settings
.timeControl().setNodeLimit(val
.toInt());
150 // Custom engine option
151 else if (name
.startsWith("option."))
152 data
.config
.addCustomOption(name
.section('.', 1), val
);
155 qWarning() << "Invalid engine option:" << name
;
163 static EngineMatch
* parseMatch(const QStringList
& args
, QObject
* parent
)
165 MatchParser
parser(args
);
166 parser
.addOption("-fcp", QVariant::StringList
, 1);
167 parser
.addOption("-scp", QVariant::StringList
, 1);
168 parser
.addOption("-both", QVariant::StringList
, 1);
169 parser
.addOption("-variant", QVariant::String
, 1, 1);
170 parser
.addOption("-book", QVariant::String
, 1, 1);
171 parser
.addOption("-bookdepth", QVariant::Int
, 1, 1);
172 parser
.addOption("-concurrency", QVariant::Int
, 1, 1);
173 parser
.addOption("-draw", QVariant::StringList
, 2, 2);
174 parser
.addOption("-resign", QVariant::StringList
, 2, 2);
175 parser
.addOption("-event", QVariant::String
, 1, 1);
176 parser
.addOption("-games", QVariant::Int
, 1, 1);
177 parser
.addOption("-debug", QVariant::Bool
, 0, 0);
178 parser
.addOption("-pgnin", QVariant::String
, 1, 1);
179 parser
.addOption("-pgnout", QVariant::StringList
, 1, 2);
180 parser
.addOption("-repeat", QVariant::Bool
, 0, 0);
181 parser
.addOption("-site", QVariant::String
, 1, 1);
182 parser
.addOption("-wait", QVariant::Int
, 1, 1);
186 EngineMatch
* match
= new EngineMatch(parent
);
190 QMap
<QString
, QVariant
> options(parser
.options());
191 QMap
<QString
, QVariant
>::const_iterator it
;
192 for (it
= options
.constBegin(); it
!= options
.constEnd(); ++it
)
195 QString name
= it
.key();
196 QVariant value
= it
.value();
197 Q_ASSERT(!value
.isNull());
199 // First chess program
201 ok
= parseEngine(value
.toStringList(), fcp
);
202 // Second chess program
203 else if (name
== "-scp")
204 ok
= parseEngine(value
.toStringList(), scp
);
205 // The engine options apply to both engines
206 else if (name
== "-both")
208 ok
= (parseEngine(value
.toStringList(), fcp
) &&
209 parseEngine(value
.toStringList(), scp
));
211 // Chess variant (default: standard chess)
212 else if (name
== "-variant")
213 ok
= match
->setVariant(Chess::Variant(value
.toString()));
214 // Opening book file (must be in Polyglot format)
215 else if (name
== "-book")
216 ok
= match
->setBookFile(value
.toString());
217 // Maximum book depth in plies (halfmoves)
218 else if (name
== "-bookdepth")
219 ok
= match
->setBookDepth(value
.toInt());
220 else if (name
== "-concurrency")
221 ok
= match
->setConcurrency(value
.toInt());
222 // Threshold for draw adjudication
223 else if (name
== "-draw")
225 QStringList list
= value
.toStringList();
227 bool scoreOk
= false;
228 int moveNumber
= list
[0].toInt(&numOk
);
229 int score
= list
[1].toInt(&scoreOk
);
231 ok
= (numOk
&& scoreOk
);
233 match
->setDrawThreshold(moveNumber
, score
);
235 // Threshold for resign adjudication
236 else if (name
== "-resign")
238 QStringList list
= value
.toStringList();
239 bool countOk
= false;
240 bool scoreOk
= false;
241 int moveCount
= list
[0].toInt(&countOk
);
242 int score
= list
[1].toInt(&scoreOk
);
244 ok
= (countOk
&& scoreOk
);
246 match
->setResignThreshold(moveCount
, -score
);
249 else if (name
== "-event")
250 match
->setEvent(value
.toString());
251 // Number of games to play
252 else if (name
== "-games")
253 ok
= match
->setGameCount(value
.toInt());
254 // Debugging mode. Prints all engine input and output.
255 else if (name
== "-debug")
256 match
->setDebugMode(true);
257 // Use a PGN file as the opening book
258 else if (name
== "-pgnin")
259 ok
= match
->setPgnInput(value
.toString());
260 // PGN file where the games should be saved
261 else if (name
== "-pgnout")
263 PgnGame::PgnMode mode
= PgnGame::Verbose
;
264 QStringList list
= value
.toStringList();
265 if (list
.size() == 2)
267 if (list
.at(1) == "min")
268 mode
= PgnGame::Minimal
;
273 match
->setPgnOutput(list
[0], mode
);
275 // Play every opening twice, just switch the players' sides
276 else if (name
== "-repeat")
277 match
->setRepeatOpening(true);
278 // Site/location name
279 else if (name
== "-site")
280 match
->setSite(value
.toString());
281 // Delay between games
282 else if (name
== "-wait")
283 ok
= match
->setWait(value
.toInt());
285 qFatal("Unknown argument: \"%s\"", qPrintable(name
));
290 if (value
.type() == QVariant::StringList
)
291 val
= value
.toStringList().join(" ");
293 val
= value
.toString();
294 qWarning("Invalid value for option \"%s\": \"%s\"",
295 qPrintable(name
), qPrintable(val
));
302 match
->addEngine(fcp
.config
, fcp
.settings
);
303 match
->addEngine(scp
.config
, scp
.settings
);
308 int main(int argc
, char* argv
[])
310 signal(SIGINT
, sigintHandler
);
312 CuteChessCoreApplication
app(argc
, argv
);
315 CuteChessCoreApplication::engineManager()->loadEngines();
317 QStringList arguments
= CuteChessCoreApplication::arguments();
318 arguments
.takeFirst(); // application name
320 // Use trivial command-line parsing for now
321 QTextStream
out(stdout
);
322 foreach (const QString
& arg
, arguments
)
324 if (arg
== "-v" || arg
== "--version")
326 out
<< "cutechess-cli " << CUTECHESS_CLI_VERSION
<< endl
;
327 out
<< "Copyright (C) 2008-2009 Ilari Pihlajisto and Arto Jonsson" << endl
;
328 out
<< "This is free software; see the source for copying ";
329 out
<< "conditions. There is NO" << endl
<< "warranty; not even for ";
330 out
<< "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.";
335 else if (arg
== "--engines")
337 const QList
<EngineConfiguration
> engines
=
338 CuteChessCoreApplication::engineManager()->engines();
340 foreach (const EngineConfiguration
& engine
, engines
)
341 out
<< engine
.name();
346 else if (arg
== "--help")
348 out
<< "Usage: cutechess-cli -fcp [eng_options] -scp [eng_options] [options]\n"
350 " --help Display this information\n"
351 " --version Display the version number\n"
352 " --engines Display the list of configured engines and exit\n\n"
353 " -fcp <options> Apply <options> to the first engine\n"
354 " -scp <options> Apply <options> to the second engine\n"
355 " -both <options> Apply <options> to both engines\n"
356 " -variant <arg> Set chess variant to <arg>. Must be Standard,\n"
357 " Fischerandom, Capablanca, Gothic or Caparandom\n"
358 " -book <file> Use <file> (Polyglot book file) as the opening book\n"
359 " -bookdepth <n> Set the maximum book depth (in plies) to <n>\n"
360 " -concurrency <n> Set the maximum number of concurrent games to <n>\n"
361 " -draw <n> <score> Adjudicate the game as a draw if the score of both\n"
362 " engines is within <score> centipawns from zero after\n"
363 " <n> full moves have been played\n"
364 " -resign <n> <score> Adjudicate the game as a loss if an engine's score is\n"
365 " at least <score> centipawns below zero for at least\n"
366 " <n> consecutive moves\n"
367 " -event <arg> Set the event name to <arg>\n"
368 " -games <n> Play <n> games\n"
369 " -debug Display all engine input and output\n"
370 " -pgnin <file> Use <file> as the opening book in PGN format\n"
371 " -pgnout <file> [min] Save the games to <file> in PGN format. Use the 'min'\n"
372 " argument to save in a minimal PGN format.\n"
373 " -repeat Play each opening twice so that both players get\n"
374 " to play it on both sides\n"
375 " -site <arg> Set the site/location to <arg>\n"
376 " -wait <n> Wait <n> milliseconds between games. The default is 0.\n\n"
378 " conf=<arg> Use an engine with the name <arg> from Cute Chess'\n"
379 " configuration file.\n"
380 " name=<arg> Set the name to <arg>\n"
381 " cmd=<arg> Set the command to <arg>\n"
382 " dir=<arg> Set the working directory to <arg>\n"
383 " arg=<arg> Pass <arg> to the engine as a command line argument\n"
384 " initstr=<arg> Send <arg> to the engine's standard input at startup\n"
385 " proto=<arg> Set the chess protocol to <arg>. Must be xboard or uci\n"
386 " tc=<arg> Set the time control to <arg>. The format is\n"
387 " moves/time+increment, where 'moves' is the number of\n"
388 " moves per tc, 'time' is time per tc (either seconds or\n"
389 " minutes:seconds), and 'increment' is time increment\n"
390 " per move in seconds\n"
391 " invertscores Inverts the engine's scores when it plays black\n"
392 " depth=<arg> Set the search depth limit to <arg>\n"
393 " nodes=<arg> Set the node count limit to <arg>\n"
394 " option.<name>=<arg> Set custom option <name> to value <arg>\n";
399 match
= parseMatch(arguments
, &app
);
402 QObject::connect(match
, SIGNAL(finished()), &app
, SLOT(quit()));
404 if (!match
->initialize())
408 int ret
= app
.exec();
409 qDebug() << "Finished match";