The engine list can be sorted with externally
[sloppygui.git] / projects / cli / src / main.cpp
blobb50ce6dd5d6ac8c06478c73ad441627d952b231c
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 <QDebug>
25 #include <QTextStream>
26 #include <QStringList>
27 #include <csignal>
28 #include <cstdlib>
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)
38 Q_UNUSED(param);
39 if (match != 0)
40 match->stop();
41 else
42 abort();
46 struct EngineData
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)
61 config = engine;
62 return true;
65 return false;
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);
74 if (name.isEmpty())
75 continue;
77 if (name == "conf")
79 if (!readEngineConfig(val, data.config))
81 qWarning() << "Unknown engine configuration:" << val;
82 return false;
85 else if (name == "name")
86 data.config.setName(val);
87 else if (name == "cmd")
89 if (val.contains(' '))
91 val.push_front('\"');
92 val.push_back('\"');
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")
102 if (val == "uci")
103 data.config.setProtocol(ChessEngine::Uci);
104 else if (val == "xboard")
105 data.config.setProtocol(ChessEngine::Xboard);
106 else
108 qWarning()<< "Usupported chess protocol:" << val;
109 return false;
112 else if (name == "initstr")
113 data.config.addInitString(val);
115 // Time control (moves/time+increment)
116 else if (name == "tc")
118 TimeControl tc(val);
119 if (!tc.isValid())
121 qWarning() << "Invalid time control:" << val;
122 return false;
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;
137 return false;
139 data.settings.timeControl().setMaxDepth(val.toInt());
141 else if (name == "nodes")
143 if (val.toInt() <= 0)
145 qWarning() << "Invalid node limit:" << val;
146 return false;
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);
153 else
155 qWarning() << "Invalid engine option:" << name;
156 return false;
160 return true;
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);
183 if (!parser.parse())
184 return 0;
186 EngineMatch* match = new EngineMatch(parent);
187 EngineData fcp;
188 EngineData scp;
190 QMap<QString, QVariant> options(parser.options());
191 QMap<QString, QVariant>::const_iterator it;
192 for (it = options.constBegin(); it != options.constEnd(); ++it)
194 bool ok = true;
195 QString name = it.key();
196 QVariant value = it.value();
197 Q_ASSERT(!value.isNull());
199 // First chess program
200 if (name == "-fcp")
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();
226 bool numOk = false;
227 bool scoreOk = false;
228 int moveNumber = list[0].toInt(&numOk);
229 int score = list[1].toInt(&scoreOk);
231 ok = (numOk && scoreOk);
232 if (ok)
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);
245 if (ok)
246 match->setResignThreshold(moveCount, -score);
248 // Event name
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;
269 else
270 ok = false;
272 if (ok)
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());
284 else
285 qFatal("Unknown argument: \"%s\"", qPrintable(name));
287 if (!ok)
289 QString val;
290 if (value.type() == QVariant::StringList)
291 val = value.toStringList().join(" ");
292 else
293 val = value.toString();
294 qWarning("Invalid value for option \"%s\": \"%s\"",
295 qPrintable(name), qPrintable(val));
297 delete match;
298 return 0;
302 match->addEngine(fcp.config, fcp.settings);
303 match->addEngine(scp.config, scp.settings);
305 return match;
308 int main(int argc, char* argv[])
310 signal(SIGINT, sigintHandler);
312 CuteChessCoreApplication app(argc, argv);
314 // Load the engines
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.";
331 out << endl << endl;
333 return 0;
335 else if (arg == "--engines")
337 const QList<EngineConfiguration> engines =
338 CuteChessCoreApplication::engineManager()->engines();
340 foreach (const EngineConfiguration& engine, engines)
341 out << engine.name();
343 out << endl;
344 return 0;
346 else if (arg == "--help")
348 out << "Usage: cutechess-cli -fcp [eng_options] -scp [eng_options] [options]\n"
349 "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"
377 "Engine options:\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";
395 return 0;
399 match = parseMatch(arguments, &app);
400 if (match == 0)
401 return 1;
402 QObject::connect(match, SIGNAL(finished()), &app, SLOT(quit()));
404 if (!match->initialize())
405 return 1;
406 match->start();
408 int ret = app.exec();
409 qDebug() << "Finished match";
410 return ret;