1 /*
2  * Copyright (C) 1995 Paul Olav Tvete <paul@troll.no>
3  * Copyright (C) 2000-2009 Stephan Kulow <coolo@kde.org>
4  *
5  * License of original code:
6  * -------------------------------------------------------------------------
7  *   Permission to use, copy, modify, and distribute this software and its
8  *   documentation for any purpose and without fee is hereby granted,
9  *   provided that the above copyright notice appear in all copies and that
10  *   both that copyright notice and this permission notice appear in
11  *   supporting documentation.
12  *
13  *   This file is provided AS IS with no warranties of any kind.  The author
14  *   shall have no liability with respect to the infringement of copyrights,
15  *   trade secrets or any patents by this file or any part thereof.  In no
16  *   event will the author be liable for any lost revenue or profits or
17  *   other special, indirect and consequential damages.
18  * -------------------------------------------------------------------------
19  *
20  * License of modifications/additions made after 2009-01-01:
21  * -------------------------------------------------------------------------
22  *   This program is free software; you can redistribute it and/or
23  *   modify it under the terms of the GNU General Public License as
24  *   published by the Free Software Foundation; either version 2 of
25  *   the License, or (at your option) any later version.
26  *
27  *   This program is distributed in the hope that it will be useful,
28  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
29  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30  *   GNU General Public License for more details.
31  *
32  *   You should have received a copy of the GNU General Public License
33  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
34  * -------------------------------------------------------------------------
35  */
36 
37 // own
38 #include "dealer.h"
39 #include "dealerinfo.h"
40 #include "kpat_debug.h"
41 #include "mainwindow.h"
42 #include "kpat_version.h"
43 #include "patsolve/solverinterface.h"
44 // KCardGame
45 #include <KCardTheme>
46 #include <KCardDeck>
47 // KF
48 #include <KAboutData>
49 #include <KCrash>
50 #include <KLocalizedString>
51 #include <KDBusService>
52 #include <Kdelibs4ConfigMigrator>
53 // Qt
54 #include <QRandomGenerator>
55 #include <QFile>
56 #include <QTime>
57 #include <QElapsedTimer>
58 #include <QDomDocument>
59 #include <QStandardPaths>
60 #include <QApplication>
61 #include <QCommandLineParser>
62 #include <QCommandLineOption>
63 // Std
64 #include <climits>
65 
getDealer(int wanted_game,const QString & name)66 static DealerScene *getDealer( int wanted_game , const QString & name )
67 {
68     const auto games = DealerInfoList::self()->games();
69     for (DealerInfo * di : games) {
70         if ( (wanted_game < 0) ? (QString::fromUtf8(di->untranslatedBaseName()) == name) : di->providesId( wanted_game ) )
71         {
72             DealerScene * d = di->createGame();
73             Q_ASSERT( d );
74             d->setDeck( new KCardDeck( KCardTheme(), d ) );
75             d->initialize();
76 
77             if ( !d->solver() )
78             {
79                 qCCritical(KPAT_LOG) << "There is no solver for" << di->baseName();
80                 return nullptr;
81             }
82 
83             return d;
84         }
85     }
86     return nullptr;
87 }
88 
89 // A function to remove all nonalphanumeric characters from a string
90 // and convert all letters to lowercase.
lowerAlphaNum(const QString & string)91 QString lowerAlphaNum( const QString & string )
92 {
93     QString result;
94     for ( int i = 0; i < string.size(); ++i )
95     {
96         QChar c = string.at( i );
97         if ( c.isLetterOrNumber() )
98             result += c.toLower();
99     }
100     return result;
101 }
102 
main(int argc,char ** argv)103 int main( int argc, char **argv )
104 {
105     QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
106 
107     QApplication app(argc, argv);
108 
109     KLocalizedString::setApplicationDomain("kpat");
110 
111     Kdelibs4ConfigMigrator migrate(QStringLiteral("kpat"));
112     migrate.setConfigFiles(QStringList() << QStringLiteral("kpatrc"));
113     migrate.setUiFiles(QStringList() << QStringLiteral("kpatui.rc"));
114     migrate.migrate();
115 
116     KAboutData aboutData( QStringLiteral("kpat"),
117                           i18n("KPatience"),
118                           QStringLiteral(KPAT_VERSION_STRING),
119                           i18n("KDE Patience Game"),
120                           KAboutLicense::GPL_V2,
121                           i18n("© 1995 Paul Olav Tvete\n© 2000 Stephan Kulow"),
122                           QString(),
123                           QStringLiteral("https://kde.org/applications/games/org.kde.kpat") );
124 
125     aboutData.setOrganizationDomain(QByteArray("kde.org"));
126     aboutData.addAuthor( i18n("Paul Olav Tvete"),
127                          i18n("Author of original Qt version"),
128                          QStringLiteral("paul@troll.no") );
129     aboutData.addAuthor( i18n("Mario Weilguni"),
130                          i18n("Initial KDE port"),
131                          QStringLiteral("mweilguni@kde.org") );
132     aboutData.addAuthor( i18n("Matthias Ettrich"),
133                          QString(),
134                          QStringLiteral("ettrich@kde.org") );
135     aboutData.addAuthor( i18n("Rodolfo Borges"),
136                          i18n("New game types"),
137                          QStringLiteral("barrett@9hells.org") );
138     aboutData.addAuthor( i18n("Peter H. Ruegg"),
139                          QString(),
140                          QStringLiteral("kpat@incense.org") );
141     aboutData.addAuthor( i18n("Michael Koch"),
142                          i18n("Bug fixes"),
143                          QStringLiteral("koch@kde.org") );
144     aboutData.addAuthor( i18n("Marcus Meissner"),
145                          i18n("Shuffle algorithm for game numbers"),
146                          QStringLiteral("mm@caldera.de") );
147     aboutData.addAuthor( i18n("Tom Holroyd"),
148                          i18n("Initial patience solver"),
149                          QStringLiteral("tomh@kurage.nimh.nih.gov") );
150     aboutData.addAuthor( i18n("Stephan Kulow"),
151                          i18n("Rewrite and current maintainer"),
152                          QStringLiteral("coolo@kde.org") );
153     aboutData.addAuthor( i18n("Erik Sigra"),
154                          i18n("Klondike improvements"),
155                          QStringLiteral("sigra@home.se") );
156     aboutData.addAuthor( i18n("Josh Metzler"),
157                          i18n("Spider implementation"),
158                          QStringLiteral("joshdeb@metzlers.org") );
159     aboutData.addAuthor( i18n("Maren Pakura"),
160                          i18n("Documentation"),
161                          QStringLiteral("maren@kde.org") );
162     aboutData.addAuthor( i18n("Inge Wallin"),
163                          i18n("Bug fixes"),
164                          QStringLiteral("inge@lysator.liu.se") );
165     aboutData.addAuthor( i18n("Simon Hürlimann"),
166                          i18n("Menu and toolbar work"),
167                          QStringLiteral("simon.huerlimann@huerlisi.ch") );
168     aboutData.addAuthor( i18n("Parker Coates"),
169                          i18n("Cleanup and polish"),
170                          QStringLiteral("coates@kde.org") );
171     aboutData.addAuthor( i18n("Shlomi Fish"),
172                          i18n("Integration with Freecell Solver and further work"),
173                          QStringLiteral("shlomif@cpan.org") );
174     aboutData.addAuthor( i18n("Michael Lang"),
175                          i18n("New game types"),
176                          QStringLiteral("criticaltemp@protonmail.com") );
177 
178     // Create a KLocale earlier than normal so that we can use i18n to translate
179     // the names of the game types in the help text.
180     QMap<QString, int> indexMap;
181     QStringList gameList;
182     const auto games = DealerInfoList::self()->games();
183     for (const DealerInfo *di : games) {
184         KLocalizedString localizedKey = ki18n( di->untranslatedBaseName().constData() );
185         //QT5 const QString translatedKey = lowerAlphaNum( localizedKey.toString( tmpLocale ) );
186         //QT5 gameList << translatedKey;
187         //QT5 indexMap.insert( translatedKey, di->baseId() );
188         indexMap.insert( di->baseIdString(), di->baseId() );
189     }
190     gameList.sort();
191     const QString listSeparator = i18nc( "List separator", ", " );
192 
193     QCommandLineParser parser;
194     KAboutData::setApplicationData(aboutData);
195     KCrash::initialize();
196 
197     parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("solvegame"), i18n( "Try to find a solution to the given savegame" ), QStringLiteral("file")));
198     parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("solve"), i18n("Dealer to solve (debug)" ), QStringLiteral("num")));
199     parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("start"), i18n("Game range start (default 0:INT_MAX)" ), QStringLiteral("num")));
200     parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("end"), i18n("Game range end (default start:start if start given)" ), QStringLiteral("num")));
201     parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("gametype"), i18n("Skip the selection screen and load a particular game type. Valid values are: %1",gameList.join(listSeparator)), QStringLiteral("game")));
202     parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("testdir"), i18n( "Directory with test cases" ), QStringLiteral("directory")));
203     parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("generate"), i18n( "Generate random test cases" )));
204     parser.addPositionalArgument(QStringLiteral("file"), i18n("File to load"));
205 
206     aboutData.setupCommandLine(&parser);
207     parser.process(app);
208     aboutData.processCommandLine(&parser);
209 
210     app.setWindowIcon(QIcon::fromTheme(QStringLiteral("kpat")));
211 
212     QString savegame = parser.value( QStringLiteral("solvegame") );
213     if ( !savegame.isEmpty() )
214     {
215         QFile of(savegame);
216         of.open(QIODevice::ReadOnly);
217         QDomDocument doc;
218         doc.setContent(&of);
219 
220         DealerScene *f = getDealer( doc.documentElement().attribute(QStringLiteral("id")).toInt(), QString() );
221 
222         f->loadLegacyFile( &of );
223         f->solver()->translate_layout();
224         int ret = f->solver()->patsolve();
225         if ( ret == SolverInterface::SolutionExists )
226             fprintf( stdout, "won\n");
227         else if ( ret == SolverInterface::NoSolutionExists )
228             fprintf( stdout, "lost\n" );
229         else
230             fprintf( stdout, "unknown\n");
231 
232         return 0;
233     }
234 
235     QString testdir = parser.value(QStringLiteral("testdir"));
236     if ( !testdir.isEmpty() ) {
237        if ( parser.isSet(QStringLiteral("generate")) ) {
238           for (int dealer = 0; dealer < 20; dealer++) {
239               DealerScene *f = getDealer( dealer, QString() );
240               if (!f) continue;
241               int count = 100;
242               QElapsedTimer mytime;
243               while (count) {
244                 if (f->deck()) f->deck()->stopAnimations();
245                 int i = QRandomGenerator::global()->bounded(INT_MAX);
246                 f->startNew( i );
247                 mytime.start();
248                 f->solver()->translate_layout();
249                 int ret = f->solver()->patsolve();
250                 if ( ret == SolverInterface::SolutionExists ) {
251                    fprintf( stdout, "%d: %d won (%lld ms)\n", dealer, i, mytime.elapsed() );
252                    count--;
253                    QFile file(QStringLiteral("%1/%2-%3-1").arg(testdir).arg(dealer).arg(i));
254                    file.open( QFile::WriteOnly );
255                    f->saveLegacyFile( &file );
256                 }
257                 else if ( ret == SolverInterface::NoSolutionExists ) {
258                    fprintf( stdout, "%d: %d lost (%lld ms)\n", dealer, i, mytime.elapsed()  );
259                    count--;
260                    QFile file(QStringLiteral("%1/%2-%3-0").arg(testdir).arg(dealer).arg(i));
261                    file.open( QFile::WriteOnly );
262                    f->saveLegacyFile( &file );
263                 } else {
264                    fprintf( stdout, "%d: %d unknown (%lld ms)\n", dealer, i, mytime.elapsed() );
265                 }
266              }
267           }
268        }
269        return 0;
270     }
271 
272     bool ok = false;
273     QString wanted_name;
274     int wanted_game = -1;
275     if ( parser.isSet( QStringLiteral("solve") ) )
276     {
277         wanted_name = parser.value(QStringLiteral("solve"));
278         ok = true;
279         bool isInt = false;
280         wanted_game = wanted_name.toInt( &isInt );
281         if (!isInt)
282             wanted_game = -1;
283     }
284     if ( ok )
285     {
286         ok = false;
287         int end_index = -1;
288         if ( parser.isSet( QStringLiteral("end") ) )
289             end_index = parser.value(QStringLiteral("end")).toInt( &ok );
290         if ( !ok )
291             end_index = -1;
292         ok = false;
293         int start_index = -1;
294         if ( parser.isSet( QStringLiteral("start") ) )
295             start_index = parser.value(QStringLiteral("start")).toInt( &ok );
296         if ( !ok ) {
297             start_index = 0;
298             end_index = INT_MAX;
299         } else {
300             if ( end_index == -1 )
301                 end_index = start_index;
302         }
303         DealerScene *f = getDealer( wanted_game , wanted_name );
304         if ( !f )
305             return 1;
306 
307         QElapsedTimer mytime;
308         for ( int i = start_index; i <= end_index; i++ )
309         {
310             mytime.start();
311             f->deck()->stopAnimations();
312             f->startNew( i );
313             f->solver()->translate_layout();
314             int ret = f->solver()->patsolve();
315             if ( ret == SolverInterface::SolutionExists )
316                 fprintf( stdout, "%d won (%lld ms)\n", i, mytime.elapsed() );
317             else if ( ret == SolverInterface::NoSolutionExists )
318                 fprintf( stdout, "%d lost (%lld ms)\n", i, mytime.elapsed()  );
319             else
320                 fprintf( stdout, "%d unknown (%lld ms)\n", i, mytime.elapsed() );
321         }
322         fprintf( stdout, "all_moves %ld\n", all_moves );
323         return 0;
324     }
325 
326     QString gametype = parser.value(QStringLiteral("gametype")).toLower();
327     QFile savedState( QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1String("/" saved_state_file));
328 
329     MainWindow *w = new MainWindow;
330     if (!parser.positionalArguments().isEmpty())
331     {
332         if ( !w->loadGame( QUrl::fromLocalFile(parser.positionalArguments().at( 0 )), true ) )
333             w->slotShowGameSelectionScreen();
334     }
335     else if (indexMap.contains(gametype))
336     {
337         w->slotGameSelected(indexMap.value(gametype));
338     }
339     else if (savedState.exists())
340     {
341         if ( !w->loadGame( QUrl::fromLocalFile(savedState.fileName()), false ) )
342             w->slotShowGameSelectionScreen();
343     }
344     else
345     {
346         w->slotShowGameSelectionScreen();
347     }
348     w->show();
349 
350     const KDBusService dbusService(KDBusService::Multiple);
351 
352     return app.exec();
353 }
354