File indexing completed on 2024-05-12 04:04:51
0001 /* 0002 * Copyright (C) 2003 Josh Metzler <joshdeb@metzlers.org> 0003 * Copyright (C) 2000-2009 Stephan Kulow <coolo@kde.org> 0004 * Copyright (C) 2010 Parker Coates <coates@kde.org> 0005 * 0006 * License of original code: 0007 * ------------------------------------------------------------------------- 0008 * Permission to use, copy, modify, and distribute this software and its 0009 * documentation for any purpose and without fee is hereby granted, 0010 * provided that the above copyright notice appear in all copies and that 0011 * both that copyright notice and this permission notice appear in 0012 * supporting documentation. 0013 * 0014 * This file is provided AS IS with no warranties of any kind. The author 0015 * shall have no liability with respect to the infringement of copyrights, 0016 * trade secrets or any patents by this file or any part thereof. In no 0017 * event will the author be liable for any lost revenue or profits or 0018 * other special, indirect and consequential damages. 0019 * ------------------------------------------------------------------------- 0020 * 0021 * License of modifications/additions made after 2009-01-01: 0022 * ------------------------------------------------------------------------- 0023 * This program is free software; you can redistribute it and/or 0024 * modify it under the terms of the GNU General Public License as 0025 * published by the Free Software Foundation; either version 2 of 0026 * the License, or (at your option) any later version. 0027 * 0028 * This program is distributed in the hope that it will be useful, 0029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0031 * GNU General Public License for more details. 0032 * 0033 * You should have received a copy of the GNU General Public License 0034 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0035 * ------------------------------------------------------------------------- 0036 */ 0037 0038 #include "spider.h" 0039 0040 // own 0041 #include "dealerinfo.h" 0042 #include "patsolve/spidersolver2.h" 0043 #include "pileutils.h" 0044 #include "settings.h" 0045 #include "speeds.h" 0046 // KF 0047 #include <KLocalizedString> 0048 #include <KSelectAction> 0049 // Qt 0050 #include <QRandomGenerator> 0051 0052 class InvisiblePile : public PatPile 0053 { 0054 public: 0055 InvisiblePile(DealerScene *scene, int index, const QString &objectName = QString()) 0056 : PatPile(scene, index, objectName){}; 0057 0058 protected: 0059 void paintGraphic(QPainter *painter, qreal highlightedness) override 0060 { 0061 Q_UNUSED(painter); 0062 Q_UNUSED(highlightedness); 0063 }; 0064 }; 0065 0066 Spider::Spider(const DealerInfo *di) 0067 : DealerScene(di) 0068 { 0069 } 0070 0071 void Spider::initialize() 0072 { 0073 m_leg = 0; 0074 m_redeal = 0; 0075 0076 const qreal dist_x = 1.12; 0077 const qreal smallNeg = -1e-6; 0078 0079 m_suits = Settings::spiderSuitCount(); 0080 0081 m_stackFaceup = Settings::spiderStackFaceup(); 0082 0083 createDeck(); 0084 0085 // Dealing the cards out into 5 piles so the user can see how many 0086 // sets of 10 cards are left to be dealt out 0087 for (int column = 0; column < 5; ++column) { 0088 redeals[column] = new InvisiblePile(this, column + 1, QStringLiteral("redeals%1").arg(column)); 0089 redeals[column]->setPileRole(PatPile::Stock); 0090 redeals[column]->setLayoutPos(dist_x * (9 - (4.0 - column) / 3), smallNeg); 0091 redeals[column]->setZValue(12 * (5 - column)); 0092 redeals[column]->setSpread(0, 0); 0093 redeals[column]->setKeyboardSelectHint(KCardPile::NeverFocus); 0094 redeals[column]->setKeyboardDropHint(KCardPile::NeverFocus); 0095 connect(redeals[column], &KCardPile::clicked, this, &DealerScene::drawDealRowOrRedeal); 0096 } 0097 0098 // The 10 playing piles 0099 for (int column = 0; column < 10; ++column) { 0100 stack[column] = new PatPile(this, column + 6, QStringLiteral("stack%1").arg(column)); 0101 stack[column]->setPileRole(PatPile::Tableau); 0102 stack[column]->setLayoutPos(dist_x * column, 0); 0103 stack[column]->setZValue(20); 0104 stack[column]->setAutoTurnTop(true); 0105 stack[column]->setBottomPadding(1.5); 0106 stack[column]->setHeightPolicy(KCardPile::GrowDown); 0107 stack[column]->setKeyboardSelectHint(KCardPile::AutoFocusDeepestRemovable); 0108 stack[column]->setKeyboardDropHint(KCardPile::AutoFocusTop); 0109 } 0110 0111 // The 8 'legs' so named by me because spiders have 8 legs - why 0112 // else the name Spider? 0113 for (int column = 0; column < 8; ++column) { 0114 legs[column] = new InvisiblePile(this, column + 16, QStringLiteral("legs%1").arg(column)); 0115 legs[column]->setPileRole(PatPile::Foundation); 0116 legs[column]->setLayoutPos(dist_x / 3 * column, smallNeg); 0117 legs[column]->setZValue(column + 1); 0118 legs[column]->setSpread(0, 0); 0119 legs[column]->setZValue(14 * column); 0120 legs[column]->setVisible(false); 0121 legs[column]->setKeyboardSelectHint(KCardPile::NeverFocus); 0122 legs[column]->setKeyboardDropHint(KCardPile::NeverFocus); 0123 } 0124 0125 // Moving an A-K run to a leg is not really an autoDrop - the 0126 // user should have no choice. 0127 setAutoDropEnabled(false); 0128 setActions(DealerScene::Hint | DealerScene::Demo | DealerScene::Deal); 0129 setSolver(new spidersolver2::SpiderSolver2(this)); 0130 0131 options = new KSelectAction(i18n("Spider &Options"), this); 0132 options->addAction(i18n("1 Suit (Easy)")); 0133 options->addAction(i18n("2 Suits (Medium)")); 0134 options->addAction(i18n("3 Suits (Hard)")); 0135 options->addAction(i18n("4 Suits (Very Hard)")); 0136 if (m_suits == 1) 0137 options->setCurrentItem(0); 0138 else if (m_suits == 2) 0139 options->setCurrentItem(1); 0140 else if (m_suits == 3) 0141 options->setCurrentItem(2); 0142 else 0143 options->setCurrentItem(3); 0144 connect(options, &KSelectAction::indexTriggered, this, &Spider::gameTypeChanged); 0145 0146 m_stackFaceupOption = new KSelectAction(i18n("S&tack Options"), this); 0147 m_stackFaceupOption->addAction(i18n("Face &Down (harder)")); 0148 m_stackFaceupOption->addAction(i18n("Face &Up (easier)")); 0149 m_stackFaceupOption->setCurrentItem(m_stackFaceup); 0150 0151 connect(m_stackFaceupOption, &KSelectAction::indexTriggered, this, &Spider::gameTypeChanged); 0152 } 0153 0154 QList<QAction *> Spider::configActions() const 0155 { 0156 return QList<QAction *>() << options << m_stackFaceupOption; 0157 } 0158 0159 void Spider::gameTypeChanged() 0160 { 0161 stopDemo(); 0162 0163 if (allowedToStartNewGame()) { 0164 if (options->currentItem() == 0) 0165 setSuits(1); 0166 else if (options->currentItem() == 1) 0167 setSuits(2); 0168 else if (options->currentItem() == 2) 0169 setSuits(3); 0170 else 0171 setSuits(4); 0172 0173 if (m_stackFaceup != m_stackFaceupOption->currentItem()) { 0174 m_stackFaceup = m_stackFaceupOption->currentItem(); 0175 Settings::setSpiderStackFaceup(m_stackFaceup); 0176 } 0177 0178 startNew(gameNumber()); 0179 } else { 0180 // If we're not allowed, reset the option to 0181 // the current number of suits. 0182 if (m_suits == 1) 0183 options->setCurrentItem(0); 0184 else if (m_suits == 2) 0185 options->setCurrentItem(1); 0186 else if (m_suits == 3) 0187 options->setCurrentItem(2); 0188 else 0189 options->setCurrentItem(3); 0190 0191 m_stackFaceupOption->setCurrentItem(m_stackFaceup); 0192 } 0193 } 0194 0195 void Spider::setSuits(int suits) 0196 { 0197 if (suits != m_suits) { 0198 m_suits = suits; 0199 0200 stopDemo(); 0201 clearHighlightedItems(); 0202 setKeyboardModeActive(false); 0203 0204 int cardWidth = deck()->cardWidth(); 0205 createDeck(); 0206 deck()->setCardWidth(cardWidth); 0207 0208 Settings::setSpiderSuitCount(m_suits); 0209 0210 if (m_suits == 1) 0211 options->setCurrentItem(0); 0212 else if (m_suits == 2) 0213 options->setCurrentItem(1); 0214 else if (m_suits == 3) 0215 options->setCurrentItem(2); 0216 else 0217 options->setCurrentItem(3); 0218 } 0219 } 0220 0221 void Spider::createDeck() 0222 { 0223 // These look a bit weird, but are needed to keep the game numbering 0224 // from breaking. The original logic always created groupings of 4 0225 // suits, while the new logic is more flexible. We maintain the card 0226 // ordering by always passing a list of 4 suits even if we really only 0227 // have one or two. 0228 QList<KCardDeck::Suit> suits; 0229 if (m_suits == 1) 0230 suits << KCardDeck::Spades << KCardDeck::Spades << KCardDeck::Spades << KCardDeck::Spades; 0231 else if (m_suits == 2) 0232 suits << KCardDeck::Hearts << KCardDeck::Spades << KCardDeck::Hearts << KCardDeck::Spades; 0233 else if (m_suits == 3) 0234 suits << KCardDeck::Clubs << KCardDeck::Spades << KCardDeck::Hearts << KCardDeck::Spades; 0235 else 0236 suits << KCardDeck::Clubs << KCardDeck::Diamonds << KCardDeck::Hearts << KCardDeck::Spades; 0237 0238 setDeckContents(2, suits); 0239 } 0240 0241 bool Spider::checkAdd(const PatPile *pile, const QList<KCard *> &oldCards, const QList<KCard *> &newCards) const 0242 { 0243 // assuming the cardlist is a valid unit, since I allowed 0244 // it to be removed - can drop any card on empty pile or 0245 // on any suit card of one higher rank 0246 return pile->pileRole() == PatPile::Tableau && (oldCards.isEmpty() || oldCards.last()->rank() == newCards.first()->rank() + 1); 0247 } 0248 0249 bool Spider::checkRemove(const PatPile *pile, const QList<KCard *> &cards) const 0250 { 0251 return pile->pileRole() == PatPile::Tableau && isSameSuitDescending(cards); 0252 } 0253 0254 QString Spider::getGameState() const 0255 { 0256 return QString::number(m_leg * 10 + m_redeal); 0257 } 0258 0259 void Spider::setGameState(const QString &state) 0260 { 0261 int n = state.toInt(); 0262 int numLegs = n / 10; 0263 int numRedeals = n % 10; 0264 0265 if (numRedeals != m_redeal || numLegs != m_leg) { 0266 m_redeal = numRedeals; 0267 for (int i = 0; i < 5; ++i) 0268 redeals[i]->setVisible(i >= m_redeal); 0269 0270 m_leg = numLegs; 0271 for (int i = 0; i < 8; ++i) 0272 legs[i]->setVisible(i < m_leg); 0273 0274 recalculatePileLayouts(); 0275 const auto piles = this->piles(); 0276 for (KCardPile *p : piles) 0277 updatePileLayout(p, 0); 0278 0279 Q_EMIT newCardsPossible(m_redeal <= 4); 0280 } 0281 } 0282 0283 QString Spider::getGameOptions() const 0284 { 0285 return QString::number(m_suits); 0286 } 0287 0288 void Spider::setGameOptions(const QString &options) 0289 { 0290 setSuits(options.toInt()); 0291 } 0292 0293 void Spider::restart(const QList<KCard *> &cards) 0294 { 0295 m_pilesWithRuns.clear(); 0296 0297 // make the redeal piles visible 0298 for (int i = 0; i < 5; ++i) 0299 redeals[i]->setVisible(true); 0300 0301 // make the leg piles invisible 0302 for (int i = 0; i < 8; ++i) 0303 legs[i]->setVisible(false); 0304 0305 recalculatePileLayouts(); 0306 0307 m_leg = 0; 0308 m_redeal = 0; 0309 0310 QList<KCard *> cardList = cards; 0311 0312 int column = 0; 0313 // deal face down cards (5 to first 4 piles, 4 to last 6) 0314 for (int i = 0; i < 44; ++i) { 0315 addCardForDeal(stack[column], cardList.takeLast(), m_stackFaceup == 1, randomPos()); 0316 column = (column + 1) % 10; 0317 } 0318 // deal face up cards, one to each pile 0319 for (int i = 0; i < 10; ++i) { 0320 addCardForDeal(stack[column], cardList.takeLast(), true, randomPos()); 0321 column = (column + 1) % 10; 0322 } 0323 // deal the remaining cards into 5 'redeal' piles 0324 for (int column = 0; column < 5; ++column) 0325 for (int i = 0; i < 10; ++i) 0326 addCardForDeal(redeals[column], cardList.takeLast(), false, randomPos()); 0327 0328 startDealAnimation(); 0329 0330 Q_EMIT newCardsPossible(true); 0331 } 0332 0333 void Spider::cardsMoved(const QList<KCard *> &cards, KCardPile *oldPile, KCardPile *newPile) 0334 { 0335 PatPile *p = dynamic_cast<PatPile *>(newPile); 0336 0337 // The solver treats the removal of complete runs from the table as a 0338 // separate move, so we don't do it automatically when the demo is active. 0339 if (!isDemoActive() && p && p->pileRole() == PatPile::Tableau && pileHasFullRun(p)) { 0340 m_pilesWithRuns << p; 0341 } 0342 0343 DealerScene::cardsMoved(cards, oldPile, newPile); 0344 } 0345 0346 bool Spider::pileHasFullRun(KCardPile *pile) 0347 { 0348 QList<KCard *> potentialRun = pile->topCards(13); 0349 return pile->count() >= 13 && potentialRun.first()->isFaceUp() && isSameSuitDescending(potentialRun); 0350 } 0351 0352 void Spider::moveFullRunToLeg(KCardPile *pile) 0353 { 0354 QList<KCard *> run = pile->topCards(13); 0355 0356 PatPile *leg = legs[m_leg]; 0357 ++m_leg; 0358 leg->setVisible(true); 0359 0360 recalculatePileLayouts(); 0361 for (int i = 0; i < 10; ++i) 0362 if (stack[i] != pile) 0363 updatePileLayout(stack[i], DURATION_RELAYOUT); 0364 0365 for (int i = 0; i < run.size(); ++i) { 0366 KCard *c = run.at(i); 0367 leg->add(c); 0368 int duration = DURATION_AUTODROP * (0.7 + i / 10.0); 0369 c->animate(leg->pos(), leg->zValue() + i, 0, true, true, duration); 0370 } 0371 0372 updatePileLayout(pile, DURATION_RELAYOUT); 0373 } 0374 0375 QPointF Spider::randomPos() 0376 { 0377 QRectF rect = sceneRect(); 0378 qreal x = rect.left() + QRandomGenerator::global()->bounded(rect.width() - deck()->cardWidth()); 0379 qreal y = rect.top() + QRandomGenerator::global()->bounded(rect.height() - deck()->cardHeight()); 0380 return QPointF(x, y); 0381 } 0382 0383 bool Spider::newCards() 0384 { 0385 // The solver doesn't distinguish between dealing a new row of cards and 0386 // removing complete runs from the tableau. So it we're in demo mode and 0387 // newCards() is called, we should check to see if there are any complete 0388 // runs to move before dealing a new row. 0389 if (isDemoActive()) { 0390 for (int i = 0; i < 10; ++i) { 0391 if (pileHasFullRun(stack[i])) { 0392 moveFullRunToLeg(stack[i]); 0393 return true; 0394 } 0395 } 0396 } 0397 0398 if (m_redeal > 4) 0399 return false; 0400 0401 redeals[m_redeal]->setVisible(false); 0402 recalculatePileLayouts(); 0403 0404 for (int column = 0; column < 10; ++column) { 0405 KCard *c = redeals[m_redeal]->topCard(); 0406 if (!c) 0407 break; 0408 0409 flipCardToPileAtSpeed(c, stack[column], DEAL_SPEED); 0410 c->setZValue(c->zValue() + 10 - column); 0411 } 0412 0413 ++m_redeal; 0414 0415 if (m_redeal > 4) 0416 Q_EMIT newCardsPossible(false); 0417 0418 return true; 0419 } 0420 0421 void Spider::animationDone() 0422 { 0423 if (!m_pilesWithRuns.isEmpty()) 0424 moveFullRunToLeg(m_pilesWithRuns.takeFirst()); 0425 else 0426 DealerScene::animationDone(); 0427 } 0428 0429 void Spider::mapOldId(int id) 0430 { 0431 switch (id) { 0432 case DealerInfo::SpiderOneSuitId: 0433 setSuits(1); 0434 break; 0435 case DealerInfo::SpiderTwoSuitId: 0436 setSuits(2); 0437 break; 0438 case DealerInfo::SpiderThreeSuitId: 0439 setSuits(3); 0440 break; 0441 case DealerInfo::SpiderFourSuitId: 0442 setSuits(4); 0443 break; 0444 } 0445 } 0446 0447 int Spider::oldId() const 0448 { 0449 switch (m_suits) { 0450 case 1: 0451 return DealerInfo::SpiderOneSuitId; 0452 case 2: 0453 return DealerInfo::SpiderTwoSuitId; 0454 case 3: 0455 return DealerInfo::SpiderThreeSuitId; 0456 case 4: 0457 default: 0458 return DealerInfo::SpiderFourSuitId; 0459 } 0460 } 0461 0462 static class SpideDealerInfo : public DealerInfo 0463 { 0464 public: 0465 SpideDealerInfo() 0466 : DealerInfo(kli18n("Spider"), SpiderGeneralId) 0467 { 0468 addSubtype(SpiderOneSuitId, kli18n("Spider (1 Suit)")); 0469 addSubtype(SpiderTwoSuitId, kli18n("Spider (2 Suit)")); 0470 addSubtype(SpiderThreeSuitId, kli18n("Spider (3 Suit)")); 0471 addSubtype(SpiderFourSuitId, kli18n("Spider (4 Suit)")); 0472 } 0473 0474 DealerScene *createGame() const override 0475 { 0476 return new Spider(this); 0477 } 0478 } spideDealerInfo; 0479 0480 #include "moc_spider.cpp"