1/***************************************************************************
2* KBlocks, a falling blocks game for KDE *
3* Copyright (C) 2010 Mauricio Piacentini <mauricio@tabuleiro.com> *
4* Zhongjie Cai <squall.leonhart.cai@gmail.com> *
5* *
6* This program is free software; you can redistribute it and/or modify *
7* it under the terms of the GNU General Public License as published by *
8* the Free Software Foundation; either version 2 of the License, or *
9* (at your option) any later version. *
10***************************************************************************/
11#include "KBlocksItemGroup.h"
12
13KBlocksItemGroup::KBlocksItemGroup(int groupID, SingleGameInterface * p, KBlocksGraphics * pG, KBlocksSound * pS, bool snapshotMode)
14{
15 mGroupID = groupID;
16 mpSingleGame = p;
17 mpGrafx = pG;
18 mpSnd = pS;
19
20 updateGraphicInfo();
21
22 mpGameLayout = new KBlocksLayout(mpSingleGame->getField(), mpSingleGame->getPiece(0), mpSingleGame->getPiece(1));
23
24 mpBackground = new KBlocksSvgItem(mpGameLayout, -1, 0, 0);
25 mpBackground->setSharedRenderer(mpGrafx->renderer());
26 mpBackground->setElementId("VIEW");
27 addToGroup(mpBackground);
28
29 mMaxPrepareCellNum = PREPARE_AREA_WIDTH * PREPARE_AREA_WIDTH;
30 maPrepareCells = new KBlocksSvgItem*[mMaxPrepareCellNum];
31 for(int i = 0; i < mMaxPrepareCellNum; i++)
32 {
33 maPrepareCells[i] = new KBlocksSvgItem( mpGameLayout, KBlocksSvgItem_PrepareArea,
34 i % PREPARE_AREA_WIDTH, i / PREPARE_AREA_WIDTH);
35 maPrepareCells[i]->setSharedRenderer(mpGrafx->renderer());
36 maPrepareCells[i]->setElementId("BLOCK_0");
37 maPrepareCells[i]->setVisible(false);
38 addToGroup(maPrepareCells[i]);
39 }
40
41 mMaxFreezeCellNum = (mFieldWidth * mFieldHeight);
42 maFreezeCells = new KBlocksSvgItem*[mMaxFreezeCellNum];
43 for(int i = 0; i < mMaxFreezeCellNum; i++)
44 {
45 maFreezeCells[i] = new KBlocksSvgItem( mpGameLayout, KBlocksSvgItem_FieldArea,
46 i % mFieldWidth, i / mFieldWidth);
47 maFreezeCells[i]->setSharedRenderer(mpGrafx->renderer());
48 maFreezeCells[i]->setElementId("BLOCK_0");
49 maFreezeCells[i]->setVisible(false);
50 addToGroup(maFreezeCells[i]);
51 }
52
53 mGameAnimEnabled = true;
54 mWaitForAllUpdate = false;
55 mpAnimator = new KBlocksAnimator();
56 connect(mpAnimator, SIGNAL(animFinished(int)), this, SLOT(endAnimation(int)));
57
58 mFadeInItems.clear();
59 mFadeOutItems.clear();
60 mDropItems.clear();
61
62 mUpdateInterval = 50;
63 mUpdateTimer.setInterval(mUpdateInterval);
64 if (snapshotMode)
65 {
66 connect(&mUpdateTimer, SIGNAL(timeout()), SLOT(updateSnapshot()));
67 }
68 else
69 {
70 connect(&mUpdateTimer, SIGNAL(timeout()), SLOT(updateGame()));
71 }
72 mUpdateTimer.stop();
73}
74
75KBlocksItemGroup::~KBlocksItemGroup()
76{
77 delete mpAnimator;
78
79 for(int i = 0; i < mMaxFreezeCellNum; i++)
80 {
81 removeFromGroup(maFreezeCells[i]);
82 delete maFreezeCells[i];
83 }
84 delete [] maFreezeCells;
85
86 for(int i = 0; i < mMaxPrepareCellNum; i++)
87 {
88 removeFromGroup(maPrepareCells[i]);
89 delete maPrepareCells[i];
90 }
91 delete [] maPrepareCells;
92
93 removeFromGroup(mpBackground);
94 delete mpBackground;
95
96 delete mpGameLayout;
97}
98
99void KBlocksItemGroup::setUpdateInterval(int interval)
100{
101 mUpdateInterval = interval;
102 mUpdateTimer.setInterval(mUpdateInterval);
103}
104
105void KBlocksItemGroup::setGameAnimEnabled(bool flag)
106{
107 mGameAnimEnabled = flag;
108}
109
110void KBlocksItemGroup::setWaitForAllUpdate(bool flag)
111{
112 mWaitForAllUpdate = flag;
113}
114
115void KBlocksItemGroup::refreshPosition()
116{
117 updateGraphicInfo();
118
119 mpBackground->setElementId("VIEW");
120 mpBackground->setPos(0, 0);
121
122 for(int i = 0; i < mMaxPrepareCellNum; i++)
123 {
124 maPrepareCells[i]->setPos(mPrepareLeft + mItemSize * (i % PREPARE_AREA_WIDTH),
125 mPrepareTop + mItemSize * (i / PREPARE_AREA_WIDTH));
126 }
127
128 for(int i = 0; i < mMaxFreezeCellNum; i++)
129 {
130 maFreezeCells[i]->setPos(mFieldLeft + mItemSize * (i % mFieldWidth),
131 mFieldTop + mItemSize * (i / mFieldWidth));
132 }
133}
134
135void KBlocksItemGroup::startGame()
136{
137 mUpdateTimer.start();
138}
139
140void KBlocksItemGroup::stopGame()
141{
142 mUpdateTimer.stop();
143}
144
145void KBlocksItemGroup::pauseGame(bool flag)
146{
147 if (flag)
148 {
149 mUpdateTimer.stop();
150 }
151 else
152 {
153 mUpdateTimer.start();
154 }
155}
156
157void KBlocksItemGroup::updateGame()
158{
159 int gameResult = mpSingleGame->updateGame();
160
161 if (gameResult == GameResult_Game_Over)
162 {
163 mUpdateTimer.stop();
164 return;
165 }
166
167 bool hasRemovedLines = updateLayout();
168
169 if (hasRemovedLines && mGameAnimEnabled)
170 {
171 mUpdateTimer.stop();
172 fadeOutOldLine();
173 dropFreezeLine();
174 }
175 else
176 {
177 refreshItems();
178 if (hasRemovedLines)
179 {
180 if (!mWaitForAllUpdate)
181 {
182 mpSingleGame->continueGame();
183 }
184 else
185 {
186 emit readyForAction(mGroupID);
187 }
188 }
189 }
190}
191
192void KBlocksItemGroup::updateSnapshot()
193{
194 mpGameLayout->updateSnapshot();
195 refreshItems();
196}
197
198void KBlocksItemGroup::endAnimation(int animType)
199{
200 switch(animType)
201 {
202 case KBlocks_Animation_Fade_In:
203 mpAnimator->deleteFadeAnim();
204 if (!mWaitForAllUpdate)
205 {
206 mpSingleGame->continueGame();
207 }
208 else
209 {
210 emit readyForAction(mGroupID);
211 }
212 mUpdateTimer.start();
213 break;
214 case KBlocks_Animation_Fade_Out:
215 mpAnimator->deleteFadeAnim();
216 fadeInNewPiece();
217 break;
218 case KBlocks_Animation_Drop:
219 mpAnimator->deleteDropAnim();
220 break;
221 default:
222 break;
223 }
224}
225
226bool KBlocksItemGroup::updateLayout()
227{
228 int tmpActionType = GameAction_None;
229 int tmpActionData = 0;
230 QList<int> tmpDataList;
231
232 bool hasAnim = false;
233 int pieceCellCount = mpSingleGame->getPiece(0)->getCellCount() * 2;
234
235 mpGameLayout->beginUpdate(&tmpDataList);
236 refreshItemByPos(tmpDataList);
237 tmpDataList.clear();
238
239 mRemovedLine.clear();
240 mPunishLine.clear();
241 mNewPiecePos.clear();
242
243 while(mpSingleGame->pickGameAction(&tmpActionType, &tmpActionData))
244 {
245 switch(tmpActionType)
246 {
247 case GameAction_Freeze_Piece_Color:
248 tmpDataList.append(tmpActionData);
249 for(int i = 0; i < pieceCellCount; i++)
250 {
251 tmpActionType = GameAction_None;
252 mpSingleGame->pickGameAction(&tmpActionType, &tmpActionData);
253 tmpDataList.append(tmpActionData);
254 }
255 mpGameLayout->updateLayout(KBlocksLayout_Update_FreezePiece, tmpDataList);
256 tmpDataList.takeFirst();
257 refreshItemByPos(tmpDataList);
258 break;
259 case GameAction_Remove_Line:
260 mRemovedLine.append(tmpActionData);
261 tmpDataList.append(tmpActionData);
262 mpGameLayout->updateLayout(KBlocksLayout_Update_RemoveLine, tmpDataList);
263 hasAnim = true;
264 break;
265 case GameAction_Punish_Line:
266 mPunishLine.append(tmpActionData);
267 tmpDataList.append(tmpActionData);
268 mpGameLayout->updateLayout(KBlocksLayout_Update_PunishLine, tmpDataList);
269 break;
270 case GameAction_New_Piece_X:
271 case GameAction_New_Piece_Y:
272 mNewPiecePos.append(tmpActionData);
273 hasAnim = true;
274 break;
275 }
276 tmpActionType = GameAction_None;
277 tmpActionData = 0;
278 tmpDataList.clear();
279 }
280
281 mpGameLayout->endUpdate();
282
283 return hasAnim;
284}
285
286void KBlocksItemGroup::refreshItems()
287{
288 for(int i = 0; i < mMaxFreezeCellNum; i++)
289 {
290 maFreezeCells[i]->updateSelf();
291 }
292 for(int i = 0; i < mMaxPrepareCellNum; i++)
293 {
294 maPrepareCells[i]->updateSelf();
295 }
296}
297
298void KBlocksItemGroup::refreshItemByPos(const QList<int> & dataList)
299{
300 int posX = 0;
301 int posY = 0;
302 int pieceCellCount = dataList.size() / 2;
303 for(int i = 0; i < pieceCellCount; i++)
304 {
305 posX = dataList[i * 2];
306 posY = dataList[i * 2 + 1];
307 if ((posX >= 0) && (posX < mFieldWidth)
308 && (posY >= 0) && (posY < mFieldHeight))
309 {
310 maFreezeCells[posX + posY * mFieldWidth]->updateSelf();
311 }
312 }
313}
314
315void KBlocksItemGroup::fadeInNewPiece()
316{
317 int count = mNewPiecePos.size() / 2;
318
319 int posX[4] = {-1, -1, -1, -1};
320 int posY[4] = {-1, -1, -1, -1};
321
322 for(int i = 0; i < count; i++)
323 {
324 posX[i] = mNewPiecePos[i * 2];
325 posY[i] = mNewPiecePos[i * 2 + 1];
326 }
327
328 mFadeInItems.clear();
329 for(int i = 0; i < 4; i++)
330 {
331 if ((posX[i] >= 0 && posX[i] < mFieldWidth)
332 && (posY[i] >= 0 && posY[i] < mFieldHeight))
333 {
334 maFreezeCells[posX[i] + posY[i] * mFieldWidth]->setOpacity(0);
335 mFadeInItems.append(maFreezeCells[posX[i] + posY[i] * mFieldWidth]);
336 }
337 }
338
339 for(int i = 0; i < mMaxFreezeCellNum; i++)
340 {
341 maFreezeCells[i]->updateSelf();
342 }
343 foreach(KBlocksSvgItem * tmpItem, mFadeOutItems)
344 {
345 tmpItem->setOpacity(1);
346 tmpItem->stopOpAnim();
347 }
348 foreach(KBlocksSvgItem * tmpItem, mDropItems)
349 {
350 tmpItem->stopPosAnim();
351 }
352
353 for(int i = 0; i < PREPARE_AREA_WIDTH * PREPARE_AREA_WIDTH; i++)
354 {
355 maPrepareCells[i]->updateSelf();
356 if (maPrepareCells[i]->isVisible())
357 {
358 maPrepareCells[i]->setOpacity(0);
359 mFadeInItems.append(maPrepareCells[i]);
360 }
361 }
362
363 mpAnimator->createFadeAnim(mFadeInItems, FADE_ANIM_TIME_LINE, QTimeLine::Forward);
364}
365
366void KBlocksItemGroup::fadeOutOldLine()
367{
368 int count = mRemovedLine.size();
369
370 mFadeOutItems.clear();
371 for(int i = 0; i < count; i++)
372 {
373 for(int j = 0; j < mFieldWidth; j++)
374 {
375 maFreezeCells[j + mRemovedLine[i] * mFieldWidth]->startOpAnim();
376 mFadeOutItems.append(maFreezeCells[j + mRemovedLine[i] * mFieldWidth]);
377 }
378 }
379
380 for(int i = 0; i < PREPARE_AREA_WIDTH * PREPARE_AREA_WIDTH; i++)
381 {
382 if (maPrepareCells[i]->isVisible())
383 {
384 mFadeOutItems.append(maPrepareCells[i]);
385 }
386 }
387
388 mpAnimator->createFadeAnim(mFadeOutItems, FADE_ANIM_TIME_LINE, QTimeLine::Backward);
389}
390
391void KBlocksItemGroup::dropFreezeLine()
392{
393 int count = mRemovedLine.size();
394
395 int *fallLine = new int[mFieldHeight];
396 int removeLine = 0;
397
398 if (count == 0)
399 {
400 delete [] fallLine;
401 return;
402 }
403
404 for(int i = mFieldHeight - 1; i >= 0; i--)
405 {
406 if (removeLine < count)
407 {
408 if (i == mRemovedLine[removeLine])
409 {
410 fallLine[i] = 0;
411 removeLine++;
412 }
413 else
414 {
415 fallLine[i] = removeLine;
416 }
417 }
418 else
419 {
420 fallLine[i] = removeLine;
421 }
422 }
423
424 mDropItems.clear();
425 for(int i = 0; i < mFieldHeight; i++)
426 {
427 if (fallLine[i] > 0)
428 {
429 QPointF target;
430 target.setX(0);
431 target.setY(fallLine[i] * mItemSize);
432 for(int j = 0; j < mFieldWidth; j++)
433 {
434 if (maFreezeCells[j + i * mFieldWidth]->isVisible())
435 {
436 maFreezeCells[j + i * mFieldWidth]->startPosAnim(target);
437 mDropItems.append(maFreezeCells[j + i * mFieldWidth]);
438 }
439 }
440 }
441 }
442
443 mpAnimator->createDropAnim(mDropItems, DROP_ANIM_TIME_LINE, QTimeLine::Forward);
444 delete[] fallLine;
445}
446
447void KBlocksItemGroup::updateGraphicInfo()
448{
449 mItemSize = mpGrafx->m_Block_Size;
450 mPrepareLeft = mpGrafx->m_PreviewArea_CenterPoint_X - PREPARE_AREA_WIDTH * mpGrafx->m_Block_Size / 2;
451 mPrepareTop = mpGrafx->m_PreviewArea_CenterPoint_Y - PREPARE_AREA_WIDTH * mpGrafx->m_Block_Size / 2;
452 mFieldLeft = mpGrafx->m_PlayArea_OffsetPoint_X;
453 mFieldTop = mpGrafx->m_PlayArea_OffsetPoint_Y;
454 mFieldWidth = mpGrafx->m_PlayArea_NumberOfBlocks_X;
455 mFieldHeight = mpGrafx->m_PlayArea_NumberOfBlocks_Y;
456}
457