File indexing completed on 2024-05-12 08:00:36

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"