File indexing completed on 2023-12-03 07:53:51

0001 /*
0002     SPDX-FileCopyrightText: 2000-2001 Nikolas Zimmermann <wildfox@kde.org>
0003     SPDX-FileCopyrightText: 2000-2001 Daniel Molkentin <molkentin@kde.org>
0004     SPDX-FileCopyrightText: 2007 Paolo Capriotti <p.capriotti@gmail.com>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "playfield.h"
0010 
0011 #include <QStatusBar>
0012 #include <QVBoxLayout>
0013 #include "knavalbattle_debug.h"
0014 #include <QInputDialog>
0015 #include <QPointer>
0016 
0017 #include <kwidgetsaddons_version.h>
0018 #include <KLocalizedString>
0019 #include <KMessageBox>
0020 #include <KScoreDialog>
0021 
0022 #include "aientity.h"
0023 #include "audioplayer.h"
0024 #include "chatwidget.h"
0025 #include "controller.h"
0026 #include "playerentity.h"
0027 #include "seaview.h"
0028 #include "settings.h"
0029 #include "simplemenu.h"
0030 #include "stats.h"
0031 #include "welcomescreen.h"
0032 
0033 static const int MINIMUM_HEIGHT = 400;
0034 
0035 PlayField::PlayField(QWidget* parent, QStatusBar* sbar)
0036 : QWidget(parent)
0037 , m_status_bar(sbar)
0038 , m_show_endofgame_message(true)
0039 {
0040     setMinimumSize(static_cast<int>(MINIMUM_HEIGHT * 1.6), MINIMUM_HEIGHT);
0041     QVBoxLayout* layout = new QVBoxLayout;
0042     
0043     m_seaView = new SeaView(this);
0044     layout->addWidget(m_seaView, 1);
0045     
0046     m_chat = new ChatWidget(this);
0047     m_chat->hide();
0048     layout->addWidget(m_chat, 1);
0049     
0050     layout->setContentsMargins(0, 0, 0, 0);
0051 //     layout->setSpacing(0);
0052     setLayout(layout);
0053         
0054     m_controller = nullptr;
0055     m_menu = nullptr;
0056     
0057     m_player = new AudioPlayer(this);
0058     m_player->setActive(Settings::enableSounds());
0059 }
0060 
0061 PlayField::~PlayField()
0062 {
0063     // controller assumes that the view is still valid
0064     // when it is destroyed
0065     delete m_controller;
0066 }
0067 
0068 Controller* PlayField::createController()
0069 {
0070     // The client or server will overwrite this default configuration when 
0071     // the network messages are interchanged
0072     m_battle_ships_configuration = Settings::severalShips() ? 
0073     BattleShipsConfiguration::defaultMultipleShipsConfiguration(Settings::adjacentShips()):
0074     BattleShipsConfiguration::defaultSingleShipsConfiguration(Settings::adjacentShips());
0075     Controller* controller = new Controller(this, m_player, m_battle_ships_configuration);
0076     connect(controller, &Controller::gameOver, this, &PlayField::gameOver);
0077     connect(controller, &Controller::restartRequested, this, &PlayField::restartRequested);
0078     connect(controller, &Controller::compatibility, this, &PlayField::setCompatibility);
0079     connect(controller, &Controller::nickChanged, this, &PlayField::updateNick);
0080     connect(controller, &Controller::turnChanged, this, &PlayField::changeTurn);
0081     connect(controller, &Controller::playerReady, this, &PlayField::playerReady);
0082     connect(controller, &Controller::restartPlacingShips, this, &PlayField::restartPlacingShips);
0083     connect(controller, &Controller::startPlacingShips, this, &PlayField::startPlacingShips);
0084 
0085     return controller;
0086 }
0087 
0088 void PlayField::setupController()
0089 {
0090     Animator::instance()->restart();
0091     m_seaView->clear();
0092     m_chat->hide();
0093     
0094     // remove welcome screen
0095     m_seaView->screen(Sea::Player(0))->fadeOut();
0096     m_seaView->screen(Sea::Player(1))->fadeOut();
0097 
0098     delete m_controller;
0099     m_controller = createController();
0100     m_menu->setupController(m_controller, nullptr, m_seaView, m_chat);
0101     startGame();
0102 }
0103 
0104 void PlayField::endGame()
0105 {
0106     Animator::instance()->restart();
0107     delete m_controller;
0108     m_controller = nullptr;
0109     m_seaView->clear();
0110 }
0111 
0112 void PlayField::newGame()
0113 {
0114     endGame();
0115     delete m_menu;
0116     
0117     Kg::difficulty()->setGameRunning(false);
0118     
0119     m_chat->hide();
0120     m_seaView->screen(Sea::Player(0))->show();
0121     m_seaView->screen(Sea::Player(1))->show();
0122     
0123     m_menu = new SimpleMenu(this, m_seaView->screen(Sea::Player(0)));
0124     connect(m_menu, &SimpleMenu::done, this, &PlayField::setupController);
0125     m_status_bar->showMessage(QLatin1String(""));
0126     Q_EMIT welcomeScreen();
0127 }
0128 
0129 void PlayField::restart()
0130 {
0131     Animator::instance()->restart();
0132     m_seaView->clear();
0133     startGame();
0134     m_controller->restart();
0135     m_menu->player(0)->stats()->reset();
0136     m_menu->player(1)->stats()->reset();
0137 }
0138 
0139 
0140 void PlayField::highscores()
0141 {
0142     KScoreDialog* highscoredialog = new KScoreDialog(
0143             KScoreDialog::Name | KScoreDialog::Score | 
0144             KScoreDialog::Custom1 | 
0145             KScoreDialog::Custom2 | 
0146             KScoreDialog::Custom3, 
0147             this);
0148     highscoredialog->initFromDifficulty(Kg::difficulty());
0149     highscoredialog->addField(KScoreDialog::Custom1, i18n("Shots"), QStringLiteral("shots"));
0150     highscoredialog->addField(KScoreDialog::Custom2, i18n("Hits"), QStringLiteral("hits"));
0151     highscoredialog->addField(KScoreDialog::Custom3, i18n("Misses"), QStringLiteral("water"));
0152     
0153     highscoredialog->exec();
0154 }
0155 
0156 void PlayField::gameOver(Sea::Player winner)
0157 {
0158     if (winner == Sea::Player(0)) {
0159         const Stats* stats = m_menu->player(0)->stats();
0160        
0161         if (stats && qobject_cast<const AIEntity*>(m_menu->player(1))) {
0162             QPointer<KScoreDialog> highscoredialog = new KScoreDialog(
0163                     KScoreDialog::Name | KScoreDialog::Score | 
0164                     KScoreDialog::Custom1 | 
0165                     KScoreDialog::Custom2 | 
0166                     KScoreDialog::Custom3, 
0167                     this);
0168             highscoredialog->initFromDifficulty(Kg::difficulty());
0169             highscoredialog->addField(KScoreDialog::Custom1, i18n("Shots"), QStringLiteral("shots"));
0170             highscoredialog->addField(KScoreDialog::Custom2, i18n("Hits"), QStringLiteral("hits"));
0171             highscoredialog->addField(KScoreDialog::Custom3, i18n("Misses"), QStringLiteral("water"));
0172         
0173             KScoreDialog::FieldInfo info;
0174             info[KScoreDialog::Name] = m_menu->player(0)->nick();
0175             info[KScoreDialog::Score].setNum(stats->score());
0176             info[KScoreDialog::Custom1] = QString::number(stats->shots());
0177             info[KScoreDialog::Custom2] = QString::number(stats->hits());
0178             info[KScoreDialog::Custom3] = QString::number(stats->misses());
0179         
0180             int temp = highscoredialog->addScore(info, KScoreDialog::AskName);
0181             qCDebug(KNAVALBATTLE_LOG) << "temp =" << temp;
0182             //if (highscoredialog->addScore(info, KScoreDialog::AskName)) {
0183             if (temp != 0) {
0184                 highscoredialog->exec();
0185                 delete highscoredialog;
0186                 return;
0187             }
0188             delete highscoredialog;
0189         }
0190         
0191         m_status_bar->showMessage(i18n("You win!"));
0192         if (m_show_endofgame_message) {
0193             KMessageBox::information(this, i18n("You win. Excellent!"));
0194         }
0195     }
0196     else {
0197         m_status_bar->showMessage(i18n("You lose."));
0198         if (m_show_endofgame_message) {
0199             KMessageBox::information(this, i18n("You lose. Better luck next time!"));
0200         }
0201     }
0202     
0203     // When we have finished, show again the welcome screen
0204     Q_EMIT gameFinished();
0205 }
0206 
0207 void PlayField::changeNick()
0208 {
0209     QString nick = QInputDialog::getText(this, i18n("Change Nickname"), i18n("Enter new nickname:"), QLineEdit::Normal, Settings::findNick());
0210     if (!nick.isEmpty()) {
0211         Settings::setNickname(nick);
0212         Settings::self()->save();
0213     }
0214 }
0215 
0216 void PlayField::toggleSounds(bool enable)
0217 {
0218     Settings::setEnableSounds(enable);
0219     Settings::self()->save();
0220     m_player->setActive(enable);
0221 }
0222 
0223 void PlayField::toggleAdjacent(bool enable)
0224 {
0225     Settings::setAdjacentShips(enable);
0226     Settings::self()->save();
0227 }
0228 
0229 void PlayField::toggleMultiple(bool enable)
0230 {
0231     Settings::setSeveralShips(enable);
0232     Settings::self()->save();
0233 }
0234 
0235 void PlayField::restartRequested()
0236 {
0237 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
0238     int ans = KMessageBox::questionTwoActions(this,
0239 #else
0240     int ans = KMessageBox::questionYesNo(this,
0241 #endif
0242                                          i18n("Restart game"),
0243                                          i18n("Your opponent has requested to restart the game. Do you accept?"),
0244                                          KGuiItem(i18nc("@action:button", "Accept"), QStringLiteral("dialog-ok")),
0245                                          KGuiItem(i18nc("@action:button", "Reject"), QStringLiteral("dialog-cancel")));
0246 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
0247     if (ans == KMessageBox::PrimaryAction) {
0248 #else
0249     if (ans == KMessageBox::Yes) {
0250 #endif
0251         restart();
0252     }
0253 }
0254 
0255 void PlayField::setCompatibility(int level)
0256 {
0257     if (level == Entity::COMPAT_KBS3) {
0258         KMessageBox::information(this, i18n("Your opponent is using a pre-KDE4 version of Naval Battle. Note that, according to the rules enforced by old clients, ships cannot be placed adjacent to one another and only one ship of each size is allowed."));
0259     }
0260 }
0261 
0262 void PlayField::updateNick(int player, const QString& nick)
0263 {
0264     m_seaView->setNick(Sea::Player(player), nick);
0265 }
0266 
0267 void PlayField::changeTurn(int player)
0268 {
0269     if (player == 0) {
0270         // local user
0271         m_status_bar->showMessage(i18n("Enemy has shot. Shoot now!"));
0272     }
0273     else {
0274         // opponent
0275         m_status_bar->showMessage(i18n("Waiting for enemy to shoot..."));
0276     }
0277 }
0278 
0279 void PlayField::playerReady(int player)
0280 {
0281     if (player == -1) {
0282         // game can start
0283         if (m_controller->turn() == 0) {
0284             m_status_bar->showMessage(i18n("Ships placed. Now shoot on the enemy field!"));
0285         }
0286         else {
0287             m_status_bar->showMessage(i18n("Waiting for other player to start the game..."));
0288         }
0289     }
0290     else if (player == 0) {
0291         m_status_bar->showMessage(i18n("Waiting for other player to place his ships..."));
0292     }
0293 }
0294 
0295 void PlayField::startGame()
0296 {
0297     //the game has a fixed difficulty level only if there is an AI
0298     Kg::difficulty()->setGameRunning(m_controller->hasAI());
0299     startPlacingShips();
0300     Q_EMIT placeShips();
0301 }
0302 
0303 void PlayField::startPlacingShips()
0304 {
0305     m_status_bar->showMessage(i18n("Place your %1 ships. Use the right mouse button to rotate them.", m_battle_ships_configuration.totalNumberOfShipsToPlay()));
0306 }
0307 
0308 
0309 void PlayField::restartPlacingShips(Sea::Player player)
0310 {
0311     m_status_bar->showMessage(i18n("You can't place your remaining ships."));
0312     KGuiItem buttonRestart(i18nc("@action", "Restart"), QStringLiteral("view-refresh"));
0313     KGuiItem buttonAbort(i18nc("@action", "Abort"), QStringLiteral("dialog-cancel"));
0314 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
0315     int res=KMessageBox::warningTwoActions(this,
0316 #else
0317     int res=KMessageBox::warningYesNo(this,
0318 #endif
0319                                       i18n("You can't place your remaining ships. Please restart placing ships or abort game"), i18n("Restart placing ships"), buttonRestart, buttonAbort);
0320 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
0321     if (res == KMessageBox::PrimaryAction) {
0322 #else
0323     if (res == KMessageBox::Yes) {
0324 #endif
0325         startPlacingShips();
0326         m_controller->notifyRestartPlacingShips(player);
0327     }
0328     else {
0329         newGame();
0330     }
0331 }
0332 
0333 
0334 void PlayField::levelChanged()
0335 {
0336     if (m_controller && m_controller->hasAI()) {
0337         restart();
0338     }
0339 }
0340 
0341 SimpleMenu* PlayField::createAuxMenu()
0342 {
0343     SimpleMenu* menu = new SimpleMenu(this, nullptr);
0344     connect(menu, &SimpleMenu::done, this, &PlayField::auxMenuDone);
0345     return menu;
0346 }
0347 
0348 void PlayField::auxMenuDone()
0349 {
0350     qCDebug(KNAVALBATTLE_LOG) << "aux menu done";
0351     SimpleMenu* menu = qobject_cast<SimpleMenu*>(sender());
0352     if (menu) {
0353         delete m_menu;
0354         m_menu = menu;
0355         setupController();
0356     }
0357 }
0358 
0359 void PlayField::localGame()
0360 {
0361     createAuxMenu()->localGame();
0362 }
0363 
0364 void PlayField::createServer()
0365 {
0366     createAuxMenu()->createServer();
0367 }
0368 
0369 void PlayField::createClient()
0370 {
0371     createAuxMenu()->createClient();
0372 }
0373 
0374 void PlayField::createClientWithUrl(const QUrl& url)
0375 {
0376     createAuxMenu()->createClientWithUrl(url);
0377 }
0378 
0379 void PlayField::toggleEndOfGameMessage(bool show)
0380 {
0381     m_show_endofgame_message = show;
0382 }
0383 
0384 void PlayField::toggleLeftGrid(bool show)
0385 {
0386     m_seaView->toggleLeftGrid(show);
0387 }
0388 
0389 void PlayField::toggleRightGrid(bool show)
0390 {
0391     m_seaView->toggleRightGrid(show);
0392 }
0393 
0394 #include "moc_playfield.cpp"