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"