[games/kpat] /: Added Castle game type with several variation presets

Albert Astals Cid null at kde.org
Wed May 26 22:54:10 BST 2021


Git commit 5595a803866db4340bf1a60b79506f66a5daf711 by Albert Astals Cid, on behalf of Michael Lang.
Committed on 26/05/2021 at 21:54.
Pushed by aacid into branch 'master'.

Added Castle game type with several variation presets

-Beleaguered Castle
-Citadel
-Exiled Kings
-Streets and Alleys
-Siegecraft
-Stronghold
-Other custom varations via settings

M  +52   -0    doc/index.docbook
A  +-    --    previews/4.png
M  +1    -0    previews/CMakeLists.txt
M  +2    -0    src/CMakeLists.txt
A  +608  -0    src/castle.cpp     [License: GPL (v2+)]
A  +94   -0    src/castle.h     [License: GPL (v2+)]
M  +10   -2    src/dealerinfo.h
M  +24   -0    src/kpat.kcfg
A  +346  -0    src/patsolve/castlesolver.cpp     [License: GPL (v2+)]
A  +44   -0    src/patsolve/castlesolver.h     [License: GPL (v2+)]

https://invent.kde.org/games/kpat/commit/5595a803866db4340bf1a60b79506f66a5daf711

diff --git a/doc/index.docbook b/doc/index.docbook
index 0dc0218f..e55079c6 100644
--- a/doc/index.docbook
+++ b/doc/index.docbook
@@ -636,6 +636,58 @@ the last card is ready to be moved to a foundation.
 
 </sect2>
 
+<sect2 id="castle">
+<title>Castle</title>
+
+<para><indexterm><primary>Castle</primary></indexterm>
+Castle is a family of patience or solitaire card games typically played with a deck of 52 playing cards. It is sometimes described as "Freecell without cells" because its game play is similar but without extra empty spaces to maneuver in most variations.
+</para>
+
+<para>
+The object of the game is to build all the cards onto the four foundations by suit, each from ace to king
+</para>
+
+<para>
+In the playing piles you have to build descending sequences, regardless of suit.
+You can only move one card that lays on top of a pile.
+</para>
+
+<variablelist>
+<varlistentry><term>Variations:</term>
+<listitem>
+<para>
+- Beleaguered Castle. Aces are dealt to the foundations. Any card can fill empty spaces.
+</para>
+
+<para>
+- Citadel is like Beleaguered Castle, but matching cards are moved to the foundation during the deal, leaving uneven piles.
+</para>
+
+<para>
+- Exiled Kings is like Citadel, but only kings can fill empty spaces.
+</para>
+
+<para>
+- Siegecraft is like Beleaguered Castle, but with one free cell.
+</para>
+
+<para>
+- Streets and Alleys is like Beleaguered Castle, but aces are included in shuffling.
+</para>
+
+<para>
+- Stronghold is like Streets and Alleys, but with one free cell.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+<para>
+To solve this game it is recommended to build evenly on the foundations. Try to create empty piles with can be used to faciliate longer moves to free up buried cards.
+</para>
+
+</sect2>
+
 </sect1>
 
 </chapter>
