1 /*
2     This file is part of the KDE games lskat program
3     SPDX-FileCopyrightText: 2006 Martin Heni <kde@heni-online.de>
4 
5     SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "mainwindow.h"
9 
10 // Include files for Qt
11 #include <QAction>
12 #include <QDir>
13 #include <QKeySequence>
14 #include <QPointer>
15 #include <QRandomGenerator>
16 #include <QStatusBar>
17 #include <QStandardPaths>
18 
19 // KF includes
20 #include <KActionCollection>
21 #include <KConfigGroup>
22 #include <KMessageBox>
23 #include <KSharedConfig>
24 #include <KStandardGameAction>
25 #include <KLocalizedString>
26 #include <KSelectAction>
27 // Application specific includes
28 #include "lskat_debug.h"
29 #include "lskatglobal.h"
30 #include "gameview.h"
31 #include "abstractengine.h"
32 #include "engine_two.h"
33 #include "display_two.h"
34 #include "config_two.h"
35 #include "display_intro.h"
36 #include "deck.h"
37 #include "player.h"
38 #include "mouseinput.h"
39 #include "aiinput.h"
40 #include "namedialogwidget.h"
41 #include "fromlibkdegames/kcarddialog.h"
42 #include "fromlibkdegames/carddeckinfo.h"
43 
44 // Configuration file
45 #include <config-src.h>
46 
47 // Forward declarations
48 const int ADVANCE_PERIOD =  20;
49 
50 // Shortcut to access the actions
51 #define ACTION(x)   (actionCollection()->action(x))
52 
53 using namespace InputDevice;
54 
55 // Construct the main application window
Mainwindow(QWidget * parent)56 Mainwindow::Mainwindow(QWidget *parent)
57           : KXmlGuiWindow(parent)
58 {
59     // Reset stuff
60     mDeck        = nullptr;
61     mEngine      = nullptr;
62     mDisplay     = nullptr;
63     mView        = nullptr;
64     mLSkatConfig = nullptr;
65     mCanvas      = nullptr;
66     mTheme       = nullptr;
67 
68     // Read theme files
69     QStringList themeList;
70     const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("grafix"), QStandardPaths::LocateDirectory);
71     for (const QString& dir : dirs) {
72         const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.desktop"));
73         for (const QString& file : fileNames) {
74             themeList.append(dir + '/' + file);
75         }
76     }
77     if (themeList.isEmpty())
78     {
79         KMessageBox::error(this, i18n("Installation error: No theme list found."));
80         QTimer::singleShot(0, this, &QWidget::close);
81         return;
82     }
83 
84     // Read theme files
85     for (int i = 0; i < themeList.size(); i++)
86     {
87         KConfig themeInfo(themeList.at(i), KConfig::SimpleConfig);
88         KConfigGroup themeGroup(&themeInfo, "Theme");
89         QString name = themeGroup.readEntry("Name", QString());
90         QString file = themeGroup.readEntry("File", QString());
91         bool isDefault = themeGroup.readEntry("Default", false);
92         mThemeFiles[name] = file;
93         if (mThemeDefault.isNull()) mThemeDefault = name;
94         if (isDefault) mThemeDefault = name;
95 
96         if (global_debug > 0) qCDebug(LSKAT_LOG) << "Found theme: " << themeList.at(i) << " Name(i18n)=" << name << " File=" << file << " default=" << isDefault;
97     }
98     mThemeIndexNo = themeIdxFromName(mThemeDefault);
99 
100     // Create menus etc
101     initGUI();
102 
103     // The LSkat config
104     mLSkatConfig = new ConfigTwo(this);
105     connect(mLSkatConfig, &ConfigTwo::signalInputType, this, &Mainwindow::setInputType);
106     mLSkatConfig->reset();
107 
108     // Read game properties and set default values (after config)
109     readProperties();
110 
111     // TODO: Bugfix: Needs to be here if initGUI is before readProperties
112     if (global_debug > 0) qCDebug(LSKAT_LOG) << "Setting current theme item to " << mThemeIndexNo;
113     ((KSelectAction *)ACTION(QLatin1String("theme")))->setCurrentItem(mThemeIndexNo);
114 
115     // Get the card deck
116     const quint32 seed = QRandomGenerator::global()->generate();
117     if (global_debug > 0) qCDebug(LSKAT_LOG) << "Random seed" << seed;
118     mDeck = new Deck(seed, this);
119 
120     // Theme manager
121     QString themeFile = themefileFromIdx(mThemeIndexNo);
122     if (global_debug > 0) qCDebug(LSKAT_LOG) << "Load theme" << themeFile << " no=" << mThemeIndexNo;
123     mTheme  = new ThemeManager(mCardTheme, themeFile, this, this->width());
124     if (mTheme->checkTheme() != 0)
125     {
126         KMessageBox::error(this, i18n("Installation error: Theme file error."));
127         QTimer::singleShot(0, this, &QWidget::close);
128         return;
129     }
130 
131     // Overall view
132     mCanvas        = new QGraphicsScene(this);
133     mView          = new GameView(QSize(880, 675), ADVANCE_PERIOD, mCanvas, mTheme, this);
134 
135     // Create intro
136     mGameMode      = Intro;
137     mDisplay       = new DisplayIntro(mDeck, mCanvas, mTheme, ADVANCE_PERIOD, mView);
138     setCentralWidget(mView);
139     connect(mView, &GameView::signalLeftMousePress, this, &Mainwindow::menuNewLSkatGame);
140 
141     // Create GUI
142     setupGUI();
143 
144     statusBar()->showMessage(i18n("Welcome to Skat! Please start a new game."));
145 
146     // Skip intro?
147     if (global_skip_intro)
148     {
149         menuNewLSkatGame();
150     }
151     // Start game automatically in demo mode
152     else if (global_demo_mode)
153     {
154         // Start intro
155         mDisplay->start();
156         QTimer::singleShot(12500, this, &Mainwindow::menuNewLSkatGame);
157     }
158     else
159     {
160         // Start intro
161         mDisplay->start();
162     }
163     if (global_debug > 0) qCDebug(LSKAT_LOG) << "Mainwindow setup constructor done";
164 }
165 
166 // Destructor
~Mainwindow()167 Mainwindow::~Mainwindow()
168 {
169     saveProperties();
170     delete mEngine;
171     delete mDisplay;
172     delete mLSkatConfig;
173     delete mDeck;
174     delete mView;
175     delete mCanvas;
176     delete mTheme;
177 }
178 
179 // Called by KMainWindow when the last window of the application is
closeEvent(QCloseEvent * event)180 void Mainwindow::closeEvent(QCloseEvent *event)
181 {
182     if (mEngine)
183     {
184         mEngine->stopGame();
185     }
186     saveProperties();
187     KXmlGuiWindow::closeEvent(event);
188 }
189 
190 // Retrieve a theme file name from the menu index number
themefileFromIdx(int idx)191 QString Mainwindow::themefileFromIdx(int idx)
192 {
193     QStringList list(mThemeFiles.keys());
194     list.sort();
195     QString themeFile = mThemeFiles[list.at(idx)];
196     return themeFile;
197 }
198 
199 // Retrieve a theme idx from a theme name
themeIdxFromName(const QString & name)200 int Mainwindow::themeIdxFromName(const QString &name)
201 {
202     QStringList list(mThemeFiles.keys());
203     list.sort();
204     for (int i = 0; i < list.size(); ++i)
205     {
206         if (list[i] == name) return i;
207     }
208     qCCritical(LSKAT_LOG) << "Theme index lookup failed for " << name;
209     return 0;
210 }
211 
212 // Save properties
saveProperties()213 void Mainwindow::saveProperties()
214 {
215     KConfig *config = KSharedConfig::openConfig().data();
216 
217     // Program data
218     KConfigGroup cfg = config->group("ProgramData");
219     cfg.writeEntry("startplayer", mStartPlayer);
220     cfg.writeEntry("ThemeIndexNo", mThemeIndexNo);
221 
222     // LSkat data
223     mLSkatConfig->save(config);
224     config->sync();
225 }
226 
227 // Load properties
readProperties()228 void Mainwindow::readProperties()
229 {
230     KConfig *config = KSharedConfig::openConfig().data();
231 
232     // Program data
233     KConfigGroup cfg = config->group("ProgramData");
234 
235     // Theme number
236     mThemeIndexNo = cfg.readEntry("ThemeIndexNo", themeIdxFromName(mThemeDefault));
237     if (mThemeIndexNo >= mThemeFiles.size()) mThemeIndexNo = 0;
238 
239     // Read card path
240     mCardTheme  = CardDeckInfo::deckName(cfg);
241 
242     int no = cfg.readEntry("startplayer", 0);
243     setStartPlayer(no);
244     mLSkatConfig->load(config);
245 }
246 
247 // Create a input with the given type
createInput(InputDeviceType inputType,AbstractDisplay * display,AbstractEngine * engine)248 AbstractInput *Mainwindow::createInput(
249                                  InputDeviceType inputType,
250                                  AbstractDisplay *display,
251                                  AbstractEngine *engine)
252 {
253     AbstractInput *input = nullptr;
254 
255     // Always use AI input in demo mode
256     if (global_demo_mode)
257     {
258         inputType = TypeAiInput;
259     }
260 
261     // Create the player input
262     if (inputType == TypeMouseInput)
263     {
264         MouseInput *mouseInput = new MouseInput(this);
265         connect(mView, &GameView::signalLeftMousePress,
266                 mouseInput, &MouseInput::mousePress);
267         connect(mouseInput, &MouseInput::signalConvertMousePress,
268                 display, &AbstractDisplay::convertMousePress);
269         connect(mouseInput, &MouseInput::signalPlayerInput,
270                 engine, &AbstractEngine::playerInput);
271         input = mouseInput;
272         if (global_debug > 0) qCDebug(LSKAT_LOG) << "Create MOUSE INPUT";
273     }
274     else if (inputType == TypeAiInput)
275     {
276         AiInput *aiInput = new AiInput((EngineTwo *)engine, this);
277         connect(aiInput, &AiInput::signalPlayerInput,
278                 engine, &AbstractEngine::playerInput);
279         input = aiInput;
280         if (global_debug > 0) qCDebug(LSKAT_LOG) << "Create AI INPUT";
281     }
282     else
283     {
284         qCCritical(LSKAT_LOG) << "Unsupported input device type" << inputType;
285     }
286 
287     return input;
288 }
289 
290 // Start a new game
startGame()291 void Mainwindow::startGame()
292 {
293     // Enable game action
294     QLatin1String endName(KStandardGameAction::name(KStandardGameAction::End));
295     ACTION(endName)->setEnabled(true);
296 
297     // Deal cards to player - Shuffle card deck and reset pile
298     mDeck->shuffle();
299 
300     // Draw Trump
301     Suite trump = mDeck->randomTrump();
302 
303     // Loop all players in the game
304     QHashIterator<int,Player *> it = mLSkatConfig->playerIterator();
305     while (it.hasNext())
306     {
307         it.next();
308         Player *player = it.value();
309         player->setDeck(mDeck);
310         // Deal cards
311         player->deal(16);
312         // Store trump
313         player->setTrump(trump);
314     }
315 
316     // Start display
317     mDisplay->start();
318 
319     // Start the game engine
320     mEngine->startGame(trump, mStartPlayer);
321 
322     // Start player for next game
323     setStartPlayer(1-mStartPlayer);
324 
325     //  statusBar()->clearMessage();
326 }
327 
328 // Here a game over is signalled
gameOver(int)329 void Mainwindow::gameOver(int /*winner*/)
330 {
331     QLatin1String endName(KStandardGameAction::name(KStandardGameAction::End));
332     ACTION(endName)->setEnabled(false);
333     statusBar()->showMessage(i18n("Game Over. Please start a new game."));
334 
335     // Automatically restart game in demo mode
336     if (global_demo_mode)
337     {
338         QTimer::singleShot(10000, this, &Mainwindow::menuNewLSkatGame);
339     }
340 }
341 
342 // Show next player
nextPlayer(Player * player)343 void Mainwindow::nextPlayer(Player *player)
344 {
345     int no       = player->id() + 1;
346     QString name = player->name();
347     statusBar()->showMessage(i18nc("Player name and number", "Next move for %1 (player %2)", name, no));
348 }
349 
350 // Setup the GUI
initGUI()351 void Mainwindow::initGUI()
352 {
353     QAction *action;
354 
355     // Start a new game
356     action = KStandardGameAction::gameNew(this, &Mainwindow::menuNewLSkatGame, actionCollection());
357     if (global_demo_mode) action->setEnabled(false);
358 
359     // Clear all time statistics
360     action = KStandardGameAction::clearStatistics(this, &Mainwindow::menuClearStatistics, actionCollection());
361     action->setWhatsThis(i18n("Clears the all time statistics which is kept in all sessions."));
362     if (global_demo_mode) action->setEnabled(false);
363 
364     // End a game
365     action = KStandardGameAction::end(this, &Mainwindow::menuEndGame, actionCollection());
366     action->setWhatsThis(i18n("Ends a currently played game. No winner will be declared."));
367     action->setEnabled(false);
368 
369     // Quit the program
370     action = KStandardGameAction::quit(this, &QWidget::close, actionCollection());
371     action->setWhatsThis(i18n("Quits the program."));
372 
373     // Determine start player
374     KSelectAction *startPlayerAct = new KSelectAction(i18n("Starting Player"), this);
375     actionCollection()->addAction(QStringLiteral("startplayer"), startPlayerAct);
376     connect(startPlayerAct, &KSelectAction::indexTriggered, this, &Mainwindow::menuStartplayer);
377     startPlayerAct->setToolTip(i18n("Changing starting player..."));
378     startPlayerAct->setWhatsThis(i18n("Chooses which player begins the next game."));
379     QStringList list;
380     list.clear();
381     list.append(i18n("Player &1"));
382     list.append(i18n("Player &2"));
383     startPlayerAct->setItems(list);
384     if (global_demo_mode) startPlayerAct->setEnabled(false);
385 
386     // Determine who plays player 1
387     KSelectAction *player1Act = new KSelectAction(i18n("Player &1 Played By"), this);
388     actionCollection()->addAction(QStringLiteral("player1"), player1Act);
389     connect(player1Act, &KSelectAction::indexTriggered, this, &Mainwindow::menuPlayer1By);
390     player1Act->setToolTip(i18n("Changing who plays player 1..."));
391     player1Act->setWhatsThis(i18n("Changing who plays player 1."));
392     list.clear();
393     list.append(i18n("&Mouse"));
394     list.append(i18n("&Computer"));
395     player1Act->setItems(list);
396     if (global_demo_mode) player1Act->setEnabled(false);
397 
398     // Determine who plays player 2
399     KSelectAction *player2Act = new KSelectAction(i18n("Player &2 Played By"), this);
400     actionCollection()->addAction(QStringLiteral("player2"), player2Act);
401     connect(player2Act, &KSelectAction::indexTriggered, this, &Mainwindow::menuPlayer2By);
402     player2Act->setToolTip(i18n("Changing who plays player 2..."));
403     player2Act->setWhatsThis(i18n("Changing who plays player 2."));
404     player2Act->setItems(list);
405     if (global_demo_mode) player2Act->setEnabled(false);
406 
407     // Add all theme files to the menu
408     QStringList themes(mThemeFiles.keys());
409     themes.sort();
410 
411     KSelectAction *themeAct = new KSelectAction(i18n("&Theme"), this);
412     actionCollection()->addAction(QStringLiteral("theme"), themeAct);
413     themeAct->setItems(themes);
414     connect(themeAct, &KSelectAction::indexTriggered, this, &Mainwindow::changeTheme);
415     if (global_debug > 0) qCDebug(LSKAT_LOG) << "Setting current theme item to " << mThemeIndexNo;
416     themeAct->setCurrentItem(mThemeIndexNo);
417     themeAct->setToolTip(i18n("Changing theme..."));
418     themeAct->setWhatsThis(i18n("Changing theme."));
419 
420     // Choose card deck
421     QAction *action1 = actionCollection()->addAction(QStringLiteral("select_carddeck"));
422     action1->setText(i18n("Select &Card Deck..."));
423     actionCollection()->setDefaultShortcut(action1, QKeySequence(Qt::Key_F10));
424     connect(action1, &QAction::triggered, this, &Mainwindow::menuCardDeck);
425     action1->setToolTip(i18n("Configure card decks..."));
426     action1->setWhatsThis(i18n("Choose how the cards should look."));
427 
428     // Change player names
429     action = actionCollection()->addAction(QStringLiteral("change_names"));
430     action->setText(i18n("&Change Player Names..."));
431     connect(action, &QAction::triggered, this, &Mainwindow::menuPlayerNames);
432     if (global_demo_mode) action->setEnabled(false);
433 }
434 
435 // Choose start player
menuStartplayer()436 void Mainwindow::menuStartplayer()
437 {
438     int i = ((KSelectAction *)ACTION(QLatin1String("startplayer")))->currentItem();
439     setStartPlayer(i);
440 }
441 
442 // Change the theme of the game
changeTheme(int idx)443 void Mainwindow::changeTheme(int idx)
444 {
445     mThemeIndexNo = idx;
446     QString themeFile = themefileFromIdx(idx);
447     if (global_debug > 0) qCDebug(LSKAT_LOG) << "Select theme " << themeFile;
448     mTheme->updateTheme(themeFile);
449 }
450 
451 // Select input for player 1
menuPlayer1By()452 void Mainwindow::menuPlayer1By()
453 {
454     int i = ((KSelectAction *)ACTION(QLatin1String("player1")))->currentItem();
455     mLSkatConfig->setInputType(0, (InputDeviceType)i);
456 }
457 
458 // Select input for player 2
menuPlayer2By()459 void Mainwindow::menuPlayer2By()
460 {
461     int i = ((KSelectAction *)ACTION(QLatin1String("player2")))->currentItem();
462     mLSkatConfig->setInputType(1, (InputDeviceType)i);
463 }
464 
465 // Choose a card deck
menuCardDeck()466 void Mainwindow::menuCardDeck()
467 {
468     QString front = mCardTheme;
469 
470     KConfigGroup grp = KSharedConfig::openConfig()->group("ProgramData");
471     KCardWidget *cardwidget = new KCardWidget();
472     QPointer<KCardDialog> dlg;
473 
474     cardwidget->readSettings(grp);
475     dlg = new KCardDialog(cardwidget);
476     if (dlg->exec() == QDialog::Accepted)
477     {
478         // Always store the settings, other things than the deck may have changed
479         cardwidget->saveSettings(grp);
480         grp.sync();
481         if (global_debug > 0) qCDebug(LSKAT_LOG) << "NEW CARDDECK: " << front;
482         bool change = false; // Avoid unnecessary changes
483         if (!cardwidget->deckName().isEmpty() && cardwidget->deckName() != mCardTheme)
484         {
485             mCardTheme    = cardwidget->deckName();
486             change = true;
487         }
488         if (change)
489         {
490             mTheme->updateCardTheme(mCardTheme);
491             mView->update(); // Be on the safe side and update
492         }
493     }
494     delete dlg;
495 }
496 
497 // Clear all time statistics
menuClearStatistics()498 void Mainwindow::menuClearStatistics()
499 {
500     QString message;
501     message = i18n("Do you really want to clear the all time "
502                     "statistical data?");
503 
504     if (KMessageBox::Yes == KMessageBox::questionYesNo(this,
505                                                        message,
506                                                        QString(),
507                                                        KStandardGuiItem::clear()))
508     {
509         QHashIterator<int,Player *> it = mLSkatConfig->playerIterator();
510         while (it.hasNext())
511         {
512             it.next();
513             Player *player = it.value();
514             player->clear();
515         }
516     }
517 }
518 
519 // Abort a game
menuEndGame()520 void Mainwindow::menuEndGame()
521 {
522     if (mEngine)
523     {
524         mEngine->stopGame();
525     }
526 }
527 
528 // Start a new game
menuNewLSkatGame()529 void Mainwindow::menuNewLSkatGame()
530 {
531     disconnect(mView, &GameView::signalLeftMousePress, this, &Mainwindow::menuNewLSkatGame);
532 
533     Player *p1 = mLSkatConfig->player(0);
534     Player *p2 = mLSkatConfig->player(1);
535 
536     // Stop running games
537     if (mEngine)
538     {
539         mEngine->stopGame();
540     }
541 
542     // Get rid of old stuff?
543     if (true || (mGameMode != LSkat)) // Yes! Fixes bugs 330308 and 228067.
544     // Jenkins objected to the indentation if the above was simply commented out.
545     {
546         // Set new game mode
547         mGameMode = LSkat;
548 
549         // Start deleting
550         delete mDisplay;
551         delete mEngine;
552 
553         auto *display = new DisplayTwo(mDeck, mCanvas, mTheme, ADVANCE_PERIOD, mView);
554         mDisplay = display;
555         mEngine = new EngineTwo(this, mDeck, (DisplayTwo *)mDisplay);
556         connect(mEngine, &AbstractEngine::signalGameOver, this, &Mainwindow::gameOver);
557         connect(mEngine, &AbstractEngine::signalNextPlayer, this, &Mainwindow::nextPlayer);
558 
559         // Connect player score widget updates
560         connect(p1, &Player::signalUpdate, display, &DisplayTwo::updatePlayer);
561         connect(p2, &Player::signalUpdate, display, &DisplayTwo::updatePlayer);
562 
563         mEngine->addPlayer(0, p1);
564         mEngine->addPlayer(1, p2);
565     }// end if
566 
567     // Create inputs and store in player
568     AbstractInput *input1 = createInput(mLSkatConfig->inputType(0), mDisplay, mEngine);
569     p1->setInput(input1);
570     AbstractInput *input2 = createInput(mLSkatConfig->inputType(1), mDisplay, mEngine);
571     p2->setInput(input2);
572 
573     statusBar()->showMessage(i18n("Dealing cards..."));
574 
575     // Start game
576     startGame();
577 }
578 
579 // Change the player names in a dialog
menuPlayerNames()580 void Mainwindow::menuPlayerNames()
581 {
582     QPointer<NameDialogWidget> dlg = new NameDialogWidget(this);
583     for (int i = 0; i < 2; i++)
584     {
585         Player *p = mLSkatConfig->player(i);
586         dlg->setName(i, p->name());
587     }
588 
589     if (dlg->exec() == QDialog::Accepted)
590     {
591         for (int i = 0; i < 2; i++)
592         {
593             Player *p = mLSkatConfig->player(i);
594             p->setName(dlg->name(i));
595         }
596     }
597 
598     delete dlg;
599 }
600 
601 // Set the start player.
setStartPlayer(int no)602 void Mainwindow::setStartPlayer(int no)
603 {
604     mStartPlayer = no;
605     ((KSelectAction *)ACTION(QLatin1String("startplayer")))->setCurrentItem(mStartPlayer);
606 }
607 
608 // Set the input type for a given player number.
setInputType(int no,InputDeviceType type)609 void Mainwindow::setInputType(int no, InputDeviceType type)
610 {
611     Player *p = nullptr;
612     // Player 1
613     if (no == 0)
614     {
615         ((KSelectAction *)ACTION(QLatin1String("player1")))->setCurrentItem((int)type);
616         p = mLSkatConfig->player(0);
617     }
618     else if (no == 1)
619     {
620         ((KSelectAction *)ACTION(QLatin1String("player2")))->setCurrentItem((int)type);
621         p = mLSkatConfig->player(1);
622     }
623 
624     // Exchange player input at runtime
625     if (mEngine && p && mDisplay && mEngine->isGameRunning())
626     {
627         AbstractInput *input = createInput(type, mDisplay, mEngine);
628         p->setInput(input);
629     }
630 }
631