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 | |
41 | KBounceBoard::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 | |
67 | KBounceBoard::~KBounceBoard() |
68 | { |
69 | qDeleteAll( m_balls ); |
70 | qDeleteAll( m_walls ); |
71 | } |
72 | |
73 | void 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 | |
112 | void 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 | |
147 | void KBounceBoard::setPaused( bool val ) |
148 | { |
149 | if ( val ) |
150 | m_clock->stop(); |
151 | else |
152 | m_clock->start(); |
153 | } |
154 | |
155 | void KBounceBoard::setBallVelocity(qreal vel) |
156 | { |
157 | m_ballVelocity = vel; |
158 | } |
159 | |
160 | void KBounceBoard::setWallVelocity(qreal vel) |
161 | { |
162 | m_wallVelocity = vel; |
163 | } |
164 | |
165 | void 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 | |
199 | int KBounceBoard::balls() |
200 | { |
201 | return m_balls.count(); |
202 | } |
203 | |
204 | int KBounceBoard::filled() |
205 | { |
206 | return m_filled; |
207 | } |
208 | |
209 | KBounceCollision 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 | |
257 | KBounceCollision 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 | |
293 | void 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 | |
311 | QPoint 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 | |
317 | QRectF 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 | |
324 | void 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 | |
348 | QPixmap 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 | |
377 | void 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 | |
418 | void 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 | |
430 | void 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 | |