Git commit 96474ebab01f45bef41cfa34b23ddb39179debfe by Albert Astals Cid, on behalf of Michael Lang. Committed on 11/03/2021 at 23:56. Pushed by aacid into branch 'master'.
Added several Freecell game variants: Baker's Game Eight Off Forecell Seahaven Towers Other custom configurations M +22 -0 doc/index.docbook M +8 -2 src/dealerinfo.h M +352 -85 src/freecell.cpp M +34 -4 src/freecell.h M +15 -0 src/kpat.kcfg M +1 -1 src/patsolve/abstract_fc_solve_solver.h M +44 -25 src/patsolve/freecellsolver.cpp M +1 -1 src/patsolve/freecellsolver.h M +1 -0 src/patsolve/patsolve.cpp https://invent.kde.org/games/kpat/commit/96474ebab01f45bef41cfa34b23ddb39179debfe diff --git a/doc/index.docbook b/doc/index.docbook index 484926a..0dc0218 100644 --- a/doc/index.docbook +++ b/doc/index.docbook @@ -333,6 +333,28 @@ The maximum amount of cards you can move is calculated by: (#{free cells} + 1) * 2<superscript>#{free piles}</superscript> </para></sidebar> +<variablelist> +<varlistentry><term>Variations:</term> +<listitem> +<para> +- Baker's Game is like Freecell, but the piles are built down by suit. +</para> + +<para> +- Eight Off is like Freecell, but the piles are built down by suit. You have 8 reserves and only kings can fill empty spaces. Four reserves are filled at game start. +</para> + +<para> +- Forecell is like Freecell, but the reserves are filled at game start, and only kings can fill empty spaces. +</para> + +<para> +- Seahaven Towers is like Freecell, but with 10 piles which are built down by suit, and only kings can fill empty spaces. Two reserves are filled at game start. +</para> +</listitem> +</varlistentry> +</variablelist> + <para> To solve this game it is recommended to grab the cards out of the playing sequences in the same order they have to be put into the foundation (first the diff --git a/src/dealerinfo.h b/src/dealerinfo.h index 0438553..56b1eb3 100644 --- a/src/dealerinfo.h +++ b/src/dealerinfo.h @@ -56,7 +56,7 @@ public: KlondikeDrawOneId = 0, GrandfatherId = 1, AcesUpId = 2, - FreecellId = 3, + FreecellGeneralId = 3, Mod3Id = 5, GypsyId = 7, FortyAndEightId = 8, @@ -75,7 +75,13 @@ public: BakersDozenSpanishId= 21, BakersDozenCastlesId= 22, BakersDozenPortugueseId= 23, - BakersDozenCustomId = 24 + BakersDozenCustomId = 24, + FreecellId = 30, + FreecellBakersId = 31, + FreecellEightOffId = 32, + FreecellForeId = 33, + FreecellSeahavenId = 34, + FreecellCustomId = 39 }; DealerInfo( const QByteArray & untranslatedBaseName, int baseId ); diff --git a/src/freecell.cpp b/src/freecell.cpp index 001acc2..ed9c374 100644 --- a/src/freecell.cpp +++ b/src/freecell.cpp @@ -46,32 +46,36 @@ #include "patsolve/freecellsolver.h" // KF #include <KLocalizedString> - +#include <kwidgetsaddons_version.h> +#include <KSelectAction> Freecell::Freecell( const DealerInfo * di ) : DealerScene( di ) { + configOptions(); + getSavedOptions(); } void Freecell::initialize() { - setDeckContents(); + setDeckContents( m_decks + 1 ); - const qreal topRowDist = 1.08; + const bool isRightFoundation = m_reserves + 4 * (m_decks + 1) > (m_stacks + 6); + const qreal topRowDist = isRightFoundation ? 1.13 : 1.08; const qreal bottomRowDist = 1.13; - const qreal targetOffsetDist = ( 7 * bottomRowDist + 1 ) - ( 3 * topRowDist + 1 ); + const qreal targetOffsetDist = ( (m_stacks + 5) * bottomRowDist + 1 ) - ( 3 * topRowDist + 1 ) * (m_decks + 1); - for ( int i = 0; i < 4; ++i ) + for ( int i = 0; i < m_reserves; ++i ) { - freecell[i] = new PatPile ( this, 1 + 8 + i, QStringLiteral( "freecell%1" ).arg( i ) ); + freecell[i] = new PatPile ( this, 1 + i, QStringLiteral( "freecell%1" ).arg( i ) ); freecell[i]->setPileRole(PatPile::Cell); freecell[i]->setLayoutPos(topRowDist * i, 0); freecell[i]->setKeyboardSelectHint( KCardPile::AutoFocusTop ); freecell[i]->setKeyboardDropHint( KCardPile::AutoFocusTop ); } - for ( int i = 0; i < 8; ++i ) + for ( int i = 0; i < (m_stacks + 6); ++i ) { store[i] = new PatPile( this, 1 + i, QStringLiteral( "store%1" ).arg( i ) ); store[i]->setPileRole(PatPile::Tableau); @@ -82,14 +86,31 @@ void Freecell::initialize() store[i]->setKeyboardDropHint( KCardPile::AutoFocusTop ); } - for ( int i = 0; i < 4; ++i ) + if ( isRightFoundation ) { - target[i] = new PatPile(this, 1 + 8 + 4 + i, QStringLiteral( "target%1" ).arg( i )); - target[i]->setPileRole(PatPile::Foundation); - target[i]->setLayoutPos(targetOffsetDist + topRowDist * i, 0); - target[i]->setSpread(0, 0); - target[i]->setKeyboardSelectHint( KCardPile::NeverFocus ); - target[i]->setKeyboardDropHint( KCardPile::ForceFocusTop ); + const int columns = std::max(m_reserves, m_stacks + 6); + for ( int i = 0; i < 4 * (m_decks + 1); ++i ) + { + const qreal offsetX = 0.25 + columns * bottomRowDist + i / 4 * bottomRowDist; + const qreal offsetY = bottomRowDist * i - i / 4 * bottomRowDist * 4; + target[i] = new PatPile( this, 1 + i, QStringLiteral("target%1").arg(i) ); + target[i]->setPileRole(PatPile::Foundation); + target[i]->setLayoutPos(offsetX, offsetY); + target[i]->setKeyboardSelectHint( KCardPile::NeverFocus ); + target[i]->setKeyboardDropHint( KCardPile::ForceFocusTop ); + } + } + else + { + for ( int i = 0; i < 4 * (m_decks + 1); ++i ) + { + target[i] = new PatPile(this, 1 + i, QStringLiteral( "target%1" ).arg( i )); + target[i]->setPileRole(PatPile::Foundation); + target[i]->setLayoutPos(targetOffsetDist + topRowDist * i, 0); + target[i]->setSpread(0, 0); + target[i]->setKeyboardSelectHint( KCardPile::NeverFocus ); + target[i]->setKeyboardDropHint( KCardPile::ForceFocusTop ); + } } setActions(DealerScene::Demo | DealerScene::Hint); @@ -100,15 +121,80 @@ void Freecell::initialize() } +QList<QAction*> Freecell::configActions() const +{ + return QList<QAction*>() << options << m_emptyStackFillOption << m_sequenceBuiltByOption << m_reservesOption << m_stacksOption; +} + + +void Freecell::gameTypeChanged() +{ + stopDemo(); + + if ( allowedToStartNewGame() ) + { + // remove existing piles + for ( int i = 0; i < m_reserves; ++i ) + removePile(freecell[i]); + + for ( int i = 0; i < (m_stacks + 6); ++i ) + removePile(store[i]); + + for ( int i = 0; i < 4 * (m_decks + 1); ++i ) + removePile(target[i]); + + + if ( m_variation != options->currentItem() ) + { + setOptions(options->currentItem()); + } + else + { + // update option selections + if ( m_emptyStackFill != m_emptyStackFillOption->currentItem() ) + m_emptyStackFill = m_emptyStackFillOption->currentItem(); + else if ( m_sequenceBuiltBy != m_sequenceBuiltByOption->currentItem() ) + m_sequenceBuiltBy = m_sequenceBuiltByOption->currentItem(); + else if ( m_reserves != m_reservesOption->currentItem() ) + m_reserves = m_reservesOption->currentItem(); + else if ( m_stacks != m_stacksOption->currentItem() ) + m_stacks = m_stacksOption->currentItem(); + else if ( m_decks != m_decksOption->currentItem() ) + m_decks = m_decksOption->currentItem(); + + matchVariant(); + } + + initialize(); + relayoutScene(); + startNew( gameNumber() ); + setSavedOptions(); + } + else + { + // If we're not allowed, reset the options + getSavedOptions(); + } +} + + void Freecell::restart( const QList<KCard*> & cards ) { QList<KCard*> cardList = cards; + // Prefill reserves for select game types + if ( m_variation == 4 ) + for ( int i = 0; i < 2; ++i ) + addCardForDeal( freecell[i], cardList.takeLast(), true, freecell[0]->pos() ); + else if ( m_variation == 1 || m_variation == 2 ) + for ( int i = 0; i < 4; ++i ) + addCardForDeal( freecell[i], cardList.takeLast(), true, freecell[0]->pos() ); + int column = 0; while ( !cardList.isEmpty() ) { addCardForDeal( store[column], cardList.takeLast(), true, store[0]->pos() ); - column = (column + 1) % 8; + column = (column + 1) % (m_stacks + 6); } startDealAnimation(); @@ -119,7 +205,7 @@ QString Freecell::solverFormat() const { QString output; QString tmp; - for (int i = 0; i < 4 ; i++) { + for (int i = 0; i < 4 * (m_decks + 1) ; i++) { if (target[i]->isEmpty()) continue; tmp += suitToString(target[i]->topCard()->suit()) + QLatin1Char('-') + rankToString(target[i]->topCard()->rank()) + QLatin1Char(' '); @@ -128,7 +214,7 @@ QString Freecell::solverFormat() const output += QStringLiteral("Foundations: %1\n").arg(tmp); tmp.truncate(0); - for (int i = 0; i < 4 ; i++) { + for (int i = 0; i < m_reserves ; i++) { const auto fc = freecell[i]; tmp += (fc->isEmpty() ? QStringLiteral("-") : cardToRankSuitString(fc->topCard())) + QLatin1Char(' '); } @@ -138,11 +224,12 @@ QString Freecell::solverFormat() const output += a.arg(tmp); } - for (int i = 0; i < 8 ; i++) + for (int i = 0; i < (m_stacks + 6) ; i++) cardsListToLine(output, store[i]->cards()); return output; } + void Freecell::cardsDroppedOnPile( const QList<KCard*> & cards, KCardPile * pile ) { if ( cards.size() <= 1 ) @@ -152,12 +239,12 @@ void Freecell::cardsDroppedOnPile( const QList<KCard*> & cards, KCardPile * pile } QList<KCardPile*> freeCells; - for ( int i = 0; i < 4; ++i ) + for ( int i = 0; i < m_reserves; ++i ) if ( freecell[i]->isEmpty() ) freeCells << freecell[i]; QList<KCardPile*> freeStores; - for ( int i = 0; i < 8; ++i ) + for ( int i = 0; i < (m_stacks + 6); ++i ) if ( store[i]->isEmpty() && store[i] != pile ) freeStores << store[i]; @@ -177,7 +264,7 @@ bool Freecell::tryAutomaticMove(KCard *c) if (allowedToRemove(c->pile(), c) && c == c->pile()->topCard()) { - for (int i = 0; i < 4; i++) + for (int i = 0; i < m_reserves; i++) { if (allowedToAdd( freecell[i], {c} )) { @@ -189,25 +276,44 @@ bool Freecell::tryAutomaticMove(KCard *c) return false; } + bool Freecell::canPutStore( const KCardPile * pile, const QList<KCard*> & cards ) const { int freeCells = 0; - for ( int i = 0; i < 4; ++i ) + for ( int i = 0; i < m_reserves; ++i ) if ( freecell[i]->isEmpty() ) ++freeCells; int freeStores = 0; - for ( int i = 0; i < 8; ++i ) - if ( store[i]->isEmpty() && store[i] != pile ) - ++freeStores; + if (m_emptyStackFill == 0) + { + for ( int i = 0; i < (m_stacks + 6); ++i ) + if ( store[i]->isEmpty() && store[i] != pile) + ++freeStores; + } - return cards.size() <= (freeCells + 1) << freeStores - && ( pile->isEmpty() - || ( pile->topCard()->rank() == cards.first()->rank() + 1 - && pile->topCard()->color() != cards.first()->color() ) ); + if (cards.size() <= (freeCells + 1) << freeStores) + { + if (pile->isEmpty()) + return m_emptyStackFill == 0 || (m_emptyStackFill == 1 && cards.first()->rank() == KCardDeck::King); + else + if (m_sequenceBuiltBy == 1) + return cards.first()->rank() == pile->topCard()->rank() - 1 + && cards.first()->suit() == pile->topCard()->suit(); + else if (m_sequenceBuiltBy == 0) + return cards.first()->rank() == pile->topCard()->rank() - 1 + && pile->topCard()->color() != cards.first()->color(); + else + return cards.first()->rank() == pile->topCard()->rank() - 1; + } + else + { + return false; + } } + bool Freecell::checkAdd(const PatPile * pile, const QList<KCard*> & oldCards, const QList<KCard*> & newCards) const { switch (pile->pileRole()) @@ -223,12 +329,18 @@ bool Freecell::checkAdd(const PatPile * pile, const QList<KCard*> & oldCards, co } } + bool Freecell::checkRemove(const PatPile * pile, const QList<KCard*> & cards) const { switch (pile->pileRole()) { case PatPile::Tableau: - return isAlternateColorDescending(cards); + if (m_sequenceBuiltBy == 1) + return isSameSuitDescending(cards); + else if (m_sequenceBuiltBy == 0) + return isAlternateColorDescending(cards); + else + return isRankDescending(cards); case PatPile::Cell: return cards.first() == pile->topCard(); case PatPile::Foundation: @@ -237,66 +349,20 @@ bool Freecell::checkRemove(const PatPile * pile, const QList<KCard*> & cards) co } } -QList<MoveHint> Freecell::getHints() -{ - QList<MoveHint> hintList = getSolverHints(); - - if ( isDemoActive() ) - return hintList; - - const auto patPiles = this->patPiles(); - for (PatPile * store : patPiles) { - if (store->isEmpty()) - continue; - - QList<KCard*> cards = store->cards(); - while (cards.count() && !cards.first()->isFaceUp()) - cards.erase(cards.begin()); - - QList<KCard*>::Iterator iti = cards.begin(); - while (iti != cards.end()) - { - if (allowedToRemove(store, (*iti))) - { - const auto patPiles = this->patPiles(); - for (PatPile * dest : patPiles) { - int cardIndex = store->indexOf(*iti); - if (cardIndex == 0 && dest->isEmpty() && !dest->isFoundation()) - continue; - - if (!checkAdd(dest, dest->cards(), cards)) - continue; - - if ( dest->isFoundation() ) // taken care by solver - continue; - - QList<KCard*> cardsBelow = cards.mid(0, cardIndex); - // if it could be here as well, then it's no use - if ((cardsBelow.isEmpty() && !dest->isEmpty()) || !checkAdd(store, cardsBelow, cards)) - { - hintList << MoveHint( *iti, dest, 0 ); - } - else if (checkPrefering( dest, dest->cards(), cards ) - && !checkPrefering( store, cardsBelow, cards )) - { // if checkPrefers says so, we add it nonetheless - hintList << MoveHint( *iti, dest, 0 ); - } - } - } - cards.erase(iti); - iti = cards.begin(); - } - } - return hintList; -} - static class FreecellDealerInfo : public DealerInfo { public: FreecellDealerInfo() - : DealerInfo(I18N_NOOP("Freecell"), FreecellId) - {} + : DealerInfo(I18N_NOOP("Freecell"), FreecellGeneralId) + { + addSubtype( FreecellBakersId, I18N_NOOP( "Baker's Game" ) ); + addSubtype( FreecellEightOffId, I18N_NOOP( "Eight Off" ) ); + addSubtype( FreecellForeId, I18N_NOOP( "Forecell" ) ); + addSubtype( FreecellId, I18N_NOOP( "Freecell" ) ); + addSubtype( FreecellSeahavenId, I18N_NOOP( "Seahaven Towers" ) ); + addSubtype( FreecellCustomId, I18N_NOOP( "Freecell (Custom)" ) ); + } DealerScene *createGame() const override { @@ -305,4 +371,205 @@ public: } freecellDealerInfo; +void Freecell::matchVariant() +{ + if ( m_emptyStackFill == 0 && m_sequenceBuiltBy == 1 && m_reserves == 4 && m_stacks == 2 ) + m_variation = 0; + else if ( m_emptyStackFill == 1 && m_sequenceBuiltBy == 1 && m_reserves == 8 && m_stacks == 2 ) + m_variation = 1; + else if ( m_emptyStackFill == 1 && m_sequenceBuiltBy == 0 && m_reserves == 4 && m_stacks == 2 ) + m_variation = 2; + else if ( m_emptyStackFill == 0 && m_sequenceBuiltBy == 0 && m_reserves == 4 && m_stacks == 2 ) + m_variation = 3; + else if ( m_emptyStackFill == 1 && m_sequenceBuiltBy == 1 && m_reserves == 4 && m_stacks == 4 ) + m_variation = 4; + else + m_variation = 5; + + options->setCurrentItem( m_variation ); +} + + +void Freecell::configOptions() +{ + options = new KSelectAction(i18n("Popular Variant Presets"), this ); + options->addAction( i18n("Baker's Game") ); + options->addAction( i18n("Eight Off") ); + options->addAction( i18n("Forecell") ); + options->addAction( i18n("Freecell") ); + options->addAction( i18n("Seahaven Towers") ); + options->addAction( i18n("Custom") ); + + m_emptyStackFillOption = new KSelectAction(i18n("Empty Stack Fill"), this ); + m_emptyStackFillOption->addAction( i18n("Any (Easy)") ); + m_emptyStackFillOption->addAction( i18n("Kings only (Medium)") ); + m_emptyStackFillOption->addAction( i18n("None (Hard)") ); + + m_sequenceBuiltByOption = new KSelectAction(i18n("Build Sequence"), this ); + m_sequenceBuiltByOption->addAction( i18n("Alternating Color") ); + m_sequenceBuiltByOption->addAction( i18n("Matching Suit") ); + m_sequenceBuiltByOption->addAction( i18n("Rank") ); + + m_reservesOption = new KSelectAction(i18n("Free cells"), this ); + m_reservesOption->addAction( i18n("0") ); + m_reservesOption->addAction( i18n("1") ); + m_reservesOption->addAction( i18n("2") ); + m_reservesOption->addAction( i18n("3") ); + m_reservesOption->addAction( i18n("4") ); + m_reservesOption->addAction( i18n("5") ); + m_reservesOption->addAction( i18n("6") ); + m_reservesOption->addAction( i18n("7") ); + m_reservesOption->addAction( i18n("8") ); + + m_stacksOption = new KSelectAction(i18n("Stacks"), this ); + m_stacksOption->addAction( i18n("6") ); + m_stacksOption->addAction( i18n("7") ); + m_stacksOption->addAction( i18n("8") ); + m_stacksOption->addAction( i18n("9") ); + m_stacksOption->addAction( i18n("10") ); + m_stacksOption->addAction( i18n("11") ); + m_stacksOption->addAction( i18n("12") ); + + m_decksOption = new KSelectAction(i18n("Decks"), this ); + m_decksOption->addAction( i18n("1") ); + m_decksOption->addAction( i18n("2") ); + m_decksOption->addAction( i18n("3") ); + +#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 78, 0) + connect(options, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged); + connect(m_emptyStackFillOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged); + connect(m_reservesOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged); + connect(m_sequenceBuiltByOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged); + connect(m_stacksOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged); + connect(m_decksOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged); +#else + connect(options, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Freecell::gameTypeChanged); + connect(m_emptyStackFillOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Freecell::gameTypeChanged); + connect(m_reservesOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Freecell::gameTypeChanged); + connect(m_sequenceBuiltByOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Freecell::gameTypeChanged); + connect(m_stacksOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Freecell::gameTypeChanged); + connect(m_decksOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Freecell::gameTypeChanged); +#endif +} + + +void Freecell::setSavedOptions() +{ + Settings::setFreecellEmptyStackFill( m_emptyStackFill ); + Settings::setFreecellSequenceBuiltBy( m_sequenceBuiltBy ); + Settings::setFreecellReserves( m_reserves ); + Settings::setFreecellStacks( m_stacks ); + Settings::setFreecellDecks( m_decks ); +} + + +void Freecell::getSavedOptions() +{ + m_emptyStackFill = Settings::freecellEmptyStackFill(); + m_sequenceBuiltBy = Settings::freecellSequenceBuiltBy(); + m_reserves = Settings::freecellReserves(); + m_stacks = Settings::freecellStacks(); + m_decks = Settings::freecellDecks(); + + matchVariant(); + + m_emptyStackFillOption->setCurrentItem( m_emptyStackFill ); + m_sequenceBuiltByOption->setCurrentItem( m_sequenceBuiltBy ); + m_reservesOption->setCurrentItem( m_reserves ); + m_stacksOption->setCurrentItem( m_stacks ); + m_decksOption->setCurrentItem( m_decks ); +} + + +void Freecell::mapOldId(int id) +{ + switch (id) { + + case DealerInfo::FreecellBakersId : + setOptions(0); + break; + case DealerInfo::FreecellEightOffId : + setOptions(1); + break; + case DealerInfo::FreecellForeId : + setOptions(2); + break; + case DealerInfo::FreecellId : + setOptions(3); + break; + case DealerInfo::FreecellSeahavenId : + setOptions(4); + break; + case DealerInfo::FreecellCustomId : + setOptions(5); + break; + default: + // Do nothing. + break; + } +} + + +int Freecell::oldId() const +{ + switch (m_variation) { + case 0 : + return DealerInfo::FreecellBakersId; + case 1 : + return DealerInfo::FreecellEightOffId; + case 2 : + return DealerInfo::FreecellForeId; + case 3 : + return DealerInfo::FreecellId; + case 4 : + return DealerInfo::FreecellSeahavenId; + default : + return DealerInfo::FreecellCustomId; + } +} + + +void Freecell::setOptions(int variation) +{ + if ( variation != m_variation ) + { + m_variation = variation; + m_emptyStackFill = 0; + m_sequenceBuiltBy = 0; + m_reserves = 4; + m_stacks = 2; + m_decks = 0; + + switch (m_variation) { + case 0 : + m_sequenceBuiltBy = 1; + break; + case 1 : + m_emptyStackFill = 1; + m_sequenceBuiltBy = 1; + m_reserves = 8; + break; + case 2 : + m_emptyStackFill = 1; + break; + case 3 : + break; + case 4 : + m_emptyStackFill = 1; + m_sequenceBuiltBy = 1; + m_stacks = 4; + break; + case 5 : + m_emptyStackFill = 2; + m_sequenceBuiltBy = 2; + break; + } + + m_emptyStackFillOption->setCurrentItem( m_emptyStackFill ); + m_sequenceBuiltByOption->setCurrentItem( m_sequenceBuiltBy ); + m_reservesOption->setCurrentItem( m_reserves ); + m_stacksOption->setCurrentItem( m_stacks ); + m_decksOption->setCurrentItem( m_decks ); + } +} diff --git a/src/freecell.h b/src/freecell.h index 059fd71..f64b8b4 100644 --- a/src/freecell.h +++ b/src/freecell.h @@ -41,6 +41,7 @@ #include "dealer.h" #include "hint.h" +class KSelectAction; class Freecell : public DealerScene { @@ -49,13 +50,18 @@ class Freecell : public DealerScene public: explicit Freecell( const DealerInfo * di ); void initialize() override; + void mapOldId(int id) override; + int oldId() const override; + QList<QAction*> configActions() const override; protected: bool checkAdd(const PatPile * pile, const QList<KCard*> & oldCards, const QList<KCard*> & newCards) const override; bool checkRemove(const PatPile * pile, const QList<KCard*> & cards) const override; void cardsDroppedOnPile( const QList<KCard*> & cards, KCardPile * pile ) override; void restart( const QList<KCard*> & cards ) override; - QList<MoveHint> getHints() override; + +private Q_SLOTS: + void gameTypeChanged(); protected Q_SLOTS: bool tryAutomaticMove( KCard * c ) override; @@ -63,10 +69,34 @@ protected Q_SLOTS: private: bool canPutStore( const KCardPile * pile, const QList<KCard*> & cards ) const; + void configOptions(); + void setOptions(int v); + void getSavedOptions(); + void setSavedOptions(); + void matchVariant(); + virtual QString solverFormat() const; - PatPile* store[8]; - PatPile* freecell[4]; - PatPile* target[4]; + PatPile* store[12]; + PatPile* freecell[8]; + PatPile* target[12]; + + KSelectAction *options; + int m_variation; + + KSelectAction *m_emptyStackFillOption; + int m_emptyStackFill; + + KSelectAction *m_sequenceBuiltByOption; + int m_sequenceBuiltBy; + + KSelectAction *m_reservesOption; + int m_reserves; + + KSelectAction *m_stacksOption; + int m_stacks; + + KSelectAction *m_decksOption; + int m_decks; friend class FreecellSolver; }; diff --git a/src/kpat.kcfg b/src/kpat.kcfg index 15c951b..9fd9d07 100644 --- a/src/kpat.kcfg +++ b/src/kpat.kcfg @@ -35,6 +35,21 @@ <entry name="FreecellSolverIterationsLimit" key="FreecellSolverIterationsLimit" type="Int"> <default>200000</default> </entry> + <entry name="FreecellEmptyStackFill" key="FreecellEmptyStackFill" type="Int"> + <default>0</default> + </entry> + <entry name="FreecellSequenceBuiltBy" key="FreecellSequenceBuiltBy" type="Int"> + <default>0</default> + </entry> + <entry name="FreecellReserves" key="FreecellReserves" type="Int"> + <default>4</default> + </entry> + <entry name="FreecellStacks" key="FreecellStacks" type="Int"> + <default>2</default> + </entry> + <entry name="FreecellDecks" key="FreecellDecks" type="Int"> + <default>0</default> + </entry> <entry name="SimpleSimonSolverIterationsLimit" key="SimpleSimonSolverIterationsLimit" type="Int"> <default>200000</default> </entry> diff --git a/src/patsolve/abstract_fc_solve_solver.h b/src/patsolve/abstract_fc_solve_solver.h index e0878de..73a883d 100644 --- a/src/patsolve/abstract_fc_solve_solver.h +++ b/src/patsolve/abstract_fc_solve_solver.h @@ -21,7 +21,7 @@ // own #include "patsolve.h" -struct FcSolveSolver : public Solver<13> +struct FcSolveSolver : public Solver<20> { public: FcSolveSolver(); diff --git a/src/patsolve/freecellsolver.cpp b/src/patsolve/freecellsolver.cpp index 56a35b2..e879e4c 100644 --- a/src/patsolve/freecellsolver.cpp +++ b/src/patsolve/freecellsolver.cpp @@ -21,6 +21,7 @@ // own #include "patsolve-config.h" #include "../freecell.h" +#include "../settings.h" // freecell-solver #include "freecell-solver/fcs_user.h" #include "freecell-solver/fcs_cl.h" @@ -28,6 +29,12 @@ #include <cstdlib> #include <cstring> +int m_reserves = Settings::freecellReserves(); +int m_stacks = Settings::freecellStacks() + 6; +int m_decks = Settings::freecellDecks() + 1; +int m_emptyStackFill = Settings::freecellEmptyStackFill(); +int m_sequenceBuiltBy = Settings::freecellSequenceBuiltBy(); + const int CHUNKSIZE = 100; const long int MAX_ITERS_LIMIT = 200000; @@ -202,7 +209,7 @@ int FreecellSolver::good_automove(int o, int r) return true; } - for (int foundation_idx = 0; foundation_idx < 4; ++foundation_idx) { + for (int foundation_idx = 0; foundation_idx < 4 * m_decks; ++foundation_idx) { KCard *c = deal->target[foundation_idx]->topCard(); if (c) { O[translateSuit( c->suit() ) >> 4] = c->rank(); @@ -210,7 +217,7 @@ int FreecellSolver::good_automove(int o, int r) } /* Check the Out piles of opposite color. */ - for (int i = 1 - (o & 1); i < 4; i += 2) { + for (int i = 1 - (o & 1); i < 4 * m_decks; i += 2) { if (O[i] < r - 1) { #if 1 /* Raymond's Rule */ @@ -221,7 +228,7 @@ int FreecellSolver::good_automove(int o, int r) the loop variable i. We return here and never make it back to the outer loop. */ - for (i = 1 - (o & 1); i < 4; i += 2) { + for (i = 1 - (o & 1); i < 4 * m_decks; i += 2) { if (O[i] < r - 2) { return false; } @@ -250,7 +257,7 @@ int FreecellSolver::get_possible_moves(int *a, int *numout) int n = 0; mp = Possible; - for (w = 0; w < Nwpiles + Ntpiles; ++w) { + for (w = 0; w < m_stacks + m_reserves; ++w) { if (Wlen[w] > 0) { card = *Wp[w]; int out_suit = SUIT(card); @@ -316,12 +323,24 @@ void FreecellSolver::setFcSolverGameParams() * * Shlomi Fish * */ - freecell_solver_user_set_num_freecells(solver_instance,4); - freecell_solver_user_set_num_stacks(solver_instance,8); - freecell_solver_user_set_num_decks(solver_instance,1); - freecell_solver_user_set_sequences_are_built_by_type(solver_instance, FCS_SEQ_BUILT_BY_ALTERNATE_COLOR); freecell_solver_user_set_sequence_move(solver_instance, 0); - freecell_solver_user_set_empty_stacks_filled_by(solver_instance, FCS_ES_FILLED_BY_ANY_CARD); + + m_reserves = Settings::freecellReserves(); + freecell_solver_user_set_num_freecells(solver_instance, m_reserves); + + m_stacks = Settings::freecellStacks() + 6; + freecell_solver_user_set_num_stacks(solver_instance, m_stacks); + + m_decks = Settings::freecellDecks() + 1; + freecell_solver_user_set_num_decks(solver_instance, m_decks); + + //FCS_ES_FILLED_BY_ANY_CARD = 0, FCS_ES_FILLED_BY_KINGS_ONLY = 1,FCS_ES_FILLED_BY_NONE = 2 + m_emptyStackFill = Settings::freecellEmptyStackFill(); + freecell_solver_user_set_empty_stacks_filled_by(solver_instance, m_emptyStackFill); + + //FCS_SEQ_BUILT_BY_ALTERNATE_COLOR = 0, FCS_SEQ_BUILT_BY_SUIT = 1, FCS_SEQ_BUILT_BY_RANK = 2 + m_sequenceBuiltBy = Settings::freecellSequenceBuiltBy(); + freecell_solver_user_set_sequences_are_built_by_type(solver_instance, m_sequenceBuiltBy); } #if 0 void FreecellSolver::unpack_cluster( unsigned int k ) @@ -409,7 +428,7 @@ MoveHint FreecellSolver::translateMove( const MOVE &m ) { PatPile *target = nullptr; PatPile *empty = nullptr; - for (int i = 0; i < 4; ++i) { + for (int i = 0; i < 4 * m_decks; ++i) { KCard *c = deal->target[i]->topCard(); if (c) { if ( c->suit() == card->suit() ) @@ -431,17 +450,17 @@ MoveHint FreecellSolver::translateMove( const MOVE &m ) // this is tricky as we need to want to build the "meta moves" PatPile *frompile = nullptr; - if ( m.from < 8 ) + if ( m.from < m_stacks ) frompile = deal->store[m.from]; else - frompile = deal->freecell[m.from-8]; + frompile = deal->freecell[m.from - m_stacks]; KCard *card = frompile->at( frompile->count() - m.card_index - 1); if ( m.totype == O_Type ) { PatPile *target = nullptr; PatPile *empty = nullptr; - for (int i = 0; i < 4; ++i) { + for (int i = 0; i < 4 * m_decks; ++i) { KCard *c = deal->target[i]->topCard(); if (c) { if ( c->suit() == card->suit() ) @@ -457,10 +476,10 @@ MoveHint FreecellSolver::translateMove( const MOVE &m ) return MoveHint( card, target, m.pri ); } else { PatPile *target = nullptr; - if ( m.to < 8 ) + if ( m.to < m_stacks ) target = deal->store[m.to]; else - target = deal->freecell[m.to-8]; + target = deal->freecell[m.to - m_stacks]; return MoveHint( card, target, m.pri ); } @@ -495,32 +514,32 @@ void FreecellSolver::translate_layout() /* Read the workspace. */ int total = 0; - for ( int w = 0; w < 8; ++w ) { - int i = translate_pile(deal->store[w], W[w], 52); + for ( int w = 0; w < m_stacks; ++w ) { + int i = translate_pile(deal->store[w], W[w], 52 * m_decks); Wp[w] = &W[w][i - 1]; Wlen[w] = i; total += i; - if (w == Nwpiles) { + if (w == m_stacks) { break; } } /* Temp cells may have some cards too. */ - for (int w = 0; w < Ntpiles; ++w) + for (int w = 0; w < m_reserves; ++w) { - int i = translate_pile( deal->freecell[w], W[w+Nwpiles], 52 ); - Wp[w+Nwpiles] = &W[w+Nwpiles][i-1]; - Wlen[w+Nwpiles] = i; + int i = translate_pile( deal->freecell[w], W[w+m_stacks], 52 * m_decks ); + Wp[w+m_stacks] = &W[w+m_stacks][i-1]; + Wlen[w+m_stacks] = i; total += i; } /* Output piles, if any. */ - for (int i = 0; i < 4; ++i) { + for (int i = 0; i < 4 * m_decks; ++i) { O[i] = NONE; } - if (total != 52) { - for (int i = 0; i < 4; ++i) { + if (total != 52 * m_decks) { + for (int i = 0; i < 4 * m_decks; ++i) { KCard *c = deal->target[i]->topCard(); if (c) { O[translateSuit( c->suit() ) >> 4] = c->rank(); diff --git a/src/patsolve/freecellsolver.h b/src/patsolve/freecellsolver.h index 3a6a7c7..a327b79 100644 --- a/src/patsolve/freecellsolver.h +++ b/src/patsolve/freecellsolver.h @@ -64,7 +64,7 @@ public: static int Xparam[]; #endif - card_t O[4]; /* output piles store only the rank or NONE */ + card_t O[12]; /* output piles store only the rank or NONE */ const Freecell *deal; }; diff --git a/src/patsolve/patsolve.cpp b/src/patsolve/patsolve.cpp index f649adf..0f4be58 100644 --- a/src/patsolve/patsolve.cpp +++ b/src/patsolve/patsolve.cpp @@ -990,4 +990,5 @@ template class Solver<34>; template class Solver<15>; template class Solver<7>; template class Solver<13>; +template class Solver<20>; template class Solver<Nwpiles + Ntpiles>;
