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"