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 "gamewidget.h" |
20 | #include "settings.h" |
21 | #include "wall.h" |
22 | |
23 | #include <QPalette> |
24 | #include <QTimer> |
25 | |
26 | #include <KStandardDirs> |
27 | #include <KLocale> |
28 | #include <KgDifficulty> |
29 | #include <KgThemeProvider> |
30 | #include <KColorScheme> |
31 | |
32 | static const int MIN_MARGIN = 50; |
33 | static const int GAME_TIME_DELAY = 1000; |
34 | static const int MIN_FILL_PERCENT = 75; |
35 | static const int POINTS_FOR_LIFE = 15; |
36 | static const int TICKS_PER_SECOND = 1000 / GAME_TIME_DELAY; |
37 | |
38 | KBounceGameWidget::KBounceGameWidget( QWidget* parent ) |
39 | : QGraphicsView( parent ) |
40 | , m_state( BeforeFirstGame ) |
41 | , m_bonus( 0 ) |
42 | , m_level( 0 ) |
43 | , m_lives( 0 ) |
44 | , m_time( 0 ) |
45 | , m_vertical( false ) |
46 | , m_soundTimeout( KStandardDirs::locate( "appdata" , "sounds/timeout.wav" ) ) |
47 | { |
48 | m_board = new KBounceBoard( &m_renderer ); |
49 | connect( m_board, SIGNAL(fillChanged(int)), this, SLOT(onFillChanged(int)) ); |
50 | connect( m_board, SIGNAL(wallDied()), this, SLOT(onWallDied()) ); |
51 | |
52 | m_overlay = new QGraphicsPixmapItem(); |
53 | m_overlay->hide(); |
54 | |
55 | m_clock = new QTimer( this ); |
56 | m_clock->setInterval( GAME_TIME_DELAY ); |
57 | connect( m_clock, SIGNAL(timeout()), this, SLOT(tick()) ); |
58 | connect( this, SIGNAL(livesChanged(int)),this,SLOT(onLivesChanged(int)) ); |
59 | |
60 | setMouseTracking( true ); |
61 | |
62 | connect(m_renderer.themeProvider(), SIGNAL(currentThemeChanged(const KgTheme*)), |
63 | SLOT(settingsChanged())); |
64 | |
65 | m_scene.addItem( m_board ); |
66 | m_scene.addItem( m_overlay ); |
67 | setScene( &m_scene ); |
68 | } |
69 | |
70 | KBounceGameWidget::~KBounceGameWidget() |
71 | { |
72 | delete m_board; |
73 | delete m_overlay; |
74 | } |
75 | |
76 | int KBounceGameWidget::level() |
77 | { |
78 | return m_level; |
79 | } |
80 | |
81 | int KBounceGameWidget::score() |
82 | { |
83 | kDebug() << "Score:" << m_score; |
84 | return m_score; |
85 | } |
86 | |
87 | QSize KBounceGameWidget::minimumSizeHint() const |
88 | { |
89 | return QSize( 576, 384 ); |
90 | } |
91 | |
92 | void KBounceGameWidget::closeGame() |
93 | { |
94 | if ( m_state != BeforeFirstGame && m_state != GameOver ) |
95 | { |
96 | m_clock->stop(); |
97 | m_board->setPaused( true ); |
98 | m_state = GameOver; |
99 | emit stateChanged( m_state ); |
100 | emit gameOver(); |
101 | |
102 | Kg::difficulty()->setGameRunning( false ); |
103 | redraw(); |
104 | } |
105 | } |
106 | |
107 | void KBounceGameWidget::newGame() |
108 | { |
109 | closeGame(); |
110 | m_level = 1; |
111 | m_score = 0; |
112 | |
113 | emit levelChanged( m_level ); |
114 | emit scoreChanged( m_score ); |
115 | |
116 | Kg::difficulty()->setGameRunning( true ); |
117 | newLevel(); |
118 | } |
119 | |
120 | void KBounceGameWidget::setPaused( bool val ) |
121 | { |
122 | if ( m_state == Paused && val == false ) |
123 | { |
124 | m_clock->start(); |
125 | m_board->setPaused( false ); |
126 | m_state = Running; |
127 | emit stateChanged( m_state ); |
128 | } |
129 | else if ( m_state == Running && val == true ) |
130 | { |
131 | m_clock->stop(); |
132 | m_board->setPaused( true ); |
133 | m_state = Paused; |
134 | emit stateChanged( m_state ); |
135 | } |
136 | |
137 | redraw(); |
138 | } |
139 | |
140 | void KBounceGameWidget::setSuspended( bool val ) |
141 | { |
142 | if ( m_state == Suspended && val == false ) |
143 | { |
144 | m_clock->start(); |
145 | m_board->setPaused( false ); |
146 | m_state = Running; |
147 | emit stateChanged( m_state ); |
148 | } |
149 | |
150 | if ( m_state == Running && val == true ) |
151 | { |
152 | m_clock->stop(); |
153 | m_board->setPaused( true ); |
154 | m_state = Suspended; |
155 | emit stateChanged( m_state ); |
156 | } |
157 | redraw(); |
158 | } |
159 | |
160 | void KBounceGameWidget::settingsChanged() |
161 | { |
162 | kDebug() << "Settings changed" ; |
163 | |
164 | if (KBounceSettings::useRandomBackgroundPictures()) |
165 | { |
166 | m_renderer.setCustomBackgroundPath(KBounceSettings::backgroundPicturePath()); |
167 | } |
168 | else |
169 | { |
170 | m_renderer.setCustomBackgroundPath(QString()); |
171 | } |
172 | redraw(); |
173 | } |
174 | |
175 | void KBounceGameWidget::setGameDifficulty( const KgDifficultyLevel * difficulty ) |
176 | { |
177 | switch ( difficulty->hardness() ) { |
178 | case KgDifficultyLevel::Easy: |
179 | m_board->setBallVelocity(0.100); |
180 | m_board->setWallVelocity(0.250); |
181 | break; |
182 | case KgDifficultyLevel::Medium: |
183 | m_board->setWallVelocity(0.125); |
184 | m_board->setBallVelocity(0.125); |
185 | break; |
186 | case KgDifficultyLevel::Hard: |
187 | m_board->setWallVelocity(0.100); |
188 | m_board->setBallVelocity(0.250); |
189 | break; |
190 | } |
191 | } |
192 | |
193 | void KBounceGameWidget::levelChanged() |
194 | { |
195 | setGameDifficulty( Kg::difficulty()->currentLevel() ); |
196 | |
197 | if ( m_state == Running || m_state == Paused ) |
198 | newGame(); |
199 | } |
200 | |
201 | void KBounceGameWidget::onFillChanged( int fill ) |
202 | { |
203 | emit filledChanged( fill ); |
204 | if ( fill >= MIN_FILL_PERCENT ) |
205 | { |
206 | closeLevel(); |
207 | m_level++; |
208 | emit levelChanged( m_level ); |
209 | |
210 | m_state = BetweenLevels; |
211 | emit stateChanged( m_state ); |
212 | |
213 | redraw(); |
214 | } |
215 | } |
216 | |
217 | void KBounceGameWidget::onWallDied() |
218 | { |
219 | if ( m_lives <= 1 ) |
220 | { |
221 | closeGame(); |
222 | } |
223 | else |
224 | { |
225 | m_lives--; |
226 | emit livesChanged( m_lives ); |
227 | } |
228 | } |
229 | |
230 | void KBounceGameWidget::onLivesChanged(int lives) |
231 | { |
232 | if ( lives < ( m_level + 1 ) |
233 | && KBounceSettings::playSounds() ) |
234 | { |
235 | m_soundTimeout.start(); |
236 | } |
237 | } |
238 | |
239 | |
240 | void KBounceGameWidget::tick() |
241 | { |
242 | static int ticks = TICKS_PER_SECOND; |
243 | ticks--; |
244 | if ( ticks <= 0 ) |
245 | { |
246 | if ( m_time == 1 ) |
247 | closeGame(); |
248 | else |
249 | { |
250 | m_time--; |
251 | emit timeChanged( m_time ); |
252 | } |
253 | ticks = TICKS_PER_SECOND; |
254 | } |
255 | } |
256 | |
257 | void KBounceGameWidget::resizeEvent( QResizeEvent* ev ) |
258 | { |
259 | kDebug() << "Size" << ev->size(); |
260 | |
261 | m_renderer.setBackgroundSize( ev->size() ); |
262 | |
263 | QRectF rect( 0, 0, ev->size().width(), ev->size().height() ); |
264 | m_scene.setSceneRect( rect ); |
265 | |
266 | QSize boardSize( sceneRect().width() - MIN_MARGIN, |
267 | sceneRect().height() - MIN_MARGIN ); |
268 | m_board->resize( boardSize ); |
269 | |
270 | qreal x = ( sceneRect().width() - m_board->boundingRect().width() ) / 2; |
271 | qreal y = ( sceneRect().height() - m_board->boundingRect().height() ) / 2; |
272 | m_board->setPos( x, y ); |
273 | |
274 | redraw(); |
275 | } |
276 | |
277 | void KBounceGameWidget::mouseReleaseEvent( QMouseEvent* event ) |
278 | { |
279 | if ( event->button() & Qt::RightButton ) |
280 | { |
281 | m_vertical = !m_vertical; |
282 | updateCursor(); |
283 | } |
284 | |
285 | if ( event->button() & Qt::LeftButton ) |
286 | { |
287 | if ( m_state == Running ) |
288 | { |
289 | m_board->buildWall( mapToScene( event->pos() ), m_vertical ); |
290 | } |
291 | else if ( m_state == Paused ) |
292 | { |
293 | setPaused( false ); |
294 | } |
295 | else if ( m_state == BetweenLevels ) |
296 | { |
297 | newLevel(); |
298 | } |
299 | else if ( m_state == BeforeFirstGame || m_state == GameOver ) |
300 | { |
301 | newGame(); |
302 | } |
303 | } |
304 | } |
305 | |
306 | |
307 | void KBounceGameWidget::closeLevel() |
308 | { |
309 | m_bonus = 0; |
310 | if ( m_board->filled() >= MIN_FILL_PERCENT ) |
311 | { |
312 | m_bonus = ( m_board->filled() - MIN_FILL_PERCENT ) * 2 * ( m_level + 5 ); |
313 | } |
314 | m_score += m_bonus; |
315 | m_score += POINTS_FOR_LIFE * m_lives; |
316 | emit scoreChanged( m_score ); |
317 | |
318 | m_clock->stop(); |
319 | m_board->setPaused( true ); |
320 | } |
321 | |
322 | void KBounceGameWidget::newLevel() |
323 | { |
324 | m_state = Running; |
325 | emit stateChanged( m_state ); |
326 | |
327 | m_clock->start(); |
328 | m_board->newLevel( m_level ); |
329 | m_board->setPaused( false ); |
330 | |
331 | m_bonus = 0; |
332 | m_lives = m_level + 1; |
333 | m_time = 30 * ( m_level + 2 ); |
334 | emit livesChanged( m_lives ); |
335 | emit timeChanged( m_time ); |
336 | |
337 | if (KBounceSettings::useRandomBackgroundPictures()) |
338 | m_renderer.loadNewBackgroundPixmap(); |
339 | |
340 | redraw(); |
341 | } |
342 | |
343 | |
344 | void KBounceGameWidget::redraw() |
345 | { |
346 | if ( size().isEmpty() ) |
347 | return; |
348 | |
349 | switch ( m_state ) |
350 | { |
351 | case BeforeFirstGame: |
352 | m_board->hide(); |
353 | generateOverlay(); |
354 | m_overlay->show(); |
355 | break; |
356 | case Running: |
357 | m_board->show(); |
358 | m_overlay->hide(); |
359 | break; |
360 | default: |
361 | m_board->show(); |
362 | generateOverlay(); |
363 | m_overlay->show(); |
364 | break; |
365 | } |
366 | |
367 | updateCursor(); |
368 | KBounceWall::loadSprites(); |
369 | m_scene.setBackgroundBrush( m_board->applyWallsOn(m_renderer.renderBackground()) ); |
370 | update(); |
371 | } |
372 | |
373 | void KBounceGameWidget::generateOverlay() |
374 | { |
375 | if ( size().isEmpty() ) |
376 | return; |
377 | |
378 | int itemWidth = qRound( 0.8 * size().width() ); |
379 | int itemHeight = qRound( 0.6 * size().height() ); |
380 | |
381 | QSize backgroundSize( itemWidth,itemHeight ); |
382 | |
383 | QPixmap px( backgroundSize ); |
384 | px.fill( Qt::transparent ); |
385 | |
386 | QPainter p( &px ); |
387 | |
388 | p.setPen( Qt::transparent ); |
389 | p.setRenderHint(QPainter::Antialiasing ); |
390 | |
391 | if ( m_renderer.spriteExists("overlayBackground" ) ) |
392 | { |
393 | QPixmap themeBackgound = m_renderer.spritePixmap("overlayBackground" ,backgroundSize); |
394 | p.setCompositionMode( QPainter::CompositionMode_Source ); |
395 | p.drawPixmap( p.viewport(), themeBackgound ); |
396 | p.setCompositionMode( QPainter::CompositionMode_DestinationIn ); |
397 | p.fillRect(px.rect(), QColor( 0, 0, 0, 160 )); |
398 | p.setCompositionMode( QPainter::CompositionMode_SourceOver ); |
399 | } |
400 | else |
401 | { |
402 | p.setBrush( QBrush( QColor( 188, 202, 222, 155 ) ) ); |
403 | p.drawRoundRect( 0, 0, itemWidth, itemHeight, 25 ); |
404 | } |
405 | |
406 | QString text; |
407 | switch( m_state ) |
408 | { |
409 | case BeforeFirstGame: |
410 | text = i18n( "Welcome to KBounce.\n Click to start a game" ); |
411 | break; |
412 | case Paused: |
413 | text = i18n( "Paused\n Click to resume" ); |
414 | break; |
415 | case BetweenLevels: |
416 | text = i18n( "You have successfully cleared more than %1% of the board\n" , MIN_FILL_PERCENT ) + |
417 | i18n( "%1 points: %2 points per remaining life\n" , m_lives * POINTS_FOR_LIFE, POINTS_FOR_LIFE ) + |
418 | i18n( "%1 points: Bonus\n" , m_bonus ) + |
419 | i18n( "%1 points: Total score for this level\n" , m_bonus + m_lives * POINTS_FOR_LIFE ) + |
420 | i18n( "On to level %1. Remember you get %2 lives this time!" , m_level, m_level + 1 ); |
421 | break; |
422 | case GameOver: |
423 | text = i18n( "Game over.\n Click to start a game" ); |
424 | break; |
425 | default: |
426 | text = QString(); |
427 | } |
428 | |
429 | QFont font; |
430 | font.setPointSize( 28 ); |
431 | p.setFont( font ); |
432 | int textWidth = p.boundingRect( p.viewport(), Qt::AlignCenter | Qt::AlignVCenter, text ).width(); |
433 | int fontSize = 28; |
434 | while ( ( textWidth > itemWidth * 0.95 ) && fontSize > 1 ) |
435 | { |
436 | fontSize--; |
437 | font.setPointSize( fontSize ); |
438 | p.setFont( font ); |
439 | textWidth = p.boundingRect( p.viewport(), Qt::AlignCenter | Qt::AlignVCenter, text ).width(); |
440 | } |
441 | KColorScheme kcs = KColorScheme( QPalette::Normal, KColorScheme::Window ); |
442 | p.setPen( kcs.foreground(KColorScheme::NormalText).color()); |
443 | p.drawText( p.viewport(), Qt::AlignCenter | Qt::AlignVCenter, text ); |
444 | p.end(); |
445 | |
446 | m_overlay->setPixmap( px ); |
447 | |
448 | QPointF pos( ( sceneRect().width() - itemWidth) / 2, |
449 | ( sceneRect().height() - itemHeight) / 2 ); |
450 | m_overlay->setPos( pos ); |
451 | } |
452 | |
453 | void KBounceGameWidget::focusOutEvent(QFocusEvent *event) |
454 | { |
455 | if (event->reason() == Qt::ActiveWindowFocusReason) |
456 | { |
457 | setPaused( true ); |
458 | } |
459 | } |
460 | |
461 | void KBounceGameWidget::updateCursor() |
462 | { |
463 | if ( m_state == Running ) |
464 | setCursor( m_vertical ? Qt::SizeVerCursor : Qt::SizeHorCursor ); |
465 | else |
466 | unsetCursor(); |
467 | } |
468 | |
469 | #include "gamewidget.moc" |
470 | |
471 | |