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

0001 /*
0002  * Copyright (C) 1997 Rodolfo Borges <barrett@labma.ufrj.br>
0003  * Copyright (C) 1998-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 "freecell.h"
0039 
0040 // own
0041 #include "dealerinfo.h"
0042 #include "kpat_debug.h"
0043 #include "patsolve/freecellsolver.h"
0044 #include "pileutils.h"
0045 #include "settings.h"
0046 #include "speeds.h"
0047 // KF
0048 #include <KLocalizedString>
0049 #include <KSelectAction>
0050 
0051 Freecell::Freecell(const DealerInfo *di)
0052     : DealerScene(di)
0053 {
0054     configOptions();
0055     getSavedOptions();
0056 }
0057 
0058 void Freecell::initialize()
0059 {
0060     setDeckContents(m_decks + 1);
0061 
0062     const bool isRightFoundation = m_reserves + 4 * (m_decks + 1) > (m_stacks + 6);
0063     const qreal topRowDist = isRightFoundation ? 1.13 : 1.08;
0064     const qreal bottomRowDist = 1.13;
0065     const qreal targetOffsetDist = ((m_stacks + 5) * bottomRowDist + 1) - (3 * topRowDist + 1) * (m_decks + 1);
0066 
0067     for (int i = 0; i < m_reserves; ++i) {
0068         freecell[i] = new PatPile(this, 1 + i, QStringLiteral("freecell%1").arg(i));
0069         freecell[i]->setPileRole(PatPile::Cell);
0070         freecell[i]->setLayoutPos(topRowDist * i, 0);
0071         freecell[i]->setKeyboardSelectHint(KCardPile::AutoFocusTop);
0072         freecell[i]->setKeyboardDropHint(KCardPile::AutoFocusTop);
0073     }
0074 
0075     for (int i = 0; i < (m_stacks + 6); ++i) {
0076         store[i] = new PatPile(this, 1 + i, QStringLiteral("store%1").arg(i));
0077         store[i]->setPileRole(PatPile::Tableau);
0078         store[i]->setLayoutPos(bottomRowDist * i, 1.3);
0079         store[i]->setBottomPadding(2.5);
0080         store[i]->setHeightPolicy(KCardPile::GrowDown);
0081         store[i]->setKeyboardSelectHint(KCardPile::AutoFocusDeepestRemovable);
0082         store[i]->setKeyboardDropHint(KCardPile::AutoFocusTop);
0083     }
0084 
0085     if (isRightFoundation) {
0086         const int columns = std::max(m_reserves, m_stacks + 6);
0087         for (int i = 0; i < 4 * (m_decks + 1); ++i) {
0088             const qreal offsetX = 0.25 + columns * bottomRowDist + i / 4 * bottomRowDist;
0089             const qreal offsetY = bottomRowDist * i - i / 4 * bottomRowDist * 4;
0090             target[i] = new PatPile(this, 1 + i, QStringLiteral("target%1").arg(i));
0091             target[i]->setPileRole(PatPile::Foundation);
0092             target[i]->setLayoutPos(offsetX, offsetY);
0093             target[i]->setKeyboardSelectHint(KCardPile::NeverFocus);
0094             target[i]->setKeyboardDropHint(KCardPile::ForceFocusTop);
0095         }
0096     } else {
0097         for (int i = 0; i < 4 * (m_decks + 1); ++i) {
0098             target[i] = new PatPile(this, 1 + i, QStringLiteral("target%1").arg(i));
0099             target[i]->setPileRole(PatPile::Foundation);
0100             target[i]->setLayoutPos(targetOffsetDist + topRowDist * i, 0);
0101             target[i]->setSpread(0, 0);
0102             target[i]->setKeyboardSelectHint(KCardPile::NeverFocus);
0103             target[i]->setKeyboardDropHint(KCardPile::ForceFocusTop);
0104         }
0105     }
0106 
0107     setActions(DealerScene::Demo | DealerScene::Hint);
0108     auto solver = new FreecellSolver(this);
0109     solver->default_max_positions = Settings::freecellSolverIterationsLimit();
0110     setSolver(solver);
0111     setNeededFutureMoves(4); // reserve some
0112 }
0113 
0114 QList<QAction *> Freecell::configActions() const
0115 {
0116     return QList<QAction *>() << options << m_emptyStackFillOption << m_sequenceBuiltByOption << m_reservesOption << m_stacksOption;
0117 }
0118 
0119 void Freecell::gameTypeChanged()
0120 {
0121     stopDemo();
0122 
0123     if (allowedToStartNewGame()) {
0124         if (m_variation != options->currentItem()) {
0125             setOptions(options->currentItem());
0126         } else {
0127             // update option selections
0128             if (m_emptyStackFill != m_emptyStackFillOption->currentItem())
0129                 m_emptyStackFill = m_emptyStackFillOption->currentItem();
0130             else if (m_sequenceBuiltBy != m_sequenceBuiltByOption->currentItem())
0131                 m_sequenceBuiltBy = m_sequenceBuiltByOption->currentItem();
0132             else if (m_reserves != m_reservesOption->currentItem())
0133                 m_reserves = m_reservesOption->currentItem();
0134             else if (m_stacks != m_stacksOption->currentItem())
0135                 m_stacks = m_stacksOption->currentItem();
0136             else if (m_decks != m_decksOption->currentItem())
0137                 m_decks = m_decksOption->currentItem();
0138 
0139             matchVariant();
0140         }
0141 
0142         // remove existing piles
0143         clearPatPiles();
0144 
0145         initialize();
0146         relayoutScene();
0147         startNew(gameNumber());
0148         setSavedOptions();
0149     } else {
0150         // If we're not allowed, reset the options
0151         getSavedOptions();
0152     }
0153 }
0154 
0155 void Freecell::restart(const QList<KCard *> &cards)
0156 {
0157     QList<KCard *> cardList = cards;
0158 
0159     // Prefill reserves for select game types
0160     if (m_variation == 4)
0161         for (int i = 0; i < 2; ++i)
0162             addCardForDeal(freecell[i], cardList.takeLast(), true, freecell[0]->pos());
0163     else if (m_variation == 1 || m_variation == 2)
0164         for (int i = 0; i < 4; ++i)
0165             addCardForDeal(freecell[i], cardList.takeLast(), true, freecell[0]->pos());
0166 
0167     int column = 0;
0168     while (!cardList.isEmpty()) {
0169         addCardForDeal(store[column], cardList.takeLast(), true, store[0]->pos());
0170         column = (column + 1) % (m_stacks + 6);
0171     }
0172 
0173     startDealAnimation();
0174 }
0175 
0176 QString Freecell::solverFormat() const
0177 {
0178     QString output;
0179     QString tmp;
0180     for (int i = 0; i < 4 * (m_decks + 1); i++) {
0181         if (target[i]->isEmpty())
0182             continue;
0183         tmp += suitToString(target[i]->topCard()->suit()) + QLatin1Char('-') + rankToString(target[i]->topCard()->rank()) + QLatin1Char(' ');
0184     }
0185     if (!tmp.isEmpty())
0186         output += QStringLiteral("Foundations: %1\n").arg(tmp);
0187 
0188     tmp.truncate(0);
0189     for (int i = 0; i < m_reserves; i++) {
0190         const auto fc = freecell[i];
0191         tmp += (fc->isEmpty() ? QStringLiteral("-") : cardToRankSuitString(fc->topCard())) + QLatin1Char(' ');
0192     }
0193     if (!tmp.isEmpty()) {
0194         QString a = QStringLiteral("Freecells: %1\n");
0195         output += a.arg(tmp);
0196     }
0197 
0198     for (int i = 0; i < (m_stacks + 6); i++)
0199         cardsListToLine(output, store[i]->cards());
0200     return output;
0201 }
0202 
0203 void Freecell::cardsDroppedOnPile(const QList<KCard *> &cards, KCardPile *pile)
0204 {
0205     if (cards.size() <= 1) {
0206         DealerScene::moveCardsToPile(cards, pile, DURATION_MOVE);
0207         return;
0208     }
0209 
0210     QList<KCardPile *> freeCells;
0211     for (int i = 0; i < m_reserves; ++i)
0212         if (freecell[i]->isEmpty())
0213             freeCells << freecell[i];
0214 
0215     QList<KCardPile *> freeStores;
0216     for (int i = 0; i < (m_stacks + 6); ++i)
0217         if (store[i]->isEmpty() && store[i] != pile)
0218             freeStores << store[i];
0219 
0220     multiStepMove(cards, pile, freeStores, freeCells, DURATION_MOVE);
0221 }
0222 
0223 bool Freecell::tryAutomaticMove(KCard *c)
0224 {
0225     // target move
0226     if (DealerScene::tryAutomaticMove(c))
0227         return true;
0228 
0229     if (c->isAnimated())
0230         return false;
0231 
0232     if (allowedToRemove(c->pile(), c) && c == c->pile()->topCard()) {
0233         for (int i = 0; i < m_reserves; i++) {
0234             if (allowedToAdd(freecell[i], {c})) {
0235                 moveCardToPile(c, freecell[i], DURATION_MOVE);
0236                 return true;
0237             }
0238         }
0239     }
0240     return false;
0241 }
0242 
0243 bool Freecell::canPutStore(const KCardPile *pile, const QList<KCard *> &cards) const
0244 {
0245     int freeCells = 0;
0246     for (int i = 0; i < m_reserves; ++i)
0247         if (freecell[i]->isEmpty())
0248             ++freeCells;
0249 
0250     int freeStores = 0;
0251     if (m_emptyStackFill == 0) {
0252         for (int i = 0; i < (m_stacks + 6); ++i)
0253             if (store[i]->isEmpty() && store[i] != pile)
0254                 ++freeStores;
0255     }
0256 
0257     if (cards.size() <= (freeCells + 1) << freeStores) {
0258         if (pile->isEmpty())
0259             return m_emptyStackFill == 0 || (m_emptyStackFill == 1 && cards.first()->rank() == KCardDeck::King);
0260         else if (m_sequenceBuiltBy == 1)
0261             return cards.first()->rank() == pile->topCard()->rank() - 1 && cards.first()->suit() == pile->topCard()->suit();
0262         else if (m_sequenceBuiltBy == 0)
0263             return cards.first()->rank() == pile->topCard()->rank() - 1 && pile->topCard()->color() != cards.first()->color();
0264         else
0265             return cards.first()->rank() == pile->topCard()->rank() - 1;
0266     } else {
0267         return false;
0268     }
0269 }
0270 
0271 bool Freecell::checkAdd(const PatPile *pile, const QList<KCard *> &oldCards, const QList<KCard *> &newCards) const
0272 {
0273     switch (pile->pileRole()) {
0274     case PatPile::Tableau:
0275         return canPutStore(pile, newCards);
0276     case PatPile::Cell:
0277         return oldCards.isEmpty() && newCards.size() == 1;
0278     case PatPile::Foundation:
0279         return checkAddSameSuitAscendingFromAce(oldCards, newCards);
0280     default:
0281         return false;
0282     }
0283 }
0284 
0285 bool Freecell::checkRemove(const PatPile *pile, const QList<KCard *> &cards) const
0286 {
0287     switch (pile->pileRole()) {
0288     case PatPile::Tableau:
0289         if (m_sequenceBuiltBy == 1)
0290             return isSameSuitDescending(cards);
0291         else if (m_sequenceBuiltBy == 0)
0292             return isAlternateColorDescending(cards);
0293         else
0294             return isRankDescending(cards);
0295     case PatPile::Cell:
0296         return cards.first() == pile->topCard();
0297     case PatPile::Foundation:
0298     default:
0299         return false;
0300     }
0301 }
0302 
0303 static class FreecellDealerInfo : public DealerInfo
0304 {
0305 public:
0306     FreecellDealerInfo()
0307         : DealerInfo(kli18n("Freecell"), FreecellGeneralId)
0308     {
0309         addSubtype(FreecellBakersId, kli18n("Baker's Game"));
0310         addSubtype(FreecellEightOffId, kli18n("Eight Off"));
0311         addSubtype(FreecellForeId, kli18n("Forecell"));
0312         addSubtype(FreecellId, kli18n("Freecell"));
0313         addSubtype(FreecellSeahavenId, kli18n("Seahaven Towers"));
0314         addSubtype(FreecellCustomId, kli18n("Freecell (Custom)"));
0315     }
0316 
0317     DealerScene *createGame() const override
0318     {
0319         return new Freecell(this);
0320     }
0321 } freecellDealerInfo;
0322 
0323 void Freecell::matchVariant()
0324 {
0325     if (m_emptyStackFill == 0 && m_sequenceBuiltBy == 1 && m_reserves == 4 && m_stacks == 2)
0326         m_variation = 0;
0327     else if (m_emptyStackFill == 1 && m_sequenceBuiltBy == 1 && m_reserves == 8 && m_stacks == 2)
0328         m_variation = 1;
0329     else if (m_emptyStackFill == 1 && m_sequenceBuiltBy == 0 && m_reserves == 4 && m_stacks == 2)
0330         m_variation = 2;
0331     else if (m_emptyStackFill == 0 && m_sequenceBuiltBy == 0 && m_reserves == 4 && m_stacks == 2)
0332         m_variation = 3;
0333     else if (m_emptyStackFill == 1 && m_sequenceBuiltBy == 1 && m_reserves == 4 && m_stacks == 4)
0334         m_variation = 4;
0335     else
0336         m_variation = 5;
0337 
0338     options->setCurrentItem(m_variation);
0339 }
0340 
0341 void Freecell::configOptions()
0342 {
0343     options = new KSelectAction(i18n("Popular Variant Presets"), this);
0344     options->addAction(i18n("Baker's Game"));
0345     options->addAction(i18n("Eight Off"));
0346     options->addAction(i18n("Forecell"));
0347     options->addAction(i18n("Freecell"));
0348     options->addAction(i18n("Seahaven Towers"));
0349     options->addAction(i18n("Custom"));
0350 
0351     m_emptyStackFillOption = new KSelectAction(i18n("Empty Stack Fill"), this);
0352     m_emptyStackFillOption->addAction(i18n("Any (Easy)"));
0353     m_emptyStackFillOption->addAction(i18n("Kings only (Medium)"));
0354     m_emptyStackFillOption->addAction(i18n("None (Hard)"));
0355 
0356     m_sequenceBuiltByOption = new KSelectAction(i18n("Build Sequence"), this);
0357     m_sequenceBuiltByOption->addAction(i18n("Alternating Color"));
0358     m_sequenceBuiltByOption->addAction(i18n("Matching Suit"));
0359     m_sequenceBuiltByOption->addAction(i18n("Rank"));
0360 
0361     m_reservesOption = new KSelectAction(i18n("Free cells"), this);
0362     m_reservesOption->addAction(i18n("0"));
0363     m_reservesOption->addAction(i18n("1"));
0364     m_reservesOption->addAction(i18n("2"));
0365     m_reservesOption->addAction(i18n("3"));
0366     m_reservesOption->addAction(i18n("4"));
0367     m_reservesOption->addAction(i18n("5"));
0368     m_reservesOption->addAction(i18n("6"));
0369     m_reservesOption->addAction(i18n("7"));
0370     m_reservesOption->addAction(i18n("8"));
0371 
0372     m_stacksOption = new KSelectAction(i18n("Stacks"), this);
0373     m_stacksOption->addAction(i18n("6"));
0374     m_stacksOption->addAction(i18n("7"));
0375     m_stacksOption->addAction(i18n("8"));
0376     m_stacksOption->addAction(i18n("9"));
0377     m_stacksOption->addAction(i18n("10"));
0378     m_stacksOption->addAction(i18n("11"));
0379     m_stacksOption->addAction(i18n("12"));
0380 
0381     m_decksOption = new KSelectAction(i18n("Decks"), this);
0382     m_decksOption->addAction(i18n("1"));
0383     m_decksOption->addAction(i18n("2"));
0384     m_decksOption->addAction(i18n("3"));
0385 
0386     connect(options, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged);
0387     connect(m_emptyStackFillOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged);
0388     connect(m_reservesOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged);
0389     connect(m_sequenceBuiltByOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged);
0390     connect(m_stacksOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged);
0391     connect(m_decksOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged);
0392 }
0393 
0394 void Freecell::setSavedOptions()
0395 {
0396     Settings::setFreecellEmptyStackFill(m_emptyStackFill);
0397     Settings::setFreecellSequenceBuiltBy(m_sequenceBuiltBy);
0398     Settings::setFreecellReserves(m_reserves);
0399     Settings::setFreecellStacks(m_stacks);
0400     Settings::setFreecellDecks(m_decks);
0401 }
0402 
0403 void Freecell::getSavedOptions()
0404 {
0405     m_emptyStackFill = Settings::freecellEmptyStackFill();
0406     m_sequenceBuiltBy = Settings::freecellSequenceBuiltBy();
0407     m_reserves = Settings::freecellReserves();
0408     m_stacks = Settings::freecellStacks();
0409     m_decks = Settings::freecellDecks();
0410 
0411     matchVariant();
0412 
0413     m_emptyStackFillOption->setCurrentItem(m_emptyStackFill);
0414     m_sequenceBuiltByOption->setCurrentItem(m_sequenceBuiltBy);
0415     m_reservesOption->setCurrentItem(m_reserves);
0416     m_stacksOption->setCurrentItem(m_stacks);
0417     m_decksOption->setCurrentItem(m_decks);
0418 }
0419 
0420 void Freecell::mapOldId(int id)
0421 {
0422     switch (id) {
0423     case DealerInfo::FreecellBakersId:
0424         setOptions(0);
0425         break;
0426     case DealerInfo::FreecellEightOffId:
0427         setOptions(1);
0428         break;
0429     case DealerInfo::FreecellForeId:
0430         setOptions(2);
0431         break;
0432     case DealerInfo::FreecellId:
0433         setOptions(3);
0434         break;
0435     case DealerInfo::FreecellSeahavenId:
0436         setOptions(4);
0437         break;
0438     case DealerInfo::FreecellCustomId:
0439         setOptions(5);
0440         break;
0441     default:
0442         // Do nothing.
0443         break;
0444     }
0445 }
0446 
0447 int Freecell::oldId() const
0448 {
0449     switch (m_variation) {
0450     case 0:
0451         return DealerInfo::FreecellBakersId;
0452     case 1:
0453         return DealerInfo::FreecellEightOffId;
0454     case 2:
0455         return DealerInfo::FreecellForeId;
0456     case 3:
0457         return DealerInfo::FreecellId;
0458     case 4:
0459         return DealerInfo::FreecellSeahavenId;
0460     default:
0461         return DealerInfo::FreecellCustomId;
0462     }
0463 }
0464 
0465 void Freecell::setOptions(int variation)
0466 {
0467     if (variation != m_variation) {
0468         m_variation = variation;
0469         m_emptyStackFill = 0;
0470         m_sequenceBuiltBy = 0;
0471         m_reserves = 4;
0472         m_stacks = 2;
0473         m_decks = 0;
0474 
0475         switch (m_variation) {
0476         case 0:
0477             m_sequenceBuiltBy = 1;
0478             break;
0479         case 1:
0480             m_emptyStackFill = 1;
0481             m_sequenceBuiltBy = 1;
0482             m_reserves = 8;
0483             break;
0484         case 2:
0485             m_emptyStackFill = 1;
0486             break;
0487         case 3:
0488             break;
0489         case 4:
0490             m_emptyStackFill = 1;
0491             m_sequenceBuiltBy = 1;
0492             m_stacks = 4;
0493             break;
0494         case 5:
0495             m_emptyStackFill = 2;
0496             m_sequenceBuiltBy = 2;
0497             break;
0498         }
0499 
0500         m_emptyStackFillOption->setCurrentItem(m_emptyStackFill);
0501         m_sequenceBuiltByOption->setCurrentItem(m_sequenceBuiltBy);
0502         m_reservesOption->setCurrentItem(m_reserves);
0503         m_stacksOption->setCurrentItem(m_stacks);
0504         m_decksOption->setCurrentItem(m_decks);
0505     }
0506 }
0507 
0508 #include "moc_freecell.cpp"