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 }