diff --git a/previews/4.png b/previews/4.png
new file mode 100644
index 00000000..aa60ee35
Binary files /dev/null and b/previews/4.png differ
diff --git a/previews/CMakeLists.txt b/previews/CMakeLists.txt
index 5f4dcc48..0c7a6a27 100644
--- a/previews/CMakeLists.txt
+++ b/previews/CMakeLists.txt
@@ -2,6 +2,7 @@ set( kpat_previews
    1.png
    2.png
    3.png
+   4.png
    5.png
    7.png
    8.png
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d3e65e32..d5114dca 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -28,6 +28,8 @@ set(kpat_SRCS ${libfcs_SRCS}
 
     bakersdozen.cpp
     patsolve/bakersdozensolver.cpp
+    castle
+    patsolve/castlesolver.cpp
     clock.cpp 
     patsolve/clocksolver.cpp
     fortyeight.cpp
diff --git a/src/castle.cpp b/src/castle.cpp
new file mode 100644
index 00000000..faa74c4b
--- /dev/null
+++ b/src/castle.cpp
@@ -0,0 +1,608 @@
+/*
+ *   Copyright 2021 Michael Lang <criticaltemp at protonmail.com>
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU General Public License as
+ *   published by the Free Software Foundation; either version 2 of 
+ *   the License, or (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "castle.h"
+
+// own
+#include "dealerinfo.h"
+#include "kpat_debug.h"
+#include "pileutils.h"
+#include "settings.h"
+#include "speeds.h"
+#include "patsolve/castlesolver.h"
+// KF
+#include <KLocalizedString>
+#include <kwidgetsaddons_version.h>
+#include <KSelectAction>
+
+
+Castle::Castle( const DealerInfo * di )
+  : DealerScene( di )
+{
+    configOptions();
+    getSavedOptions();
+}
+
+
+void Castle::initialize()
+{
+    setDeckContents( m_decks );
+
+    const qreal topRowDist = 1.125;
+    const qreal bottomRowDist = 1.125;
+    const qreal offsetY = m_reserves > 0 ? 1.3 : 0;
+
+    for ( int i = 0; i < m_reserves; ++i )
+    {
+        freecell[i] = new PatPile ( this, 1 + i, QStringLiteral( "freecell%1" ).arg( i ) );
+        freecell[i]->setPileRole(PatPile::Cell);
+        freecell[i]->setLayoutPos(3.25 - topRowDist * (m_reserves - 1) / 2 + topRowDist * i, 0);
+        freecell[i]->setKeyboardSelectHint( KCardPile::AutoFocusTop );
+        freecell[i]->setKeyboardDropHint( KCardPile::AutoFocusTop );
+    }
+
+    for ( int i = 0; i < m_stacks; ++i )
+    {
+        store[i] = new PatPile( this, 1 + i, QStringLiteral( "store%1" ).arg( i ) );
+        store[i]->setPileRole(PatPile::Tableau);
+        store[i]->setAutoTurnTop(true);
+        if (m_layout == 1) {
+            store[i]->setLayoutPos( 4.5 * (i % 2) + 0, offsetY + bottomRowDist * static_cast<int>((i + 0) / 2 ));
+        } else {
+            store[i]->setLayoutPos( 2.5 * (i % 2) + 2, offsetY + bottomRowDist * static_cast<int>((i + 0) / 2 ));
+        }
+        
+        
+        if (i % 2 || m_layout == 1) {
+            store[i]->setSpread(0.21, 0);
+            store[i]->setRightPadding( 2 );
+            store[i]->setWidthPolicy( KCardPile::GrowRight );
+        } else {
+            store[i]->setSpread(-0.21, 0);
+            store[i]->setLeftPadding( 3 );
+            store[i]->setWidthPolicy( KCardPile::GrowLeft );
+        }
+        
+        store[i]->setKeyboardSelectHint( KCardPile::AutoFocusDeepestRemovable );
+        store[i]->setKeyboardDropHint( KCardPile::AutoFocusTop );
+    }
+    
+    for ( int i = 0; i < 4 * m_decks; ++i )
+    {
+        target[i] = new PatPile( this, 1 + i, QStringLiteral("target%1").arg(i) );
+        target[i]->setPileRole(PatPile::Foundation);
+        target[i]->setLayoutPos(3.25, offsetY + bottomRowDist * i);
+        target[i]->setKeyboardSelectHint( KCardPile::NeverFocus );
+        target[i]->setKeyboardDropHint( KCardPile::ForceFocusTop );
+    }
+
+    setActions(DealerScene::Demo | DealerScene::Hint);
+    auto solver = new CastleSolver( this );
+    solver->default_max_positions = Settings::castleSolverIterationsLimit();
+    setSolver( solver );
+    setNeededFutureMoves( 4 ); // reserve some
+}
+
+
+QList<QAction*> Castle::configActions() const
+{
+    return QList<QAction*>() << options << m_emptyStackFillOption << m_sequenceBuiltByOption << m_reservesOption << m_stacksOption << m_stackFaceupOption << m_foundationOption << m_layoutOption;
+}
+
+
+void Castle::gameTypeChanged()
+{
+    stopDemo();
+
+    if ( allowedToStartNewGame() )
+    {
+        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 - 6 != m_stacksOption->currentItem() )
+                m_stacks = m_stacksOption->currentItem() + 6;
+            else if ( m_stackFaceup != m_stackFaceupOption->currentItem() )
+                m_stackFaceup = m_stackFaceupOption->currentItem();
+            else if ( m_foundation != m_foundationOption->currentItem() )
+                m_foundation = m_foundationOption->currentItem();
+            else if ( m_layout != m_layoutOption->currentItem() )
+                m_layout = m_layoutOption->currentItem();
+
+            matchVariant();
+        }
+        
+        // remove existing piles
+        clearPatPiles();
+
+        initialize();
+        relayoutScene();
+        startNew( gameNumber() );
+        setSavedOptions();
+    }
+    else
+    {
+        // If we're not allowed, reset the options
+        getSavedOptions();
+    }
+}
+
+
+void Castle::restart( const QList<KCard*> & cards )
+{
+    QList<KCard*> cardList = cards;
+
+    int column = 0;
+    int fdx = 0;
+    
+    // Prefill aces to foundation for select game types
+    if ( m_foundation >= 1 )
+    {
+        for ( int i = 0; i < cardList.size(); ++i )
+        {
+            if (cardList.at(i)->rank() == KCardDeck::Ace)
+            {
+                addCardForDeal( target[fdx], cardList.takeAt(i), true, target[fdx]->pos() );
+                --i;
+                fdx++;
+            }
+        }
+    }
+    
+    bool alternate = false;
+    while ( !cardList.isEmpty() )
+    {
+        // Add matching cards to foundation during deal for select game types
+        if ( m_foundation == 2 && fdx > 0)
+        {
+            KCard * card = cardList.last();
+            int i = 0;
+            for ( i = 0; i < fdx; ++i )
+            {
+                if ( card->rank() == target[i]->topCard()->rank() + 1 && card->suit() == target[i]->topCard()->suit() )
+                {
+                    addCardForDeal( target[i], cardList.takeLast(), true, target[i]->pos() );
+                    column = (column + 1) % m_stacks;
+                    break;
+                }
+            }
+            
+            // Skip if card already added
+            if ( i < fdx ) continue;
+        }
+        
+        bool faceUp = m_stackFaceup == 1 || cardList.length() <= m_stacks || ( m_stackFaceup == 2 && alternate );
+        addCardForDeal( store[column], cardList.takeLast(), faceUp, store[0]->pos() );
+        column = (column + 1) % m_stacks;
+        if ( column % m_stacks == 0)
+            alternate = !alternate;
+    }
+
+    startDealAnimation();
+}
+
+
+QString Castle::solverFormat() const
+{
+    QString output;
+    QString tmp;
+    for (int i = 0; i < 4 * m_decks ; i++) {
+        if (target[i]->isEmpty())
+            continue;
+        tmp += suitToString(target[i]->topCard()->suit()) + QLatin1Char('-') + rankToString(target[i]->topCard()->rank()) + QLatin1Char(' ');
+    }
+    if (!tmp.isEmpty())
+        output += QStringLiteral("Foundations: %1\n").arg(tmp);
+
+    tmp.truncate(0);
+    for (int i = 0; i < m_reserves ; i++) {
+        const auto fc = freecell[i];
+        tmp += (fc->isEmpty() ? QStringLiteral("-") : cardToRankSuitString(fc->topCard())) + QLatin1Char(' ');
+    }
+    if (!tmp.isEmpty())
+    {
+        QString a = QStringLiteral("Freecells: %1\n");
+        output += a.arg(tmp);
+    }
+
+    for (int i = 0; i < m_stacks ; i++)
+        cardsListToLine(output, store[i]->cards());
+    return output;
+}
+
+
+void Castle::cardsDroppedOnPile( const QList<KCard*> & cards, KCardPile * pile )
+{
+    if ( cards.size() <= 1 )
+    {
+        DealerScene::moveCardsToPile( cards, pile, DURATION_MOVE );
+        return;
+    }
+
+    QList<KCardPile*> freeCells;
+    for ( int i = 0; i < m_reserves; ++i )
+        if ( freecell[i]->isEmpty() )
+            freeCells << freecell[i];
+
+    QList<KCardPile*> freeStores;
+    for ( int i = 0; i < m_stacks; ++i )
+        if ( store[i]->isEmpty() && store[i] != pile )
+            freeStores << store[i];
+
+    multiStepMove( cards, pile, freeStores, freeCells, DURATION_MOVE );
+}
+
+
+bool Castle::tryAutomaticMove(KCard *c)
+{
+    // target move
+    if (DealerScene::tryAutomaticMove(c))
+        return true;
+
+    if (c->isAnimated())
+        return false;
+
+    if (allowedToRemove(c->pile(), c)
+        && c == c->pile()->topCard())
+    {
+        for (int i = 0; i < m_reserves; i++)
+        {
+            if (allowedToAdd( freecell[i], {c} ))
+            {
+                moveCardToPile( c, freecell[i], DURATION_MOVE );
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+
+bool Castle::canPutStore( const KCardPile * pile, const QList<KCard*> & cards ) const
+{
+    int freeCells = 0;
+    for ( int i = 0; i < m_reserves; ++i )
+        if ( freecell[i]->isEmpty() )
+            ++freeCells;
+
+    int freeStores = 0;
+    if (m_emptyStackFill == 0)
+    {
+        for ( int i = 0; i < m_stacks; ++i )
+            if ( store[i]->isEmpty() && store[i] != pile)
+                ++freeStores;
+    }
+
+    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 Castle::checkAdd(const PatPile * pile, const QList<KCard*> & oldCards, const QList<KCard*> & newCards) const
+{
+    switch (pile->pileRole())
+    {
+    case PatPile::Tableau:
+        return canPutStore(pile, newCards);
+    case PatPile::Cell:
+        return oldCards.isEmpty() && newCards.size() == 1;
+    case PatPile::Foundation:
+        return checkAddSameSuitAscendingFromAce(oldCards, newCards);
+    default:
+        return false;
+    }
+}
+
+
+bool Castle::checkRemove(const PatPile * pile, const QList<KCard*> & cards) const
+{
+    switch (pile->pileRole())
+    {
+    case PatPile::Tableau:
+        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:
+        return true;
+    default:
+        return false;
+    }
+}
+
+
+static class CastleDealerInfo : public DealerInfo
+{
+public:
+    CastleDealerInfo()
+      : DealerInfo(I18N_NOOP("Castle"), CastleGeneralId)
+    {
+        addSubtype( CastleBeleagueredId, I18N_NOOP( "Beleaguered Castle" ) );
+        addSubtype( CastleCitadelId, I18N_NOOP( "Citadel" ) );
+        addSubtype( CastleExiledKingsId, I18N_NOOP( "Exiled Kings" ) );
+        addSubtype( CastleStreetAlleyId, I18N_NOOP( "Streets and Alleys" ) );
+        addSubtype( CastleSiegecraftId, I18N_NOOP( "Siegecraft" ) );
+        addSubtype( CastleStrongholdId, I18N_NOOP( "Stronghold" ) );
+        addSubtype( CastleCustomId, I18N_NOOP( "Castle (Custom)" ) );
+    }
+
+    DealerScene *createGame() const override
+    {
+        return new Castle( this );
+    }
+} castleDealerInfo;
+
+
+void Castle::matchVariant()
+{
+    if ( m_emptyStackFill == 0 && m_sequenceBuiltBy == 2 && m_reserves == 0  && m_stacks == 8 && m_foundation == 1 )
+        m_variation = 0;
+    else if ( m_emptyStackFill == 0 && m_sequenceBuiltBy == 2 && m_reserves == 0  && m_stacks == 8 && m_foundation == 2 )
+        m_variation = 1;
+    else if ( m_emptyStackFill == 1 && m_sequenceBuiltBy == 2 && m_reserves == 0  && m_stacks == 8 && m_foundation == 2 )
+        m_variation = 2;
+    else if ( m_emptyStackFill == 0 && m_sequenceBuiltBy == 2 && m_reserves == 0  && m_stacks == 8 && m_foundation == 0 )
+        m_variation = 3;
+    else if ( m_emptyStackFill == 0 && m_sequenceBuiltBy == 2 && m_reserves == 1  && m_stacks == 8 && m_foundation == 1 )
+        m_variation = 4;
+    else if ( m_emptyStackFill == 0 && m_sequenceBuiltBy == 2 && m_reserves == 1  && m_stacks == 8 && m_foundation == 0 )
+        m_variation = 5;
+    else
+        m_variation = 6;
+
+    options->setCurrentItem( m_variation );
+}
+
+
+void Castle::configOptions()
+{
+    options = new KSelectAction(i18n("Popular Variant Presets"), this );
+    options->addAction( i18n("Beleaguered Castle") );
+    options->addAction( i18n("Citadel") );
+    options->addAction( i18n("Exiled Kings") );
+    options->addAction( i18n("Streets and Alleys") );
+    options->addAction( i18n("Siegecraft") );
+    options->addAction( i18n("Stronghold") );
+    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_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_stackFaceupOption = new KSelectAction(i18n("S&tack Options"), this );
+    m_stackFaceupOption->addAction( i18n("Face &Down") );
+    m_stackFaceupOption->addAction( i18n("Face &Up") );
+    m_stackFaceupOption->addAction( i18n("Alternating Face &Up") );
+    
+    m_foundationOption = new KSelectAction(i18n("Foundation Deal"), this );
+    m_foundationOption->addAction( i18n("None") );
+    m_foundationOption->addAction( i18n("Aces") );
+    m_foundationOption->addAction( i18n("Any") );
+    
+    m_layoutOption = new KSelectAction(i18n("Layout"), this );
+    m_layoutOption->addAction( i18n("Classic") );
+    m_layoutOption->addAction( i18n("Modern") );
+
+#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 78, 0)
+    connect(options, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
+    connect(m_emptyStackFillOption, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
+    connect(m_reservesOption, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
+    connect(m_sequenceBuiltByOption, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
+    connect(m_stacksOption, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
+    connect(m_stackFaceupOption, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
+    connect(m_foundationOption, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
+    connect(m_layoutOption, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
+#else
+    connect(options, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
+    connect(m_emptyStackFillOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
+    connect(m_reservesOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
+    connect(m_sequenceBuiltByOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
+    connect(m_stacksOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
+    connect(m_stackFaceupOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
+    connect(m_foundationOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
+    connect(m_layoutOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
+#endif
+}
+
+
+void Castle::setSavedOptions()
+{
+    Settings::setCastleEmptyStackFill( m_emptyStackFill );
+    Settings::setCastleSequenceBuiltBy( m_sequenceBuiltBy );
+    Settings::setCastleReserves( m_reserves );
+    Settings::setCastleStacks( m_stacks );
+    Settings::setCastleStackFaceup( m_stackFaceup );
+    Settings::setCastleFoundation( m_foundation );
+    Settings::setCastleLayout( m_layout );
+}
+
+
+void Castle::getSavedOptions()
+{
+    m_emptyStackFill = Settings::castleEmptyStackFill();
+    m_sequenceBuiltBy = Settings::castleSequenceBuiltBy();
+    m_reserves = Settings::castleReserves();
+    m_stacks = Settings::castleStacks();
+    m_stackFaceup = Settings::castleStackFaceup();
+    m_foundation = Settings::castleFoundation();
+    m_layout = Settings::castleLayout();
+    m_decks = 1;
+    
+    if ( m_stacks < 6) m_stacks = 6;
+
+    matchVariant();
+
+    m_emptyStackFillOption->setCurrentItem( m_emptyStackFill );
+    m_sequenceBuiltByOption->setCurrentItem( m_sequenceBuiltBy );
+    m_reservesOption->setCurrentItem( m_reserves );
+    m_stacksOption->setCurrentItem( m_stacks - 6 );
+    m_stackFaceupOption->setCurrentItem( m_stackFaceup );
+    m_foundationOption->setCurrentItem( m_foundation );
+    m_layoutOption->setCurrentItem( m_layout );
+}
+
+
+void Castle::mapOldId(int id)
+{
+    switch (id) {
+
+    case DealerInfo::CastleBeleagueredId :
+        setOptions(0);
+        break;
+    case DealerInfo::CastleCitadelId :
+        setOptions(1);
+        break;
+    case DealerInfo::CastleExiledKingsId :
+        setOptions(2);
+        break;
+    case DealerInfo::CastleStreetAlleyId :
+        setOptions(3);
+        break;
+    case DealerInfo::CastleSiegecraftId :
+        setOptions(4);
+        break;
+    case DealerInfo::CastleStrongholdId :
+        setOptions(5);
+        break;
+    case DealerInfo::CastleCustomId :
+        setOptions(6);
+        break;
+    default:
+        // Do nothing.
+        break;
+    }
+}
+
+
+int Castle::oldId() const
+{
+    switch (m_variation) {
+    case 0 :
+        return DealerInfo::CastleBeleagueredId;
+    case 1 :
+        return DealerInfo::CastleCitadelId;
+    case 2 :
+        return DealerInfo::CastleExiledKingsId;
+    case 3 :
+        return DealerInfo::CastleStreetAlleyId;
+    case 4 :
+        return DealerInfo::CastleSiegecraftId;
+    case 5 :
+        return DealerInfo::CastleStrongholdId;
+    default :
+        return DealerInfo::CastleCustomId;
+    }
+}
+
+
+void Castle::setOptions(int variation)
+{
+    if ( variation != m_variation )
+    {
+        m_variation = variation;
+        m_emptyStackFill = 0;
+        m_sequenceBuiltBy = 2;
+        m_reserves = 0;
+        m_stacks = 8;
+        m_stackFaceup = 1;
+        m_decks = 1;
+        m_foundation = 1;
+
+        switch (m_variation) {
+        case 0 :
+            break;
+        case 1 :
+            m_foundation = 2;
+            break;
+        case 2 :
+            m_foundation = 2;
+            m_emptyStackFill = 1;
+            break;
+        case 3 :
+            m_foundation = 0;
+            break;
+        case 4 :
+            m_reserves = 1;
+            break;
+        case 5 :
+            m_foundation = 0;
+            m_reserves = 1;
+            break;
+        case 6 :
+            m_sequenceBuiltBy = 0;
+            break;
+        }
+
+        m_emptyStackFillOption->setCurrentItem( m_emptyStackFill );
+        m_sequenceBuiltByOption->setCurrentItem( m_sequenceBuiltBy );
+        m_reservesOption->setCurrentItem( m_reserves );
+        m_stacksOption->setCurrentItem( m_stacks - 6 );
+        m_stackFaceupOption->setCurrentItem( m_stackFaceup );
+        m_foundationOption->setCurrentItem( m_foundation );
+    }
+}
+
diff --git a/src/castle.h b/src/castle.h
new file mode 100644
index 00000000..0982632f
--- /dev/null
+++ b/src/castle.h
@@ -0,0 +1,94 @@
+/*
+ *   Copyright 2021 Michael Lang <criticaltemp at protonmail.com>
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU General Public License as
+ *   published by the Free Software Foundation; either version 2 of 
+ *   the License, or (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CASTLE_H
+#define CASTLE_H
+
+// own
+#include "dealer.h"
+#include "hint.h"
+
+class KSelectAction;
+
+class Castle : public DealerScene
+{
+    Q_OBJECT
+
+public:
+    explicit Castle( 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;
+
+private Q_SLOTS:
+    void gameTypeChanged();
+
+protected Q_SLOTS:
+    bool tryAutomaticMove( KCard * c ) override;
+
+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[10];
+    PatPile* freecell[4];
+    PatPile* target[8];
+
+    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_stackFaceupOption;
+    int m_stackFaceup;
+
+    KSelectAction *m_decksOption;
+    int m_decks;
+    
+    KSelectAction *m_foundationOption;
+    int m_foundation;
+    
+    KSelectAction *m_layoutOption;
+    int m_layout;
+
+    friend class CastleSolver;
+};
+
+#endif
diff --git a/src/dealerinfo.h b/src/dealerinfo.h
index 56b1eb35..9884f6fb 100644
--- a/src/dealerinfo.h
+++ b/src/dealerinfo.h
@@ -57,6 +57,7 @@ public:
         GrandfatherId       = 1,
         AcesUpId            = 2,
         FreecellGeneralId   = 3,
+        CastleGeneralId     = 4,
         Mod3Id              = 5,
         GypsyId             = 7,
         FortyAndEightId     = 8,
@@ -78,10 +79,17 @@ public:
         BakersDozenCustomId = 24,
         FreecellId          = 30,
         FreecellBakersId    = 31,
-        FreecellEightOffId  = 32,
+        FreecellEightOffId   = 32,
         FreecellForeId      = 33,
         FreecellSeahavenId  = 34,
-        FreecellCustomId    = 39
+        FreecellCustomId    = 39,
+        CastleBeleagueredId = 40,
+        CastleCitadelId     = 41,
+        CastleExiledKingsId = 42,
+        CastleStreetAlleyId = 43,
+        CastleSiegecraftId  = 44,
+        CastleStrongholdId  = 45,
+        CastleCustomId      = 49
     };
 
     DealerInfo( const QByteArray & untranslatedBaseName, int baseId );
diff --git a/src/kpat.kcfg b/src/kpat.kcfg
index 9fd9d071..dc09f8cf 100644
--- a/src/kpat.kcfg
+++ b/src/kpat.kcfg
@@ -65,5 +65,29 @@
         <entry name="BakersDozenSequenceBuiltBy" key="BakersDozenSequenceBuiltBy" type="Int">
             <default>2</default>
         </entry>
+        <entry name="CastleSolverIterationsLimit" key="CastleSolverIterationsLimit" type="Int">
+            <default>200000</default>
+        </entry>
+        <entry name="CastleEmptyStackFill" key="CastleEmptyStackFill" type="Int">
+            <default>0</default>
+        </entry>
+        <entry name="CastleSequenceBuiltBy" key="CastleSequenceBuiltBy" type="Int">
+            <default>2</default>
+        </entry>
+        <entry name="CastleReserves" key="CastleReserves" type="Int">
+            <default>0</default>
+        </entry>
+        <entry name="CastleStacks" key="CastleStacks" type="Int">
+            <default>8</default>
+        </entry>
+        <entry name="CastleStackFaceup" key="CastleStackFaceup" type="Int">
+            <default>1</default>
+        </entry>
+        <entry name="CastleFoundation" key="CastleFoundation" type="Int">
+            <default>1</default>
+        </entry>
+        <entry name="CastleLayout" key="CastleLayout" type="Int">
+            <default>0</default>
+        </entry>
     </group>
 </kcfg>
diff --git a/src/patsolve/castlesolver.cpp b/src/patsolve/castlesolver.cpp
new file mode 100644
index 00000000..c9631dd9
--- /dev/null
+++ b/src/patsolve/castlesolver.cpp
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 1998-2002 Tom Holroyd <tomh at kurage.nimh.nih.gov>
+ * Copyright (C) 2006-2009 Stephan Kulow <coolo at kde.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of 
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "castlesolver.h"
+
+// own
+#include "patsolve-config.h"
+#include "../castle.h"
+#include "../settings.h"
+// freecell-solver
+#include "freecell-solver/fcs_user.h"
+#include "freecell-solver/fcs_cl.h"
+// St
+#include <cstdlib>
+#include <cstring>
+
+namespace {
+    int m_reserves = Settings::castleReserves();
+    int m_stacks = Settings::castleStacks();
+    int m_decks = 1;
+    int m_emptyStackFill = Settings::castleEmptyStackFill();
+    int m_sequenceBuiltBy = Settings::castleSequenceBuiltBy();
+}
+
+/* Automove logic.  Freecell games must avoid certain types of automoves. */
+int CastleSolver::good_automove(int o, int r)
+{
+    if (r <= 2) {
+        return true;
+    }
+
+    if (m_sequenceBuiltBy == 1) {
+        return true;
+    }
+
+    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();
+        }
+    }
+    /* Check the Out piles of opposite color. */
+
+    for (int i = 1 - (o & 1); i < 4 * m_decks; i += 2) {
+        if (O[i] < r - 1) {
+
+#if 1   /* Raymond's Rule */
+            /* Not all the N-1's of opposite color are out
+            yet.  We can still make an automove if either
+            both N-2's are out or the other same color N-3
+            is out (Raymond's rule).  Note the re-use of
+            the loop variable i.  We return here and never
+            make it back to the outer loop. */
+
+            for (i = 1 - (o & 1); i < 4 * m_decks; i += 2) {
+                if (O[i] < r - 2) {
+                    return false;
+                }
+            }
+            if (O[(o + 2) & 3] < r - 3) {
+                return false;
+            }
+
+            return true;
+#else   /* Horne's Rule */
+            return false;
+#endif
+        }
+    }
+
+    return true;
+}
+
+int CastleSolver::get_possible_moves(int *a, int *numout)
+{
+    int w;
+    card_t card;
+    MOVE *mp;
+
+    /* Check for moves from W to O. */
+
+    int n = 0;
+    mp = Possible;
+    for (w = 0; w < m_stacks + m_reserves; ++w) {
+        if (Wlen[w] > 0) {
+            card = *Wp[w];
+            int out_suit = SUIT(card);
+            const bool empty = (O[out_suit] == NONE);
+            if ((empty && (RANK(card) == PS_ACE)) ||
+                (!empty && (RANK(card) == O[out_suit] + 1))) {
+                mp->is_fcs = false;
+                mp->card_index = 0;
+                mp->from = w;
+                mp->to = out_suit;
+                mp->totype = O_Type;
+                mp->turn_index = -1;
+                mp->pri = 0;    /* unused */
+                n++;
+                mp++;
+
+                /* If it's an automove, just do it. */
+
+                if (good_automove(out_suit, RANK(card))) {
+                    *a = true;
+                    mp[-1].pri = 127;
+                    if (n != 1) {
+                        Possible[0] = mp[-1];
+                        return (*numout = 1);
+                    }
+                    return (*numout = n);
+                }
+            }
+        }
+    }
+    return (*numout = 0);
+}
+
+
+#define CMD_LINE_ARGS_NUM 2
+
+static const char * freecell_solver_cmd_line_args[CMD_LINE_ARGS_NUM] =
+{
+#ifdef WITH_FCS_SOFT_SUSPEND
+    "--load-config", "video-editing"
+#else
+    "--load-config", "slick-rock"
+#endif
+};
+
+int CastleSolver::get_cmd_line_arg_count()
+{
+    return CMD_LINE_ARGS_NUM;
+}
+
+const char * * CastleSolver::get_cmd_line_args()
+{
+    return freecell_solver_cmd_line_args;
+}
+
+
+void CastleSolver::setFcSolverGameParams()
+{
+    /*
+     * I'm using the more standard interface instead of the depracated
+     * user_set_game one. I'd like that each function will have its
+     * own dedicated purpose.
+     *
+     *     Shlomi Fish
+     * */
+    freecell_solver_user_set_sequence_move(solver_instance, 0);
+
+    m_reserves = Settings::castleReserves();
+    freecell_solver_user_set_num_freecells(solver_instance, m_reserves);
+
+    m_stacks = Settings::castleStacks();
+    freecell_solver_user_set_num_stacks(solver_instance, m_stacks);
+
+    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::castleEmptyStackFill();
+    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::castleSequenceBuiltBy();
+    freecell_solver_user_set_sequences_are_built_by_type(solver_instance, m_sequenceBuiltBy);
+}
+
+
+CastleSolver::CastleSolver(const Castle *dealer)
+    : FcSolveSolver()
+{
+    deal = dealer;
+}
+
+MoveHint CastleSolver::translateMove( const MOVE &m )
+{
+    if (m.is_fcs)
+    {
+        fcs_move_t move = m.fcs;
+        int cards = fcs_move_get_num_cards_in_seq(move);
+        PatPile *from = nullptr;
+        PatPile *to = nullptr;
+
+        switch(fcs_move_get_type(move))
+        {
+            case FCS_MOVE_TYPE_STACK_TO_STACK:
+            from = deal->store[fcs_move_get_src_stack(move)];
+            to = deal->store[fcs_move_get_dest_stack(move)];
+            break;
+
+            case FCS_MOVE_TYPE_FREECELL_TO_STACK:
+            from = deal->freecell[fcs_move_get_src_freecell(move)];
+            to = deal->store[fcs_move_get_dest_stack(move)];
+            cards = 1;
+            break;
+
+            case FCS_MOVE_TYPE_FREECELL_TO_FREECELL:
+            from = deal->freecell[fcs_move_get_src_freecell(move)];
+            to = deal->freecell[fcs_move_get_dest_freecell(move)];
+            cards = 1;
+            break;
+
+            case FCS_MOVE_TYPE_STACK_TO_FREECELL:
+            from = deal->store[fcs_move_get_src_stack(move)];
+            to = deal->freecell[fcs_move_get_dest_freecell(move)];
+            cards = 1;
+            break;
+
+            case FCS_MOVE_TYPE_STACK_TO_FOUNDATION:
+            from = deal->store[fcs_move_get_src_stack(move)];
+            cards = 1;
+            to = nullptr;
+            break;
+
+            case FCS_MOVE_TYPE_FREECELL_TO_FOUNDATION:
+            from = deal->freecell[fcs_move_get_src_freecell(move)];
+            cards = 1;
+            to = nullptr;
+        }
+        Q_ASSERT(from);
+        Q_ASSERT(cards <= from->cards().count());
+        Q_ASSERT(to || cards == 1);
+        KCard *card = from->cards()[from->cards().count() - cards];
+
+        if (!to)
+        {
+            PatPile *target = nullptr;
+            PatPile *empty = nullptr;
+            for (int i = 0; i < 4 * m_decks; ++i) {
+                KCard *c = deal->target[i]->topCard();
+                if (c) {
+                    if ( c->suit() == card->suit() )
+                    {
+                        target = deal->target[i];
+                        break;
+                    }
+                } else if ( !empty )
+                    empty = deal->target[i];
+            }
+            to = target ? target : empty;
+        }
+        Q_ASSERT(to);
+
+        return MoveHint(card, to, 0);
+    }
+    else
+    {
+        // this is tricky as we need to want to build the "meta moves"
+
+        PatPile *frompile = nullptr;
+        if ( m.from < m_stacks )
+            frompile = deal->store[m.from];
+        else
+            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 * m_decks; ++i) {
+                KCard *c = deal->target[i]->topCard();
+                if (c) {
+                    if ( c->suit() == card->suit() )
+                    {
+                        target = deal->target[i];
+                        break;
+                    }
+                } else if ( !empty )
+                    empty = deal->target[i];
+            }
+            if ( !target )
+                target = empty;
+            return MoveHint( card, target, m.pri );
+        } else {
+            PatPile *target = nullptr;
+            if ( m.to < m_stacks )
+                target = deal->store[m.to];
+            else
+                target = deal->freecell[m.to - m_stacks];
+
+            return MoveHint( card, target, m.pri );
+        }
+    }
+}
+
+void CastleSolver::translate_layout()
+{
+    strcpy(board_as_string, deal->solverFormat().toLatin1().constData());
+
+    make_solver_instance_ready();
+    /* Read the workspace. */
+
+    int total = 0;
+    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 == m_stacks) {
+            break;
+        }
+    }
+
+    /* Temp cells may have some cards too. */
+
+    for (int w = 0; w < m_reserves; ++w)
+        {
+            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 * m_decks; ++i) {
+        O[i] = NONE;
+    }
+    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();
+                    total += c->rank();
+                }
+            }
+    }
+
+}
+
diff --git a/src/patsolve/castlesolver.h b/src/patsolve/castlesolver.h
new file mode 100644
index 00000000..d111b808
--- /dev/null
+++ b/src/patsolve/castlesolver.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 1998-2002 Tom Holroyd <tomh at kurage.nimh.nih.gov>
+ * Copyright (C) 2006-2009 Stephan Kulow <coolo at kde.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of 
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CASTLESOLVER_H
+#define CASTLESOLVER_H
+
+// own
+#include "abstract_fc_solve_solver.h"
+
+constexpr auto Nwpiles = 8;
+constexpr auto Ntpiles = 4;
+class Castle;
+
+class CastleSolver : public FcSolveSolver
+{
+public:
+    explicit CastleSolver(const Castle *dealer);
+    int good_automove(int o, int r);
+    int get_possible_moves(int *a, int *numout) override;
+    void translate_layout() override;
+    MoveHint translateMove(const MOVE &m) override;
+    void setFcSolverGameParams() override;
+    int get_cmd_line_arg_count() override;
+    const char * * get_cmd_line_args() override;
+    card_t O[12]; /* output piles store only the rank or NONE */
+    const Castle *deal;
+};
+
+#endif // CASTLESOLVER_H


More information about the kde-doc-english mailing list