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"
24 #include <QCoreApplication>
26 #include <QTextStream>
29 #include <QStringList>
31 #include "enginematch.h"
33 static EngineMatch
* match
= 0;
35 void msgOutput(QtMsgType type
, const char *msg
)
39 fprintf(stdout
, "%s\n", msg
);
42 fprintf(stderr
, "Warning: %s\n", msg
);
45 fprintf(stderr
, "Critical: %s\n", msg
);
48 fprintf(stderr
, "Fatal: %s\n", msg
);
53 void sigintHandler(int param
)
67 static QList
<CmdOption
> getOptions(const QStringList
& args
)
69 QList
<CmdOption
> options
;
71 QStringList::const_iterator it
;
72 for (it
= args
.constBegin(); it
!= args
.constEnd(); ++it
)
76 if (it
->startsWith('-'))
78 for (++it
; it
!= args
.constEnd() && !it
->startsWith('-'); ++it
)
79 option
.args
.append(*it
);
82 if (option
.args
.size() > 0)
83 option
.value
= option
.args
.at(0);
84 options
.append(option
);
92 EngineConfiguration config
;
93 EngineSettings settings
;
96 static void listEngines()
101 int size
= settings
.beginReadArray("engines");
102 for (int i
= 0; i
< size
; i
++)
104 settings
.setArrayIndex(i
);
105 list
.append(settings
.value("name").toString());
109 foreach (const QString
& str
, list
)
110 qDebug("%s", qPrintable(str
));
113 static bool readEngineConfig(const QString
& name
, EngineConfiguration
& config
)
117 int size
= settings
.beginReadArray("engines");
118 for (int i
= 0; i
< size
; i
++)
120 settings
.setArrayIndex(i
);
122 if (settings
.value("name").toString() == name
)
124 config
.setName(settings
.value("name").toString());
125 config
.setCommand(settings
.value("command").toString());
126 config
.setWorkingDirectory(
127 settings
.value("working_directory").toString());
128 config
.setProtocol(ChessEngine::Protocol(
129 settings
.value("protocol").toInt()));
138 static bool parseEngine(const QStringList
& args
, EngineData
& data
)
140 foreach (const QString
& arg
, args
)
142 QString name
= arg
.section('=', 0, 0);
143 QString val
= arg
.section('=', 1);
149 if (!readEngineConfig(val
, data
.config
))
151 qWarning() << "Unknown engine configuration:" << val
;
155 else if (name
== "name")
156 data
.config
.setName(val
);
157 else if (name
== "cmd")
159 if (val
.contains(' '))
161 val
.push_front('\"');
164 data
.config
.setCommand(val
);
166 else if (name
== "dir")
167 data
.config
.setWorkingDirectory(val
);
168 else if (name
== "arg")
169 data
.settings
.addArgument(val
);
170 else if (name
== "proto")
173 data
.config
.setProtocol(ChessEngine::Uci
);
174 else if (val
== "xboard")
175 data
.config
.setProtocol(ChessEngine::Xboard
);
178 qWarning()<< "Usupported chess protocol:" << val
;
182 else if (name
== "initstr")
183 data
.settings
.addInitString(val
);
185 // Time control (moves/time+increment)
186 else if (name
== "tc")
191 qWarning() << "Invalid time control:" << val
;
194 tc
.setMaxDepth(data
.settings
.timeControl().maxDepth());
195 tc
.setNodeLimit(data
.settings
.timeControl().nodeLimit());
196 data
.settings
.setTimeControl(tc
);
198 else if (name
== "invertscores")
200 data
.settings
.setWhiteEvalPov(true);
202 else if (name
== "depth")
204 if (val
.toInt() <= 0)
206 qWarning() << "Invalid depth limit:" << val
;
209 data
.settings
.timeControl().setMaxDepth(val
.toInt());
211 else if (name
== "nodes")
213 if (val
.toInt() <= 0)
215 qWarning() << "Invalid node limit:" << val
;
218 data
.settings
.timeControl().setNodeLimit(val
.toInt());
220 // Max. number of cpus the engine can use
221 else if (name
== "cpus")
223 if (val
.toInt() <= 0)
225 qWarning() << "Invalid cpu count:" << val
;
228 data
.settings
.setConcurrency(val
.toInt());
230 // Path to endgame bitbases
231 else if (name
== "egbbpath")
232 data
.settings
.setEgbbPath(val
);
233 // Path to endgame tablebases
234 else if (name
== "egtbpath")
235 data
.settings
.setEgtbPath(val
);
237 else if (name
.startsWith("uci."))
238 data
.settings
.addUciSetting(name
.section('.', 1), val
);
241 qWarning() << "Invalid engine option:" << name
;
249 static EngineMatch
* parseMatch(const QStringList
& args
, QObject
* parent
)
251 EngineMatch
* match
= new EngineMatch(parent
);
252 QList
<CmdOption
> options
= getOptions(args
);
258 foreach (const CmdOption
& opt
, options
)
262 // First chess program
263 if (opt
.name
== "-fcp")
264 optOk
= ok
= parseEngine(opt
.args
, fcp
);
265 // Second chess program
266 else if (opt
.name
== "-scp")
267 optOk
= ok
= parseEngine(opt
.args
, scp
);
268 // The engine options apply to both engines
269 else if (opt
.name
== "-both")
271 optOk
= ok
= (parseEngine(opt
.args
, fcp
) &&
272 parseEngine(opt
.args
, scp
));
274 // Chess variant (default: standard chess)
275 else if (opt
.name
== "-variant")
276 match
->setVariant(Chess::Variant(opt
.value
));
277 // Opening book file (must be in Polyglot format)
278 else if (opt
.name
== "-book")
279 match
->setBookFile(opt
.value
);
280 // Maximum book depth in plies (halfmoves)
281 else if (opt
.name
== "-bookdepth")
282 match
->setBookDepth(opt
.value
.toInt());
283 // Threshold for draw adjudication
284 else if (opt
.name
== "-draw")
286 if (opt
.args
.size() != 2)
288 qWarning() << "-draw needs two arguments";
292 int moveNumber
= opt
.args
[0].toInt();
293 int score
= opt
.args
[1].toInt();
294 match
->setDrawThreshold(moveNumber
, score
);
296 // Threshold for resign adjudication
297 else if (opt
.name
== "-resign")
299 if (opt
.args
.size() != 2)
301 qWarning() << "-resign needs two arguments";
305 int moveCount
= opt
.args
[0].toInt();
306 int score
= opt
.args
[1].toInt();
307 match
->setResignThreshold(moveCount
, -score
);
310 else if (opt
.name
== "-event")
311 match
->setEvent(opt
.value
);
312 // Number of games to play
313 else if (opt
.name
== "-games")
314 match
->setGameCount(opt
.value
.toInt());
315 // Debugging mode. Prints all engine input and output.
316 else if (opt
.name
== "-debug")
317 match
->setDebugMode(true);
318 // Use a PGN file as the opening book
319 else if (opt
.name
== "-pgnin")
320 match
->setPgnInput(opt
.value
);
321 // PGN file where the games should be saved
322 else if (opt
.name
== "-pgnout")
324 bool minimal
= false;
325 if (opt
.args
.size() > 1 && opt
.args
[1] == "min")
327 match
->setPgnOutput(opt
.value
, minimal
);
329 // Play every opening twice, just switch the players' sides
330 else if (opt
.name
== "-repeat")
331 match
->setRepeatOpening(true);
332 // Site/location name
333 else if (opt
.name
== "-site")
334 match
->setSite(opt
.value
);
335 // Delay between games
336 else if (opt
.name
== "-wait")
337 match
->setWait(opt
.value
.toInt());
340 qWarning() << "Invalid argument:" << opt
.name
;
354 match
->addEngine(fcp
.config
, fcp
.settings
);
355 match
->addEngine(scp
.config
, scp
.settings
);
360 int main(int argc
, char* argv
[])
362 signal(SIGTERM
, sigintHandler
);
363 signal(SIGINT
, sigintHandler
);
365 qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
366 qInstallMsgHandler(msgOutput
);
368 QCoreApplication::setOrganizationName("cutechess");
369 QCoreApplication::setOrganizationDomain("cutechess.org");
370 QCoreApplication::setApplicationName("cutechess");
372 QCoreApplication
app(argc
, argv
);
374 // Use Ini format on all platforms
375 QSettings::setDefaultFormat(QSettings::IniFormat
);
377 QStringList arguments
= QCoreApplication::arguments();
378 arguments
.takeFirst(); // application name
380 // Use trivial command-line parsing for now
381 QTextStream
out(stdout
);
382 foreach (const QString
& arg
, arguments
)
384 if (arg
== "-v" || arg
== "--version")
386 out
<< "cutechess-cli " << CUTECHESS_CLI_VERSION
<< endl
;
387 out
<< "Copyright (C) 2008-2009 Ilari Pihlajisto and Arto Jonsson" << endl
;
388 out
<< "This is free software; see the source for copying ";
389 out
<< "conditions. There is NO" << endl
<< "warranty; not even for ";
390 out
<< "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.";
395 else if (arg
== "--engines")
400 else if (arg
== "--help")
402 out
<< "Usage: cutechess-cli -fcp [eng_options] -scp [eng_options] [options]\n"
404 " --help Display this information\n"
405 " --version Display the version number\n"
406 " --engines Display the list of configured engines and exit\n\n"
407 " -fcp <options> Apply <options> to the first engine\n"
408 " -scp <options> Apply <options> to the second engine\n"
409 " -both <options> Apply <options> to both engines\n"
410 " -variant <arg> Set chess variant to <arg>. Must be Standard,\n"
411 " Fischerandom, Capablanca, Gothic or Caparandom\n"
412 " -book <file> Use <file> (Polyglot book file) as the opening book\n"
413 " -bookdepth <n> Set the maximum book depth (in plies) to <n>\n"
414 " -draw <n> <score> Adjudicate the game as a draw if the score of both\n"
415 " engines is within <score> centipawns from zero after\n"
416 " <n> full moves have been played\n"
417 " -resign <n> <score> Adjudicate the game as a loss if an engine's score is\n"
418 " at least <score> centipawns below zero for at least\n"
419 " <n> consecutive moves\n"
420 " -event <arg> Set the event name to <arg>\n"
421 " -games <n> Play <n> games\n"
422 " -debug Display all engine input and output\n"
423 " -pgnin <file> Use <file> as the opening book in PGN format\n"
424 " -pgnout <file> [min] Save the games to <file> in PGN format. Use the 'min'\n"
425 " argument to save in a minimal PGN format.\n"
426 " -repeat Play each opening twice so that both players get\n"
427 " to play it on both sides\n"
428 " -site <arg> Set the site/location to <arg>\n"
429 " -wait <n> Wait <n> milliseconds between games. The default is 0.\n\n"
431 " conf=<arg> Use an engine with the name <arg> from Cute Chess'\n"
432 " configuration file.\n"
433 " name=<arg> Set the name to <arg>\n"
434 " cmd=<arg> Set the command to <arg>\n"
435 " dir=<arg> Set the working directory to <arg>\n"
436 " arg=<arg> Pass <arg> to the engine as a command line argument\n"
437 " initstr=<arg> Send <arg> to the engine's standard input at startup\n"
438 " proto=<arg> Set the chess protocol to <arg>. Must be xboard or uci\n"
439 " tc=<arg> Set the time control to <arg>. The format is\n"
440 " moves/time+increment, where 'moves' is the number of\n"
441 " moves per tc, 'time' is time per tc (either seconds or\n"
442 " minutes:seconds), and 'increment' is time increment\n"
443 " per move in seconds\n"
444 " invertscores Inverts the engine's scores when it plays black\n"
445 " depth=<arg> Set the search depth limit to <arg>\n"
446 " nodes=<arg> Set the node count limit to <arg>\n"
447 " cpus=<n> Tell the engine to use a maximum of <n> cpus\n"
448 " egbbpath=<dir> Set the path to endgame bitbases to <dir>\n"
449 " egtbpath=<dir> Set the path to endgame tablebases to <dir>\n"
450 " uci.<name>=<arg> Set UCI option <name> to value <arg>\n";
455 match
= parseMatch(arguments
, &app
);
458 QObject::connect(match
, SIGNAL(finished()), &app
, SLOT(quit()));
460 if (!match
->initialize())