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