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"