Cutechess-cli exits after displaying the list.
[sloppygui.git] / projects / cli / src / main.cpp
blobb0d8fd723c0a683e58bd6f2f2e0ba700a3bc85cf
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 <QtGlobal>
20 #if QT_VERSION < 0x040400
21 #error "Qt version 4.4.0 or later is required"
22 #endif
24 #include <QCoreApplication>
25 #include <QDebug>
26 #include <QTextStream>
27 #include <QTime>
28 #include <QSettings>
29 #include <QStringList>
30 #include <csignal>
31 #include "enginematch.h"
33 static EngineMatch* match = 0;
35 void msgOutput(QtMsgType type, const char *msg)
37 switch (type) {
38 case QtDebugMsg:
39 fprintf(stdout, "%s\n", msg);
40 break;
41 case QtWarningMsg:
42 fprintf(stderr, "Warning: %s\n", msg);
43 break;
44 case QtCriticalMsg:
45 fprintf(stderr, "Critical: %s\n", msg);
46 break;
47 case QtFatalMsg:
48 fprintf(stderr, "Fatal: %s\n", msg);
49 abort();
53 void sigintHandler(int param)
55 Q_UNUSED(param);
56 if (match != 0)
57 match->stop();
60 struct CmdOption
62 QString name;
63 QString value;
64 QStringList args;
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)
74 CmdOption option;
75 option.name = *it;
76 if (it->startsWith('-'))
78 for (++it; it != args.constEnd() && !it->startsWith('-'); ++it)
79 option.args.append(*it);
80 --it;
82 if (option.args.size() > 0)
83 option.value = option.args.at(0);
84 options.append(option);
87 return options;
90 struct EngineData
92 EngineConfiguration config;
93 EngineSettings settings;
96 static void listEngines()
98 QStringList list;
99 QSettings settings;
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());
108 list.sort();
109 foreach (const QString& str, list)
110 qDebug("%s", qPrintable(str));
113 static bool readEngineConfig(const QString& name, EngineConfiguration& config)
115 QSettings settings;
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()));
131 return true;
135 return false;
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);
144 if (name.isEmpty())
145 continue;
147 if (name == "conf")
149 if (!readEngineConfig(val, data.config))
151 qWarning() << "Unknown engine configuration:" << val;
152 return false;
155 else if (name == "name")
156 data.config.setName(val);
157 else if (name == "cmd")
159 if (val.contains(' '))
161 val.push_front('\"');
162 val.push_back('\"');
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")
172 if (val == "uci")
173 data.config.setProtocol(ChessEngine::Uci);
174 else if (val == "xboard")
175 data.config.setProtocol(ChessEngine::Xboard);
176 else
178 qWarning()<< "Usupported chess protocol:" << val;
179 return false;
182 else if (name == "initstr")
183 data.settings.addInitString(val);
185 // Time control (moves/time+increment)
186 else if (name == "tc")
188 TimeControl tc(val);
189 if (!tc.isValid())
191 qWarning() << "Invalid time control:" << val;
192 return false;
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;
207 return false;
209 data.settings.timeControl().setMaxDepth(val.toInt());
211 else if (name == "nodes")
213 if (val.toInt() <= 0)
215 qWarning() << "Invalid node limit:" << val;
216 return false;
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;
226 return false;
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);
236 // Custom UCI option
237 else if (name.startsWith("uci."))
238 data.settings.addUciSetting(name.section('.', 1), val);
239 else
241 qWarning() << "Invalid engine option:" << name;
242 return false;
246 return true;
249 static EngineMatch* parseMatch(const QStringList& args, QObject* parent)
251 EngineMatch* match = new EngineMatch(parent);
252 QList<CmdOption> options = getOptions(args);
254 EngineData fcp;
255 EngineData scp;
256 bool ok = false;
258 foreach (const CmdOption& opt, options)
260 bool optOk = true;
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";
289 ok = false;
290 break;
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";
302 ok = false;
303 break;
305 int moveCount = opt.args[0].toInt();
306 int score = opt.args[1].toInt();
307 match->setResignThreshold(moveCount, -score);
309 // Event name
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")
326 minimal = true;
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());
338 else
340 qWarning() << "Invalid argument:" << opt.name;
341 optOk = ok = false;
344 if (!optOk)
345 break;
348 if (!ok)
350 delete match;
351 return 0;
354 match->addEngine(fcp.config, fcp.settings);
355 match->addEngine(scp.config, scp.settings);
357 return match;
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.";
391 out << endl << endl;
393 return 0;
395 else if (arg == "--engines")
397 listEngines();
398 return 0;
400 else if (arg == "--help")
402 out << "Usage: cutechess-cli -fcp [eng_options] -scp [eng_options] [options]\n"
403 "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"
430 "Engine options:\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";
451 return 0;
455 match = parseMatch(arguments, &app);
456 if (match == 0)
457 return 1;
458 QObject::connect(match, SIGNAL(finished()), &app, SLOT(quit()));
460 if (!match->initialize())
461 return 1;
462 match->start();
464 return app.exec();