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 <[email protected]> + * + * 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 <[email protected]> + * + * 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 <[email protected]> + * Copyright (C) 2006-2009 Stephan Kulow <[email protected]> + * + * 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 <[email protected]> + * Copyright (C) 2006-2009 Stephan Kulow <[email protected]> + * + * 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
