[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