File indexing completed on 2024-04-28 04:02:02

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 "board.h"
0008 
0009 #include <QGraphicsScene>
0010 #include <QTimer>
0011 #include <QPainter>
0012 #include <QRandomGenerator>
0013 
0014 #include "ball.h"
0015 #include "wall.h"
0016 #include "debug.h"
0017 
0018 #include <cmath>
0019 
0020 #define DIR_UP 0
0021 #define DIR_RIGHT 1
0022 #define DIR_DOWN 2
0023 #define DIR_LEFT 3
0024 
0025 
0026 KBounceBoard::KBounceBoard( KBounceRenderer* renderer )
0027     : QGraphicsObject()
0028     , m_renderer( renderer )
0029 {
0030     m_clock = new QTimer( this );
0031     m_clock->setInterval( GAME_DELAY );
0032     connect(m_clock, &QTimer::timeout, this, &KBounceBoard::tick);
0033 
0034     m_walls.append( new KBounceWall( KBounceWall::Up, m_renderer, this ) );
0035     m_walls.append( new KBounceWall( KBounceWall::Right, m_renderer, this ) );
0036     m_walls.append( new KBounceWall( KBounceWall::Down, m_renderer, this ) );
0037     m_walls.append( new KBounceWall( KBounceWall::Left, m_renderer, this ) );
0038     for (KBounceWall* wall : std::as_const(m_walls)) {
0039         wall->hide();
0040         connect( wall, &KBounceWall::died, this, &KBounceBoard::wallDied );
0041         connect( wall, &KBounceWall::finished, this, &KBounceBoard::wallFinished );
0042     }
0043 
0044     clear();
0045 
0046     // Initialize these members with the old default values.
0047     m_ballVelocity = 0.125;
0048     m_wallVelocity = 0.125;
0049 }
0050 
0051 KBounceBoard::~KBounceBoard()
0052 {
0053     qDeleteAll( m_balls );
0054     qDeleteAll( m_walls );
0055 }
0056 
0057 void KBounceBoard::resize( QSize& size )
0058 {
0059     //Pause the clock to prevent ticks during resize ...
0060     bool alreadyPaused = false;
0061     if (!m_clock->isActive())
0062     {
0063         // ... but only when we are not already paused
0064         alreadyPaused = true;
0065     }
0066     else
0067     {
0068         setPaused(true);
0069     }
0070 
0071     int minTileSize;
0072     if ( TILE_NUM_H * size.width() - TILE_NUM_W * size.height() > 0 ) {
0073         minTileSize = size.height() / TILE_NUM_H;
0074     } else {
0075         minTileSize = size.width() / TILE_NUM_W;
0076     }
0077 
0078     m_tileSize = QSize( minTileSize, minTileSize );
0079 
0080     for (KBounceBall* ball : std::as_const(m_balls)) {
0081         ball->resize( m_tileSize );
0082     }
0083 
0084     for (KBounceWall* wall : std::as_const(m_walls)) {
0085         wall->resize( m_tileSize );
0086     }
0087 
0088     size.setWidth( minTileSize * TILE_NUM_W );
0089     size.setHeight( minTileSize * TILE_NUM_H );
0090     if (!alreadyPaused)
0091     {
0092         setPaused(false);
0093     }
0094 }
0095 
0096 void KBounceBoard::newLevel( int level )
0097 {
0098     m_clock->stop();
0099     clear();
0100     Q_EMIT fillChanged( m_filled );
0101 
0102     while ( m_balls.count() > level + 1 )
0103     {
0104         delete m_balls.back();
0105         m_balls.removeLast();
0106     }
0107     while ( m_balls.count() < level + 1)
0108     {
0109         KBounceBall* ball = new KBounceBall( m_renderer, this );
0110         ball->resize( m_tileSize );
0111         m_balls.append( ball );
0112     }
0113     for (KBounceBall* ball : std::as_const(m_balls)) {
0114         ball->setRelativePos( 4 + QRandomGenerator::global()->bounded( TILE_NUM_W - 8 ),
0115                 4 + QRandomGenerator::global()->bounded( TILE_NUM_H - 8 ) );
0116 
0117         ball->setVelocity( (QRandomGenerator::global()->bounded(2)*2-1)*m_ballVelocity,
0118                 (QRandomGenerator::global()->bounded(2)*2-1)*m_ballVelocity );
0119         ball->setRandomFrame();
0120         ball->show();
0121     }
0122     Q_EMIT ballsChanged( level + 1 );
0123 
0124     for (KBounceWall* wall : std::as_const(m_walls)) {
0125         wall->setWallVelocity(m_wallVelocity);
0126         wall->hide();
0127     }
0128 }
0129 
0130 void KBounceBoard::setPaused( bool val )
0131 {
0132     if ( val )
0133         m_clock->stop();
0134     else
0135         m_clock->start();
0136 }
0137 
0138 void KBounceBoard::setBallVelocity(qreal vel)
0139 {
0140     m_ballVelocity = vel;
0141 }
0142 
0143 void KBounceBoard::setWallVelocity(qreal vel)
0144 {
0145     m_wallVelocity = vel;
0146 }
0147 
0148 void KBounceBoard::buildWall( const QPointF& pos, bool vertical )
0149 {
0150     QPointF unmapped( pos.x() - x(), pos.y() - y());
0151     int x = static_cast<int>( unmapped.x() / m_tileSize.width() );
0152     int y = static_cast<int>( unmapped.y() / m_tileSize.height() );
0153 
0154     if ( x < 0 || x >= TILE_NUM_W )
0155     {
0156         qCDebug(KBOUNCE_LOG) << "Wall x position out of board.";
0157         return;
0158     }
0159     if ( y < 0 || y >= TILE_NUM_H )
0160     {
0161         qCDebug(KBOUNCE_LOG) << "Wall y position out of board.";
0162         return;
0163     }
0164     if ( m_tiles[x][y] != Free )
0165     {
0166         qCDebug(KBOUNCE_LOG) << "Wall could not be build in a field which is not free.";
0167         return;
0168     }
0169 
0170     if ( !vertical )
0171     {
0172         m_walls[DIR_LEFT]->build( x, y );
0173         m_walls[DIR_RIGHT]->build( x, y );
0174     }
0175     else
0176     {
0177         m_walls[DIR_UP]->build( x, y );
0178         m_walls[DIR_DOWN]->build( x, y );
0179     }
0180 }
0181 
0182 int KBounceBoard::balls()
0183 {
0184     return m_balls.count();
0185 }
0186 
0187 int KBounceBoard::filled()
0188 {
0189     return m_filled;
0190 }
0191 
0192 KBounceCollision KBounceBoard::checkCollision( void* object, const QRectF& rect, int type )
0193 {
0194     KBounceCollision result;
0195 
0196     if ( (type & TILE) != 0 )
0197     {
0198         result += checkCollisionTiles( rect );
0199     }
0200 
0201     if ( (type & WALL) != 0 )
0202     {
0203         for (KBounceWall* wall : std::as_const(m_walls)) {
0204             if ( object != wall )
0205             {
0206                 if ( wall->isVisible() && rect.intersects( wall->nextBoundingRect() ) )
0207                 {
0208                     KBounceHit hit;
0209                     hit.type = WALL;
0210                     hit.boundingRect = wall->nextBoundingRect();
0211                     hit.normal = KBounceVector::normal( rect, hit.boundingRect );
0212                     result += hit;
0213                 }
0214             }
0215         }
0216     }
0217 
0218     if ( (type & BALL) != 0 )
0219     {
0220         for (KBounceBall* ball : std::as_const(m_balls)) {
0221             if ( object != ball )
0222             {
0223                 if ( rect.intersects( ball->nextBoundingRect() ) )
0224                 {
0225                     KBounceHit hit;
0226                     hit.type = BALL;
0227                     hit.boundingRect = ball->nextBoundingRect();
0228                     hit.normal = KBounceVector::normal( rect, hit.boundingRect );
0229                     result += hit;
0230                 }
0231             }
0232         }
0233     }
0234 
0235     return result;
0236 }
0237 
0238 KBounceCollision KBounceBoard::checkCollisionTiles( const QRectF& rect)
0239 {
0240     KBounceVector normal( 0, 0 );
0241 
0242     // This small constant is added to each of the coordinates to 
0243     // avoid positive collision test result when tested rect lies 
0244     // on the edge of non-free space
0245     qreal D = 0.01;
0246 
0247     QPointF p = rect.topLeft();  
0248     int ul = m_tiles[static_cast<int>( p.x() + D )][static_cast<int>( p.y() + D )];
0249     if ( ul != Free ) normal += KBounceVector( 1, 1 );
0250 
0251     p = rect.topRight();
0252     int ur = m_tiles[static_cast<int>( p.x() - D )][static_cast<int>( p.y() + D )];
0253     if ( ur != Free) normal += KBounceVector( -1, 1 );
0254 
0255     p = rect.bottomRight();
0256     int lr = m_tiles[static_cast<int>( p.x() - D )][static_cast<int>( p.y() - D )];
0257     if ( lr != Free ) normal += KBounceVector( -1, -1 );
0258 
0259     p = rect.bottomLeft();
0260     int ll = m_tiles[static_cast<int>( p.x() + D )][static_cast<int>( p.y() - D )];
0261     if ( ll != Free ) normal += KBounceVector( 1, -1 );
0262 
0263     KBounceCollision collision;
0264     if ( (ul != Free ) || ( ur != Free ) || ( lr != Free ) || ( ll != Free ) )
0265     {
0266         KBounceHit hit;
0267         hit.type = TILE;
0268         hit.normal = normal;
0269         collision += hit;
0270     }
0271     return collision;
0272 }
0273 
0274 void KBounceBoard::checkCollisions()
0275 {
0276     for (KBounceWall* wall : std::as_const(m_walls)){
0277         QRectF rect = wall->nextBoundingRect();
0278         KBounceCollision collision;
0279         collision = checkCollision( wall, rect, ALL );
0280         wall->collide( collision );
0281     }
0282     for (KBounceBall* ball : std::as_const(m_balls)) {
0283         QRectF rect = ball->nextBoundingRect();
0284         KBounceCollision collision;
0285         collision = checkCollision( ball, rect, ALL );
0286         ball->collide( collision );
0287     }
0288 }
0289 
0290 QPoint KBounceBoard::mapPosition( const QPointF& pos ) const
0291 {
0292     return QPoint( static_cast<int>( m_tileSize.width() * pos.x() ),
0293             static_cast<int>(  m_tileSize.height() * pos.y() ) );
0294 }
0295 
0296 QRectF KBounceBoard::boundingRect() const
0297 {
0298     return QRectF( x(), y(),
0299             TILE_NUM_W * m_tileSize.width(),
0300             TILE_NUM_H * m_tileSize.height() );
0301 }
0302 
0303 void KBounceBoard::tick()
0304 {
0305     checkCollisions();
0306 
0307     for (KBounceBall* ball : std::as_const(m_balls)) {
0308         ball->goForward();
0309     }
0310     for (KBounceWall* wall : std::as_const(m_walls)) {
0311         wall->goForward();
0312     }
0313 
0314     for (KBounceBall* ball : std::as_const(m_balls)) {
0315         ball->update();
0316     }
0317 
0318     for (KBounceWall* wall : std::as_const(m_walls)) {
0319         wall->update();
0320     }
0321 }
0322 
0323 QPixmap KBounceBoard::applyWallsOn(const QPixmap &background) const
0324 {
0325     if (m_tileSize.isEmpty())
0326         return background;
0327 
0328     QPixmap walledBackground = background;
0329     const QPixmap gridTile = m_renderer->spritePixmap(QStringLiteral("gridTile"), m_tileSize);
0330     const QPixmap wallTile = m_renderer->spritePixmap(QStringLiteral("wallTile"), m_tileSize);
0331     QPainter p(&walledBackground);
0332     for (int i = 0; i < TILE_NUM_W; ++i) {
0333         for (int j = 0; j < TILE_NUM_H; ++j) {
0334             switch (m_tiles[i][j]) {
0335                 case Free:
0336                     p.drawPixmap(x() + i * m_tileSize.width(), y() + j * m_tileSize.height(), gridTile);
0337                     break;
0338 
0339                 case Border:
0340                 case Wall:
0341                     p.drawPixmap(x() + i * m_tileSize.width(), y() + j * m_tileSize.height(), wallTile);
0342                     break;
0343 
0344                 default:
0345                     break;
0346             }
0347         }
0348     }
0349     return walledBackground;
0350 }
0351 
0352 void KBounceBoard::wallFinished( int x1, int y1, int x2, int y2 )
0353 {
0354     for ( int x = x1; x < x2; x++ )
0355         for ( int y = y1; y < y2; y++ )
0356             m_tiles[x][y] = Wall;
0357 
0358     for (KBounceBall* ball : std::as_const(m_balls)) {
0359         int x1 = static_cast<int>( ball->ballBoundingRect().x() );
0360         int y1 = static_cast<int>( ball->ballBoundingRect().y() );
0361         int x2 = static_cast<int>( ball->ballBoundingRect().right() );
0362         int y2 = static_cast<int>( ball->ballBoundingRect().bottom() );
0363         // try to fill from all edges
0364         // this way we can avoid most precision-related issues
0365         fill(x1, y1);
0366         fill(x1, y2);
0367         fill(x2, y1);
0368         fill(x2, y2);
0369     }
0370 
0371     for ( int x = 0; x < TILE_NUM_W; x++ )
0372         for ( int y = 0; y < TILE_NUM_H; y++ )
0373             if ( m_tiles[x][y] == Free )
0374                 m_tiles[x][y] = Wall;
0375     for ( int x = 0; x < TILE_NUM_W; x++ )
0376         for ( int y = 0; y < TILE_NUM_H; y++ )
0377             if ( m_tiles[x][y] == Temp )
0378                 m_tiles[x][y] = Free;
0379 
0380     int filled = 0;
0381     for ( int i = 1; i < TILE_NUM_W - 1; i++ )
0382         for ( int j = 1; j < TILE_NUM_H - 1; j++ )
0383             if ( m_tiles[i][j] == Wall )
0384                 filled++;
0385     m_filled = filled * 100 / ( ( TILE_NUM_W - 2 ) * ( TILE_NUM_H - 2 ) );
0386 
0387     scene()->setBackgroundBrush(applyWallsOn(m_renderer->renderBackground()));
0388 
0389     Q_EMIT fillChanged( m_filled );
0390 }
0391 
0392 void KBounceBoard::clear()
0393 {
0394     for ( int i = 0; i < TILE_NUM_W; i++ )
0395         m_tiles[i][0] = m_tiles[i][TILE_NUM_H-1] = Border;
0396     for ( int j = 0; j < TILE_NUM_H; j++ )
0397         m_tiles[0][j] = m_tiles[TILE_NUM_W-1][j] = Border;
0398     for ( int i = 1; i < TILE_NUM_W - 1; i++ )
0399         for ( int j = 1; j < TILE_NUM_H -1; j++ )
0400             m_tiles[i][j] = Free;
0401     m_filled = 0;
0402 }
0403 
0404 void KBounceBoard::fill( int x, int y )
0405 {
0406     if ( m_tiles[x][y] != Free )
0407         return;
0408     m_tiles[x][y] = Temp;
0409 
0410     if ( y > 0 ) fill( x, y - 1 );
0411     if ( x < TILE_NUM_W - 1 ) fill ( x + 1, y );
0412     if ( y < TILE_NUM_H - 1 ) fill ( x, y + 1 );
0413     if ( x > 0 ) fill ( x - 1, y );
0414 }
0415 
0416 #include "moc_board.cpp"