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

0001 /*
0002  *   Copyright 2021 Michael Lang <criticaltemp@protonmail.com>
0003  *
0004  *   This program is free software; you can redistribute it and/or
0005  *   modify it under the terms of the GNU General Public License as
0006  *   published by the Free Software Foundation; either version 2 of
0007  *   the License, or (at your option) any later version.
0008  *
0009  *   This program is distributed in the hope that it will be useful,
0010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012  *   GNU General Public License for more details.
0013  *
0014  *   You should have received a copy of the GNU General Public License
0015  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
0016  */
0017 
0018 #include "bakersdozen.h"
0019 
0020 // own
0021 #include "dealerinfo.h"
0022 #include "patsolve/bakersdozensolver.h"
0023 #include "pileutils.h"
0024 #include "settings.h"
0025 #include "speeds.h"
0026 // KF
0027 #include <KLocalizedString>
0028 #include <KSelectAction>
0029 
0030 BakersDozen::BakersDozen(const DealerInfo *di)
0031     : DealerScene(di)
0032 {
0033 }
0034 
0035 void BakersDozen::initialize()
0036 {
0037     setDeckContents();
0038 
0039     const qreal dist_x = 1.11;
0040 
0041     for (int i = 0; i < 4; ++i) {
0042         target[i] = new PatPile(this, i + 1, QStringLiteral("target%1").arg(i));
0043         target[i]->setPileRole(PatPile::Foundation);
0044         target[i]->setLayoutPos((i * 2 + 3) * dist_x, 0);
0045         target[i]->setSpread(0, 0);
0046         target[i]->setKeyboardSelectHint(KCardPile::NeverFocus);
0047         target[i]->setKeyboardDropHint(KCardPile::AutoFocusTop);
0048     }
0049 
0050     for (int i = 0; i < 13; ++i) {
0051         store[i] = new PatPile(this, 0 + i, QStringLiteral("store%1").arg(i));
0052         store[i]->setPileRole(PatPile::Tableau);
0053         store[i]->setLayoutPos(dist_x * i, 1.2);
0054         store[i]->setAutoTurnTop(true);
0055         store[i]->setBottomPadding(2.0);
0056         store[i]->setHeightPolicy(KCardPile::GrowDown);
0057         store[i]->setZValue(0.01 * i);
0058         store[i]->setKeyboardSelectHint(KCardPile::FreeFocus);
0059         store[i]->setKeyboardDropHint(KCardPile::AutoFocusTop);
0060     }
0061 
0062     setActions(DealerScene::Hint | DealerScene::Demo);
0063     auto solver = new BakersDozenSolver(this);
0064     solver->default_max_positions = Settings::bakersDozenSolverIterationsLimit();
0065     setSolver(solver);
0066     setNeededFutureMoves(4); // reserve some
0067 
0068     options = new KSelectAction(i18n("Popular Variant Presets"), this);
0069     options->addAction(i18n("Baker's Dozen"));
0070     options->addAction(i18n("Spanish Patience"));
0071     options->addAction(i18n("Castles in Spain"));
0072     options->addAction(i18n("Portuguese Solitaire"));
0073     options->addAction(i18n("Custom"));
0074 
0075     m_emptyStackFillOption = new KSelectAction(i18n("Empty Stack Fill"), this);
0076     m_emptyStackFillOption->addAction(i18n("Any (Easy)"));
0077     m_emptyStackFillOption->addAction(i18n("Kings only (Medium)"));
0078     m_emptyStackFillOption->addAction(i18n("None (Hard)"));
0079 
0080     m_stackFacedownOption = new KSelectAction(i18n("Stack Options"), this);
0081     m_stackFacedownOption->addAction(i18n("Face &Up (Easier)"));
0082     m_stackFacedownOption->addAction(i18n("Face &Down (Harder)"));
0083 
0084     m_sequenceBuiltByOption = new KSelectAction(i18n("Build Sequence"), this);
0085     m_sequenceBuiltByOption->addAction(i18n("Alternating Color"));
0086     m_sequenceBuiltByOption->addAction(i18n("Matching Suit"));
0087     m_sequenceBuiltByOption->addAction(i18n("Rank"));
0088 
0089     connect(options, &KSelectAction::indexTriggered, this, &BakersDozen::gameTypeChanged);
0090     connect(m_emptyStackFillOption, &KSelectAction::indexTriggered, this, &BakersDozen::gameTypeChanged);
0091     connect(m_stackFacedownOption, &KSelectAction::indexTriggered, this, &BakersDozen::gameTypeChanged);
0092     connect(m_sequenceBuiltByOption, &KSelectAction::indexTriggered, this, &BakersDozen::gameTypeChanged);
0093 
0094     getSavedOptions();
0095 }
0096 
0097 QList<QAction *> BakersDozen::configActions() const
0098 {
0099     return QList<QAction *>() << options << m_emptyStackFillOption << m_stackFacedownOption << m_sequenceBuiltByOption;
0100 }
0101 
0102 void BakersDozen::gameTypeChanged()
0103 {
0104     stopDemo();
0105 
0106     if (allowedToStartNewGame()) {
0107         if (m_variation != options->currentItem()) {
0108             setOptions(options->currentItem());
0109         } else {
0110             // update option selections
0111             if (m_emptyStackFill != m_emptyStackFillOption->currentItem())
0112                 m_emptyStackFill = m_emptyStackFillOption->currentItem();
0113             else if (m_stackFacedown != m_stackFacedownOption->currentItem())
0114                 m_stackFacedown = m_stackFacedownOption->currentItem();
0115             else if (m_sequenceBuiltBy != m_sequenceBuiltByOption->currentItem())
0116                 m_sequenceBuiltBy = m_sequenceBuiltByOption->currentItem();
0117 
0118             matchVariant();
0119         }
0120 
0121         auto solver = new BakersDozenSolver(this);
0122         solver->default_max_positions = Settings::bakersDozenSolverIterationsLimit();
0123         setSolver(solver);
0124         startNew(gameNumber());
0125         setSavedOptions();
0126     } else {
0127         // If we're not allowed, reset the options
0128         getSavedOptions();
0129     }
0130 }
0131 
0132 void BakersDozen::restart(const QList<KCard *> &cards)
0133 {
0134     QList<KCard *> cardList = cards;
0135 
0136     QPointF initPos(0, -deck()->cardHeight());
0137 
0138     for (int row = 0; row < 4; ++row) {
0139         bool isFaceUp = m_stackFacedown == 1 && row < 3 ? false : true;
0140 
0141         for (int column = 0; column < 13; ++column) {
0142             addCardForDeal(store[column], cardList.takeLast(), isFaceUp, initPos);
0143         }
0144     }
0145 
0146     // Move kings to bottom of pile without changing relative order
0147     for (int column = 0; column < 13; ++column) {
0148         int counter = 0;
0149 
0150         const auto cards = store[column]->cards();
0151         for (KCard *c : cards) {
0152             if (c->rank() == KCardDeck::King) {
0153                 int index = store[column]->indexOf(c);
0154                 store[column]->swapCards(index, counter);
0155                 counter++;
0156 
0157                 if (m_stackFacedown == 1)
0158                     c->setFaceUp(false);
0159             }
0160         }
0161     }
0162 
0163     startDealAnimation();
0164 }
0165 
0166 QString BakersDozen::solverFormat() const
0167 {
0168     QString output;
0169     QString tmp;
0170     for (int i = 0; i < 4; i++) {
0171         if (target[i]->isEmpty())
0172             continue;
0173         tmp += suitToString(target[i]->topCard()->suit()) + QLatin1Char('-') + rankToString(target[i]->topCard()->rank()) + QLatin1Char(' ');
0174     }
0175     if (!tmp.isEmpty())
0176         output += QStringLiteral("Foundations: %1\n").arg(tmp);
0177 
0178     for (int i = 0; i < 13; i++)
0179         cardsListToLine(output, store[i]->cards());
0180     return output;
0181 }
0182 
0183 bool BakersDozen::checkAdd(const PatPile *pile, const QList<KCard *> &oldCards, const QList<KCard *> &newCards) const
0184 {
0185     if (pile->pileRole() == PatPile::Tableau) {
0186         int freeStores = 0;
0187         if (m_emptyStackFill == 0) {
0188             for (int i = 0; i < 13; ++i)
0189                 if (store[i]->isEmpty() && store[i] != pile)
0190                     ++freeStores;
0191         }
0192 
0193         if (newCards.size() <= 1 << freeStores) {
0194             if (oldCards.isEmpty())
0195                 return m_emptyStackFill == 0 || (m_emptyStackFill == 1 && newCards.first()->rank() == KCardDeck::King);
0196             else if (m_sequenceBuiltBy == 1)
0197                 return newCards.first()->suit() == oldCards.last()->suit() && newCards.first()->rank() == oldCards.last()->rank() - 1;
0198             else if (m_sequenceBuiltBy == 0)
0199                 return checkAddAlternateColorDescending(oldCards, newCards);
0200             else
0201                 return newCards.first()->rank() == oldCards.last()->rank() - 1;
0202         } else {
0203             return false;
0204         }
0205     } else {
0206         return checkAddSameSuitAscendingFromAce(oldCards, newCards);
0207     }
0208 }
0209 
0210 bool BakersDozen::checkRemove(const PatPile *pile, const QList<KCard *> &cards) const
0211 {
0212     switch (pile->pileRole()) {
0213     case PatPile::Tableau:
0214         if (m_emptyStackFill == 2) {
0215             return cards.size() == 1;
0216         } else {
0217             if (m_sequenceBuiltBy == 1)
0218                 return isSameSuitDescending(cards);
0219             else if (m_sequenceBuiltBy == 0)
0220                 return isAlternateColorDescending(cards);
0221             else
0222                 return isRankDescending(cards);
0223         }
0224     case PatPile::Cell:
0225         return cards.first() == pile->topCard();
0226     case PatPile::Foundation:
0227         return true;
0228     default:
0229         return false;
0230     }
0231 }
0232 
0233 static class BakersDozenDealerInfo : public DealerInfo
0234 {
0235 public:
0236     BakersDozenDealerInfo()
0237         : DealerInfo(kli18n("Baker's Dozen"), BakersDozenGeneralId)
0238     {
0239         addSubtype(BakersDozenId, kli18n("Baker's Dozen"));
0240         addSubtype(BakersDozenSpanishId, kli18n("Spanish Patience"));
0241         addSubtype(BakersDozenCastlesId, kli18n("Castles in Spain"));
0242         addSubtype(BakersDozenPortugueseId, kli18n("Portuguese Solitaire"));
0243         addSubtype(BakersDozenCustomId, kli18n("Baker's Dozen (Custom)"));
0244     }
0245 
0246     DealerScene *createGame() const override
0247     {
0248         return new BakersDozen(this);
0249     }
0250 } BakersDozenDealerInfo;
0251 
0252 void BakersDozen::matchVariant()
0253 {
0254     if (m_emptyStackFill == 2 && m_stackFacedown == 0 && m_sequenceBuiltBy == 2)
0255         m_variation = 0;
0256     else if (m_emptyStackFill == 0 && m_stackFacedown == 0 && m_sequenceBuiltBy == 2)
0257         m_variation = 1;
0258     else if (m_emptyStackFill == 0 && m_stackFacedown == 1 && m_sequenceBuiltBy == 0)
0259         m_variation = 2;
0260     else if (m_emptyStackFill == 1 && m_stackFacedown == 0 && m_sequenceBuiltBy == 2)
0261         m_variation = 3;
0262     else
0263         m_variation = 4;
0264 
0265     options->setCurrentItem(m_variation);
0266 }
0267 
0268 void BakersDozen::setSavedOptions()
0269 {
0270     Settings::setBakersDozenEmptyStackFill(m_emptyStackFill);
0271     Settings::setBakersDozenStackFacedown(m_stackFacedown);
0272     Settings::setBakersDozenSequenceBuiltBy(m_sequenceBuiltBy);
0273 }
0274 
0275 void BakersDozen::getSavedOptions()
0276 {
0277     m_emptyStackFill = Settings::bakersDozenEmptyStackFill();
0278     m_stackFacedown = Settings::bakersDozenStackFacedown();
0279     m_sequenceBuiltBy = Settings::bakersDozenSequenceBuiltBy();
0280 
0281     matchVariant();
0282 
0283     m_emptyStackFillOption->setCurrentItem(m_emptyStackFill);
0284     m_stackFacedownOption->setCurrentItem(m_stackFacedown);
0285     m_sequenceBuiltByOption->setCurrentItem(m_sequenceBuiltBy);
0286 }
0287 
0288 void BakersDozen::mapOldId(int id)
0289 {
0290     switch (id) {
0291     case DealerInfo::BakersDozenId:
0292         setOptions(0);
0293         break;
0294     case DealerInfo::BakersDozenSpanishId:
0295         setOptions(1);
0296         break;
0297     case DealerInfo::BakersDozenCastlesId:
0298         setOptions(2);
0299         break;
0300     case DealerInfo::BakersDozenPortugueseId:
0301         setOptions(3);
0302         break;
0303     case DealerInfo::BakersDozenCustomId:
0304         setOptions(4);
0305         break;
0306     default:
0307         // Do nothing.
0308         break;
0309     }
0310 }
0311 
0312 int BakersDozen::oldId() const
0313 {
0314     switch (m_variation) {
0315     case 0:
0316         return DealerInfo::BakersDozenId;
0317     case 1:
0318         return DealerInfo::BakersDozenSpanishId;
0319     case 2:
0320         return DealerInfo::BakersDozenCastlesId;
0321     case 3:
0322         return DealerInfo::BakersDozenPortugueseId;
0323     default:
0324         return DealerInfo::BakersDozenCustomId;
0325     }
0326 }
0327 
0328 void BakersDozen::setOptions(int variation)
0329 {
0330     if (variation != m_variation) {
0331         m_variation = variation;
0332 
0333         switch (m_variation) {
0334         case 0:
0335             m_emptyStackFill = 2;
0336             m_stackFacedown = 0;
0337             m_sequenceBuiltBy = 2;
0338             break;
0339         case 1:
0340             m_emptyStackFill = 0;
0341             m_stackFacedown = 0;
0342             m_sequenceBuiltBy = 2;
0343             break;
0344         case 2:
0345             m_emptyStackFill = 0;
0346             m_stackFacedown = 1;
0347             m_sequenceBuiltBy = 0;
0348             break;
0349         case 3:
0350             m_emptyStackFill = 1;
0351             m_stackFacedown = 0;
0352             m_sequenceBuiltBy = 2;
0353             break;
0354         case 4:
0355             m_emptyStackFill = 0;
0356             m_stackFacedown = 1;
0357             m_sequenceBuiltBy = 1;
0358             break;
0359         }
0360 
0361         m_emptyStackFillOption->setCurrentItem(m_emptyStackFill);
0362         m_stackFacedownOption->setCurrentItem(m_stackFacedown);
0363         m_sequenceBuiltByOption->setCurrentItem(m_sequenceBuiltBy);
0364     }
0365 }
0366 
0367 #include "moc_bakersdozen.cpp"