1/*
2 * Copyright (C) 2000-2005 Stefan Schimanski <1Stein@gmx.de>
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this program; if not, write to the Free
16 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#include "board.h"
20
21#include <kdebug.h>
22#include <KGlobal>
23#include <KRandom>
24
25#include <QGraphicsScene>
26#include <QTimer>
27#include <QPainter>
28
29#include "ball.h"
30#include "gameobject.h"
31#include "wall.h"
32
33#include <cmath>
34
35#define DIR_UP 0
36#define DIR_RIGHT 1
37#define DIR_DOWN 2
38#define DIR_LEFT 3
39
40
41KBounceBoard::KBounceBoard( KBounceRenderer* renderer )
42: QGraphicsObject()
43, m_renderer( renderer )
44{
45 m_clock = new QTimer( this );
46 m_clock->setInterval( GAME_DELAY );
47 connect( m_clock, SIGNAL(timeout()), this, SLOT(tick()) );
48
49 m_walls.append( new KBounceWall( KBounceWall::Up, m_renderer, this ) );
50 m_walls.append( new KBounceWall( KBounceWall::Right, m_renderer, this ) );
51 m_walls.append( new KBounceWall( KBounceWall::Down, m_renderer, this ) );
52 m_walls.append( new KBounceWall( KBounceWall::Left, m_renderer, this ) );
53 foreach( KBounceWall* wall, m_walls )
54 {
55 wall->hide();
56 connect( wall, SIGNAL(died()), this, SIGNAL(wallDied()) );
57 connect( wall, SIGNAL(finished(int,int,int,int)), this, SLOT(wallFinished(int,int,int,int)) );
58 }
59
60 clear();
61
62 // Initialize this members with the old default values.
63 m_ballVelocity = 0.125;
64 m_wallVelocity = 0.125;
65}
66
67KBounceBoard::~KBounceBoard()
68{
69 qDeleteAll( m_balls );
70 qDeleteAll( m_walls );
71}
72
73void KBounceBoard::resize( QSize& size )
74{
75 //Pause the clock to prevent ticks during resize ...
76 bool alreadyPaused = false;
77 if (!m_clock->isActive())
78 {
79 // ... but only when we are not already paused
80 alreadyPaused = true;
81 }
82 else
83 {
84 setPaused(true);
85 }
86
87 int minTileSize;
88 if ( TILE_NUM_H * size.width() - TILE_NUM_W * size.height() > 0 ) {
89 minTileSize = size.height() / TILE_NUM_H;
90 } else {
91 minTileSize = size.width() / TILE_NUM_W;
92 }
93
94 m_tileSize = QSize( minTileSize, minTileSize );
95
96 foreach( KBounceBall* ball, m_balls ) {
97 ball->resize( m_tileSize );
98 }
99
100 foreach( KBounceWall* wall, m_walls ) {
101 wall->resize( m_tileSize );
102 }
103
104 size.setWidth( minTileSize * TILE_NUM_W );
105 size.setHeight( minTileSize * TILE_NUM_H );
106 if (!alreadyPaused)
107 {
108 setPaused(false);
109 }
110}
111
112void KBounceBoard::newLevel( int level )
113{
114 m_clock->stop();
115 clear();
116 emit fillChanged( m_filled );
117
118 while ( m_balls.count() > level + 1 )
119 {
120 delete m_balls.back();
121 m_balls.removeLast();
122 }
123 while ( m_balls.count() < level + 1)
124 {
125 KBounceBall* ball = new KBounceBall( m_renderer, this );
126 ball->resize( m_tileSize );
127 m_balls.append( ball );
128 }
129 foreach( KBounceBall* ball, m_balls )
130 {
131 ball->setRelativePos( 4 + KRandom::random() % ( TILE_NUM_W - 8 ),
132 4 + KRandom::random() % ( TILE_NUM_H - 8 ) );
133 ball->setVelocity( ((KRandom::random() & 1)*2-1)*m_ballVelocity,
134 ((KRandom::random() & 1)*2-1)*m_ballVelocity );
135 ball->setRandomFrame();
136 ball->show();
137 }
138 emit ballsChanged( level + 1 );
139
140 foreach( KBounceWall* wall, m_walls )
141 {
142 wall->setWallVelocity(m_wallVelocity);
143 wall->hide();
144 }
145}
146
147void KBounceBoard::setPaused( bool val )
148{
149 if ( val )
150 m_clock->stop();
151 else
152 m_clock->start();
153}
154
155void KBounceBoard::setBallVelocity(qreal vel)
156{
157 m_ballVelocity = vel;
158}
159
160void KBounceBoard::setWallVelocity(qreal vel)
161{
162 m_wallVelocity = vel;
163}
164
165void KBounceBoard::buildWall( const QPointF& pos, bool vertical )
166{
167 QPointF unmapped( pos.x() - x(), pos.y() - y());
168 int x = static_cast<int>( unmapped.x() / m_tileSize.width() );
169 int y = static_cast<int>( unmapped.y() / m_tileSize.height() );
170
171 if ( x < 0 || x >= TILE_NUM_W )
172 {
173 kDebug() << "Wall x position out of board.";
174 return;
175 }
176 if ( y < 0 || y >= TILE_NUM_H )
177 {
178 kDebug() << "Wall y position out of board.";
179 return;
180 }
181 if ( m_tiles[x][y] != Free )
182 {
183 kDebug() << "Wall could not be build in a field which is not free.";
184 return;
185 }
186
187 if ( !vertical )
188 {
189 m_walls[DIR_LEFT]->build( x, y );
190 m_walls[DIR_RIGHT]->build( x, y );
191 }
192 else
193 {
194 m_walls[DIR_UP]->build( x, y );
195 m_walls[DIR_DOWN]->build( x, y );
196 }
197}
198
199int KBounceBoard::balls()
200{
201 return m_balls.count();
202}
203
204int KBounceBoard::filled()
205{
206 return m_filled;
207}
208
209KBounceCollision KBounceBoard::checkCollision( void* object, const QRectF& rect, int type )
210{
211 KBounceCollision result;
212
213 if ( (type & TILE) != 0 )
214 {
215 result += checkCollisionTiles( rect );
216 }
217
218 if ( (type & WALL) != 0 )
219 {
220 foreach( KBounceWall* wall, m_walls )
221 {
222 if ( object != wall )
223 {
224 if ( wall->isVisible() && rect.intersects( wall->nextBoundingRect() ) )
225 {
226 KBounceHit hit;
227 hit.type = WALL;
228 hit.boundingRect = wall->nextBoundingRect();
229 hit.normal = KBounceVector::normal( rect, hit.boundingRect );
230 result += hit;
231 }
232 }
233 }
234 }
235
236 if ( (type & BALL) != 0 )
237 {
238 foreach( KBounceBall* ball, m_balls )
239 {
240 if ( object != ball )
241 {
242 if ( rect.intersects( ball->nextBoundingRect() ) )
243 {
244 KBounceHit hit;
245 hit.type = BALL;
246 hit.boundingRect = ball->nextBoundingRect();
247 hit.normal = KBounceVector::normal( rect, hit.boundingRect );
248 result += hit;
249 }
250 }
251 }
252 }
253
254 return result;
255}
256
257KBounceCollision KBounceBoard::checkCollisionTiles( const QRectF& rect)
258{
259 KBounceVector normal( 0, 0 );
260
261 // This small constant is added to each of the coordinates to
262 // avoid positive collision test result when tested rect lies
263 // on the edge of non-free space
264 qreal D = 0.01;
265
266 QPointF p = rect.topLeft();
267 int ul = m_tiles[static_cast<int>( p.x() + D )][static_cast<int>( p.y() + D )];
268 if ( ul != Free ) normal += KBounceVector( 1, 1 );
269
270 p = rect.topRight();
271 int ur = m_tiles[static_cast<int>( p.x() - D )][static_cast<int>( p.y() + D )];
272 if ( ur != Free) normal += KBounceVector( -1, 1 );
273
274 p = rect.bottomRight();
275 int lr = m_tiles[static_cast<int>( p.x() - D )][static_cast<int>( p.y() - D )];
276 if ( lr != Free ) normal += KBounceVector( -1, -1 );
277
278 p = rect.bottomLeft();
279 int ll = m_tiles[static_cast<int>( p.x() + D )][static_cast<int>( p.y() - D )];
280 if ( ll != Free ) normal += KBounceVector( 1, -1 );
281
282 KBounceCollision collision;
283 if ( (ul != Free ) || ( ur != Free ) || ( lr != Free ) || ( ll != Free ) )
284 {
285 KBounceHit hit;
286 hit.type = TILE;
287 hit.normal = normal;
288 collision += hit;
289 }
290 return collision;
291}
292
293void KBounceBoard::checkCollisions()
294{
295 foreach( KBounceWall* wall, m_walls )
296 {
297 QRectF rect = wall->nextBoundingRect();
298 KBounceCollision collision;
299 collision = checkCollision( wall, rect, ALL );
300 wall->collide( collision );
301 }
302 foreach( KBounceBall* ball, m_balls )
303 {
304 QRectF rect = ball->nextBoundingRect();
305 KBounceCollision collision;
306 collision = checkCollision( ball, rect, ALL );
307 ball->collide( collision );
308 }
309}
310
311QPoint KBounceBoard::mapPosition( const QPointF& pos ) const
312{
313 return QPoint( static_cast<int>( m_tileSize.width() * pos.x() ),
314 static_cast<int>( m_tileSize.height() * pos.y() ) );
315}
316
317QRectF KBounceBoard::boundingRect() const
318{
319 return QRectF( x(), y(),
320 TILE_NUM_W * m_tileSize.width(),
321 TILE_NUM_H * m_tileSize.height() );
322}
323
324void KBounceBoard::tick()
325{
326 checkCollisions();
327
328 foreach( KBounceBall* ball, m_balls )
329 {
330 ball->goForward();
331 }
332 foreach( KBounceWall* wall, m_walls )
333 {
334 wall->goForward();
335 }
336
337 foreach( KBounceBall* ball, m_balls )
338 {
339 ball->update();
340 }
341
342 foreach( KBounceWall* wall, m_walls )
343 {
344 wall->update();
345 }
346}
347
348QPixmap KBounceBoard::applyWallsOn(QPixmap background) const
349{
350 if (m_tileSize.isEmpty())
351 return background;
352
353 QPixmap walledBackground = background;
354 const QPixmap gridTile = m_renderer->spritePixmap("gridTile", m_tileSize);
355 const QPixmap wallTile = m_renderer->spritePixmap("wallTile", m_tileSize);
356 QPainter p(&walledBackground);
357 for (int i = 0; i < TILE_NUM_W; ++i) {
358 for (int j = 0; j < TILE_NUM_H; ++j) {
359 switch (m_tiles[i][j]) {
360 case Free:
361 p.drawPixmap(x() + i * m_tileSize.width(), y() + j * m_tileSize.height(), gridTile);
362 break;
363
364 case Border:
365 case Wall:
366 p.drawPixmap(x() + i * m_tileSize.width(), y() + j * m_tileSize.height(), wallTile);
367 break;
368
369 default:
370 break;
371 }
372 }
373 }
374 return walledBackground;
375}
376
377void KBounceBoard::wallFinished( int x1, int y1, int x2, int y2 )
378{
379 for ( int x = x1; x < x2; x++ )
380 for ( int y = y1; y < y2; y++ )
381 m_tiles[x][y] = Wall;
382
383 foreach ( KBounceBall* ball, m_balls )
384 {
385 int x1 = static_cast<int>( ball->ballBoundingRect().x() );
386 int y1 = static_cast<int>( ball->ballBoundingRect().y() );
387 int x2 = static_cast<int>( ball->ballBoundingRect().right() );
388 int y2 = static_cast<int>( ball->ballBoundingRect().bottom() );
389 // try to fill from all edges
390 // this way we can avoid most precision-related issues
391 fill(x1, y1);
392 fill(x1, y2);
393 fill(x2, y1);
394 fill(x2, y2);
395 }
396
397 for ( int x = 0; x < TILE_NUM_W; x++ )
398 for ( int y = 0; y < TILE_NUM_H; y++ )
399 if ( m_tiles[x][y] == Free )
400 m_tiles[x][y] = Wall;
401 for ( int x = 0; x < TILE_NUM_W; x++ )
402 for ( int y = 0; y < TILE_NUM_H; y++ )
403 if ( m_tiles[x][y] == Temp )
404 m_tiles[x][y] = Free;
405
406 int filled = 0;
407 for ( int i = 1; i < TILE_NUM_W - 1; i++ )
408 for ( int j = 1; j < TILE_NUM_H - 1; j++ )
409 if ( m_tiles[i][j] == Wall )
410 filled++;
411 m_filled = filled * 100 / ( ( TILE_NUM_W - 2 ) * ( TILE_NUM_H - 2 ) );
412
413 scene()->setBackgroundBrush(applyWallsOn(m_renderer->renderBackground()));
414
415 emit fillChanged( m_filled );
416}
417
418void KBounceBoard::clear()
419{
420 for ( int i = 0; i < TILE_NUM_W; i++ )
421 m_tiles[i][0] = m_tiles[i][TILE_NUM_H-1] = Border;
422 for ( int j = 0; j < TILE_NUM_H; j++ )
423 m_tiles[0][j] = m_tiles[TILE_NUM_W-1][j] = Border;
424 for ( int i = 1; i < TILE_NUM_W - 1; i++ )
425 for ( int j = 1; j < TILE_NUM_H -1; j++ )
426 m_tiles[i][j] = Free;
427 m_filled = 0;
428}
429
430void KBounceBoard::fill( int x, int y )
431{
432 if ( m_tiles[x][y] != Free )
433 return;
434 m_tiles[x][y] = Temp;
435
436 if ( y > 0 ) fill( x, y - 1 );
437 if ( x < TILE_NUM_W - 1 ) fill ( x + 1, y );
438 if ( y < TILE_NUM_H - 1 ) fill ( x, y + 1 );
439 if ( x > 0 ) fill ( x - 1, y );
440}
441
442#include "board.moc"
443
444