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"