File indexing completed on 2024-03-03 04:11: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 <KLocalizedString>
0018 #include <KMessageBox>
0019 
0020 #include <KGameHighScoreDialog>
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     KGameDifficulty::global()->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     KGameHighScoreDialog* highscoredialog = new KGameHighScoreDialog(
0143             KGameHighScoreDialog::Name | KGameHighScoreDialog::Score |
0144             KGameHighScoreDialog::Custom1 |
0145             KGameHighScoreDialog::Custom2 |
0146             KGameHighScoreDialog::Custom3,
0147             this);
0148     highscoredialog->initFromDifficulty(KGameDifficulty::global());
0149     highscoredialog->addField(KGameHighScoreDialog::Custom1, i18n("Shots"), QStringLiteral("shots"));
0150     highscoredialog->addField(KGameHighScoreDialog::Custom2, i18n("Hits"), QStringLiteral("hits"));
0151     highscoredialog->addField(KGameHighScoreDialog::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<KGameHighScoreDialog> highscoredialog = new KGameHighScoreDialog(
0163                     KGameHighScoreDialog::Name | KGameHighScoreDialog::Score |
0164                     KGameHighScoreDialog::Custom1 |
0165                     KGameHighScoreDialog::Custom2 |
0166                     KGameHighScoreDialog::Custom3,
0167                     this);
0168             highscoredialog->initFromDifficulty(KGameDifficulty::global());
0169             highscoredialog->addField(KGameHighScoreDialog::Custom1, i18n("Shots"), QStringLiteral("shots"));
0170             highscoredialog->addField(KGameHighScoreDialog::Custom2, i18n("Hits"), QStringLiteral("hits"));
0171             highscoredialog->addField(KGameHighScoreDialog::Custom3, i18n("Misses"), QStringLiteral("water"));
0172         
0173             KGameHighScoreDialog::FieldInfo info;
0174             info[KGameHighScoreDialog::Name] = m_menu->player(0)->nick();
0175             info[KGameHighScoreDialog::Score].setNum(stats->score());
0176             info[KGameHighScoreDialog::Custom1] = QString::number(stats->shots());
0177             info[KGameHighScoreDialog::Custom2] = QString::number(stats->hits());
0178             info[KGameHighScoreDialog::Custom3] = QString::number(stats->misses());
0179         
0180             int temp = highscoredialog->addScore(info, KGameHighScoreDialog::AskName);
0181             qCDebug(KNAVALBATTLE_LOG) << "temp =" << temp;
0182             //if (highscoredialog->addScore(info, KGameHighScoreDialog::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     int ans = KMessageBox::questionTwoActions(this,
0238                                          i18n("Restart game"),
0239                                          i18n("Your opponent has requested to restart the game. Do you accept?"),
0240                                          KGuiItem(i18nc("@action:button", "Accept"), QStringLiteral("dialog-ok")),
0241                                          KGuiItem(i18nc("@action:button", "Reject"), QStringLiteral("dialog-cancel")));
0242     if (ans == KMessageBox::PrimaryAction) {
0243         restart();
0244     }
0245 }
0246 
0247 void PlayField::setCompatibility(int level)
0248 {
0249     if (level == Entity::COMPAT_KBS3) {
0250         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."));
0251     }
0252 }
0253 
0254 void PlayField::updateNick(int player, const QString& nick)
0255 {
0256     m_seaView->setNick(Sea::Player(player), nick);
0257 }
0258 
0259 void PlayField::changeTurn(int player)
0260 {
0261     if (player == 0) {
0262         // local user
0263         m_status_bar->showMessage(i18n("Enemy has shot. Shoot now!"));
0264     }
0265     else {
0266         // opponent
0267         m_status_bar->showMessage(i18n("Waiting for enemy to shoot..."));
0268     }
0269 }
0270 
0271 void PlayField::playerReady(int player)
0272 {
0273     if (player == -1) {
0274         // game can start
0275         if (m_controller->turn() == 0) {
0276             m_status_bar->showMessage(i18n("Ships placed. Now shoot on the enemy field!"));
0277         }
0278         else {
0279             m_status_bar->showMessage(i18n("Waiting for other player to start the game..."));
0280         }
0281     }
0282     else if (player == 0) {
0283         m_status_bar->showMessage(i18n("Waiting for other player to place his ships..."));
0284     }
0285 }
0286 
0287 void PlayField::startGame()
0288 {
0289     //the game has a fixed difficulty level only if there is an AI
0290     KGameDifficulty::global()->setGameRunning(m_controller->hasAI());
0291     startPlacingShips();
0292     Q_EMIT placeShips();
0293 }
0294 
0295 void PlayField::startPlacingShips()
0296 {
0297     m_status_bar->showMessage(i18n("Place your %1 ships. Use the right mouse button to rotate them.", m_battle_ships_configuration.totalNumberOfShipsToPlay()));
0298 }
0299 
0300 
0301 void PlayField::restartPlacingShips(Sea::Player player)
0302 {
0303     m_status_bar->showMessage(i18n("You can't place your remaining ships."));
0304     KGuiItem buttonRestart(i18nc("@action", "Restart"), QStringLiteral("view-refresh"));
0305     KGuiItem buttonAbort(i18nc("@action", "Abort"), QStringLiteral("dialog-cancel"));
0306     int res=KMessageBox::warningTwoActions(this,
0307                                       i18n("You can't place your remaining ships. Please restart placing ships or abort game"), i18n("Restart placing ships"), buttonRestart, buttonAbort);
0308     if (res == KMessageBox::PrimaryAction) {
0309         startPlacingShips();
0310         m_controller->notifyRestartPlacingShips(player);
0311     }
0312     else {
0313         newGame();
0314     }
0315 }
0316 
0317 
0318 void PlayField::levelChanged()
0319 {
0320     if (m_controller && m_controller->hasAI()) {
0321         restart();
0322     }
0323 }
0324 
0325 SimpleMenu* PlayField::createAuxMenu()
0326 {
0327     SimpleMenu* menu = new SimpleMenu(this, nullptr);
0328     connect(menu, &SimpleMenu::done, this, &PlayField::auxMenuDone);
0329     return menu;
0330 }
0331 
0332 void PlayField::auxMenuDone()
0333 {
0334     qCDebug(KNAVALBATTLE_LOG) << "aux menu done";
0335     SimpleMenu* menu = qobject_cast<SimpleMenu*>(sender());
0336     if (menu) {
0337         delete m_menu;
0338         m_menu = menu;
0339         setupController();
0340     }
0341 }
0342 
0343 void PlayField::localGame()
0344 {
0345     createAuxMenu()->localGame();
0346 }
0347 
0348 void PlayField::createServer()
0349 {
0350     createAuxMenu()->createServer();
0351 }
0352 
0353 void PlayField::createClient()
0354 {
0355     createAuxMenu()->createClient();
0356 }
0357 
0358 void PlayField::createClientWithUrl(const QUrl& url)
0359 {
0360     createAuxMenu()->createClientWithUrl(url);
0361 }
0362 
0363 void PlayField::toggleEndOfGameMessage(bool show)
0364 {
0365     m_show_endofgame_message = show;
0366 }
0367 
0368 void PlayField::toggleLeftGrid(bool show)
0369 {
0370     m_seaView->toggleLeftGrid(show);
0371 }
0372 
0373 void PlayField::toggleRightGrid(bool show)
0374 {
0375     m_seaView->toggleRightGrid(show);
0376 }
0377 
0378 #include "moc_playfield.cpp"