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