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
32static const int MIN_MARGIN = 50;
33static const int GAME_TIME_DELAY = 1000;
34static const int MIN_FILL_PERCENT = 75;
35static const int POINTS_FOR_LIFE = 15;
36static const int TICKS_PER_SECOND = 1000 / GAME_TIME_DELAY;
37
38KBounceGameWidget::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
70KBounceGameWidget::~KBounceGameWidget()
71{
72 delete m_board;
73 delete m_overlay;
74}
75
76int KBounceGameWidget::level()
77{
78 return m_level;
79}
80
81int KBounceGameWidget::score()
82{
83 kDebug() << "Score:" << m_score;
84 return m_score;
85}
86
87QSize KBounceGameWidget::minimumSizeHint() const
88{
89 return QSize( 576, 384 );
90}
91
92void 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
107void 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
120void 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
140void 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
160void 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
175void 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
193void KBounceGameWidget::levelChanged()
194{
195 setGameDifficulty( Kg::difficulty()->currentLevel() );
196
197 if ( m_state == Running || m_state == Paused )
198 newGame();
199}
200
201void 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
217void 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
230void KBounceGameWidget::onLivesChanged(int lives)
231{
232 if ( lives < ( m_level + 1 )
233 && KBounceSettings::playSounds() )
234 {
235 m_soundTimeout.start();
236 }
237}
238
239
240void 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
257void 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
277void 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
307void 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
322void 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
344void 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
373void 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
453void KBounceGameWidget::focusOutEvent(QFocusEvent *event)
454{
455 if (event->reason() == Qt::ActiveWindowFocusReason)
456 {
457 setPaused( true );
458 }
459}
460
461void 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