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"