Sort layouts in the list
[kdegames.git] / kollision / mainarea.cpp
blob05a1b1ba8d23289fb5645c1946323180cf4ad71b
1 /*
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.
8 */
10 #include "mainarea.h"
12 #include <QApplication>
13 #include <QGraphicsView>
14 #include <QGraphicsSceneMouseEvent>
15 #include <QPainter>
17 #include <KDebug>
18 #include <KGameDifficulty>
19 #include <KLocalizedString>
20 #include <Phonon/MediaObject>
22 #include "renderer.h"
23 #include "ball.h"
24 #include "kollisionconfig.h"
26 // for rand
27 #include <math.h>
28 #include <stdio.h>
29 #include <time.h>
30 #include <sys/time.h>
32 struct Collision
34 double square_distance;
35 QPointF line;
38 MainArea::MainArea()
39 : m_man(0)
40 , m_death(false)
41 , m_game_over(false)
42 , m_paused(false)
43 , m_pause_time(0)
44 , m_penalty(0)
46 m_size = 500;
47 QRect rect(0, 0, m_size, m_size);
48 setSceneRect(rect);
50 srand(time(0));
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);
63 // draw gradient
64 QPainter p(&tmp);
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);
74 // setup audio player
75 updateSounds();
78 MainArea::~MainArea()
80 delete m_renderer;
83 void MainArea::enableSounds(bool enable)
85 KollisionConfig::setEnableSounds(enable);
86 updateSounds();
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);
99 addItem(message);
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,
107 sprite->position(),
108 QPointF(0, -0.1),
109 1500));
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));
114 sequence->add(move);
116 m_animator.add(sequence);
118 return 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);
130 if (fade) {
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);
140 return anim;
142 else {
143 return 0;
147 void MainArea::displayMessages(const QList<KSharedPtr<Message> >& messages)
149 const int step = 45;
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);
156 msg->show();
157 addItem(msg.data());
159 pos.ry() += step;
163 double MainArea::radius() const
165 return m_renderer->size().width() / 2.0;
168 void MainArea::togglePause()
170 if (!m_man) return;
172 if (m_paused) {
173 m_paused = false;
174 m_timer.start();
175 m_welcome_msg.clear();
177 m_pause_time += m_time.elapsed() - m_last_time;
178 m_last_time = m_time.elapsed();
180 else {
181 m_paused = true;
182 m_timer.stop();
183 writeText(i18n("Game paused\nClick or press P to resume"), false);
185 if(m_last_game_time >= 5) {
186 m_penalty += 5000;
187 m_last_game_time -= 5;
189 else {
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()
210 m_death = false;
211 m_game_over = false;
213 kDebug() << "difficulty:" << KollisionConfig::gameDifficulty();
215 switch (KollisionConfig::gameDifficulty()) {
216 case KGameDifficulty::Easy:
217 m_ball_timeout = 30;
218 break;
219 case KGameDifficulty::Medium:
220 m_ball_timeout = 25;
221 break;
222 case KGameDifficulty::Hard:
223 default:
224 m_ball_timeout = 20;
225 break;
228 m_welcome_msg.clear();
230 addBall("red_ball");
231 addBall("red_ball");
232 addBall("red_ball");
233 addBall("red_ball");
235 m_pause_time = 0;
236 m_penalty = 0;
237 m_time.restart();
238 m_last_time = 0;
239 m_last_game_time = 0;
241 m_timer.start();
243 writeMessage(i18np("%1 ball", "%1 balls", 4));
245 emit changeGameTime(0);
246 emit starting();
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)
265 QPoint pos;
266 for (bool done = false; !done; ) {
267 Collision tmp;
269 done = true;
270 pos = randomPoint().toPoint();
271 foreach (Ball* ball, m_fading) {
272 if (collide(pos, ball->position(), ball->radius() * 2.0, tmp)) {
273 done = false;
274 break;
279 Ball* ball = new Ball(m_renderer, id);
280 ball->setPosition(pos);
281 addItem(ball);
283 // speed depends of game difficulty
284 double speed;
285 switch (KollisionConfig::gameDifficulty())
287 case KGameDifficulty::Easy:
288 speed = 0.2;
289 break;
290 case KGameDifficulty::Medium:
291 speed = 0.28;
292 break;
293 case KGameDifficulty::Hard:
294 default:
295 speed = 0.4;
296 break;
298 ball->setVelocity(randomDirection(speed));
300 ball->setOpacityF(0.0);
301 ball->show();
302 m_fading.push_back(ball);
304 // update statusbar
305 emit changeBallNumber(m_balls.size() + m_fading.size());
307 return ball;
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()
320 if (m_man) {
321 if (m_paused) {
322 togglePause();
324 m_death = true;
326 m_man->setVelocity(QPointF(0, 0));
327 m_balls.push_back(m_man);
328 m_man = 0;
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);
336 m_fading.clear();
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);
355 Collision collision;
357 // handle fade in
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);
365 else {
366 ++it;
370 // handle deadly collisions
371 foreach (Ball* ball, m_balls) {
372 if (m_man && collide(
373 ball->position(),
374 m_man->position(),
375 radius() * 2, collision)) {
376 m_player.play(AudioPlayer::YOU_LOSE);
377 abort();
378 break;
382 // integrate
383 foreach (Ball* ball, m_balls) {
384 // position
385 ball->setPosition(ball->position() +
386 ball->velocity() * t);
388 // velocity
389 if (m_death) {
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());
406 hit_wall = true;
408 if (pos.x() >= m_size - radius()) {
409 vel.setX(-fabs(vel.x()));
410 pos.setX(2 * (m_size - radius()) - pos.x());
411 hit_wall = true;
413 if (pos.y() <= radius()) {
414 vel.setY(fabs(vel.y()));
415 pos.setY(2 * radius() - pos.y());
416 hit_wall = true;
418 if (!m_death) {
419 if (pos.y() >= m_size - radius()) {
420 vel.setY(-fabs(vel.y()));
421 pos.setY(2 * (m_size - radius()) - pos.y());
422 hit_wall = true;
425 if (hit_wall) {
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)) {
436 // onCollision();
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;
449 vel += drift;
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 *
455 (2.0 * radius() /
456 sqrt(collision.square_distance)
457 - 1);
458 pos -= adj;
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(); ) {
470 Ball* ball = *it;
471 QPointF pos = ball->position();
473 if (m_death && pos.y() >= height() + radius() + 10) {
474 m_player.play(AudioPlayer::BALL_LEAVING);
475 delete ball;
476 it = m_balls.erase(it);
478 else {
479 ++it;
483 if (!m_death && m_time.elapsed() - m_pause_time >= m_ball_timeout * 1000 *
484 (m_balls.size() + m_fading.size() - 3)) {
485 addBall("red_ball");
486 writeMessage(i18np("%1 ball", "%1 balls", m_balls.size() + 1));
489 if (m_death && m_balls.isEmpty() && m_fading.isEmpty()) {
490 m_game_over = true;
491 m_timer.stop();
492 int time = (m_time.restart() - m_pause_time - m_penalty) / 1000;
493 QString text = i18np(
494 "GAME OVER\n"
495 "You survived for %1 second\n"
496 "Click to restart",
497 "GAME OVER\n"
498 "You survived for %1 seconds\n"
499 "Click to restart", time);
500 emit gameOver(time);
501 Animation* a = writeText(text);
502 connect(this, SIGNAL(starting()), a, SLOT(stop()));
506 void MainArea::setManPosition(const QPointF& p)
508 Q_ASSERT(m_man);
510 QPointF pos = 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) {
523 if (m_paused) {
524 togglePause();
525 setManPosition(e->scenePos());
527 else if (!m_man) {
528 m_man = new Ball(m_renderer, "blue_ball");
529 m_man->setZValue(1.0);
530 setManPosition(e->scenePos());
531 addItem(m_man);
533 start();
534 emit changeState(true);
539 void MainArea::focusOutEvent(QFocusEvent*)
541 if (!m_paused) {
542 togglePause();
546 void MainArea::drawBackground(QPainter* painter, const QRectF& rect)
548 painter->drawPixmap(rect, m_background, rect);