File indexing completed on 2023-10-03 03:27:48
0001 /* 0002 SPDX-FileCopyrightText: 2008-2010 Stefan Majewsky <majewsky@gmx.net> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "board.h" 0008 #include "diamond.h" 0009 0010 #include <QPropertyAnimation> 0011 #include <QRandomGenerator> 0012 #include <KgDifficulty> 0013 0014 const int KDiamond::Board::MoveDuration = 100; //duration of a move animation (per coordinate unit) in milliseconds 0015 const int KDiamond::Board::RemoveDuration = 200; //duration of a move animation in milliseconds 0016 0017 //NOTE: The corresponding difficulty values are {20, 30, 40, 50, 60} (see KgDifficultyLevel::StandardLevel). 0018 static int boardSizes[] = { 12, 10, 8, 8, 8 }; 0019 static int boardColorCounts[] = { 5, 5, 5, 6, 7 }; 0020 0021 KDiamond::Board::Board(KGameRenderer *renderer) 0022 : m_difficultyIndex(Kg::difficultyLevel() / 10 - 2) 0023 , m_size(boardSizes[m_difficultyIndex]) 0024 , m_colorCount(boardColorCounts[m_difficultyIndex]) 0025 , m_paused(false) 0026 , m_renderer(renderer) 0027 , m_diamonds(m_size *m_size, nullptr) 0028 { 0029 for (QPoint point; point.x() < m_size; ++point.rx()) 0030 for (point.ry() = 0; point.y() < m_size; ++point.ry()) { 0031 //displacement vectors needed for the following algorithm 0032 const QPoint dispY1(0, -1), dispY2(0, -2); 0033 const QPoint dispX1(-1, 0), dispX2(-2, 0); 0034 //roll the dice to get a color, but ensure that there are not three of a color in a row from the start 0035 int color; 0036 while (true) { 0037 color = QRandomGenerator::global()->bounded(1, m_colorCount + 1); 0038 //condition: no triplet in y axis (attention: only the diamonds above us are defined already) 0039 if (point.y() >= 2) { //no triplet possible for i = 0, 1 0040 const int otherColor1 = diamond(point + dispY1)->color(); 0041 const int otherColor2 = diamond(point + dispY2)->color(); 0042 if (otherColor1 == color && otherColor2 == color) { 0043 continue; //roll the dice again 0044 } 0045 } 0046 //same condition on x axis 0047 if (point.x() >= 2) { 0048 const int otherColor1 = diamond(point + dispX1)->color(); 0049 const int otherColor2 = diamond(point + dispX2)->color(); 0050 if (otherColor1 == color && otherColor2 == color) { 0051 continue; 0052 } 0053 } 0054 break; 0055 } 0056 rDiamond(point) = spawnDiamond(color); 0057 diamond(point)->setPos(point); 0058 } 0059 } 0060 0061 Diamond *KDiamond::Board::spawnDiamond(int color) 0062 { 0063 Diamond *diamond = new Diamond((KDiamond::Color) color, m_renderer, this); 0064 connect(diamond, &Diamond::clicked, this, &Board::slotClicked); 0065 connect(diamond, &Diamond::dragged, this, &Board::slotDragged); 0066 return diamond; 0067 } 0068 0069 QPoint KDiamond::Board::findDiamond(Diamond *diamond) const 0070 { 0071 int index = m_diamonds.indexOf(diamond); 0072 if (index == -1) { 0073 return QPoint(-1, -1); 0074 } else { 0075 return QPoint(index % m_size, index / m_size); 0076 } 0077 } 0078 0079 Diamond *&KDiamond::Board::rDiamond(const QPoint &point) 0080 { 0081 return m_diamonds[point.x() + point.y() * m_size]; 0082 } 0083 0084 Diamond *KDiamond::Board::diamond(const QPoint &point) const 0085 { 0086 return m_diamonds.value(point.x() + point.y() * m_size); 0087 } 0088 0089 int KDiamond::Board::gridSize() const 0090 { 0091 return m_size; 0092 } 0093 0094 bool KDiamond::Board::hasDiamond(const QPoint &point) const 0095 { 0096 return 0 <= point.x() && point.x() < m_size && 0 <= point.y() && point.y() < m_size; 0097 } 0098 0099 bool KDiamond::Board::hasRunningAnimations() const 0100 { 0101 return !m_runningAnimations.isEmpty(); 0102 } 0103 0104 void KDiamond::Board::slotAnimationFinished() 0105 { 0106 if (m_runningAnimations.isEmpty()) { 0107 return; 0108 } 0109 //static_cast is enough, no need for a qobject_cast 0110 //because result pointer is never dereferenced here 0111 m_runningAnimations.removeAll(static_cast<QAbstractAnimation *>(sender())); 0112 if (m_runningAnimations.isEmpty()) { 0113 Q_EMIT animationsFinished(); 0114 } 0115 } 0116 0117 QList<QPoint> KDiamond::Board::selections() const 0118 { 0119 return m_selections; 0120 } 0121 0122 bool KDiamond::Board::hasSelection(const QPoint &point) const 0123 { 0124 return m_selections.contains(point); 0125 } 0126 0127 void KDiamond::Board::setSelection(const QPoint &point, bool selected) 0128 { 0129 const int index = m_selections.indexOf(point); 0130 if ((index >= 0) == selected) 0131 //nothing to do 0132 { 0133 return; 0134 } 0135 if (selected) { 0136 //add selection, possibly by reusing an old item instance 0137 Diamond *selector; 0138 if (!m_inactiveSelectors.isEmpty()) { 0139 selector = m_inactiveSelectors.takeLast(); 0140 } else { 0141 selector = new Diamond(KDiamond::Selection, m_renderer, this); 0142 } 0143 m_activeSelectors << selector; 0144 m_selections << point; 0145 selector->setPos(point); 0146 selector->show(); 0147 } else { 0148 //remove selection, but try to reuse item instance later 0149 m_selections.removeAt(index); 0150 Diamond *selector = m_activeSelectors.takeAt(index); 0151 m_inactiveSelectors << selector; 0152 selector->hide(); 0153 } 0154 } 0155 0156 void KDiamond::Board::clearSelection() 0157 { 0158 for (Diamond *selector : std::as_const(m_activeSelectors)) { 0159 selector->hide(); 0160 m_inactiveSelectors << selector; 0161 } 0162 m_selections.clear(); 0163 m_activeSelectors.clear(); 0164 } 0165 0166 void KDiamond::Board::setPaused(bool paused) 0167 { 0168 //During pauses, the board is hidden and any animations are suspended. 0169 const bool visible = !paused; 0170 if (isVisible() == visible) { 0171 return; 0172 } 0173 setVisible(visible); 0174 QList<QAbstractAnimation *>::const_iterator it1 = m_runningAnimations.constBegin(), it2 = m_runningAnimations.constEnd(); 0175 for (; it1 != it2; ++it1) { 0176 (*it1)->setPaused(paused); 0177 } 0178 } 0179 0180 void KDiamond::Board::removeDiamond(const QPoint &point) 0181 { 0182 Diamond *diamond = this->diamond(point); 0183 if (!diamond) { 0184 return; //diamond has already been removed 0185 } 0186 rDiamond(point) = nullptr; 0187 //play remove animation (TODO: For non-animated sprites, play an opacity animation instead.) 0188 QPropertyAnimation *animation = new QPropertyAnimation(diamond, "frame", this); 0189 animation->setStartValue(0); 0190 animation->setEndValue(diamond->frameCount() - 1); 0191 animation->setDuration(KDiamond::Board::RemoveDuration); 0192 animation->start(QAbstractAnimation::DeleteWhenStopped); 0193 connect(animation, &QPropertyAnimation::finished, this, &Board::slotAnimationFinished); 0194 connect(animation, &QPropertyAnimation::finished, diamond, &Diamond::deleteLater); 0195 m_runningAnimations << animation; 0196 } 0197 0198 void KDiamond::Board::spawnMoveAnimations(const QList<MoveAnimSpec> &specs) 0199 { 0200 for (const MoveAnimSpec &spec : specs) { 0201 const int duration = KDiamond::Board::MoveDuration * (spec.to - spec.from).manhattanLength(); 0202 QPropertyAnimation *animation = new QPropertyAnimation(spec.diamond, "pos", this); 0203 animation->setStartValue(spec.from); 0204 animation->setEndValue(spec.to); 0205 animation->setDuration(duration); 0206 animation->start(QAbstractAnimation::DeleteWhenStopped); 0207 connect(animation, &QPropertyAnimation::finished, this, &Board::slotAnimationFinished); 0208 m_runningAnimations << animation; 0209 } 0210 } 0211 0212 void KDiamond::Board::swapDiamonds(const QPoint &point1, const QPoint &point2) 0213 { 0214 //swap diamonds in internal representation 0215 Diamond *diamond1 = this->diamond(point1); 0216 Diamond *diamond2 = this->diamond(point2); 0217 rDiamond(point1) = diamond2; 0218 rDiamond(point2) = diamond1; 0219 //play movement animations 0220 const MoveAnimSpec spec1 = { diamond1, point1, point2 }; 0221 const MoveAnimSpec spec2 = { diamond2, point2, point1 }; 0222 spawnMoveAnimations(QList<MoveAnimSpec>() << spec1 << spec2); 0223 } 0224 0225 void KDiamond::Board::fillGaps() 0226 { 0227 QList<MoveAnimSpec> specs; 0228 //fill gaps 0229 int x, y, yt; //counters - (x, yt) is the target position of diamond (x,y) 0230 for (x = 0; x < m_size; ++x) { 0231 //We have to search from the bottom of the column. Exclude the lowest element (x = m_size - 1) because it cannot move down. 0232 for (y = m_size - 2; y >= 0; --y) { 0233 if (!diamond(QPoint(x, y))) 0234 //no need to move gaps -> these are moved later 0235 { 0236 continue; 0237 } 0238 if (diamond(QPoint(x, y + 1))) 0239 //there is something right below this diamond -> Do not move. 0240 { 0241 continue; 0242 } 0243 //search for the lowest possible position 0244 for (yt = y; yt < m_size - 1; ++yt) { 0245 if (diamond(QPoint(x, yt + 1))) { 0246 break; //xt now holds the lowest possible position 0247 } 0248 } 0249 rDiamond(QPoint(x, yt)) = diamond(QPoint(x, y)); 0250 rDiamond(QPoint(x, y)) = nullptr; 0251 const MoveAnimSpec spec = { diamond(QPoint(x, yt)), QPoint(x, y), QPoint(x, yt) }; 0252 specs << spec; 0253 //if this element is selected, move the selection, too 0254 const int index = m_selections.indexOf(QPoint(x, y)); 0255 if (index != -1) { 0256 m_selections.replace(index, QPoint(x, yt)); 0257 const MoveAnimSpec spec = { m_activeSelectors[index], QPoint(x, y), QPoint(x, yt) }; 0258 specs << spec; 0259 } 0260 } 0261 } 0262 //fill top rows with new elements 0263 for (x = 0; x < m_size; ++x) { 0264 yt = 0; //now: holds the position from where the diamond comes (-1 for the lowest new diamond) 0265 for (y = m_size - 1; y >= 0; --y) { 0266 Diamond *&diamond = this->rDiamond(QPoint(x, y)); 0267 if (diamond) { 0268 continue; //inside of diamond stack - no gaps to fill 0269 } 0270 --yt; 0271 const quint32 randValue = QRandomGenerator::global()->bounded(1, m_colorCount + 1); //high value is excluse 0272 diamond = spawnDiamond(randValue); 0273 diamond->setPos(QPoint(x, yt)); 0274 const MoveAnimSpec spec = { diamond, QPoint(x, yt), QPoint(x, y) }; 0275 specs << spec; 0276 } 0277 } 0278 spawnMoveAnimations(specs); 0279 } 0280 0281 KGameRenderer *KDiamond::Board::renderer() const 0282 { 0283 return m_renderer; 0284 } 0285 0286 void KDiamond::Board::slotClicked() 0287 { 0288 const QPoint point = findDiamond(qobject_cast<Diamond *>(sender())); 0289 if (point.x() >= 0 && point.y() >= 0) { 0290 Q_EMIT clicked(point); 0291 } 0292 } 0293 0294 void KDiamond::Board::slotDragged(const QPoint &direction) 0295 { 0296 const QPoint point = findDiamond(qobject_cast<Diamond *>(sender())); 0297 if (point.x() >= 0 && point.y() >= 0) { 0298 Q_EMIT dragged(point, direction); 0299 } 0300 } 0301 0302 QRectF KDiamond::Board::boundingRect() const 0303 { 0304 return QRectF(); 0305 } 0306 0307 void KDiamond::Board::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 0308 { 0309 Q_UNUSED(painter) Q_UNUSED(option) Q_UNUSED(widget) 0310 } 0311 0312 #include "moc_board.cpp"