[games/kpat] /: Added several Freecell game variants:

Albert Astals Cid null at kde.org
Thu Mar 11 23:56:38 GMT 2021


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>;


More information about the kde-doc-english mailing list