2 Copyright (c) 2007 Paolo Capriotti <p.capriotti@gmail.com>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
12 #include <QApplication>
13 #include <QGraphicsView>
14 #include <QGraphicsSceneMouseEvent>
18 #include <KGameDifficulty>
19 #include <KLocalizedString>
20 #include <Phonon/MediaObject>
24 #include "kollisionconfig.h"
34 double square_distance
;
47 QRect
rect(0, 0, m_size
, m_size
);
52 m_renderer
= new Renderer
;
53 m_renderer
->resize(QSize(28, 28));
55 m_timer
.setInterval(20);
56 connect(&m_timer
, SIGNAL(timeout()), this, SLOT(tick()));
58 m_msg_font
= QApplication::font();
59 m_msg_font
.setPointSize(15);
61 QImage
tmp(rect
.size(), QImage::Format_ARGB32_Premultiplied
);
65 QLinearGradient
grad(QPointF(0, 0), QPointF(0, height()));
66 grad
.setColorAt(0, QColor(240, 240, 240));
67 grad
.setColorAt(1, QColor(180, 180, 180));
68 p
.fillRect(rect
, grad
);
70 m_background
= QPixmap::fromImage(tmp
);
72 writeText(i18n("Welcome to Kollision\nClick to start a game"), false);
83 void MainArea::enableSounds(bool enable
)
85 KollisionConfig::setEnableSounds(enable
);
87 KollisionConfig::self()->writeConfig();
90 void MainArea::updateSounds()
92 m_player
.setActive(KollisionConfig::enableSounds());
95 Animation
* MainArea::writeMessage(const QString
& text
)
97 Message
* message
= new Message(text
, m_msg_font
);
98 message
->setPosition(QPointF(m_size
, m_size
) / 2.0);
100 message
->setOpacityF(0.0);
102 SpritePtr
sprite(message
);
104 AnimationGroup
* move
= new AnimationGroup
;
105 move
->add(new FadeAnimation(sprite
, 1.0, 0.0, 1500));
106 move
->add(new MovementAnimation(sprite
,
110 AnimationSequence
* sequence
= new AnimationSequence
;
111 sequence
->add(new PauseAnimation(200));
112 sequence
->add(new FadeAnimation(sprite
, 0.0, 1.0, 1000));
113 sequence
->add(new PauseAnimation(500));
116 m_animator
.add(sequence
);
121 Animation
* MainArea::writeText(const QString
& text
, bool fade
)
123 m_welcome_msg
.clear();
124 foreach (const QString
&line
, text
.split("\n")) {
125 m_welcome_msg
.append(
126 KSharedPtr
<Message
>(new Message(line
, m_msg_font
)));
128 displayMessages(m_welcome_msg
);
131 AnimationGroup
* anim
= new AnimationGroup
;
132 foreach (KSharedPtr
<Message
> message
, m_welcome_msg
) {
133 message
->setOpacityF(0.0);
134 anim
->add(new FadeAnimation(
135 SpritePtr::staticCast(message
), 0.0, 1.0, 1000));
138 m_animator
.add(anim
);
147 void MainArea::displayMessages(const QList
<KSharedPtr
<Message
> >& messages
)
150 QPointF
pos(m_size
/ 2.0, (m_size
- step
* messages
.size()) / 2.0);
152 for (int i
= 0; i
< messages
.size(); i
++) {
153 KSharedPtr
<Message
> msg
= messages
[i
];
154 msg
->setPosition(pos
);
155 msg
->setZValue(10.0);
163 double MainArea::radius() const
165 return m_renderer
->size().width() / 2.0;
168 void MainArea::togglePause()
175 m_welcome_msg
.clear();
177 m_pause_time
+= m_time
.elapsed() - m_last_time
;
178 m_last_time
= m_time
.elapsed();
183 writeText(i18n("Game paused\nClick or press P to resume"), false);
185 if(m_last_game_time
>= 5) {
187 m_last_game_time
-= 5;
190 m_penalty
+= m_last_game_time
* 1000;
191 m_last_game_time
= 0;
194 emit
changeGameTime(m_last_game_time
);
197 m_man
->setVisible(!m_paused
);
198 foreach (Ball
* ball
, m_balls
) {
199 ball
->setVisible(!m_paused
);
201 foreach (Ball
* ball
, m_fading
) {
202 ball
->setVisible(!m_paused
);
205 emit
pause(m_paused
);
208 void MainArea::start()
213 kDebug() << "difficulty:" << KollisionConfig::gameDifficulty();
215 switch (KollisionConfig::gameDifficulty()) {
216 case KGameDifficulty::Easy
:
219 case KGameDifficulty::Medium
:
222 case KGameDifficulty::Hard
:
228 m_welcome_msg
.clear();
239 m_last_game_time
= 0;
243 writeMessage(i18np("%1 ball", "%1 balls", 4));
245 emit
changeGameTime(0);
247 m_player
.play(AudioPlayer::START
);
250 QPointF
MainArea::randomPoint() const
252 double x
= (double)rand() * (m_size
- radius() * 2) / RAND_MAX
+ radius();
253 double y
= (double)rand() * (m_size
- radius() * 2) / RAND_MAX
+ radius();
254 return QPointF(x
, y
);
257 QPointF
MainArea::randomDirection(double val
) const
259 double angle
= (double)rand() * 2 * M_PI
/ RAND_MAX
;
260 return QPointF(val
* sin(angle
), val
* cos(angle
));
263 Ball
* MainArea::addBall(const QString
& id
)
266 for (bool done
= false; !done
; ) {
270 pos
= randomPoint().toPoint();
271 foreach (Ball
* ball
, m_fading
) {
272 if (collide(pos
, ball
->position(), ball
->radius() * 2.0, tmp
)) {
279 Ball
* ball
= new Ball(m_renderer
, id
);
280 ball
->setPosition(pos
);
283 // speed depends of game difficulty
285 switch (KollisionConfig::gameDifficulty())
287 case KGameDifficulty::Easy
:
290 case KGameDifficulty::Medium
:
293 case KGameDifficulty::Hard
:
298 ball
->setVelocity(randomDirection(speed
));
300 ball
->setOpacityF(0.0);
302 m_fading
.push_back(ball
);
305 emit
changeBallNumber(m_balls
.size() + m_fading
.size());
310 bool MainArea::collide(const QPointF
& a
, const QPointF
& b
, double diam
, Collision
& collision
)
312 collision
.line
= b
- a
;
313 collision
.square_distance
= collision
.line
.x() * collision
.line
.x()
314 + collision
.line
.y() * collision
.line
.y();
315 return collision
.square_distance
<= diam
* diam
;
318 void MainArea::abort()
326 m_man
->setVelocity(QPointF(0, 0));
327 m_balls
.push_back(m_man
);
329 emit
changeState(false);
331 foreach (Ball
* fball
, m_fading
) {
332 fball
->setOpacityF(1.0);
333 fball
->setVelocity(QPointF(0.0, 0.0));
334 m_balls
.push_back(fball
);
340 void MainArea::tick()
342 if (!m_death
&& m_man
&& !m_paused
) {
343 setManPosition(views().first()->mapFromGlobal(QCursor().pos()));
346 int t
= m_time
.elapsed() - m_last_time
;
347 m_last_time
= m_time
.elapsed();
349 // compute game time && update statusbar
350 if ((m_time
.elapsed() - m_pause_time
- m_penalty
) / 1000 > m_last_game_time
) {
351 m_last_game_time
= (m_time
.elapsed() - m_pause_time
- m_penalty
) / 1000;
352 emit
changeGameTime(m_last_game_time
);
358 for (QList
<Ball
*>::iterator it
= m_fading
.begin();
359 it
!= m_fading
.end(); ) {
360 (*it
)->setOpacityF((*it
)->opacityF() + t
* 0.0005);
361 if ((*it
)->opacityF() >= 1.0) {
362 m_balls
.push_back(*it
);
363 it
= m_fading
.erase(it
);
370 // handle deadly collisions
371 foreach (Ball
* ball
, m_balls
) {
372 if (m_man
&& collide(
375 radius() * 2, collision
)) {
376 m_player
.play(AudioPlayer::YOU_LOSE
);
383 foreach (Ball
* ball
, m_balls
) {
385 ball
->setPosition(ball
->position() +
386 ball
->velocity() * t
);
390 ball
->setVelocity(ball
->velocity() +
391 QPointF(0, 0.001) * t
);
395 for (int i
= 0; i
< m_balls
.size(); i
++) {
396 Ball
* ball
= m_balls
[i
];
398 QPointF vel
= ball
->velocity();
399 QPointF pos
= ball
->position();
401 // handle collisions with borders
402 bool hit_wall
= false;
403 if (pos
.x() <= radius()) {
404 vel
.setX(fabs(vel
.x()));
405 pos
.setX(2 * radius() - pos
.x());
408 if (pos
.x() >= m_size
- radius()) {
409 vel
.setX(-fabs(vel
.x()));
410 pos
.setX(2 * (m_size
- radius()) - pos
.x());
413 if (pos
.y() <= radius()) {
414 vel
.setY(fabs(vel
.y()));
415 pos
.setY(2 * radius() - pos
.y());
419 if (pos
.y() >= m_size
- radius()) {
420 vel
.setY(-fabs(vel
.y()));
421 pos
.setY(2 * (m_size
- radius()) - pos
.y());
426 m_player
.play(AudioPlayer::HIT_WALL
);
429 // handle collisions with next balls
430 for (int j
= i
+ 1; j
< m_balls
.size(); j
++) {
431 Ball
* other
= m_balls
[j
];
433 QPointF other_pos
= other
->position();
435 if (collide(pos
, other_pos
, radius() * 2, collision
)) {
437 QPointF other_vel
= other
->velocity();
439 // compute the parallel component of the
440 // velocity with respect to the collision line
441 double v_par
= vel
.x() * collision
.line
.x()
442 + vel
.y() * collision
.line
.y();
443 double w_par
= other_vel
.x() * collision
.line
.x()
444 + other_vel
.y() * collision
.line
.y();
446 // swap those components
447 QPointF drift
= collision
.line
* (w_par
- v_par
) /
448 collision
.square_distance
;
450 other
->setVelocity(other_vel
- drift
);
452 // adjust positions, reflecting along the collision
453 // line as much as the amount of compenetration
454 QPointF adj
= collision
.line
*
456 sqrt(collision
.square_distance
)
459 other
->setPosition(other_pos
+ adj
);
464 ball
->setPosition(pos
);
465 ball
->setVelocity(vel
);
468 for (QList
<Ball
*>::iterator it
= m_balls
.begin();
469 it
!= m_balls
.end(); ) {
471 QPointF pos
= ball
->position();
473 if (m_death
&& pos
.y() >= height() + radius() + 10) {
474 m_player
.play(AudioPlayer::BALL_LEAVING
);
476 it
= m_balls
.erase(it
);
483 if (!m_death
&& m_time
.elapsed() - m_pause_time
>= m_ball_timeout
* 1000 *
484 (m_balls
.size() + m_fading
.size() - 3)) {
486 writeMessage(i18np("%1 ball", "%1 balls", m_balls
.size() + 1));
489 if (m_death
&& m_balls
.isEmpty() && m_fading
.isEmpty()) {
492 int time
= (m_time
.restart() - m_pause_time
- m_penalty
) / 1000;
493 QString text
= i18np(
495 "You survived for %1 second\n"
498 "You survived for %1 seconds\n"
499 "Click to restart", time
);
501 Animation
* a
= writeText(text
);
502 connect(this, SIGNAL(starting()), a
, SLOT(stop()));
506 void MainArea::setManPosition(const QPointF
& p
)
512 if (pos
.x() <= radius()) pos
.setX((int) radius());
513 if (pos
.x() >= m_size
- radius()) pos
.setX(m_size
- (int) radius());
514 if (pos
.y() <= radius()) pos
.setY((int) radius());
515 if (pos
.y() >= m_size
- radius()) pos
.setY(m_size
- (int) radius());
517 m_man
->setPosition(pos
);
520 void MainArea::mousePressEvent(QGraphicsSceneMouseEvent
* e
)
522 if (!m_death
|| m_game_over
) {
525 setManPosition(e
->scenePos());
528 m_man
= new Ball(m_renderer
, "blue_ball");
529 m_man
->setZValue(1.0);
530 setManPosition(e
->scenePos());
534 emit
changeState(true);
539 void MainArea::focusOutEvent(QFocusEvent
*)
546 void MainArea::drawBackground(QPainter
* painter
, const QRectF
& rect
)
548 painter
->drawPixmap(rect
, m_background
, rect
);