File indexing completed on 2024-04-21 04:02:08

0001 /******************************************************************************
0002 *   KBlocks, a falling blocks game by KDE                                     *
0003 *   SPDX-FileCopyrightText: 2010-2021 Mauricio Piacentini <mauricio@tabuleiro.com>      *
0004 *                           Zhongjie Cai <squall.leonhart.cai@gmail.com>      *
0005 *                           Julian Helfferich <julian.helfferich@mailbox.org> *
0006 *                                                                             *
0007 *   SPDX-License-Identifier: GPL-2.0-or-later
0008 ******************************************************************************/
0009 #include "KBlocksScene.h"
0010 
0011 #include "settings.h"
0012 
0013 #include <QVarLengthArray>
0014 #include <KLocalizedString>
0015 
0016 #include "GraphicsInterface.h"
0017 #include "SoundInterface.h"
0018 
0019 KBlocksScene::KBlocksScene(
0020     GameLogicInterface *p,
0021     GraphicsInterface *graphics,
0022     SoundInterface *sound,
0023     int capacity
0024 )
0025 {
0026     mpGameLogic = p;
0027     mGameStarted = false;
0028 
0029     mSnapshotMode = false;
0030     mTopGameLevel = 0;
0031     mGroupCount = 0;
0032     mMaxCapacity = capacity;
0033     mSceneGamesPerLine = 4;
0034     mGameAnimEnabled = true;
0035     mWaitForAllUpdate = false;
0036 
0037     maGroupList = new KBlocksItemGroup*[capacity]();
0038     maGameScoreList = new KBlocksScore*[capacity]();
0039     maGameReadySignal = new bool[capacity]();
0040 
0041     mpGrafx = graphics;
0042     mpSnd = sound;
0043 
0044     int width = (capacity >= mSceneGamesPerLine) ? mSceneGamesPerLine : (capacity % mSceneGamesPerLine);
0045     int height = (int)(capacity / (mSceneGamesPerLine + 1)) + 1;
0046     setSceneRect(0, 0, mpGrafx->m_View_Size_Width * width,
0047                  mpGrafx->m_View_Size_Height * height);
0048 
0049     setItemIndexMethod(NoIndex);
0050 
0051     mUpdateInterval = 50;
0052     mUpdateTimer.setInterval(mUpdateInterval);
0053     connect(&mUpdateTimer, &QTimer::timeout, this, &KBlocksScene::updateGame);
0054     mUpdateTimer.stop();
0055 
0056     mMessageBox = nullptr;
0057 }
0058 
0059 KBlocksScene::~KBlocksScene()
0060 {
0061     deleteGameItemGroups();
0062     delete [] maGameReadySignal;
0063     delete [] maGameScoreList;
0064     delete [] maGroupList;
0065 }
0066 
0067 KBlocksItemGroup *KBlocksScene::getItemGroup(int index)
0068 {
0069     return maGroupList[index];
0070 }
0071 
0072 KBlocksScore *KBlocksScene::getScoreHandler(int index)
0073 {
0074     return maGameScoreList[index];
0075 }
0076 
0077 void KBlocksScene::createGameItemGroups(int groupCount, bool snapshotMode)
0078 {
0079     if (groupCount > mMaxCapacity) {
0080         mGroupCount = mMaxCapacity;
0081     }
0082     mGroupCount = groupCount;
0083     mSnapshotMode = snapshotMode;
0084     mTopGameLevel = 0;
0085 
0086     for (int i = 0; i < mGroupCount; i++) {
0087         maGroupList[i] = new KBlocksItemGroup(i, mpGameLogic->getSingleGame(i), mpGrafx, mpSnd, snapshotMode);
0088         maGroupList[i]->setUpdateInterval(mUpdateInterval);
0089         maGroupList[i]->setGameAnimEnabled(mGameAnimEnabled);
0090         maGroupList[i]->setWaitForAllUpdate(mWaitForAllUpdate);
0091         addItem(maGroupList[i]);
0092 
0093         maGameScoreList[i] = new KBlocksScore();
0094         maGameScoreList[i]->setLevelUpFactor(KBlocksScore_Level_x_Level_x_Factor, 1000);
0095         maGameScoreList[i]->setScoreUpFactor(10);
0096 
0097         maGameReadySignal[i] = false;
0098         connect(maGroupList[i], &KBlocksItemGroup::readyForAction, this, &KBlocksScene::readyForAction);
0099     }
0100 
0101     updateDimensions();
0102 
0103     //Our Message Item, hidden by default
0104     mMessageBox = new KGamePopupItem();
0105     mMessageBox->setMessageOpacity(0.9);
0106     addItem(mMessageBox);
0107 }
0108 
0109 void KBlocksScene::deleteGameItemGroups()
0110 {
0111     if (mMessageBox) {
0112         removeItem(mMessageBox);
0113         delete mMessageBox;
0114         mMessageBox = nullptr;
0115     }
0116 
0117     for (int i = 0; i < mGroupCount; i++) {
0118         disconnect(maGroupList[i], &KBlocksItemGroup::readyForAction, this, &KBlocksScene::readyForAction);
0119 
0120         delete maGameScoreList[i];
0121         maGameScoreList[i] = nullptr;
0122 
0123         removeItem(maGroupList[i]);
0124         delete maGroupList[i];
0125         maGroupList[i] = nullptr;
0126     }
0127     mGroupCount = 0;
0128 }
0129 
0130 void KBlocksScene::setGamesPerLine(int count)
0131 {
0132     mSceneGamesPerLine = count;
0133 }
0134 
0135 void KBlocksScene::setGameAnimEnabled(bool flag)
0136 {
0137     mGameAnimEnabled = flag;
0138     for (int i = 0; i < mGroupCount; i++) {
0139         maGroupList[i]->setGameAnimEnabled(flag);
0140     }
0141 }
0142 
0143 void KBlocksScene::setWaitForAllUpdate(bool flag)
0144 {
0145     mWaitForAllUpdate = flag;
0146     for (int i = 0; i < mGroupCount; i++) {
0147         maGroupList[i]->setWaitForAllUpdate(flag);
0148     }
0149 }
0150 
0151 void KBlocksScene::setUpdateInterval(int interval)
0152 {
0153     mUpdateInterval = interval;
0154     mUpdateTimer.setInterval(mUpdateInterval);
0155     for (int i = 0; i < mGroupCount; i++) {
0156         maGroupList[i]->setUpdateInterval(mUpdateInterval);
0157     }
0158 }
0159 
0160 void KBlocksScene::setSoundsEnabled(bool enabled)
0161 {
0162     mpSnd->setSoundsEnabled(enabled);
0163 }
0164 
0165 void KBlocksScene::loadTheme(const KGameTheme *theme)
0166 {
0167     mpGrafx->loadTheme(theme);
0168     mpSnd->loadTheme(theme);
0169     // update layout to new theme data
0170     updateDimensions();
0171 }
0172 
0173 void KBlocksScene::readSettings()
0174 {
0175     // nothing to do currently, no other settings beside the theme, which is handled separately
0176 }
0177 
0178 void KBlocksScene::startGame()
0179 {
0180     if (mGameStarted) {
0181         return;
0182     }
0183     mGameStarted = true;
0184 
0185     mTopGameLevel = 0;
0186     for (int i = 0; i < mGroupCount; i++) {
0187         maGroupList[i]->startGame();
0188     }
0189 
0190     if (!mSnapshotMode) {
0191         mUpdateTimer.start();
0192         QTimer::singleShot(500, this, &KBlocksScene::greetPlayer);
0193     }
0194 }
0195 
0196 void KBlocksScene::stopGame()
0197 {
0198     if (!mGameStarted) {
0199         return;
0200     }
0201     mGameStarted = false;
0202 
0203     for (int i = 0; i < mGroupCount; i++) {
0204         maGroupList[i]->stopGame();
0205     }
0206 
0207     mUpdateTimer.stop();
0208 }
0209 
0210 void KBlocksScene::pauseGame(bool flag, bool fromUI)
0211 {
0212     if (!mGameStarted) {
0213         return;
0214     }
0215 
0216     QString resuming(i18n("Game Resumed!"));
0217     QString pausing(i18n("Game Paused!"));
0218 
0219     for (int i = 0; i < mGroupCount; i++) {
0220         maGroupList[i]->pauseGame(flag);
0221     }
0222 
0223     if (!mSnapshotMode) {
0224         if (flag) {
0225             mUpdateTimer.stop();
0226             if (!fromUI) {
0227                 showMessage(pausing, 2000);
0228             }
0229         } else {
0230             mUpdateTimer.start();
0231             if (!fromUI) {
0232                 showMessage(resuming, 2000);
0233             }
0234         }
0235     }
0236 }
0237 
0238 void KBlocksScene::addScore(int gameIndex, int lineCount)
0239 {
0240     if (!mSnapshotMode) {
0241         return;
0242     }
0243     maGameScoreList[gameIndex]->addScore(lineCount);
0244     Q_EMIT scoreChanged(gameIndex, maGameScoreList[gameIndex]->getScorePoint(),
0245                       maGameScoreList[gameIndex]->getLineCount(),
0246                       maGameScoreList[gameIndex]->getGameLevel());
0247 }
0248 
0249 void KBlocksScene::updateDimensions()
0250 {
0251     // TODO : Reset item position and scale
0252     int width = (mGroupCount >= mSceneGamesPerLine) ? mSceneGamesPerLine : (mGroupCount % mSceneGamesPerLine);
0253     int height = (int)(mGroupCount / (mSceneGamesPerLine + 1)) + 1;
0254 
0255     setSceneRect(0, 0, mpGrafx->m_View_Size_Width * width,
0256                  mpGrafx->m_View_Size_Height * height);
0257 
0258     for (int i = 0; i < mGroupCount; i++) {
0259         int left = mpGrafx->m_View_Size_Width * (i % mSceneGamesPerLine);
0260         int top = mpGrafx->m_View_Size_Height * ((int)(i / mSceneGamesPerLine));
0261 
0262         maGroupList[i]->setPos(left, top);
0263         maGroupList[i]->refreshPosition();
0264     }
0265 
0266     mBackgroundSize = mpGrafx->renderer()->boundsOnElement(QStringLiteral("BACKGROUND")).size();
0267 }
0268 
0269 void KBlocksScene::greetPlayer()
0270 {
0271     QString greets(i18n("Game Start!"));
0272     showMessage(greets, 2000);
0273 }
0274 
0275 void KBlocksScene::gameOverPlayer()
0276 {
0277     QString greets(i18n("Game Over!"));
0278     showMessage(greets, 2000);
0279 }
0280 
0281 void KBlocksScene::gameOverMultiWin()
0282 {
0283     QString gameOver(i18n("You Win!"));
0284     showMessage(gameOver, 2000);
0285 }
0286 
0287 void KBlocksScene::gameOverMultiLose()
0288 {
0289     QString gameOver(i18n("You Lose!"));
0290     showMessage(gameOver, 2000);
0291 }
0292 
0293 void KBlocksScene::showMessage(const QString &message, int ms)
0294 {
0295     mMessageBox->setMessageTimeout(ms);
0296     mMessageBox->showMessage(message, KGamePopupItem::TopLeft);
0297 }
0298 
0299 void KBlocksScene::updateGame()
0300 {
0301     if (mSnapshotMode) {
0302         return;
0303     }
0304 
0305     QVarLengthArray<int, 16> removedLines(mGroupCount);
0306     int gameCount = mpGameLogic->updateGame(removedLines.data());
0307 
0308     for (int i = 0; i < mGroupCount; i++) {
0309         if (removedLines[i] > 0) {
0310             if (maGameScoreList[i]->addScore(removedLines[i])) {
0311                 int tmpLevel = maGameScoreList[i]->getGameLevel();
0312                 if (mTopGameLevel < tmpLevel) {
0313                     mpGameLogic->levelUpGame(tmpLevel - mTopGameLevel);
0314                     mTopGameLevel = tmpLevel;
0315                 }
0316             }
0317             Q_EMIT scoreChanged(i, maGameScoreList[i]->getScorePoint(),
0318                               maGameScoreList[i]->getLineCount(),
0319                               maGameScoreList[i]->getGameLevel());
0320             // Play sound only for human player
0321             if (i == 0) {
0322                 mpSnd->playSound(Sound::BlockRemove);
0323             }
0324         } else if (removedLines[i] == -1) {
0325             maGroupList[i]->stopGame();
0326             if (mGroupCount == 1) {
0327                 QTimer::singleShot(500, this, &KBlocksScene::gameOverPlayer);
0328                 Q_EMIT isHighscore(0, maGameScoreList[0]->getScorePoint(),
0329                                  maGameScoreList[0]->getGameLevel());
0330             } else {
0331                 if (i == 0) {
0332                     for (int j = 0; j < mGroupCount; j++) {
0333                         maGroupList[j]->stopGame();
0334                     }
0335                     QTimer::singleShot(500, this, &KBlocksScene::gameOverMultiLose);
0336                     Q_EMIT isHighscore(0, maGameScoreList[0]->getScorePoint(),
0337                                      maGameScoreList[0]->getGameLevel());
0338                 } else if (gameCount <= 1) {
0339                     maGroupList[0]->stopGame();
0340                     QTimer::singleShot(500, this, &KBlocksScene::gameOverMultiWin);
0341                     Q_EMIT isHighscore(0, maGameScoreList[0]->getScorePoint(),
0342                                      maGameScoreList[0]->getGameLevel());
0343                 }
0344             }
0345         }
0346     }
0347 }
0348 
0349 void KBlocksScene::readyForAction(int groupID)
0350 {
0351     maGameReadySignal[groupID] = true;
0352     bool allReady = true;
0353     for (int i = 0; i < mGroupCount; i++) {
0354         if (!maGameReadySignal[i]) {
0355             allReady = false;
0356         }
0357     }
0358     if (allReady) {
0359         for (int i = 0; i < mGroupCount; i++) {
0360             if (mpGameLogic->getSingleGame(i)->isGameRunning()) {
0361                 maGameReadySignal[i] = false;
0362             }
0363         }
0364         mpGameLogic->continueGame();
0365     }
0366 }
0367 
0368 void KBlocksScene::playMoveSound()
0369 {
0370     mpSnd->playSound(Sound::BlockMove);
0371 }
0372 
0373 void KBlocksScene::playDropSound()
0374 {
0375     mpSnd->playSound(Sound::BlockFall);
0376 }
0377 
0378 void KBlocksScene::drawBackground(QPainter *painter, const QRectF &rect)
0379 {
0380     if (mpGrafx->renderer()->isValid()) {
0381         // QtSvgRenderer only supports KeepAspectRatio, so we have to adjust the
0382         // bounds instead.
0383         const QSizeF newSize = mBackgroundSize.scaled(rect.size(), Qt::KeepAspectRatioByExpanding);
0384 
0385         QRectF adjustedRect = rect;
0386 
0387         switch(mpGrafx->m_BackgroundLocation) {
0388             case BackgroundLocation::Stretch:
0389                 break;
0390             case BackgroundLocation::TopLeft:
0391                 adjustedRect.setSize(newSize);
0392                 break;
0393             case BackgroundLocation::Center:
0394                 adjustedRect.setSize(newSize);
0395                 adjustedRect.moveCenter(rect.center());
0396                 break;
0397         }
0398 
0399         mpGrafx->renderer()->render(painter, QStringLiteral("BACKGROUND"), adjustedRect);
0400     }
0401 }
0402 
0403 #include "moc_KBlocksScene.cpp"