File indexing completed on 2024-05-12 08:00:34
0001 /* 0002 * Copyright (C) 1995 Paul Olav Tvete <paul@troll.no> 0003 * Copyright (C) 2000-2009 Stephan Kulow <coolo@kde.org> 0004 * 0005 * License of original code: 0006 * ------------------------------------------------------------------------- 0007 * Permission to use, copy, modify, and distribute this software and its 0008 * documentation for any purpose and without fee is hereby granted, 0009 * provided that the above copyright notice appear in all copies and that 0010 * both that copyright notice and this permission notice appear in 0011 * supporting documentation. 0012 * 0013 * This file is provided AS IS with no warranties of any kind. The author 0014 * shall have no liability with respect to the infringement of copyrights, 0015 * trade secrets or any patents by this file or any part thereof. In no 0016 * event will the author be liable for any lost revenue or profits or 0017 * other special, indirect and consequential damages. 0018 * ------------------------------------------------------------------------- 0019 * 0020 * License of modifications/additions made after 2009-01-01: 0021 * ------------------------------------------------------------------------- 0022 * This program is free software; you can redistribute it and/or 0023 * modify it under the terms of the GNU General Public License as 0024 * published by the Free Software Foundation; either version 2 of 0025 * the License, or (at your option) any later version. 0026 * 0027 * This program is distributed in the hope that it will be useful, 0028 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0029 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0030 * GNU General Public License for more details. 0031 * 0032 * You should have received a copy of the GNU General Public License 0033 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0034 * ------------------------------------------------------------------------- 0035 */ 0036 0037 // own 0038 #include "dealer.h" 0039 #include "dealerinfo.h" 0040 #include "kpat_debug.h" 0041 #include "kpat_version.h" 0042 #include "mainwindow.h" 0043 #include "patsolve/solverinterface.h" 0044 // KCardGame 0045 #include <KCardDeck> 0046 #include <KCardTheme> 0047 // KF 0048 #include <KAboutData> 0049 #include <KCrash> 0050 #include <KDBusService> 0051 #include <KLocalizedString> 0052 // Qt 0053 #include <QApplication> 0054 #include <QCommandLineOption> 0055 #include <QCommandLineParser> 0056 #include <QDomDocument> 0057 #include <QElapsedTimer> 0058 #include <QFile> 0059 #include <QRandomGenerator> 0060 #include <QStandardPaths> 0061 #include <QTime> 0062 // Std 0063 #include <climits> 0064 0065 static DealerScene *getDealer(int wanted_game, const QString &name) 0066 { 0067 const auto games = DealerInfoList::self()->games(); 0068 for (DealerInfo *di : games) { 0069 if ((wanted_game < 0) ? (di->untranslatedBaseName().toString() == name) : di->providesId(wanted_game)) { 0070 DealerScene *d = di->createGame(); 0071 Q_ASSERT(d); 0072 d->setDeck(new KCardDeck(KCardTheme(), d)); 0073 d->initialize(); 0074 0075 if (!d->solver()) { 0076 qCCritical(KPAT_LOG) << "There is no solver for" << di->baseName(); 0077 return nullptr; 0078 } 0079 0080 return d; 0081 } 0082 } 0083 return nullptr; 0084 } 0085 0086 KAboutData fillAboutData() 0087 { 0088 KAboutData aboutData(QStringLiteral("kpat"), 0089 i18n("KPatience"), 0090 QStringLiteral(KPAT_VERSION_STRING), 0091 i18n("KDE Patience Game"), 0092 KAboutLicense::GPL_V2, 0093 i18n("© 1995 Paul Olav Tvete\n© 2000 Stephan Kulow"), 0094 QString(), 0095 QStringLiteral("https://apps.kde.org/kpat")); 0096 0097 aboutData.addAuthor(i18n("Paul Olav Tvete"), i18n("Author of original Qt version"), QStringLiteral("paul@troll.no")); 0098 aboutData.addAuthor(i18n("Mario Weilguni"), i18n("Initial KDE port"), QStringLiteral("mweilguni@kde.org")); 0099 aboutData.addAuthor(i18n("Matthias Ettrich"), QString(), QStringLiteral("ettrich@kde.org")); 0100 aboutData.addAuthor(i18n("Rodolfo Borges"), i18n("New game types"), QStringLiteral("barrett@9hells.org")); 0101 aboutData.addAuthor(i18n("Peter H. Ruegg"), QString(), QStringLiteral("kpat@incense.org")); 0102 aboutData.addAuthor(i18n("Michael Koch"), i18n("Bug fixes"), QStringLiteral("koch@kde.org")); 0103 aboutData.addAuthor(i18n("Marcus Meissner"), i18n("Shuffle algorithm for game numbers"), QStringLiteral("mm@caldera.de")); 0104 aboutData.addAuthor(i18n("Tom Holroyd"), i18n("Initial patience solver"), QStringLiteral("tomh@kurage.nimh.nih.gov")); 0105 aboutData.addAuthor(i18n("Stephan Kulow"), i18n("Rewrite and current maintainer"), QStringLiteral("coolo@kde.org")); 0106 aboutData.addAuthor(i18n("Erik Sigra"), i18n("Klondike improvements"), QStringLiteral("sigra@home.se")); 0107 aboutData.addAuthor(i18n("Josh Metzler"), i18n("Spider implementation"), QStringLiteral("joshdeb@metzlers.org")); 0108 aboutData.addAuthor(i18n("Maren Pakura"), i18n("Documentation"), QStringLiteral("maren@kde.org")); 0109 aboutData.addAuthor(i18n("Inge Wallin"), i18n("Bug fixes"), QStringLiteral("inge@lysator.liu.se")); 0110 aboutData.addAuthor(i18n("Simon Hürlimann"), i18n("Menu and toolbar work"), QStringLiteral("simon.huerlimann@huerlisi.ch")); 0111 aboutData.addAuthor(i18n("Parker Coates"), i18n("Cleanup and polish"), QStringLiteral("coates@kde.org")); 0112 aboutData.addAuthor(i18n("Shlomi Fish"), i18n("Integration with Freecell Solver and further work"), QStringLiteral("shlomif@cpan.org")); 0113 aboutData.addAuthor(i18n("Michael Lang"), i18n("New game types"), QStringLiteral("criticaltemp@protonmail.com")); 0114 return aboutData; 0115 } 0116 0117 void addParserOptions(QCommandLineParser &parser) 0118 { 0119 parser.addOption( 0120 QCommandLineOption(QStringList() << QStringLiteral("solvegame"), i18n("Try to find a solution to the given savegame"), QStringLiteral("file"))); 0121 parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("solve"), i18n("Dealer to solve (debug)"), QStringLiteral("num"))); 0122 parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("start"), i18n("Game range start (default 0:INT_MAX)"), QStringLiteral("num"))); 0123 parser.addOption( 0124 QCommandLineOption(QStringList() << QStringLiteral("end"), i18n("Game range end (default start:start if start given)"), QStringLiteral("num"))); 0125 parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("testdir"), i18n("Directory with test cases"), QStringLiteral("directory"))); 0126 parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("generate"), i18n("Generate random test cases"))); 0127 parser.addPositionalArgument(QStringLiteral("file"), i18n("File to load")); 0128 } 0129 0130 DealerScene *loadSaveGame(QString savegame) 0131 { 0132 QFile of(savegame); 0133 of.open(QIODevice::ReadOnly); 0134 QDomDocument doc; 0135 doc.setContent(&of); 0136 0137 DealerScene *dealer; 0138 QString id_attr = doc.documentElement().attribute(QStringLiteral("id")); 0139 if (!id_attr.isEmpty()) { 0140 dealer = getDealer(id_attr.toInt(), QString()); 0141 if (!dealer) 0142 return nullptr; 0143 dealer->loadLegacyFile(&of); 0144 } else { 0145 of.seek(0); 0146 QXmlStreamReader xml(&of); 0147 if (!xml.readNextStartElement()) { 0148 qCritical() << "Failed to read XML" << savegame; 0149 } 0150 dealer = getDealer(DealerInfoList::self()->gameIdForFile(xml), QString()); 0151 if (!dealer) 0152 return nullptr; 0153 0154 of.seek(0); 0155 dealer->loadFile(&of, false); 0156 } 0157 return dealer; 0158 } 0159 0160 bool singleSolve(QCommandLineParser &parser) 0161 { 0162 QString savegame = parser.value(QStringLiteral("solvegame")); 0163 if (savegame.isEmpty()) 0164 return false; 0165 0166 DealerScene *dealer = loadSaveGame(savegame); 0167 if (!dealer) { 0168 // we tried 0169 return true; 0170 } 0171 dealer->solver()->translate_layout(); 0172 0173 int ret = dealer->solver()->patsolve(); 0174 if (ret == SolverInterface::SolutionExists) 0175 fprintf(stdout, "won\n"); 0176 else if (ret == SolverInterface::NoSolutionExists) 0177 fprintf(stdout, "lost\n"); 0178 else 0179 fprintf(stdout, "unknown\n"); 0180 0181 delete dealer; 0182 return true; 0183 } 0184 0185 bool generateDeal(DealerScene *dealer, int dealer_id, QString testdir) 0186 { 0187 QElapsedTimer mytime; 0188 0189 if (dealer->deck()) 0190 dealer->deck()->stopAnimations(); 0191 int game_id = QRandomGenerator::global()->bounded(INT_MAX); 0192 dealer->startNew(game_id); 0193 mytime.start(); 0194 dealer->solver()->translate_layout(); 0195 int ret = dealer->solver()->patsolve(); 0196 if (ret == SolverInterface::SolutionExists) { 0197 fprintf(stdout, "%d: %d won (%lld ms)\n", dealer_id, game_id, mytime.elapsed()); 0198 QFile file(QStringLiteral("%1/%2-%3-1").arg(testdir).arg(dealer_id).arg(game_id)); 0199 file.open(QFile::WriteOnly); 0200 dealer->saveLegacyFile(&file); 0201 return true; 0202 } else if (ret == SolverInterface::NoSolutionExists) { 0203 fprintf(stdout, "%d: %d lost (%lld ms)\n", dealer_id, game_id, mytime.elapsed()); 0204 QFile file(QStringLiteral("%1/%2-%3-0").arg(testdir).arg(dealer_id).arg(game_id)); 0205 file.open(QFile::WriteOnly); 0206 dealer->saveLegacyFile(&file); 0207 return true; 0208 } else { 0209 fprintf(stdout, "%d: %d unknown (%lld ms)\n", dealer_id, game_id, mytime.elapsed()); 0210 return false; 0211 } 0212 } 0213 0214 bool generate(QCommandLineParser &parser) 0215 { 0216 QString testdir = parser.value(QStringLiteral("testdir")); 0217 if (testdir.isEmpty()) 0218 return false; 0219 if (!parser.isSet(QStringLiteral("generate"))) 0220 return false; 0221 for (int dealer_id = 0; dealer_id < 20; dealer_id++) { 0222 DealerScene *dealer = getDealer(dealer_id, QString()); 0223 if (!dealer) 0224 continue; 0225 int count = 100; 0226 while (count) { 0227 if (generateDeal(dealer, dealer_id, testdir)) 0228 count--; 0229 } 0230 delete dealer; 0231 } 0232 return true; 0233 } 0234 0235 bool determineRange(QCommandLineParser &parser, int &wanted_game, QString &wanted_name, int &start_index, int &end_index) 0236 { 0237 wanted_game = -1; 0238 if (parser.isSet(QStringLiteral("solve"))) { 0239 wanted_name = parser.value(QStringLiteral("solve")); 0240 bool isInt = false; 0241 wanted_game = wanted_name.toInt(&isInt); 0242 if (!isInt) 0243 wanted_game = -1; 0244 } else { 0245 return false; 0246 } 0247 0248 bool ok = false; 0249 end_index = -1; 0250 if (parser.isSet(QStringLiteral("end"))) 0251 end_index = parser.value(QStringLiteral("end")).toInt(&ok); 0252 if (!ok) 0253 end_index = -1; 0254 ok = false; 0255 start_index = -1; 0256 if (parser.isSet(QStringLiteral("start"))) 0257 start_index = parser.value(QStringLiteral("start")).toInt(&ok); 0258 if (!ok) { 0259 start_index = 0; 0260 } 0261 if (end_index == -1) 0262 end_index = start_index; 0263 0264 return ok; 0265 } 0266 0267 bool solveRange(QCommandLineParser &parser) 0268 { 0269 int wanted_game, start_index, end_index; 0270 QString wanted_name; 0271 if (!determineRange(parser, wanted_game, wanted_name, start_index, end_index)) 0272 return false; 0273 0274 DealerScene *dealer = getDealer(wanted_game, wanted_name); 0275 if (!dealer) 0276 return true; 0277 0278 QElapsedTimer mytime; 0279 for (int i = start_index; i <= end_index; i++) { 0280 mytime.start(); 0281 dealer->deck()->stopAnimations(); 0282 dealer->startNew(i); 0283 dealer->solver()->translate_layout(); 0284 int ret = dealer->solver()->patsolve(); 0285 if (ret == SolverInterface::SolutionExists) 0286 fprintf(stdout, "%d won (%lld ms)\n", i, mytime.elapsed()); 0287 else if (ret == SolverInterface::NoSolutionExists) 0288 fprintf(stdout, "%d lost (%lld ms)\n", i, mytime.elapsed()); 0289 else 0290 fprintf(stdout, "%d unknown (%lld ms)\n", i, mytime.elapsed()); 0291 } 0292 fprintf(stdout, "all_moves %ld\n", all_moves); 0293 delete dealer; 0294 return true; 0295 } 0296 0297 int main(int argc, char **argv) 0298 { 0299 QApplication app(argc, argv); 0300 0301 KLocalizedString::setApplicationDomain("kpat"); 0302 0303 KAboutData aboutData = fillAboutData(); 0304 KAboutData::setApplicationData(aboutData); 0305 0306 KCrash::initialize(); 0307 0308 QCommandLineParser parser; 0309 addParserOptions(parser); 0310 aboutData.setupCommandLine(&parser); 0311 parser.process(app); 0312 aboutData.processCommandLine(&parser); 0313 0314 app.setWindowIcon(QIcon::fromTheme(QStringLiteral("kpat"))); 0315 0316 if (singleSolve(parser)) { 0317 return 0; 0318 } 0319 0320 if (generate(parser)) { 0321 return 0; 0322 } 0323 0324 if (solveRange(parser)) { 0325 return 0; 0326 } 0327 0328 QFile savedState(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1String("/" saved_state_file)); 0329 0330 MainWindow *w = new MainWindow; 0331 if (!parser.positionalArguments().isEmpty()) { 0332 if (!w->loadGame(QUrl::fromLocalFile(parser.positionalArguments().at(0)), true)) 0333 w->slotShowGameSelectionScreen(); 0334 } else if (savedState.exists()) { 0335 if (!w->loadGame(QUrl::fromLocalFile(savedState.fileName()), false)) 0336 w->slotShowGameSelectionScreen(); 0337 } else { 0338 w->slotShowGameSelectionScreen(); 0339 } 0340 w->show(); 0341 0342 const KDBusService dbusService(KDBusService::Multiple); 0343 0344 return app.exec(); 0345 }