1 /******************************************************************************
2 * KBlocks, a falling blocks game by KDE *
3 * Copyright (C) 2010-2021 Mauricio Piacentini <mauricio@tabuleiro.com> *
4 * Zhongjie Cai <squall.leonhart.cai@gmail.com> *
5 * Julian Helfferich <julian.helfferich@mailbox.org> *
6 * *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
11 ******************************************************************************/
12 #include "KBlocksScene.h"
13
14 #include "settings.h"
15
16 #include <QVarLengthArray>
17 #include <KLocalizedString>
18
19 #include "GraphicsInterface.h"
20 #include "SoundInterface.h"
21
KBlocksScene(GameLogicInterface * p,GraphicsInterface * graphics,SoundInterface * sound,int capacity)22 KBlocksScene::KBlocksScene(
23 GameLogicInterface *p,
24 GraphicsInterface *graphics,
25 SoundInterface *sound,
26 int capacity
27 )
28 {
29 mpGameLogic = p;
30 mGameStarted = false;
31
32 mSnapshotMode = false;
33 mTopGameLevel = 0;
34 mGroupCount = 0;
35 mMaxCapacity = capacity;
36 mSceneGamesPerLine = 4;
37 mGameAnimEnabled = true;
38 mWaitForAllUpdate = false;
39
40 maGroupList = new KBlocksItemGroup*[capacity]();
41 maGameScoreList = new KBlocksScore*[capacity]();
42 maGameReadySignal = new bool[capacity]();
43
44 mpGrafx = graphics;
45 mpSnd = sound;
46
47 int width = (capacity >= mSceneGamesPerLine) ? mSceneGamesPerLine : (capacity % mSceneGamesPerLine);
48 int height = (int)(capacity / (mSceneGamesPerLine + 1)) + 1;
49 setSceneRect(0, 0, mpGrafx->m_View_Size_Width * width,
50 mpGrafx->m_View_Size_Height * height);
51
52 setItemIndexMethod(NoIndex);
53
54 mUpdateInterval = 50;
55 mUpdateTimer.setInterval(mUpdateInterval);
56 connect(&mUpdateTimer, &QTimer::timeout, this, &KBlocksScene::updateGame);
57 mUpdateTimer.stop();
58
59 mMessageBox = nullptr;
60 }
61
~KBlocksScene()62 KBlocksScene::~KBlocksScene()
63 {
64 deleteGameItemGroups();
65 delete [] maGameReadySignal;
66 delete [] maGameScoreList;
67 delete [] maGroupList;
68 }
69
getItemGroup(int index)70 KBlocksItemGroup *KBlocksScene::getItemGroup(int index)
71 {
72 return maGroupList[index];
73 }
74
getScoreHandler(int index)75 KBlocksScore *KBlocksScene::getScoreHandler(int index)
76 {
77 return maGameScoreList[index];
78 }
79
createGameItemGroups(int groupCount,bool snapshotMode)80 void KBlocksScene::createGameItemGroups(int groupCount, bool snapshotMode)
81 {
82 if (groupCount > mMaxCapacity) {
83 mGroupCount = mMaxCapacity;
84 }
85 mGroupCount = groupCount;
86 mSnapshotMode = snapshotMode;
87 mTopGameLevel = 0;
88
89 for (int i = 0; i < mGroupCount; i++) {
90 maGroupList[i] = new KBlocksItemGroup(i, mpGameLogic->getSingleGame(i), mpGrafx, mpSnd, snapshotMode);
91 maGroupList[i]->setUpdateInterval(mUpdateInterval);
92 maGroupList[i]->setGameAnimEnabled(mGameAnimEnabled);
93 maGroupList[i]->setWaitForAllUpdate(mWaitForAllUpdate);
94 addItem(maGroupList[i]);
95
96 maGameScoreList[i] = new KBlocksScore();
97 maGameScoreList[i]->setLevelUpFactor(KBlocksScore_Level_x_Level_x_Factor, 1000);
98 maGameScoreList[i]->setScoreUpFactor(10);
99
100 maGameReadySignal[i] = false;
101 connect(maGroupList[i], &KBlocksItemGroup::readyForAction, this, &KBlocksScene::readyForAction);
102 }
103
104 updateDimensions();
105
106 //Our Message Item, hidden by default
107 mMessageBox = new KGamePopupItem();
108 mMessageBox->setMessageOpacity(0.9);
109 addItem(mMessageBox);
110 }
111
deleteGameItemGroups()112 void KBlocksScene::deleteGameItemGroups()
113 {
114 if (mMessageBox) {
115 removeItem(mMessageBox);
116 delete mMessageBox;
117 mMessageBox = nullptr;
118 }
119
120 for (int i = 0; i < mGroupCount; i++) {
121 disconnect(maGroupList[i], &KBlocksItemGroup::readyForAction, this, &KBlocksScene::readyForAction);
122
123 delete maGameScoreList[i];
124 maGameScoreList[i] = nullptr;
125
126 removeItem(maGroupList[i]);
127 delete maGroupList[i];
128 maGroupList[i] = nullptr;
129 }
130 mGroupCount = 0;
131 }
132
setGamesPerLine(int count)133 void KBlocksScene::setGamesPerLine(int count)
134 {
135 mSceneGamesPerLine = count;
136 }
137
setGameAnimEnabled(bool flag)138 void KBlocksScene::setGameAnimEnabled(bool flag)
139 {
140 mGameAnimEnabled = flag;
141 for (int i = 0; i < mGroupCount; i++) {
142 maGroupList[i]->setGameAnimEnabled(flag);
143 }
144 }
145
setWaitForAllUpdate(bool flag)146 void KBlocksScene::setWaitForAllUpdate(bool flag)
147 {
148 mWaitForAllUpdate = flag;
149 for (int i = 0; i < mGroupCount; i++) {
150 maGroupList[i]->setWaitForAllUpdate(flag);
151 }
152 }
153
setUpdateInterval(int interval)154 void KBlocksScene::setUpdateInterval(int interval)
155 {
156 mUpdateInterval = interval;
157 mUpdateTimer.setInterval(mUpdateInterval);
158 for (int i = 0; i < mGroupCount; i++) {
159 maGroupList[i]->setUpdateInterval(mUpdateInterval);
160 }
161 }
162
setSoundsEnabled(bool enabled)163 void KBlocksScene::setSoundsEnabled(bool enabled)
164 {
165 mpSnd->setSoundsEnabled(enabled);
166 }
167
readSettings(const QSize & viewSize)168 void KBlocksScene::readSettings(const QSize &viewSize)
169 {
170 if (mpGrafx->theme()->fileName() != Settings::theme()) {
171 mpGrafx->loadTheme(Settings::theme());
172 mpGrafx->adjustForSize(viewSize);
173 updateDimensions();
174 // also update sounds fo rthe theme
175 mpSnd->loadTheme(Settings::theme());
176 }
177 }
178
startGame()179 void KBlocksScene::startGame()
180 {
181 if (mGameStarted) {
182 return;
183 }
184 mGameStarted = true;
185
186 mTopGameLevel = 0;
187 for (int i = 0; i < mGroupCount; i++) {
188 maGroupList[i]->startGame();
189 }
190
191 if (!mSnapshotMode) {
192 mUpdateTimer.start();
193 QTimer::singleShot(500, this, &KBlocksScene::greetPlayer);
194 }
195 }
196
stopGame()197 void KBlocksScene::stopGame()
198 {
199 if (!mGameStarted) {
200 return;
201 }
202 mGameStarted = false;
203
204 for (int i = 0; i < mGroupCount; i++) {
205 maGroupList[i]->stopGame();
206 }
207
208 mUpdateTimer.stop();
209 }
210
pauseGame(bool flag,bool fromUI)211 void KBlocksScene::pauseGame(bool flag, bool fromUI)
212 {
213 if (!mGameStarted) {
214 return;
215 }
216
217 QString resuming(i18n("Game Resumed!"));
218 QString pausing(i18n("Game Paused!"));
219
220 for (int i = 0; i < mGroupCount; i++) {
221 maGroupList[i]->pauseGame(flag);
222 }
223
224 if (!mSnapshotMode) {
225 if (flag) {
226 mUpdateTimer.stop();
227 if (!fromUI) {
228 showMessage(pausing, 2000);
229 }
230 } else {
231 mUpdateTimer.start();
232 if (!fromUI) {
233 showMessage(resuming, 2000);
234 }
235 }
236 }
237 }
238
addScore(int gameIndex,int lineCount)239 void KBlocksScene::addScore(int gameIndex, int lineCount)
240 {
241 if (!mSnapshotMode) {
242 return;
243 }
244 maGameScoreList[gameIndex]->addScore(lineCount);
245 Q_EMIT scoreChanged(gameIndex, maGameScoreList[gameIndex]->getScorePoint(),
246 maGameScoreList[gameIndex]->getLineCount(),
247 maGameScoreList[gameIndex]->getGameLevel());
248 }
249
updateDimensions()250 void KBlocksScene::updateDimensions()
251 {
252 // TODO : Reset item position and scale
253 int width = (mGroupCount >= mSceneGamesPerLine) ? mSceneGamesPerLine : (mGroupCount % mSceneGamesPerLine);
254 int height = (int)(mGroupCount / (mSceneGamesPerLine + 1)) + 1;
255
256 setSceneRect(0, 0, mpGrafx->m_View_Size_Width * width,
257 mpGrafx->m_View_Size_Height * height);
258
259 for (int i = 0; i < mGroupCount; i++) {
260 int left = mpGrafx->m_View_Size_Width * (i % mSceneGamesPerLine);
261 int top = mpGrafx->m_View_Size_Height * ((int)(i / mSceneGamesPerLine));
262
263 maGroupList[i]->setPos(left, top);
264 maGroupList[i]->refreshPosition();
265 }
266 }
267
greetPlayer()268 void KBlocksScene::greetPlayer()
269 {
270 QString greets(i18n("Game Start!"));
271 showMessage(greets, 2000);
272 }
273
gameOverPlayer()274 void KBlocksScene::gameOverPlayer()
275 {
276 QString greets(i18n("Game Over!"));
277 showMessage(greets, 2000);
278 }
279
gameOverMultiWin()280 void KBlocksScene::gameOverMultiWin()
281 {
282 QString gameOver(i18n("You Win!"));
283 showMessage(gameOver, 2000);
284 }
285
gameOverMultiLose()286 void KBlocksScene::gameOverMultiLose()
287 {
288 QString gameOver(i18n("You Lose!"));
289 showMessage(gameOver, 2000);
290 }
291
showMessage(const QString & message,int ms)292 void KBlocksScene::showMessage(const QString &message, int ms)
293 {
294 mMessageBox->setMessageTimeout(ms);
295 mMessageBox->showMessage(message, KGamePopupItem::TopLeft);
296 }
297
updateGame()298 void KBlocksScene::updateGame()
299 {
300 if (mSnapshotMode) {
301 return;
302 }
303
304 QVarLengthArray<int, 16> removedLines(mGroupCount);
305 int gameCount = mpGameLogic->updateGame(removedLines.data());
306
307 for (int i = 0; i < mGroupCount; i++) {
308 if (removedLines[i] > 0) {
309 if (maGameScoreList[i]->addScore(removedLines[i])) {
310 int tmpLevel = maGameScoreList[i]->getGameLevel();
311 if (mTopGameLevel < tmpLevel) {
312 mpGameLogic->levelUpGame(tmpLevel - mTopGameLevel);
313 mTopGameLevel = tmpLevel;
314 }
315 }
316 Q_EMIT scoreChanged(i, maGameScoreList[i]->getScorePoint(),
317 maGameScoreList[i]->getLineCount(),
318 maGameScoreList[i]->getGameLevel());
319 // Play sound only for human player
320 if (i == 0) {
321 mpSnd->playSound(Sound::BlockRemove);
322 }
323 } else if (removedLines[i] == -1) {
324 maGroupList[i]->stopGame();
325 if (mGroupCount == 1) {
326 QTimer::singleShot(500, this, &KBlocksScene::gameOverPlayer);
327 Q_EMIT isHighscore(0, maGameScoreList[0]->getScorePoint(),
328 maGameScoreList[0]->getGameLevel());
329 } else {
330 if (i == 0) {
331 for (int j = 0; j < mGroupCount; j++) {
332 maGroupList[j]->stopGame();
333 }
334 QTimer::singleShot(500, this, &KBlocksScene::gameOverMultiLose);
335 Q_EMIT isHighscore(0, maGameScoreList[0]->getScorePoint(),
336 maGameScoreList[0]->getGameLevel());
337 } else if (gameCount <= 1) {
338 maGroupList[0]->stopGame();
339 QTimer::singleShot(500, this, &KBlocksScene::gameOverMultiWin);
340 Q_EMIT isHighscore(0, maGameScoreList[0]->getScorePoint(),
341 maGameScoreList[0]->getGameLevel());
342 }
343 }
344 }
345 }
346 }
347
readyForAction(int groupID)348 void KBlocksScene::readyForAction(int groupID)
349 {
350 maGameReadySignal[groupID] = true;
351 bool allReady = true;
352 for (int i = 0; i < mGroupCount; i++) {
353 if (!maGameReadySignal[i]) {
354 allReady = false;
355 }
356 }
357 if (allReady) {
358 for (int i = 0; i < mGroupCount; i++) {
359 if (mpGameLogic->getSingleGame(i)->isGameRunning()) {
360 maGameReadySignal[i] = false;
361 }
362 }
363 mpGameLogic->continueGame();
364 }
365 }
366
playMoveSound()367 void KBlocksScene::playMoveSound()
368 {
369 mpSnd->playSound(Sound::BlockMove);
370 }
371
playDropSound()372 void KBlocksScene::playDropSound()
373 {
374 mpSnd->playSound(Sound::BlockFall);
375 }
376
drawBackground(QPainter * painter,const QRectF & rect)377 void KBlocksScene::drawBackground(QPainter *painter, const QRectF &rect)
378 {
379 if (mpGrafx->renderer()->isValid()) {
380 mpGrafx->renderer()->render(painter, QStringLiteral("BACKGROUND"), rect);
381 }
382 }
383
384