File indexing completed on 2024-05-12 07:58:33

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 "GraphicsInterface.h"
0010 #include "SoundInterface.h"
0011 
0012 #include "KBlocksItemGroup.h"
0013 #include "KBlocksSvgItem.h"
0014 #include "SvgItemInterface.h"
0015 
0016 KBlocksItemGroup::KBlocksItemGroup(int groupID, SingleGameInterface *p, GraphicsInterface *pG, SoundInterface *pS, bool snapshotMode)
0017 {
0018     mGroupID = groupID;
0019     mpSingleGame = p;
0020     mpGrafx = pG;
0021     mpSnd = pS;
0022 
0023     updateGraphicInfo();
0024 
0025     mpGameLayout = new KBlocksLayout(mpSingleGame->getField(), mpSingleGame->getPiece(0), mpSingleGame->getPiece(1));
0026 
0027     mpBackground = new KBlocksSvgItem(mpGameLayout, -1, 0, 0);
0028     mpBackground->setSharedRenderer(mpGrafx->renderer());
0029     mpBackground->setElementId(QStringLiteral("VIEW"));
0030     addToGroup(mpBackground);
0031 
0032     mMaxPrepareCellNum = PREPARE_AREA_WIDTH * PREPARE_AREA_WIDTH;
0033     maPrepareCells = new SvgItemInterface*[mMaxPrepareCellNum];
0034     for (int i = 0; i < mMaxPrepareCellNum; i++) {
0035         maPrepareCells[i] = new KBlocksSvgItem(mpGameLayout, KBlocksSvgItem_PrepareArea,
0036                                                i % PREPARE_AREA_WIDTH, i / PREPARE_AREA_WIDTH);
0037         maPrepareCells[i]->setSharedRenderer(mpGrafx->renderer());
0038         maPrepareCells[i]->setElementId(QStringLiteral("BLOCK_0"));
0039         maPrepareCells[i]->setVisible(false);
0040         addToGroup(maPrepareCells[i]);
0041     }
0042 
0043     mMaxFreezeCellNum = (mFieldWidth * mFieldHeight);
0044     maFreezeCells = new SvgItemInterface*[mMaxFreezeCellNum];
0045     for (int i = 0; i < mMaxFreezeCellNum; i++) {
0046         maFreezeCells[i] = new KBlocksSvgItem(mpGameLayout, KBlocksSvgItem_FieldArea,
0047                                               i % mFieldWidth, i / mFieldWidth);
0048         maFreezeCells[i]->setSharedRenderer(mpGrafx->renderer());
0049         maFreezeCells[i]->setElementId(QStringLiteral("BLOCK_0"));
0050         maFreezeCells[i]->setVisible(false);
0051         addToGroup(maFreezeCells[i]);
0052     }
0053 
0054     mGameAnimEnabled = true;
0055     mWaitForAllUpdate = false;
0056     mpAnimator = new KBlocksAnimator();
0057     connect(mpAnimator, &KBlocksAnimator::animFinished, this, &KBlocksItemGroup::endAnimation);
0058 
0059     mFadeInItems.clear();
0060     mFadeOutItems.clear();
0061     mDropItems.clear();
0062 
0063     mUpdateInterval = 50;
0064     mUpdateTimer.setInterval(mUpdateInterval);
0065     if (snapshotMode) {
0066         connect(&mUpdateTimer, &QTimer::timeout, this, &KBlocksItemGroup::updateSnapshot);
0067     } else {
0068         connect(&mUpdateTimer, &QTimer::timeout, this, &KBlocksItemGroup::updateGame);
0069     }
0070     mUpdateTimer.stop();
0071 }
0072 
0073 KBlocksItemGroup::~KBlocksItemGroup()
0074 {
0075     delete mpAnimator;
0076 
0077     for (int i = 0; i < mMaxFreezeCellNum; i++) {
0078         removeFromGroup(maFreezeCells[i]);
0079         delete maFreezeCells[i];
0080     }
0081     delete [] maFreezeCells;
0082 
0083     for (int i = 0; i < mMaxPrepareCellNum; i++) {
0084         removeFromGroup(maPrepareCells[i]);
0085         delete maPrepareCells[i];
0086     }
0087     delete [] maPrepareCells;
0088 
0089     removeFromGroup(mpBackground);
0090     delete mpBackground;
0091 
0092     delete mpGameLayout;
0093 }
0094 
0095 void KBlocksItemGroup::setUpdateInterval(int interval)
0096 {
0097     mUpdateInterval = interval;
0098     mUpdateTimer.setInterval(mUpdateInterval);
0099 }
0100 
0101 void KBlocksItemGroup::setGameAnimEnabled(bool flag)
0102 {
0103     mGameAnimEnabled = flag;
0104 }
0105 
0106 void KBlocksItemGroup::setWaitForAllUpdate(bool flag)
0107 {
0108     mWaitForAllUpdate = flag;
0109 }
0110 
0111 void KBlocksItemGroup::refreshPosition()
0112 {
0113     updateGraphicInfo();
0114 
0115     mpBackground->setElementId(QStringLiteral("VIEW"));
0116     mpBackground->setPos(0, 0);
0117 
0118     for (int i = 0; i < mMaxPrepareCellNum; i++) {
0119         maPrepareCells[i]->setPos(mPrepareLeft + mItemSize * (i % PREPARE_AREA_WIDTH),
0120                                   mPrepareTop + mItemSize * (i / PREPARE_AREA_WIDTH));
0121         maPrepareCells[i]->clearCache();
0122     }
0123 
0124     for (int i = 0; i < mMaxFreezeCellNum; i++) {
0125         maFreezeCells[i]->setPos(mFieldLeft + mItemSize * (i % mFieldWidth),
0126                                  mFieldTop + mItemSize * (i / mFieldWidth));
0127         maFreezeCells[i]->clearCache();
0128     }
0129 }
0130 
0131 void KBlocksItemGroup::startGame()
0132 {
0133     mUpdateTimer.start();
0134 }
0135 
0136 void KBlocksItemGroup::stopGame()
0137 {
0138     updateGame();
0139     mUpdateTimer.stop();
0140 }
0141 
0142 void KBlocksItemGroup::pauseGame(bool flag)
0143 {
0144     if (flag) {
0145         mUpdateTimer.stop();
0146     } else {
0147         mUpdateTimer.start();
0148     }
0149 }
0150 
0151 void KBlocksItemGroup::updateGame()
0152 {
0153     int gameResult = mpSingleGame->updateGame();
0154 
0155     bool hasRemovedLines = updateLayout();
0156 
0157     if (gameResult == GameResult_Game_Over) {
0158         refreshItems();
0159         mUpdateTimer.stop();
0160         return;
0161     }
0162 
0163     if (hasRemovedLines && mGameAnimEnabled) {
0164         mUpdateTimer.stop();
0165         fadeOutOldLine();
0166         dropFreezeLine();
0167     } else {
0168         refreshItems();
0169         if (hasRemovedLines) {
0170             if (!mWaitForAllUpdate) {
0171                 mpSingleGame->continueGame();
0172             } else {
0173                 Q_EMIT readyForAction(mGroupID);
0174             }
0175         }
0176     }
0177 }
0178 
0179 void KBlocksItemGroup::updateSnapshot()
0180 {
0181     mpGameLayout->updateSnapshot();
0182     refreshItems();
0183 }
0184 
0185 void KBlocksItemGroup::endAnimation(int animType)
0186 {
0187     switch (animType) {
0188     case KBlocks_Animation_Fade_In:
0189         mpAnimator->deleteFadeAnim();
0190         if (!mWaitForAllUpdate) {
0191             mpSingleGame->continueGame();
0192         } else {
0193             Q_EMIT readyForAction(mGroupID);
0194         }
0195         mUpdateTimer.start();
0196         break;
0197     case KBlocks_Animation_Fade_Out:
0198         mpAnimator->deleteFadeAnim();
0199         fadeInNewPiece();
0200         break;
0201     case KBlocks_Animation_Drop:
0202         mpAnimator->deleteDropAnim();
0203         break;
0204     default:
0205         break;
0206     }
0207 }
0208 
0209 bool KBlocksItemGroup::updateLayout()
0210 {
0211     int tmpActionType = GameAction_None;
0212     int tmpActionData = 0;
0213     QList<int> tmpDataList;
0214 
0215     bool hasAnim = false;
0216     int pieceCellCount = mpSingleGame->getPiece(0)->getCellCount() * 2;
0217 
0218     mpGameLayout->beginUpdate(&tmpDataList);
0219     refreshItemByPos(tmpDataList);
0220     tmpDataList.clear();
0221 
0222     mRemovedLine.clear();
0223     mPunishLine.clear();
0224     mNewPiecePos.clear();
0225 
0226     while (mpSingleGame->pickGameAction(&tmpActionType, &tmpActionData)) {
0227         switch (tmpActionType) {
0228         case GameAction_Freeze_Piece_Color:
0229             tmpDataList.append(tmpActionData);
0230             for (int i = 0; i < pieceCellCount; i++) {
0231                 tmpActionType = GameAction_None;
0232                 mpSingleGame->pickGameAction(&tmpActionType, &tmpActionData);
0233                 tmpDataList.append(tmpActionData);
0234             }
0235             mpGameLayout->updateLayout(KBlocksLayout_Update_FreezePiece, tmpDataList);
0236             tmpDataList.takeFirst();
0237             refreshItemByPos(tmpDataList);
0238             break;
0239         case GameAction_Remove_Line:
0240             mRemovedLine.append(tmpActionData);
0241             tmpDataList.append(tmpActionData);
0242             mpGameLayout->updateLayout(KBlocksLayout_Update_RemoveLine, tmpDataList);
0243             hasAnim = true;
0244             break;
0245         case GameAction_Punish_Line:
0246             mPunishLine.append(tmpActionData);
0247             tmpDataList.append(tmpActionData);
0248             mpGameLayout->updateLayout(KBlocksLayout_Update_PunishLine, tmpDataList);
0249             break;
0250         case GameAction_New_Piece_X:
0251         case GameAction_New_Piece_Y:
0252             mNewPiecePos.append(tmpActionData);
0253             hasAnim = true;
0254             break;
0255         }
0256         tmpActionType = GameAction_None;
0257         tmpActionData = 0;
0258         tmpDataList.clear();
0259     }
0260 
0261     mpGameLayout->endUpdate();
0262 
0263     return hasAnim;
0264 }
0265 
0266 void KBlocksItemGroup::refreshItems()
0267 {
0268     for (int i = 0; i < mMaxFreezeCellNum; i++) {
0269         maFreezeCells[i]->updateSelf();
0270     }
0271     for (int i = 0; i < mMaxPrepareCellNum; i++) {
0272         maPrepareCells[i]->updateSelf();
0273     }
0274 }
0275 
0276 void KBlocksItemGroup::refreshItemByPos(const QList<int> &dataList)
0277 {
0278     int posX = 0;
0279     int posY = 0;
0280     int pieceCellCount = dataList.size() / 2;
0281     for (int i = 0; i < pieceCellCount; i++) {
0282         posX = dataList[i * 2];
0283         posY = dataList[i * 2 + 1];
0284         if ((posX >= 0) && (posX < mFieldWidth)
0285                 && (posY >= 0) && (posY < mFieldHeight)) {
0286             maFreezeCells[posX + posY * mFieldWidth]->updateSelf();
0287         }
0288     }
0289 }
0290 
0291 void KBlocksItemGroup::fadeInNewPiece()
0292 {
0293     int count = mNewPiecePos.size() / 2;
0294 
0295     int posX[4] = { -1, -1, -1, -1};
0296     int posY[4] = { -1, -1, -1, -1};
0297 
0298     for (int i = 0; i < count; i++) {
0299         posX[i] = mNewPiecePos[i * 2];
0300         posY[i] = mNewPiecePos[i * 2 + 1];
0301     }
0302 
0303     mFadeInItems.clear();
0304     for (int i = 0; i < 4; i++) {
0305         if ((posX[i] >= 0 && posX[i] < mFieldWidth)
0306                 && (posY[i] >= 0 && posY[i] < mFieldHeight)) {
0307             maFreezeCells[posX[i] + posY[i] * mFieldWidth]->setOpacity(0);
0308             mFadeInItems.append(maFreezeCells[posX[i] + posY[i] * mFieldWidth]);
0309         }
0310     }
0311 
0312     for (int i = 0; i < mMaxFreezeCellNum; i++) {
0313         maFreezeCells[i]->updateSelf();
0314     }
0315     for (SvgItemInterface *tmpItem : std::as_const(mFadeOutItems)) {
0316         tmpItem->setOpacity(1);
0317         tmpItem->stopOpAnim();
0318     }
0319     for (SvgItemInterface *tmpItem : std::as_const(mDropItems)) {
0320         tmpItem->stopPosAnim();
0321     }
0322 
0323     for (int i = 0; i < PREPARE_AREA_WIDTH * PREPARE_AREA_WIDTH; i++) {
0324         maPrepareCells[i]->updateSelf();
0325         if (maPrepareCells[i]->isVisible()) {
0326             maPrepareCells[i]->setOpacity(0);
0327             mFadeInItems.append(maPrepareCells[i]);
0328         }
0329     }
0330 
0331     mpAnimator->createFadeAnim(mFadeInItems, FADE_ANIM_TIME_LINE, QTimeLine::Forward);
0332 }
0333 
0334 void KBlocksItemGroup::fadeOutOldLine()
0335 {
0336     int count = mRemovedLine.size();
0337 
0338     mFadeOutItems.clear();
0339     for (int i = 0; i < count; i++) {
0340         for (int j = 0; j < mFieldWidth; j++) {
0341             maFreezeCells[j + mRemovedLine[i] * mFieldWidth]->startOpAnim();
0342             mFadeOutItems.append(maFreezeCells[j + mRemovedLine[i] * mFieldWidth]);
0343         }
0344     }
0345 
0346     for (int i = 0; i < PREPARE_AREA_WIDTH * PREPARE_AREA_WIDTH; i++) {
0347         if (maPrepareCells[i]->isVisible()) {
0348             mFadeOutItems.append(maPrepareCells[i]);
0349         }
0350     }
0351 
0352     mpAnimator->createFadeAnim(mFadeOutItems, FADE_ANIM_TIME_LINE, QTimeLine::Backward);
0353 }
0354 
0355 void KBlocksItemGroup::dropFreezeLine()
0356 {
0357     int count = mRemovedLine.size();
0358 
0359     int *fallLine = new int[mFieldHeight];
0360     int removeLine = 0;
0361 
0362     if (count == 0) {
0363         delete [] fallLine;
0364         return;
0365     }
0366 
0367     for (int i = mFieldHeight - 1; i >= 0; i--) {
0368         if (removeLine < count) {
0369             if (i == mRemovedLine[removeLine]) {
0370                 fallLine[i] = 0;
0371                 removeLine++;
0372             } else {
0373                 fallLine[i] = removeLine;
0374             }
0375         } else {
0376             fallLine[i] = removeLine;
0377         }
0378     }
0379 
0380     mDropItems.clear();
0381     for (int i = 0; i < mFieldHeight; i++) {
0382         if (fallLine[i] > 0) {
0383             QPointF target;
0384             target.setX(0);
0385             target.setY(fallLine[i] * mItemSize);
0386             for (int j = 0; j < mFieldWidth; j++) {
0387                 if (maFreezeCells[j + i * mFieldWidth]->isVisible()) {
0388                     maFreezeCells[j + i * mFieldWidth]->startPosAnim(target);
0389                     mDropItems.append(maFreezeCells[j + i * mFieldWidth]);
0390                 }
0391             }
0392         }
0393     }
0394 
0395     mpAnimator->createDropAnim(mDropItems, DROP_ANIM_TIME_LINE, QTimeLine::Forward);
0396     delete[] fallLine;
0397 }
0398 
0399 void KBlocksItemGroup::updateGraphicInfo()
0400 {
0401     mItemSize = mpGrafx->m_Block_Size;
0402     mPrepareLeft = mpGrafx->m_PreviewArea_CenterPoint_X - PREPARE_AREA_WIDTH * mpGrafx->m_Block_Size / 2;
0403     mPrepareTop = mpGrafx->m_PreviewArea_CenterPoint_Y - PREPARE_AREA_WIDTH * mpGrafx->m_Block_Size / 2;
0404     mFieldLeft = mpGrafx->m_PlayArea_OffsetPoint_X;
0405     mFieldTop = mpGrafx->m_PlayArea_OffsetPoint_Y;
0406     mFieldWidth = mpGrafx->m_PlayArea_NumberOfBlocks_X;
0407     mFieldHeight = mpGrafx->m_PlayArea_NumberOfBlocks_Y;
0408 }
0409 
0410 #include "moc_KBlocksItemGroup.cpp"