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>
27 #include <QStringList>
29 #include "enginematch.h"
30 #include "cutechesscoreapp.h"
32 static EngineMatch
* match
= 0;
34 void sigintHandler(int param
)
48 static QList
<CmdOption
> getOptions(const QStringList
& args
)
50 QList
<CmdOption
> options
;
52 QStringList::const_iterator it
;
53 for (it
= args
.constBegin(); it
!= args
.constEnd(); ++it
)
57 if (it
->startsWith('-'))
59 for (++it
; it
!= args
.constEnd() && !it
->startsWith('-'); ++it
)
60 option
.args
.append(*it
);
63 if (option
.args
.size() > 0)
64 option
.value
= option
.args
.at(0);
65 options
.append(option
);
73 EngineConfiguration config
;
74 EngineSettings settings
;
77 static void listEngines()
82 int size
= settings
.beginReadArray("engines");
83 for (int i
= 0; i
< size
; i
++)
85 settings
.setArrayIndex(i
);
86 list
.append(settings
.value("name").toString());
90 foreach (const QString
& str
, list
)
91 qDebug("%s", qPrintable(str
));
94 static bool readEngineConfig(const QString
& name
, EngineConfiguration
& config
)
98 int size
= settings
.beginReadArray("engines");
99 for (int i
= 0; i
< size
; i
++)
101 settings
.setArrayIndex(i
);
103 if (settings
.value("name").toString() == name
)
105 config
.setName(settings
.value("name").toString());
106 config
.setCommand(settings
.value("command").toString());
107 config
.setWorkingDirectory(
108 settings
.value("working_directory").toString());
109 config
.setProtocol(ChessEngine::Protocol(
110 settings
.value("protocol").toInt()));
119 static bool parseEngine(const QStringList
& args
, EngineData
& data
)
121 foreach (const QString
& arg
, args
)
123 QString name
= arg
.section('=', 0, 0);
124 QString val
= arg
.section('=', 1);
130 if (!readEngineConfig(val
, data
.config
))
132 qWarning() << "Unknown engine configuration:" << val
;
136 else if (name
== "name")
137 data
.config
.setName(val
);
138 else if (name
== "cmd")
140 if (val
.contains(' '))
142 val
.push_front('\"');
145 data
.config
.setCommand(val
);
147 else if (name
== "dir")
148 data
.config
.setWorkingDirectory(val
);
149 else if (name
== "arg")
150 data
.settings
.addArgument(val
);
151 else if (name
== "proto")
154 data
.config
.setProtocol(ChessEngine::Uci
);
155 else if (val
== "xboard")
156 data
.config
.setProtocol(ChessEngine::Xboard
);
159 qWarning()<< "Usupported chess protocol:" << val
;
163 else if (name
== "initstr")
164 data
.settings
.addInitString(val
);
166 // Time control (moves/time+increment)
167 else if (name
== "tc")
172 qWarning() << "Invalid time control:" << val
;
175 tc
.setMaxDepth(data
.settings
.timeControl().maxDepth());
176 tc
.setNodeLimit(data
.settings
.timeControl().nodeLimit());
177 data
.settings
.setTimeControl(tc
);
179 else if (name
== "invertscores")
181 data
.settings
.setWhiteEvalPov(true);
183 else if (name
== "depth")
185 if (val
.toInt() <= 0)
187 qWarning() << "Invalid depth limit:" << val
;
190 data
.settings
.timeControl().setMaxDepth(val
.toInt());
192 else if (name
== "nodes")
194 if (val
.toInt() <= 0)
196 qWarning() << "Invalid node limit:" << val
;
199 data
.settings
.timeControl().setNodeLimit(val
.toInt());
201 // Max. number of cpus the engine can use
202 else if (name
== "cpus")
204 if (val
.toInt() <= 0)
206 qWarning() << "Invalid cpu count:" << val
;
209 data
.settings
.setConcurrency(val
.toInt());
211 // Path to endgame bitbases
212 else if (name
== "egbbpath")
213 data
.settings
.setEgbbPath(val
);
214 // Path to endgame tablebases
215 else if (name
== "egtbpath")
216 data
.settings
.setEgtbPath(val
);
218 else if (name
.startsWith("uci."))
219 data
.settings
.addUciSetting(name
.section('.', 1), val
);
222 qWarning() << "Invalid engine option:" << name
;
230 static EngineMatch
* parseMatch(const QStringList
& args
, QObject
* parent
)
232 EngineMatch
* match
= new EngineMatch(parent
);
233 QList
<CmdOption
> options
= getOptions(args
);
239 foreach (const CmdOption
& opt
, options
)
243 // First chess program
244 if (opt
.name
== "-fcp")
245 optOk
= ok
= parseEngine(opt
.args
, fcp
);
246 // Second chess program
247 else if (opt
.name
== "-scp")
248 optOk
= ok
= parseEngine(opt
.args
, scp
);
249 // The engine options apply to both engines
250 else if (opt
.name
== "-both")
252 optOk
= ok
= (parseEngine(opt
.args
, fcp
) &&
253 parseEngine(opt
.args
, scp
));
255 // Chess variant (default: standard chess)
256 else if (opt
.name
== "-variant")
257 match
->setVariant(Chess::Variant(opt
.value
));
258 // Opening book file (must be in Polyglot format)
259 else if (opt
.name
== "-book")
260 match
->setBookFile(opt
.value
);
261 // Maximum book depth in plies (halfmoves)
262 else if (opt
.name
== "-bookdepth")
263 match
->setBookDepth(opt
.value
.toInt());
264 // Threshold for draw adjudication
265 else if (opt
.name
== "-draw")
267 if (opt
.args
.size() != 2)
269 qWarning() << "-draw needs two arguments";
273 int moveNumber
= opt
.args
[0].toInt();
274 int score
= opt
.args
[1].toInt();
275 match
->setDrawThreshold(moveNumber
, score
);
277 // Threshold for resign adjudication
278 else if (opt
.name
== "-resign")
280 if (opt
.args
.size() != 2)
282 qWarning() << "-resign needs two arguments";
286 int moveCount
= opt
.args
[0].toInt();
287 int score
= opt
.args
[1].toInt();
288 match
->setResignThreshold(moveCount
, -score
);
291 else if (opt
.name
== "-event")
292 match
->setEvent(opt
.value
);
293 // Number of games to play
294 else if (opt
.name
== "-games")
295 match
->setGameCount(opt
.value
.toInt());
296 // Debugging mode. Prints all engine input and output.
297 else if (opt
.name
== "-debug")
298 match
->setDebugMode(true);
299 // Use a PGN file as the opening book
300 else if (opt
.name
== "-pgnin")
301 match
->setPgnInput(opt
.value
);
302 // PGN file where the games should be saved
303 else if (opt
.name
== "-pgnout")
305 bool minimal
= false;
306 if (opt
.args
.size() > 1 && opt
.args
[1] == "min")
308 match
->setPgnOutput(opt
.value
, minimal
);
310 // Play every opening twice, just switch the players' sides
311 else if (opt
.name
== "-repeat")
312 match
->setRepeatOpening(true);
313 // Site/location name
314 else if (opt
.name
== "-site")
315 match
->setSite(opt
.value
);
316 // Delay between games
317 else if (opt
.name
== "-wait")
318 match
->setWait(opt
.value
.toInt());
321 qWarning() << "Invalid argument:" << opt
.name
;
335 match
->addEngine(fcp
.config
, fcp
.settings
);
336 match
->addEngine(scp
.config
, scp
.settings
);
341 int main(int argc
, char* argv
[])
343 signal(SIGTERM
, sigintHandler
);
344 signal(SIGINT
, sigintHandler
);
346 CuteChessCoreApplication
app(argc
, argv
);
348 QStringList arguments
= CuteChessCoreApplication::arguments();
349 arguments
.takeFirst(); // application name
351 // Use trivial command-line parsing for now
352 QTextStream
out(stdout
);
353 foreach (const QString
& arg
, arguments
)
355 if (arg
== "-v" || arg
== "--version")
357 out
<< "cutechess-cli " << CUTECHESS_CLI_VERSION
<< endl
;
358 out
<< "Copyright (C) 2008-2009 Ilari Pihlajisto and Arto Jonsson" << endl
;
359 out
<< "This is free software; see the source for copying ";
360 out
<< "conditions. There is NO" << endl
<< "warranty; not even for ";
361 out
<< "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.";
366 else if (arg
== "--engines")
371 else if (arg
== "--help")
373 out
<< "Usage: cutechess-cli -fcp [eng_options] -scp [eng_options] [options]\n"
375 " --help Display this information\n"
376 " --version Display the version number\n"
377 " --engines Display the list of configured engines and exit\n\n"
378 " -fcp <options> Apply <options> to the first engine\n"
379 " -scp <options> Apply <options> to the second engine\n"
380 " -both <options> Apply <options> to both engines\n"
381 " -variant <arg> Set chess variant to <arg>. Must be Standard,\n"
382 " Fischerandom, Capablanca, Gothic or Caparandom\n"
383 " -book <file> Use <file> (Polyglot book file) as the opening book\n"
384 " -bookdepth <n> Set the maximum book depth (in plies) to <n>\n"
385 " -draw <n> <score> Adjudicate the game as a draw if the score of both\n"
386 " engines is within <score> centipawns from zero after\n"
387 " <n> full moves have been played\n"
388 " -resign <n> <score> Adjudicate the game as a loss if an engine's score is\n"
389 " at least <score> centipawns below zero for at least\n"
390 " <n> consecutive moves\n"
391 " -event <arg> Set the event name to <arg>\n"
392 " -games <n> Play <n> games\n"
393 " -debug Display all engine input and output\n"
394 " -pgnin <file> Use <file> as the opening book in PGN format\n"
395 " -pgnout <file> [min] Save the games to <file> in PGN format. Use the 'min'\n"
396 " argument to save in a minimal PGN format.\n"
397 " -repeat Play each opening twice so that both players get\n"
398 " to play it on both sides\n"
399 " -site <arg> Set the site/location to <arg>\n"
400 " -wait <n> Wait <n> milliseconds between games. The default is 0.\n\n"
402 " conf=<arg> Use an engine with the name <arg> from Cute Chess'\n"
403 " configuration file.\n"
404 " name=<arg> Set the name to <arg>\n"
405 " cmd=<arg> Set the command to <arg>\n"
406 " dir=<arg> Set the working directory to <arg>\n"
407 " arg=<arg> Pass <arg> to the engine as a command line argument\n"
408 " initstr=<arg> Send <arg> to the engine's standard input at startup\n"
409 " proto=<arg> Set the chess protocol to <arg>. Must be xboard or uci\n"
410 " tc=<arg> Set the time control to <arg>. The format is\n"
411 " moves/time+increment, where 'moves' is the number of\n"
412 " moves per tc, 'time' is time per tc (either seconds or\n"
413 " minutes:seconds), and 'increment' is time increment\n"
414 " per move in seconds\n"
415 " invertscores Inverts the engine's scores when it plays black\n"
416 " depth=<arg> Set the search depth limit to <arg>\n"
417 " nodes=<arg> Set the node count limit to <arg>\n"
418 " cpus=<n> Tell the engine to use a maximum of <n> cpus\n"
419 " egbbpath=<dir> Set the path to endgame bitbases to <dir>\n"
420 " egtbpath=<dir> Set the path to endgame tablebases to <dir>\n"
421 " uci.<name>=<arg> Set UCI option <name> to value <arg>\n";
426 match
= parseMatch(arguments
, &app
);
429 QObject::connect(match
, SIGNAL(finished()), &app
, SLOT(quit()));
431 if (!match
->initialize())