File indexing completed on 2024-04-28 07:51:28
0001 /* 0002 SPDX-FileCopyrightText: 2000-2005 Stefan Schimanski <1Stein@gmx.de> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "gamewidget.h" 0008 #include "settings.h" 0009 #include "wall.h" 0010 #include "debug.h" 0011 0012 #include <QPalette> 0013 #include <QTimer> 0014 #include <QStandardPaths> 0015 0016 #include <KGameThemeProvider> 0017 #include <KColorScheme> 0018 #include <KLocalizedString> 0019 0020 static const int MIN_MARGIN = 50; 0021 static const int GAME_TIME_DELAY = 1000; 0022 static const int MIN_FILL_PERCENT = 75; 0023 static const int POINTS_FOR_LIFE = 15; 0024 static const int TICKS_PER_SECOND = 1000 / GAME_TIME_DELAY; 0025 0026 KBounceGameWidget::KBounceGameWidget( QWidget* parent ) 0027 : QGraphicsView( parent ) 0028 , m_state( BeforeFirstGame ) 0029 , m_bonus( 0 ) 0030 , m_level( 0 ) 0031 , m_lives( 0 ) 0032 , m_time( 0 ) 0033 , m_vertical( false ) 0034 , m_soundTimeout( QStandardPaths::locate( QStandardPaths::AppDataLocation, QStringLiteral("sounds/timeout.wav") ) ) 0035 { 0036 m_board = new KBounceBoard( &m_renderer ); 0037 connect(m_board, &KBounceBoard::fillChanged, this, &KBounceGameWidget::onFillChanged); 0038 connect(m_board, &KBounceBoard::wallDied, this, &KBounceGameWidget::onWallDied); 0039 0040 m_overlay = new QGraphicsPixmapItem(); 0041 m_overlay->hide(); 0042 0043 m_clock = new QTimer( this ); 0044 m_clock->setInterval( GAME_TIME_DELAY ); 0045 connect(m_clock, &QTimer::timeout, this, &KBounceGameWidget::tick); 0046 connect(this, &KBounceGameWidget::livesChanged, this, &KBounceGameWidget::onLivesChanged); 0047 0048 setMouseTracking( true ); 0049 0050 connect(m_renderer.themeProvider(), &KGameThemeProvider::currentThemeChanged, 0051 this, &KBounceGameWidget::settingsChanged); 0052 0053 m_scene.addItem( m_board ); 0054 m_scene.addItem( m_overlay ); 0055 setScene( &m_scene ); 0056 } 0057 0058 KBounceGameWidget::~KBounceGameWidget() 0059 { 0060 delete m_board; 0061 delete m_overlay; 0062 } 0063 0064 int KBounceGameWidget::level() 0065 { 0066 return m_level; 0067 } 0068 0069 int KBounceGameWidget::score() 0070 { 0071 qCDebug(KBOUNCE_LOG) << "Score:" << m_score; 0072 return m_score; 0073 } 0074 0075 QSize KBounceGameWidget::minimumSizeHint() const 0076 { 0077 return QSize( 576, 384 ); 0078 } 0079 0080 void KBounceGameWidget::closeGame() 0081 { 0082 if ( m_state != BeforeFirstGame && m_state != GameOver ) 0083 { 0084 m_clock->stop(); 0085 m_board->setPaused( true ); 0086 m_state = GameOver; 0087 Q_EMIT stateChanged( m_state ); 0088 Q_EMIT gameOver(); 0089 0090 KGameDifficulty::global()->setGameRunning( false ); 0091 redraw(); 0092 } 0093 } 0094 0095 void KBounceGameWidget::newGame() 0096 { 0097 closeGame(); 0098 m_level = 1; 0099 m_score = 0; 0100 0101 Q_EMIT levelChanged( m_level ); 0102 Q_EMIT scoreChanged( m_score ); 0103 0104 KGameDifficulty::global()->setGameRunning( true ); 0105 newLevel(); 0106 } 0107 0108 void KBounceGameWidget::setPaused( bool val ) 0109 { 0110 if ( m_state == Paused && val == false ) 0111 { 0112 m_clock->start(); 0113 m_board->setPaused( false ); 0114 m_state = Running; 0115 Q_EMIT stateChanged( m_state ); 0116 } 0117 else if ( m_state == Running && val == true ) 0118 { 0119 m_clock->stop(); 0120 m_board->setPaused( true ); 0121 m_state = Paused; 0122 Q_EMIT stateChanged( m_state ); 0123 } 0124 0125 redraw(); 0126 } 0127 0128 void KBounceGameWidget::setSuspended( bool val ) 0129 { 0130 if ( m_state == Suspended && val == false ) 0131 { 0132 m_clock->start(); 0133 m_board->setPaused( false ); 0134 m_state = Running; 0135 Q_EMIT stateChanged( m_state ); 0136 } 0137 0138 if ( m_state == Running && val == true ) 0139 { 0140 m_clock->stop(); 0141 m_board->setPaused( true ); 0142 m_state = Suspended; 0143 Q_EMIT stateChanged( m_state ); 0144 } 0145 redraw(); 0146 } 0147 0148 void KBounceGameWidget::settingsChanged() 0149 { 0150 qCDebug(KBOUNCE_LOG) << "Settings changed"; 0151 0152 if (KBounceSettings::useRandomBackgroundPictures()) 0153 { 0154 m_renderer.setCustomBackgroundPath(KBounceSettings::backgroundPicturePath()); 0155 } 0156 else 0157 { 0158 m_renderer.setCustomBackgroundPath(QString()); 0159 } 0160 redraw(); 0161 } 0162 0163 void KBounceGameWidget::setGameDifficulty( const KGameDifficultyLevel * difficulty ) 0164 { 0165 switch ( difficulty->hardness() ) { 0166 case KGameDifficultyLevel::Easy: 0167 m_board->setWallVelocity(0.250); 0168 m_board->setBallVelocity(0.100); 0169 break; 0170 case KGameDifficultyLevel::Medium: 0171 m_board->setWallVelocity(0.125); 0172 m_board->setBallVelocity(0.125); 0173 break; 0174 case KGameDifficultyLevel::Hard: 0175 m_board->setWallVelocity(0.100); 0176 m_board->setBallVelocity(0.250); 0177 break; 0178 } 0179 } 0180 0181 void KBounceGameWidget::handleLevelChanged() 0182 { 0183 setGameDifficulty( KGameDifficulty::global()->currentLevel() ); 0184 0185 if ( m_state == Running || m_state == Paused ) 0186 newGame(); 0187 } 0188 0189 void KBounceGameWidget::onFillChanged( int fill ) 0190 { 0191 Q_EMIT filledChanged( fill ); 0192 if ( fill >= MIN_FILL_PERCENT ) 0193 { 0194 closeLevel(); 0195 m_level++; 0196 Q_EMIT levelChanged( m_level ); 0197 0198 m_state = BetweenLevels; 0199 Q_EMIT stateChanged( m_state ); 0200 0201 redraw(); 0202 } 0203 } 0204 0205 void KBounceGameWidget::onWallDied() 0206 { 0207 if ( m_lives <= 1 ) 0208 { 0209 closeGame(); 0210 } 0211 else 0212 { 0213 m_lives--; 0214 Q_EMIT livesChanged( m_lives ); 0215 } 0216 } 0217 0218 void KBounceGameWidget::onLivesChanged(int lives) 0219 { 0220 if ( lives < ( m_level + 1 ) 0221 && KBounceSettings::playSounds() ) 0222 { 0223 m_soundTimeout.start(); 0224 } 0225 } 0226 0227 0228 void KBounceGameWidget::tick() 0229 { 0230 static int ticks = TICKS_PER_SECOND; 0231 ticks--; 0232 if ( ticks <= 0 ) 0233 { 0234 Q_EMIT timeChanged( --m_time ); 0235 if ( m_time == 0 ) 0236 { 0237 closeGame(); 0238 } 0239 ticks = TICKS_PER_SECOND; 0240 } 0241 } 0242 0243 void KBounceGameWidget::resizeEvent( QResizeEvent* ev ) 0244 { 0245 qCDebug(KBOUNCE_LOG) << "Size" << ev->size(); 0246 0247 m_renderer.setBackgroundSize( ev->size() ); 0248 0249 QRectF rect( 0, 0, ev->size().width(), ev->size().height() ); 0250 m_scene.setSceneRect( rect ); 0251 0252 QSize boardSize( sceneRect().width() - MIN_MARGIN, 0253 sceneRect().height() - MIN_MARGIN ); 0254 m_board->resize( boardSize ); 0255 0256 qreal x = ( sceneRect().width() - m_board->boundingRect().width() ) / 2; 0257 qreal y = ( sceneRect().height() - m_board->boundingRect().height() ) / 2; 0258 m_board->setPos( x, y ); 0259 0260 redraw(); 0261 } 0262 0263 void KBounceGameWidget::mouseReleaseEvent( QMouseEvent* event ) 0264 { 0265 if ( event->button() & Qt::RightButton ) 0266 { 0267 m_vertical = !m_vertical; 0268 updateCursor(); 0269 } 0270 0271 if ( event->button() & Qt::LeftButton ) 0272 { 0273 if ( m_state == Running ) 0274 { 0275 m_board->buildWall( mapToScene( event->pos() ), m_vertical ); 0276 } 0277 else if ( m_state == Paused ) 0278 { 0279 setPaused( false ); 0280 } 0281 else if ( m_state == BetweenLevels ) 0282 { 0283 newLevel(); 0284 } 0285 else if ( m_state == BeforeFirstGame || m_state == GameOver ) 0286 { 0287 newGame(); 0288 } 0289 } 0290 } 0291 0292 0293 void KBounceGameWidget::closeLevel() 0294 { 0295 m_bonus = 0; 0296 if ( m_board->filled() >= MIN_FILL_PERCENT ) 0297 { 0298 m_bonus = ( m_board->filled() - MIN_FILL_PERCENT ) * 2 * ( m_level + 5 ); 0299 } 0300 m_score += m_bonus; 0301 m_score += POINTS_FOR_LIFE * m_lives; 0302 Q_EMIT scoreChanged( m_score ); 0303 0304 m_clock->stop(); 0305 m_board->setPaused( true ); 0306 } 0307 0308 void KBounceGameWidget::newLevel() 0309 { 0310 m_state = Running; 0311 Q_EMIT stateChanged( m_state ); 0312 0313 m_clock->start(); 0314 m_board->newLevel( m_level ); 0315 m_board->setPaused( false ); 0316 0317 m_bonus = 0; 0318 m_lives = m_level + 1; 0319 m_time = 30 * ( m_level + 2 ); 0320 Q_EMIT livesChanged( m_lives ); 0321 Q_EMIT timeChanged( m_time ); 0322 0323 if (KBounceSettings::useRandomBackgroundPictures()) 0324 m_renderer.loadNewBackgroundPixmap(); 0325 0326 redraw(); 0327 } 0328 0329 0330 void KBounceGameWidget::redraw() 0331 { 0332 if ( size().isEmpty() ) 0333 return; 0334 0335 switch ( m_state ) 0336 { 0337 case BeforeFirstGame: 0338 m_board->hide(); 0339 generateOverlay(); 0340 m_overlay->show(); 0341 break; 0342 case Running: 0343 m_board->show(); 0344 m_overlay->hide(); 0345 break; 0346 default: 0347 m_board->show(); 0348 generateOverlay(); 0349 m_overlay->show(); 0350 break; 0351 } 0352 0353 updateCursor(); 0354 KBounceWall::loadSprites(); 0355 m_scene.setBackgroundBrush( m_board->applyWallsOn(m_renderer.renderBackground()) ); 0356 update(); 0357 } 0358 0359 void KBounceGameWidget::generateOverlay() 0360 { 0361 if ( size().isEmpty() ) 0362 return; 0363 0364 int itemWidth = qRound( 0.8 * size().width() ); 0365 int itemHeight = qRound( 0.6 * size().height() ); 0366 0367 QSize backgroundSize( itemWidth,itemHeight ); 0368 0369 QPixmap px( backgroundSize ); 0370 px.fill( Qt::transparent ); 0371 0372 QPainter p( &px ); 0373 0374 p.setPen( Qt::transparent ); 0375 p.setRenderHint(QPainter::Antialiasing ); 0376 0377 if ( m_renderer.spriteExists(QStringLiteral("overlayBackground")) ) 0378 { 0379 QPixmap themeBackgound = m_renderer.spritePixmap(QStringLiteral("overlayBackground"),backgroundSize); 0380 p.setCompositionMode( QPainter::CompositionMode_Source ); 0381 p.drawPixmap( p.viewport(), themeBackgound ); 0382 p.setCompositionMode( QPainter::CompositionMode_DestinationIn ); 0383 p.fillRect(px.rect(), QColor( 0, 0, 0, 160 )); 0384 p.setCompositionMode( QPainter::CompositionMode_SourceOver ); 0385 } 0386 else 0387 { 0388 p.setBrush( QBrush( QColor( 188, 202, 222, 155 ) ) ); 0389 p.drawRoundedRect( 0, 0, itemWidth, itemHeight, 25, 25 ); 0390 } 0391 0392 QString text; 0393 switch( m_state ) 0394 { 0395 case BeforeFirstGame: 0396 text = i18n( "Welcome to KBounce.\n Click to start a game" ); 0397 break; 0398 case Paused: 0399 text = i18n( "Paused\n Click to resume" ); 0400 break; 0401 case BetweenLevels: 0402 text = i18n( "You have successfully cleared more than %1% of the board\n", MIN_FILL_PERCENT ) + 0403 i18n( "%1 points: %2 points per remaining life\n", m_lives * POINTS_FOR_LIFE, POINTS_FOR_LIFE ) + 0404 i18n( "%1 points: Bonus\n", m_bonus ) + 0405 i18n( "%1 points: Total score for this level\n", m_bonus + m_lives * POINTS_FOR_LIFE ) + 0406 i18n( "On to level %1. Remember you get %2 lives this time!", m_level, m_level + 1 ); 0407 break; 0408 case GameOver: 0409 text = i18n( "Game over.\n Click to start a game" ); 0410 break; 0411 default: 0412 text = QString(); 0413 } 0414 0415 QFont font; 0416 font.setPointSize( 28 ); 0417 p.setFont( font ); 0418 int textWidth = p.boundingRect( p.viewport(), Qt::AlignCenter | Qt::AlignVCenter, text ).width(); 0419 int fontSize = 28; 0420 while ( ( textWidth > itemWidth * 0.95 ) && fontSize > 1 ) 0421 { 0422 fontSize--; 0423 font.setPointSize( fontSize ); 0424 p.setFont( font ); 0425 textWidth = p.boundingRect( p.viewport(), Qt::AlignCenter | Qt::AlignVCenter, text ).width(); 0426 } 0427 KColorScheme kcs = KColorScheme( QPalette::Normal, KColorScheme::Window ); 0428 p.setPen( kcs.foreground(KColorScheme::NormalText).color()); 0429 p.drawText( p.viewport(), Qt::AlignCenter | Qt::AlignVCenter, text ); 0430 p.end(); 0431 0432 m_overlay->setPixmap( px ); 0433 0434 QPointF pos( ( sceneRect().width() - itemWidth) / 2, 0435 ( sceneRect().height() - itemHeight) / 2 ); 0436 m_overlay->setPos( pos ); 0437 } 0438 0439 void KBounceGameWidget::focusOutEvent(QFocusEvent *event) 0440 { 0441 if (event->reason() == Qt::ActiveWindowFocusReason) 0442 { 0443 setPaused( true ); 0444 } 0445 } 0446 0447 void KBounceGameWidget::updateCursor() 0448 { 0449 if ( m_state == Running ) 0450 setCursor( m_vertical ? Qt::SizeVerCursor : Qt::SizeHorCursor ); 0451 else 0452 unsetCursor(); 0453 } 0454 0455 #include "moc_gamewidget.cpp"