File indexing completed on 2024-05-05 04:02:03

0001 /*
0002     SPDX-FileCopyrightText: 2007-2008 John-Paul Stanford <jp@stanwood.org.uk>
0003     SPDX-FileCopyrightText: 2010 Stefan Majewsky <majewsky@gmx.net>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 // own
0009 #include "bomberwidget.h"
0010 
0011 // Qt
0012 #include <QGraphicsItem>
0013 #include <QTimer>
0014 
0015 // KF
0016 #include <KLocalizedString>
0017 
0018 // Bomber
0019 #include "settings.h"
0020 
0021 #define NEW_LIVE_AT_SCORE 10000;
0022 
0023 /** The amount the score increases when a bomb hits something */
0024 static const unsigned int SCORE_INCREMENT = 5;
0025 
0026 static const unsigned int GAME_TIME_DELAY = 1000;
0027 static const unsigned int TICKS_PER_SECOND = 1000 / GAME_TIME_DELAY;
0028 /** The z-value for overlays */
0029 static const unsigned int OVERLAY_Z_VALUE = 1000;
0030 
0031 BomberGameWidget::BomberGameWidget(KGameThemeProvider * provider, QWidget * parent)
0032     : QGraphicsView(parent)
0033     , m_state(State::BeforeFirstGame)
0034     , m_level(0)
0035     , m_lives(0)
0036     , m_time(0)
0037     , m_renderer(provider)
0038     , m_soundBomb(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("bomber/sounds/bomb.ogg")))
0039     , m_soundCrash(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("bomber/sounds/crash.ogg")))
0040 {
0041     // Gameboard
0042     m_board = new BomberBoard(&m_renderer, this, this);
0043     connect(m_board, &BomberBoard::playBombSound, this, &BomberGameWidget::playBombSound);
0044     connect(m_board, &BomberBoard::playCrashSound, this, &BomberGameWidget::playCrashSound);
0045     connect(m_board, &BomberBoard::onPlaneCrash, this, &BomberGameWidget::onPlaneCrashed);
0046     connect(m_board, &BomberBoard::onBombHit, this, &BomberGameWidget::onBombHit);
0047     connect(m_board, &BomberBoard::levelCleared, this, &BomberGameWidget::onLevelCleared);
0048     setScene(m_board);
0049     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0050     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0051     setFrameStyle(QFrame::NoFrame);
0052     setCacheMode(QGraphicsView::CacheBackground); // Optimize caching
0053     setFocusPolicy(Qt::StrongFocus);
0054     setAlignment(Qt::AlignLeft | Qt::AlignTop);
0055     // Overlay
0056     m_overlay = new QGraphicsPixmapItem();
0057 
0058     m_board->addItem(m_overlay);
0059 
0060     m_clock = new QTimer(this);
0061     m_clock->setInterval(GAME_TIME_DELAY);
0062 
0063     connect(m_clock, &QTimer::timeout, this, &BomberGameWidget::tick);
0064 
0065     setMouseTracking(true);
0066     generateOverlay();
0067 }
0068 
0069 BomberGameWidget::~BomberGameWidget()
0070 {
0071     delete m_board;
0072 }
0073 
0074 unsigned int BomberGameWidget::level() const
0075 {
0076     return m_level;
0077 }
0078 
0079 unsigned int BomberGameWidget::score() const
0080 {
0081     return m_score;
0082 }
0083 
0084 void BomberGameWidget::closeGame()
0085 {
0086     if (m_state != State::BeforeFirstGame && m_state != State::GameOver) {
0087         closeLevel();
0088 
0089         m_state = State::GameOver;
0090         Q_EMIT stateChanged(m_state);
0091         Q_EMIT gameOver();
0092 
0093         redraw();
0094     }
0095 }
0096 
0097 void BomberGameWidget::newGame()
0098 {
0099     closeGame();
0100 
0101     m_level = 1;
0102     m_score = 0;
0103     m_lives = 3;
0104     m_scoreLeftBeforeNewLife = NEW_LIVE_AT_SCORE;
0105 
0106     Q_EMIT levelChanged(m_level);
0107     Q_EMIT scoreChanged(m_score);
0108     Q_EMIT livesChanged(m_lives);
0109 
0110     newLevel();
0111 }
0112 
0113 void BomberGameWidget::setPaused(bool val)
0114 {
0115     if (m_state == State::Paused && val == false) {
0116         m_clock->start();
0117         m_board->setPaused(false);
0118         m_state = State::Running;
0119     } else if (m_state == State::Running && val == true) {
0120         m_clock->stop();
0121         m_board->setPaused(true);
0122         m_state = State::Paused;
0123     }
0124     Q_EMIT stateChanged(m_state);
0125     redraw();
0126 }
0127 
0128 void BomberGameWidget::setSuspended(bool val)
0129 {
0130     if (m_state == State::Suspended && val == false) {
0131         m_clock->start();
0132         m_board->setPaused(false);
0133         m_state = State::Running;
0134     } else if (m_state == State::Running && val == true) {
0135         m_clock->stop();
0136         m_board->setPaused(true);
0137         m_state = State::Suspended;
0138     }
0139     Q_EMIT stateChanged(m_state);
0140     redraw();
0141 }
0142 
0143 void BomberGameWidget::settingsChanged()
0144 {
0145     m_board->settingsChanged();
0146 }
0147 
0148 void BomberGameWidget::setSoundsEnabled(bool enable)
0149 {
0150     BomberSettings::setPlaySounds(enable);
0151     BomberSettings::self()->save();
0152     m_board->settingsChanged();
0153 }
0154 
0155 void BomberGameWidget::onPlaneCrashed()
0156 {
0157     --m_lives;
0158     Q_EMIT livesChanged(m_lives);
0159     if (m_lives == 0) {
0160         closeGame();
0161     } else {
0162         m_board->resetPlane();
0163     }
0164     if (BomberSettings::playSounds() == true) {
0165         m_soundCrash.stop();
0166     }
0167 }
0168 
0169 void BomberGameWidget::onBombHit()
0170 {
0171     unsigned int bonus = SCORE_INCREMENT * m_level;
0172     m_score += bonus;
0173     Q_EMIT scoreChanged(m_score);
0174     m_scoreLeftBeforeNewLife -= bonus;
0175     if (m_scoreLeftBeforeNewLife <= 0) {
0176         m_scoreLeftBeforeNewLife = NEW_LIVE_AT_SCORE;
0177         ++m_lives;
0178         Q_EMIT livesChanged(m_lives);
0179     }
0180     if (BomberSettings::playSounds() == true) {
0181         m_soundBomb.stop();
0182     }
0183 }
0184 
0185 void BomberGameWidget::playBombSound()
0186 {
0187     if (BomberSettings::playSounds() == true) {
0188         m_soundBomb.start();
0189     }
0190 }
0191 
0192 void BomberGameWidget::playCrashSound()
0193 {
0194     if (BomberSettings::playSounds() == true) {
0195         m_soundCrash.start();
0196     }
0197 }
0198 
0199 void BomberGameWidget::tick()
0200 {
0201     static signed int ticks = TICKS_PER_SECOND;
0202     --ticks;
0203     if (ticks <= 0) {
0204         ++m_time;
0205         Q_EMIT timeChanged(m_time);
0206         ticks = TICKS_PER_SECOND;
0207     }
0208 }
0209 
0210 void BomberGameWidget::resizeEvent(QResizeEvent * ev)
0211 {
0212     QSize boardSize = ev->size();
0213     m_board->resize(boardSize);
0214     redraw();
0215 }
0216 
0217 void BomberGameWidget::closeLevel()
0218 {
0219     m_clock->stop();
0220     m_board->setPaused(true);
0221 }
0222 
0223 void BomberGameWidget::newLevel()
0224 {
0225     m_state = State::Running;
0226     Q_EMIT stateChanged(m_state);
0227 
0228     m_clock->start();
0229     m_board->newLevel(m_level);
0230     m_board->setPaused(false);
0231 
0232     Q_EMIT livesChanged(m_lives);
0233     Q_EMIT timeChanged(m_time);
0234 
0235     redraw();
0236 }
0237 
0238 void BomberGameWidget::mouseReleaseEvent(QMouseEvent * event)
0239 {
0240     if (event->button() & Qt::LeftButton) {
0241         onDropBomb();
0242     }
0243 }
0244 
0245 void BomberGameWidget::onDropBomb()
0246 {
0247     if (m_state == State::Running) {
0248         m_board->dropBomb();
0249     } else if (m_state == State::BetweenLevels) {
0250         newLevel();
0251     } else if (m_state == State::BeforeFirstGame) {
0252         newGame();
0253     }
0254 }
0255 
0256 void BomberGameWidget::redraw()
0257 {
0258     if (size().isEmpty()) {
0259         return;
0260     }
0261     switch (m_state) {
0262         case State::BeforeFirstGame: {
0263             const auto items = m_board->items();
0264             for (QGraphicsItem * item : items) {
0265                 item->hide();
0266             }
0267             generateOverlay();
0268             m_overlay->show();
0269             break;
0270         }
0271         case State::Running: {
0272             const auto items = m_board->items();
0273             for (QGraphicsItem * item : items) {
0274                 item->show();
0275             }
0276             m_overlay->hide();
0277             break;
0278         }
0279         default: {
0280             const auto items = m_board->items();
0281             for (QGraphicsItem * item : items) {
0282                 item->show();
0283             }
0284             generateOverlay();
0285             m_overlay->show();
0286             break;
0287         }
0288     }
0289     m_board->redraw();
0290     update();
0291 }
0292 
0293 void BomberGameWidget::generateOverlay()
0294 {
0295     unsigned int itemWidth = qRound(0.8 * size().width());
0296     unsigned int itemHeight = qRound(0.6 * size().height());
0297 
0298     QPixmap px(itemWidth, itemHeight);
0299     px.fill(QColor(0, 0, 0, 0));
0300 
0301     QPainter p(&px);
0302     p.setPen(QColor(0, 0, 0, 0));
0303     p.setBrush(QBrush(QColor(188, 202, 222, 155)));
0304     p.setRenderHint(QPainter::Antialiasing);
0305     p.drawRoundedRect(0, 0, itemWidth, itemHeight, 25, Qt::RelativeSize);
0306 
0307     QString text;
0308     switch (m_state) {
0309         case State::BeforeFirstGame:
0310             text = i18nc("Message show to the user when the game is loaded", "Welcome to Bomber.\nClick to start a game");
0311             break;
0312         case State::Paused:
0313             text = i18nc("Message show to the user while the game is paused", "Paused");
0314             break;
0315         case State::BetweenLevels:
0316             text = i18nc("Message telling user which level they just completed", "You have successfully cleared level %1\n", m_level - 1)
0317                 + i18nc("Message telling user which level they are about to start", "On to level %1.", m_level);
0318             break;
0319         case State::GameOver:
0320             text = i18nc("Used to tell the user that the game is over", "Game over.");
0321             break;
0322         default:
0323             text.clear();
0324     }
0325 
0326     QFont font;
0327     font.setPointSize(28);
0328     p.setFont(font);
0329     unsigned int textWidth = p.boundingRect(p.viewport(), Qt::AlignCenter | Qt::AlignVCenter, text).width();
0330     unsigned int fontSize = 28;
0331     while (textWidth > itemWidth * 0.95) {
0332         --fontSize;
0333         font.setPointSize(fontSize);
0334         p.setFont(font);
0335         textWidth = p.boundingRect(p.viewport(), Qt::AlignCenter | Qt::AlignVCenter, text).width();
0336     }
0337 
0338     p.setPen(QColor(0, 0, 0, 255));
0339     p.drawText(p.viewport(), Qt::AlignCenter | Qt::AlignVCenter, text);
0340     p.end();
0341 
0342     m_overlay->setPixmap(px);
0343     m_overlay->setPos((size().width() - itemWidth) / 2, (size().height() - itemHeight) / 2);
0344     m_overlay->setZValue(OVERLAY_Z_VALUE);
0345 }
0346 
0347 void BomberGameWidget::onLevelCleared()
0348 {
0349     if (m_state == State::Running) {
0350         m_state = State::BetweenLevels;
0351         closeLevel();
0352         ++m_level;
0353         Q_EMIT levelChanged(m_level);
0354         Q_EMIT stateChanged(m_state);
0355         redraw();
0356     }
0357 }
0358 
0359 BomberGameWidget::State BomberGameWidget::state() const
0360 {
0361     return m_state;
0362 }
0363 
0364 #include "moc_bomberwidget.cpp"