1 /*
2     SPDX-FileCopyrightText: 2007-2008 John-Paul Stanford <jp@stanwood.org.uk>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 // own
8 #include "board.h"
9 
10 // Qt
11 #include <QGraphicsView>
12 #include <QStandardPaths>
13 #include <QTimer>
14 #include <QRandomGenerator>
15 
16 
17 // Bomber
18 #include "bomb.h"
19 #include "building.h"
20 #include "plane.h"
21 #include "settings.h"
22 
23 
24 /** The value that the plane velocity increases by */
25 const qreal PLANE_INC_VELOCITY = 0.0005;
26 /** The value of this controls the speed of the game */
27 const unsigned int GAME_DELAY = 15;
28 /** The number of tiles vertical in the playing area */
29 const unsigned int TILE_NUM_H = 20;
30 /** The number of builds to display */
31 const unsigned int NUMBER_BUILDINGS = 10;
32 /** The number of tiles horizontally in the playing area */
33 const unsigned int TILE_NUM_W = ((NUMBER_BUILDINGS) + 2);
34 /** The maximum level number before the game stops getting harder */
35 const unsigned int MAX_LEVEL = 11;
36 
37 /** This time in milliseconds that the plane exploding animation is played for */
38 const unsigned int PLANE_EXPLODE_TIME = 2000;
39 
40 /** This time in milliseconds that the bomb exploding animation is played for */
41 const unsigned int BOMB_EXPLODE_TIME = 1000;
42 
BomberBoard(KGameRenderer * renderer,QGraphicsView * view,QObject * parent)43 BomberBoard::BomberBoard(KGameRenderer * renderer, QGraphicsView * view, QObject * parent)
44     : QGraphicsScene(parent)
45     , m_renderer(renderer)
46     , m_bomb(nullptr)
47     , m_view(view)
48 {
49     m_clock = new QTimer(this);
50     m_clock->setInterval(GAME_DELAY);
51     connect(m_clock, &QTimer::timeout, this, &BomberBoard::tick);
52     m_plane = new Plane(m_renderer, this);
53     m_plane->resize(m_tileSize);
54     this->addItem(m_plane);
55     m_plane->show();
56     resetPlane();
57     clear();
58 }
59 
~BomberBoard()60 BomberBoard::~BomberBoard()
61 {
62     delete m_bomb;
63     delete m_plane;
64     qDeleteAll(m_buildings);
65     qDeleteAll(m_explodingBombs);
66 }
67 
resetPlane()68 void BomberBoard::resetPlane()
69 {
70     m_plane->setState(Explodable::State::Moving);
71     m_plane->resetPosition();
72 }
73 
resize(QSize & size)74 void BomberBoard::resize(QSize & size)
75 {
76     setBackgroundBrush(m_renderer->spritePixmap(QStringLiteral("background"), size));
77 
78     unsigned int minTileSizeWidth = size.width() / TILE_NUM_W;
79     unsigned int minTileSizeHeight = size.height() / TILE_NUM_H;
80 
81     m_tileSize = QSize(minTileSizeWidth, minTileSizeHeight);
82 
83     for (Building * building : std::as_const(m_buildings)) {
84         building->resize(m_tileSize);
85     }
86 
87     m_plane->resize(m_tileSize);
88     if (m_bomb != nullptr) {
89         m_bomb->resize(m_tileSize);
90     }
91 
92     redraw();
93 
94     size.setWidth(minTileSizeWidth * TILE_NUM_W);
95     size.setHeight(minTileSizeHeight * TILE_NUM_H);
96 }
97 
redraw()98 void BomberBoard::redraw()
99 {
100     m_plane->resetPixmaps();
101     if (m_bomb != nullptr) {
102         m_bomb->resetPixmaps();
103     }
104 }
105 
newLevel(unsigned int level)106 void BomberBoard::newLevel(unsigned int level)
107 {
108     if (level > MAX_LEVEL) {
109         level = MAX_LEVEL;
110     }
111 
112     if (level == 1) {
113         m_plane->setVelocity(Plane::DEFAULT_VELOCITY);
114     } else if (level % 2 == 0) {
115         m_plane->setVelocity(m_plane->velocity() + PLANE_INC_VELOCITY);
116     }
117 
118     m_clock->stop();
119     clear();
120     m_plane->setState(Explodable::State::Moving);
121     m_buildingBlocks = 0;
122     //Create the buildings
123     for (unsigned int i = 0; i < NUMBER_BUILDINGS; ++i) {
124         unsigned int min = level;
125         if (min < 3) {
126             min = 3;
127         }
128         unsigned int max = level + 3;
129         if (max < 5) {
130             max = 5;
131         }
132         unsigned int height = QRandomGenerator::global()->bounded(max - min) + min;
133 
134         m_buildingBlocks += height;
135         auto building = new Building(m_renderer, this, i + 1, height);
136 
137         building->resize(m_tileSize);
138         building->show();
139 
140         m_buildings.append(building);
141     }
142 }
143 
setPaused(bool val)144 void BomberBoard::setPaused(bool val)
145 {
146     if (val) {
147         m_clock->stop();
148     } else {
149         m_clock->start();
150     }
151 }
152 
tick()153 void BomberBoard::tick()
154 {
155     checkCollisions();
156 
157     m_plane->advanceItem();
158 
159     if (m_bomb != nullptr) {
160         m_bomb->advanceItem();
161     }
162 
163     for (Bomb * bomb : std::as_const(m_explodingBombs)) {
164         bomb->advanceItem();
165     }
166 
167     // Draw everything
168     m_plane->update();
169 
170     if (m_bomb != nullptr) {
171         m_bomb->update();
172     }
173 
174     for (Bomb * bomb : std::as_const(m_explodingBombs)) {
175         bomb->update();
176     }
177 }
178 
dropBomb()179 void BomberBoard::dropBomb()
180 {
181     if (m_bomb == nullptr && m_plane->state() == Explodable::State::Moving) {
182         QPointF planePos = m_plane->position();
183         m_bomb = new Bomb(m_renderer, this, planePos.x(), planePos.y() + 1, m_tileSize);
184         this->addItem(m_bomb);
185         m_bomb->show();
186     }
187 }
188 
checkCollisions()189 void BomberBoard::checkCollisions()
190 {
191     const auto currentBuildings = m_buildings;
192     for (Building * building : currentBuildings) {
193         if (m_plane->nextBoundingRect().intersects(building->boundingRect()) && m_plane->state() == Explodable::State::Moving) {
194             // Plane crashed into the building
195             building->destoryTop();
196             --m_buildingBlocks;
197             crashed();
198         }
199 
200         if (m_bomb != nullptr) {
201             if (m_bomb->nextBoundingRect().intersects(building->boundingRect()) && m_bomb->state() == Explodable::State::Moving) {
202                 // Bomb hit a building
203                 building->destoryTop();
204                 --m_buildingBlocks;
205                 Q_EMIT onBombHit();
206                 bombHit(m_bomb, building->position().x(), Building::BUILD_BASE_LOCATION - (building->height()));
207                 m_bomb = nullptr;
208             } else if (m_bomb->position().y() >= Building::BUILD_BASE_LOCATION + 1) {
209                 // Bomb hit the ground
210                 bombHit(m_bomb, (unsigned int)m_bomb->position().x(), Building::BUILD_BASE_LOCATION);
211                 m_bomb = nullptr;
212             }
213         }
214 
215         if (m_plane->state() == Explodable::State::Moving && m_buildingBlocks == 0) {
216             Q_EMIT levelCleared();
217         }
218     }
219 }
220 
bombHit(Bomb * bomb,qreal moveBombToX,qreal moveBombToY)221 void BomberBoard::bombHit(Bomb * bomb, qreal moveBombToX, qreal moveBombToY)
222 {
223     bomb->setPosition(moveBombToX, moveBombToY);
224     bomb->setState(Bomb::State::Exploding);
225     m_explodingBombs.enqueue(bomb);
226     QTimer::singleShot(BOMB_EXPLODE_TIME, this, &BomberBoard::bombExploded);
227     Q_EMIT playBombSound();
228 }
229 
bombExploded()230 void BomberBoard::bombExploded()
231 {
232     Bomb * bomb = m_explodingBombs.dequeue();
233     bomb->hide();
234     delete bomb;
235 }
236 
settingsChanged()237 void BomberBoard::settingsChanged()
238 {
239     setBackgroundBrush(m_renderer->spritePixmap(QStringLiteral("background"), m_view->size()));
240     redraw();
241 }
242 
planeExploded()243 void BomberBoard::planeExploded()
244 {
245     m_plane->setState(Plane::State::Exploded);
246     Q_EMIT onPlaneCrash();
247 }
248 
crashed()249 void BomberBoard::crashed()
250 {
251     QPointF pos = m_plane->position();
252     m_plane->setPosition(pos.x() + 1, pos.y());
253     m_plane->setState(Plane::State::Exploding);
254     QTimer::singleShot(PLANE_EXPLODE_TIME, this, &BomberBoard::planeExploded);
255     Q_EMIT playCrashSound();
256 }
257 
clear()258 void BomberBoard::clear()
259 {
260     qDeleteAll(m_buildings);
261     m_buildings.clear();
262 
263     delete m_bomb;
264     m_bomb = nullptr;
265 
266     resetPlane();
267 }
268 
mapPosition(const QPointF & pos) const269 QPoint BomberBoard::mapPosition(const QPointF & pos) const
270 {
271     return QPoint(static_cast<unsigned int>(m_tileSize.width() * pos.x()), static_cast<int>(m_tileSize.height() * pos.y()));
272 }
273 
unmapPosition(const QPoint & pos) const274 QPointF BomberBoard::unmapPosition(const QPoint & pos) const
275 {
276     return QPointF(1.0 * pos.x() / m_tileSize.width(), 1.0 * pos.y() / m_tileSize.height());
277 }
278