Add CuteChessCoreApplication class.
[sloppygui.git] / projects / cli / src / main.cpp
blob0487c004f79c37d6d2e9d4070f910fe74103846e
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 <QSettings>
27 #include <QStringList>
28 #include <csignal>
29 #include "enginematch.h"
30 #include "cutechesscoreapp.h"
32 static EngineMatch* match = 0;
34 void sigintHandler(int param)
36 Q_UNUSED(param);
37 if (match != 0)
38 match->stop();
41 struct CmdOption
43 QString name;
44 QString value;
45 QStringList args;
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)
55 CmdOption option;
56 option.name = *it;
57 if (it->startsWith('-'))
59 for (++it; it != args.constEnd() && !it->startsWith('-'); ++it)
60 option.args.append(*it);
61 --it;
63 if (option.args.size() > 0)
64 option.value = option.args.at(0);
65 options.append(option);
68 return options;
71 struct EngineData
73 EngineConfiguration config;
74 EngineSettings settings;
77 static void listEngines()
79 QStringList list;
80 QSettings settings;
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());
89 list.sort();
90 foreach (const QString& str, list)
91 qDebug("%s", qPrintable(str));
94 static bool readEngineConfig(const QString& name, EngineConfiguration& config)
96 QSettings settings;
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()));
112 return true;
116 return false;
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);
125 if (name.isEmpty())
126 continue;
128 if (name == "conf")
130 if (!readEngineConfig(val, data.config))
132 qWarning() << "Unknown engine configuration:" << val;
133 return false;
136 else if (name == "name")
137 data.config.setName(val);
138 else if (name == "cmd")
140 if (val.contains(' '))
142 val.push_front('\"');
143 val.push_back('\"');
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")
153 if (val == "uci")
154 data.config.setProtocol(ChessEngine::Uci);
155 else if (val == "xboard")
156 data.config.setProtocol(ChessEngine::Xboard);
157 else
159 qWarning()<< "Usupported chess protocol:" << val;
160 return false;
163 else if (name == "initstr")
164 data.settings.addInitString(val);
166 // Time control (moves/time+increment)
167 else if (name == "tc")
169 TimeControl tc(val);
170 if (!tc.isValid())
172 qWarning() << "Invalid time control:" << val;
173 return false;
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;
188 return false;
190 data.settings.timeControl().setMaxDepth(val.toInt());
192 else if (name == "nodes")
194 if (val.toInt() <= 0)
196 qWarning() << "Invalid node limit:" << val;
197 return false;
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;
207 return false;
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);
217 // Custom UCI option
218 else if (name.startsWith("uci."))
219 data.settings.addUciSetting(name.section('.', 1), val);
220 else
222 qWarning() << "Invalid engine option:" << name;
223 return false;
227 return true;
230 static EngineMatch* parseMatch(const QStringList& args, QObject* parent)
232 EngineMatch* match = new EngineMatch(parent);
233 QList<CmdOption> options = getOptions(args);
235 EngineData fcp;
236 EngineData scp;
237 bool ok = false;
239 foreach (const CmdOption& opt, options)
241 bool optOk = true;
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";
270 ok = false;
271 break;
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";
283 ok = false;
284 break;
286 int moveCount = opt.args[0].toInt();
287 int score = opt.args[1].toInt();
288 match->setResignThreshold(moveCount, -score);
290 // Event name
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")
307 minimal = true;
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());
319 else
321 qWarning() << "Invalid argument:" << opt.name;
322 optOk = ok = false;
325 if (!optOk)
326 break;
329 if (!ok)
331 delete match;
332 return 0;
335 match->addEngine(fcp.config, fcp.settings);
336 match->addEngine(scp.config, scp.settings);
338 return match;
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.";
362 out << endl << endl;
364 return 0;
366 else if (arg == "--engines")
368 listEngines();
369 return 0;
371 else if (arg == "--help")
373 out << "Usage: cutechess-cli -fcp [eng_options] -scp [eng_options] [options]\n"
374 "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"
401 "Engine options:\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";
422 return 0;
426 match = parseMatch(arguments, &app);
427 if (match == 0)
428 return 1;
429 QObject::connect(match, SIGNAL(finished()), &app, SLOT(quit()));
431 if (!match->initialize())
432 return 1;
433 match->start();
435 return app.exec();