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