Git commit 0623605edfef76a88ef684a1c4e4a15adba22ddb by Albert Astals Cid, on behalf of Michael Lang. Committed on 11/02/2021 at 23:16. Pushed by aacid into branch 'master'.
Add new game type called Baker's Dozen Added Baker's Dozen game type with several subtype variants: - Spanish Patience - Castles in Spain - Portuguese Solitaire - Other custom configurations M +52 -0 doc/index.docbook A +- -- previews/19.png M +1 -0 previews/CMakeLists.txt M +2 -0 src/CMakeLists.txt A +425 -0 src/bakersdozen.cpp [License: MIT GPL (v2+)] C +46 -71 src/bakersdozen.h [from: src/dealerinfo.h - 051% similarity] M +7 -1 src/dealerinfo.h M +12 -0 src/kpat.kcfg M +3 -0 src/main.cpp M +1 -1 src/patsolve/abstract_fc_solve_solver.h A +288 -0 src/patsolve/bakersdozensolver.cpp [License: GPL (v2+)] A +47 -0 src/patsolve/bakersdozensolver.h [License: GPL (v2+)] M +1 -0 src/patsolve/patsolve.cpp M +17 -0 src/pileutils.cpp M +1 -0 src/pileutils.h https://invent.kde.org/games/kpat/commit/0623605edfef76a88ef684a1c4e4a15adba22ddb diff --git a/doc/index.docbook b/doc/index.docbook index 019f8b4..484926a 100644 --- a/doc/index.docbook +++ b/doc/index.docbook @@ -562,6 +562,58 @@ win at Easy level, and very difficult to win at Hard level. </sect2> +<sect2 id="bakers-dozen"> +<title>Baker's Dozen</title> + +<para><indexterm><primary>Baker's Dozen</primary></indexterm> +Baker's Dozen is played with one card deck. The game's name originates from +the 13 columns in the game, the number in a baker's dozen. +The cards are dealt into columns of four on the tableau, resulting in 13 columns. +Any king that is in the top or middle of each column must be placed on the +bottom before the game starts. Two kings that are mixed into one column are +placed on the bottom without changing their order. +</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> +- In Spanish Patience, any card can fill empty tableau spaces. (In some sources, +the foundations are built up regardless of suit) +</para> + +<para> +- Castles in Spain is akin to Spanish Patience, but the cards in the tableau are +built down by alternate color. In some variations, the tableau is dealt face-down +aside from the top cards of each column. +</para> + +<para> +- Portuguese Solitaire is halfway between Baker's Dozen and Spanish Patience because +empty columns can only be filled with Kings. +</para> +</listitem> +</varlistentry> +</variablelist> + +<para> +To solve this game it is recommended to grab the cards out of the playing +sequences in the same order they have to be put into the foundation (first the +aces, then the twos, &etc;) Typically, you want to avoid emptying a column until +the last card is ready to be moved to a foundation. +</para> + +</sect2> + </sect1> </chapter> diff --git a/previews/19.png b/previews/19.png new file mode 100644 index 0000000..e30627f Binary files /dev/null and b/previews/19.png differ diff --git a/previews/CMakeLists.txt b/previews/CMakeLists.txt index 19971fc..5f4dcc4 100644 --- a/previews/CMakeLists.txt +++ b/previews/CMakeLists.txt @@ -11,6 +11,7 @@ set( kpat_previews 12.png 17.png 18.png + 19.png ) install( FILES ${kpat_previews} DESTINATION ${KDE_INSTALL_DATADIR}/kpat/previews ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3996384..19486cd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,6 +26,8 @@ set(kpat_SRCS ${libfcs_SRCS} patsolve/memory.cpp patsolve/patsolve.cpp + bakersdozen.cpp + patsolve/bakersdozensolver.cpp clock.cpp patsolve/clocksolver.cpp fortyeight.cpp diff --git a/src/bakersdozen.cpp b/src/bakersdozen.cpp new file mode 100644 index 0000000..632a026 --- /dev/null +++ b/src/bakersdozen.cpp @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2000-2009 Stephan Kulow <co...@kde.org> + * Copyright (C) 2010 Parker Coates <coa...@kde.org> + * + * License of original code: + * ------------------------------------------------------------------------- + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, + * provided that the above copyright notice appear in all copies and that + * both that copyright notice and this permission notice appear in + * supporting documentation. + * + * This file is provided AS IS with no warranties of any kind. The author + * shall have no liability with respect to the infringement of copyrights, + * trade secrets or any patents by this file or any part thereof. In no + * event will the author be liable for any lost revenue or profits or + * other special, indirect and consequential damages. + * ------------------------------------------------------------------------- + * + * License of modifications/additions made after 2009-01-01: + * ------------------------------------------------------------------------- + * 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 "bakersdozen.h" + +// own +#include "dealerinfo.h" +#include "pileutils.h" +#include "settings.h" +#include "speeds.h" +#include "patsolve/bakersdozensolver.h" +// KF +#include <KLocalizedString> +#include <kwidgetsaddons_version.h> +#include <KSelectAction> + + +BakersDozen::BakersDozen( const DealerInfo * di ) + : DealerScene( di ) +{ +} + + +void BakersDozen::initialize() +{ + setDeckContents(); + + const qreal dist_x = 1.11; + + for ( int i = 0; i < 4; ++i ) + { + target[i] = new PatPile( this, i + 1, QStringLiteral( "target%1" ).arg( i ) ); + target[i]->setPileRole(PatPile::Foundation); + target[i]->setLayoutPos((i*2+3)*dist_x, 0); + target[i]->setSpread(0, 0); + target[i]->setKeyboardSelectHint( KCardPile::NeverFocus ); + target[i]->setKeyboardDropHint( KCardPile::AutoFocusTop ); + } + + for ( int i = 0; i < 13; ++i ) + { + store[i] = new PatPile( this, 0 + i, QStringLiteral( "store%1" ).arg( i ) ); + store[i]->setPileRole(PatPile::Tableau); + store[i]->setLayoutPos(dist_x*i, 1.2); + store[i]->setAutoTurnTop(true); + store[i]->setBottomPadding( 2.0 ); + store[i]->setHeightPolicy( KCardPile::GrowDown ); + store[i]->setZValue( 0.01 * i ); + store[i]->setKeyboardSelectHint( KCardPile::FreeFocus ); + store[i]->setKeyboardDropHint( KCardPile::AutoFocusTop ); + } + + setActions(DealerScene::Hint | DealerScene::Demo); + auto solver = new BakersDozenSolver( this ); + solver->default_max_positions = Settings::bakersDozenSolverIterationsLimit(); + setSolver( solver ); + setNeededFutureMoves( 4 ); // reserve some + + options = new KSelectAction(i18n("Popular Variant Presets"), this ); + options->addAction( i18n("Baker's Dozen") ); + options->addAction( i18n("Spanish Patience") ); + options->addAction( i18n("Castles in Spain") ); + options->addAction( i18n("Portuguese Solitaire") ); + 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_stackFacedownOption = new KSelectAction(i18n("Stack Options"), this ); + m_stackFacedownOption->addAction( i18n("Face &Up (Easier)") ); + m_stackFacedownOption->addAction( i18n("Face &Down (Harder)") ); + + 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") ); + +#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 78, 0) + connect(options, &KSelectAction::indexTriggered, this, &BakersDozen::gameTypeChanged); + connect(m_emptyStackFillOption, &KSelectAction::indexTriggered, this, &BakersDozen::gameTypeChanged); + connect(m_stackFacedownOption, &KSelectAction::indexTriggered, this, &BakersDozen::gameTypeChanged); + connect(m_sequenceBuiltByOption, &KSelectAction::indexTriggered, this, &BakersDozen::gameTypeChanged); +#else + connect(options, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &BakersDozen::gameTypeChanged); + connect(m_emptyStackFillOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &BakersDozen::gameTypeChanged); + connect(m_stackFacedownOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &BakersDozen::gameTypeChanged); + connect(m_sequenceBuiltByOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &BakersDozen::gameTypeChanged); +#endif + + getSavedOptions(); +} + + +QList<QAction*> BakersDozen::configActions() const +{ + return QList<QAction*>() << options << m_emptyStackFillOption << m_stackFacedownOption << m_sequenceBuiltByOption; +} + + +void BakersDozen::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_stackFacedown != m_stackFacedownOption->currentItem() ) + m_stackFacedown = m_stackFacedownOption->currentItem(); + else if ( m_sequenceBuiltBy != m_sequenceBuiltByOption->currentItem() ) + m_sequenceBuiltBy = m_sequenceBuiltByOption->currentItem(); + + matchVariant(); + } + + auto solver = new BakersDozenSolver( this ); + solver->default_max_positions = Settings::bakersDozenSolverIterationsLimit(); + setSolver( solver ); + startNew( gameNumber() ); + setSavedOptions(); + } + else + { + // If we're not allowed, reset the options + getSavedOptions(); + } +} + + +void BakersDozen::restart( const QList<KCard*> & cards ) +{ + QList<KCard*> cardList = cards; + + QPointF initPos( 0, -deck()->cardHeight() ); + + for ( int row = 0; row < 4; ++row ) + { + bool isFaceUp = m_stackFacedown == 1 && row < 3 ? false : true; + + for ( int column = 0; column < 13; ++column ) + { + addCardForDeal( store[column], cardList.takeLast(), isFaceUp, initPos ); + } + } + + // Move kings to bottom of pile without changing relative order + for ( int column = 0; column < 13; ++column ) + { + int counter = 0; + + const auto cards = store[column]->cards(); + for (KCard * c : cards) { + if(c->rank() == KCardDeck::King) + { + int index = store[column]->indexOf(c); + store[column]->swapCards(index, counter); + counter++; + + if(m_stackFacedown == 1) + c->setFaceUp( false ); + } + } + } + + startDealAnimation(); +} + + +QString BakersDozen::solverFormat() const +{ + QString output; + QString tmp; + for (int i = 0; i < 4 ; 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); + + for (int i = 0; i < 13 ; i++) + cardsListToLine(output, store[i]->cards()); + return output; +} + + +bool BakersDozen::checkAdd(const PatPile * pile, const QList<KCard*> & oldCards, const QList<KCard*> & newCards) const +{ + if (pile->pileRole() == PatPile::Tableau) + { + int freeStores = 0; + if (m_emptyStackFill == 0) + { + for ( int i = 0; i < 13; ++i ) + if ( store[i]->isEmpty() && store[i] != pile) + ++freeStores; + } + + if (newCards.size() <= 1 << freeStores) + { + if (oldCards.isEmpty()) + return m_emptyStackFill == 0 || (m_emptyStackFill == 1 && newCards.first()->rank() == KCardDeck::King); + else + if (m_sequenceBuiltBy == 1) + return newCards.first()->suit() == oldCards.last()->suit() && newCards.first()->rank() == oldCards.last()->rank() - 1; + else if (m_sequenceBuiltBy == 0) + return checkAddAlternateColorDescending(oldCards, newCards); + else + return newCards.first()->rank() == oldCards.last()->rank() - 1; + } + else + { + return false; + } + } + else + { + return checkAddSameSuitAscendingFromAce(oldCards, newCards); + } +} + +bool BakersDozen::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 BakersDozenDealerInfo : public DealerInfo +{ +public: + BakersDozenDealerInfo() + : DealerInfo(I18N_NOOP("Baker's Dozen"), BakersDozenGeneralId ) + { + addSubtype( BakersDozenId, I18N_NOOP( "Baker's Dozen" ) ); + addSubtype( BakersDozenSpanishId, I18N_NOOP( "Spanish Patience" ) ); + addSubtype( BakersDozenCastlesId, I18N_NOOP( "Castles in Spain" ) ); + addSubtype( BakersDozenPortugueseId, I18N_NOOP( "Portuguese Solitaire" ) ); + addSubtype( BakersDozenCustomId, I18N_NOOP( "Baker's Dozen (Custom)" ) ); + } + + + DealerScene *createGame() const override + { + return new BakersDozen( this ); + } +} BakersDozenDealerInfo; + + +void BakersDozen::matchVariant() +{ + if (m_emptyStackFill == 2 && m_stackFacedown == 0 && m_sequenceBuiltBy == 2) + m_variation = 0; + else if (m_emptyStackFill == 0 && m_stackFacedown == 0 && m_sequenceBuiltBy == 2) + m_variation = 1; + else if (m_emptyStackFill == 0 && m_stackFacedown == 1 && m_sequenceBuiltBy == 0) + m_variation = 2; + else if (m_emptyStackFill == 1 && m_stackFacedown == 0 && m_sequenceBuiltBy == 2) + m_variation = 3; + else + m_variation = 4; + + options->setCurrentItem( m_variation ); +} + + +void BakersDozen::setSavedOptions() +{ + Settings::setBakersDozenEmptyStackFill( m_emptyStackFill ); + Settings::setBakersDozenStackFacedown( m_stackFacedown ); + Settings::setBakersDozenSequenceBuiltBy( m_sequenceBuiltBy ); +} + + +void BakersDozen::getSavedOptions() +{ + m_emptyStackFill = Settings::bakersDozenEmptyStackFill(); + m_stackFacedown = Settings::bakersDozenStackFacedown(); + m_sequenceBuiltBy = Settings::bakersDozenSequenceBuiltBy(); + + matchVariant(); + + m_emptyStackFillOption->setCurrentItem( m_emptyStackFill ); + m_stackFacedownOption->setCurrentItem( m_stackFacedown ); + m_sequenceBuiltByOption->setCurrentItem( m_sequenceBuiltBy ); +} + + +void BakersDozen::mapOldId(int id) +{ + switch (id) { + case DealerInfo::BakersDozenId : + setOptions(0); + break; + case DealerInfo::BakersDozenSpanishId : + setOptions(1); + break; + case DealerInfo::BakersDozenCastlesId : + setOptions(2); + break; + case DealerInfo::BakersDozenPortugueseId : + setOptions(3); + break; + case DealerInfo::BakersDozenCustomId : + setOptions(4); + break; + default: + // Do nothing. + break; + } +} + + +int BakersDozen::oldId() const +{ + switch (m_variation) { + case 0 : + return DealerInfo::BakersDozenId; + case 1 : + return DealerInfo::BakersDozenSpanishId; + case 2 : + return DealerInfo::BakersDozenCastlesId; + case 3 : + return DealerInfo::BakersDozenPortugueseId; + default : + return DealerInfo::BakersDozenCustomId; + } +} + + +void BakersDozen::setOptions(int variation) +{ + if ( variation != m_variation ) + { + m_variation = variation; + + switch (m_variation) { + case 0 : + m_emptyStackFill = 2; + m_stackFacedown = 0; + m_sequenceBuiltBy = 2; + break; + case 1 : + m_emptyStackFill = 0; + m_stackFacedown = 0; + m_sequenceBuiltBy = 2; + break; + case 2 : + m_emptyStackFill = 0; + m_stackFacedown = 1; + m_sequenceBuiltBy = 0; + break; + case 3 : + m_emptyStackFill = 1; + m_stackFacedown = 0; + m_sequenceBuiltBy = 2; + break; + case 4 : + m_emptyStackFill = 0; + m_stackFacedown = 1; + m_sequenceBuiltBy = 1; + break; + } + + m_emptyStackFillOption->setCurrentItem( m_emptyStackFill ); + m_stackFacedownOption->setCurrentItem( m_stackFacedown ); + m_sequenceBuiltByOption->setCurrentItem( m_sequenceBuiltBy ); + } +} + diff --git a/src/dealerinfo.h b/src/bakersdozen.h similarity index 51% copy from src/dealerinfo.h copy to src/bakersdozen.h index e095877..45d1cbb 100644 --- a/src/dealerinfo.h +++ b/src/bakersdozen.h @@ -1,7 +1,5 @@ /* - * Copyright (C) 1995 Paul Olav Tvete <p...@troll.no> * Copyright (C) 2000-2009 Stephan Kulow <co...@kde.org> - * Copyright (C) 2009 Parker Coates <coa...@kde.org> * * License of original code: * ------------------------------------------------------------------------- @@ -35,83 +33,60 @@ * ------------------------------------------------------------------------- */ -#ifndef DEALERINFO_H -#define DEALERINFO_H +#ifndef BAKERSDOZEN_H +#define BAKERSDOZEN_H -// Qt -#include <QByteArray> -#include <QList> -#include <QMap> -#include <QString> +// own +#include "dealer.h" +#include "hint.h" -class DealerInfoList; -class DealerScene; +class KSelectAction; - -class DealerInfo +class BakersDozen : public DealerScene { -public: - enum GameIds - { - KlondikeDrawOneId = 0, - GrandfatherId = 1, - AcesUpId = 2, - FreecellId = 3, - Mod3Id = 5, - GypsyId = 7, - FortyAndEightId = 8, - SimpleSimonId = 9, - YukonId = 10, - GrandfathersClockId = 11, - GolfId = 12, - KlondikeDrawThreeId = 13, - SpiderOneSuitId = 14, - SpiderTwoSuitId = 15, - SpiderFourSuitId = 16, - SpiderGeneralId = 17, - KlondikeGeneralId = 18 - }; - - DealerInfo( const QByteArray & untranslatedBaseName, int baseId ); - virtual ~DealerInfo(); - - QString baseName() const; - QByteArray untranslatedBaseName() const; - QString baseIdString() const; - int baseId() const; - - void addSubtype( int id, const QByteArray & untranslatedName ); - QList<int> subtypeIds() const; - - QList<int> distinctIds() const; - bool providesId( int id ) const; - QString nameForId( int id ) const; - - virtual DealerScene * createGame() const = 0; - -protected: - QByteArray m_baseName; - QString m_baseIdString; - int m_baseId; - - QMap<int,QByteArray> m_subtypes; -}; - - -class DealerInfoList -{ -private: - friend class DealerInfoListPrivate; - explicit DealerInfoList(); - virtual ~DealerInfoList(); + Q_OBJECT public: - static DealerInfoList * self(); - void add( DealerInfo * di ); - const QList<DealerInfo*> games() const; + explicit BakersDozen( 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 restart( const QList<KCard*> & cards ) override; + //QList<MoveHint> getHints() override; + +private Q_SLOTS: + void gameTypeChanged(); + private: - QList<DealerInfo*> m_list; + void setOptions(int v); + void getSavedOptions(); + void setSavedOptions(); + void matchVariant(); + + virtual QString solverFormat() const; + PatPile* store[13]; + PatPile* target[4]; + + KSelectAction *options; + int m_variation; + + KSelectAction *m_emptyStackFillOption; + int m_emptyStackFill; + + KSelectAction *m_stackFacedownOption; + int m_stackFacedown; + + KSelectAction *m_sequenceBuiltByOption; + int m_sequenceBuiltBy; + + + friend class BakersDozenSolver; }; #endif + diff --git a/src/dealerinfo.h b/src/dealerinfo.h index e095877..0438553 100644 --- a/src/dealerinfo.h +++ b/src/dealerinfo.h @@ -69,7 +69,13 @@ public: SpiderTwoSuitId = 15, SpiderFourSuitId = 16, SpiderGeneralId = 17, - KlondikeGeneralId = 18 + KlondikeGeneralId = 18, + BakersDozenGeneralId= 19, + BakersDozenId = 20, + BakersDozenSpanishId= 21, + BakersDozenCastlesId= 22, + BakersDozenPortugueseId= 23, + BakersDozenCustomId = 24 }; DealerInfo( const QByteArray & untranslatedBaseName, int baseId ); diff --git a/src/kpat.kcfg b/src/kpat.kcfg index c57d0ce..15c951b 100644 --- a/src/kpat.kcfg +++ b/src/kpat.kcfg @@ -38,5 +38,17 @@ <entry name="SimpleSimonSolverIterationsLimit" key="SimpleSimonSolverIterationsLimit" type="Int"> <default>200000</default> </entry> + <entry name="BakersDozenSolverIterationsLimit" key="BakersDozenSolverIterationsLimit" type="Int"> + <default>200000</default> + </entry> + <entry name="BakersDozenEmptyStackFill" key="BakersDozenEmptyStackFill" type="Int"> + <default>2</default> + </entry> + <entry name="BakersDozenStackFacedown" key="BakersDozenStackFacedown" type="Int"> + <default>0</default> + </entry> + <entry name="BakersDozenSequenceBuiltBy" key="BakersDozenSequenceBuiltBy" type="Int"> + <default>2</default> + </entry> </group> </kcfg> diff --git a/src/main.cpp b/src/main.cpp index e8ba653..9d142b4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -171,6 +171,9 @@ int main( int argc, char **argv ) aboutData.addAuthor( i18n("Shlomi Fish"), i18n("Integration with Freecell Solver and further work"), QStringLiteral("shlo...@cpan.org") ); + aboutData.addAuthor( i18n("Michael Lang"), + i18n("New game types"), + QStringLiteral("criticalt...@protonmail.com") ); // Create a KLocale earlier than normal so that we can use i18n to translate // the names of the game types in the help text. diff --git a/src/patsolve/abstract_fc_solve_solver.h b/src/patsolve/abstract_fc_solve_solver.h index 9db6bbf..e0878de 100644 --- a/src/patsolve/abstract_fc_solve_solver.h +++ b/src/patsolve/abstract_fc_solve_solver.h @@ -21,7 +21,7 @@ // own #include "patsolve.h" -struct FcSolveSolver : public Solver<12> +struct FcSolveSolver : public Solver<13> { public: FcSolveSolver(); diff --git a/src/patsolve/bakersdozensolver.cpp b/src/patsolve/bakersdozensolver.cpp new file mode 100644 index 0000000..072266a --- /dev/null +++ b/src/patsolve/bakersdozensolver.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (C) 1998-2002 Tom Holroyd <t...@kurage.nimh.nih.gov> + * Copyright (C) 2006-2009 Stephan Kulow <co...@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 "bakersdozensolver.h" + +// own +#include "patsolve-config.h" +#include "../bakersdozen.h" +#include "../settings.h" +// freecell-solver +#include "freecell-solver/fcs_user.h" +#include "freecell-solver/fcs_cl.h" +// St +#include <cstdlib> +#include <cstring> + +#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 BakersDozenSolver::get_cmd_line_arg_count() +{ + return CMD_LINE_ARGS_NUM; +} + + +const char * * BakersDozenSolver::get_cmd_line_args() +{ + return freecell_solver_cmd_line_args; +} + + +void BakersDozenSolver::setFcSolverGameParams() +{ + freecell_solver_user_set_num_freecells(solver_instance, 0); + freecell_solver_user_set_num_stacks(solver_instance, 13); + freecell_solver_user_set_num_decks(solver_instance, 1); + freecell_solver_user_set_sequence_move(solver_instance, 0); + + //FCS_ES_FILLED_BY_ANY_CARD = 0, FCS_ES_FILLED_BY_KINGS_ONLY = 1,FCS_ES_FILLED_BY_NONE = 2 + int emptyStackFill = Settings::bakersDozenEmptyStackFill(); + freecell_solver_user_set_empty_stacks_filled_by(solver_instance, emptyStackFill); + + //FCS_SEQ_BUILT_BY_ALTERNATE_COLOR = 0, FCS_SEQ_BUILT_BY_SUIT = 1, FCS_SEQ_BUILT_BY_RANK = 2 + int sequenceBuiltBy = Settings::bakersDozenSequenceBuiltBy(); + freecell_solver_user_set_sequences_are_built_by_type(solver_instance, sequenceBuiltBy); +} + + +BakersDozenSolver::BakersDozenSolver(const BakersDozen *dealer) + : FcSolveSolver() +{ + deal = dealer; +} + +/* Automove logic. Freecell games must avoid certain types of automoves. */ +int BakersDozenSolver::good_automove(int o, int r) +{ + if (r <= 2) + return true; + + for (int foundation_idx = 0; foundation_idx < 4; ++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; 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; 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 BakersDozenSolver::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 < Nwpiles + Ntpiles; ++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); +} + + +MoveHint BakersDozenSolver::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_STACK_TO_FOUNDATION: + from = deal->store[fcs_move_get_src_stack(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; ++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 < 13 ) + frompile = deal->store[m.from]; + + KCard *card = frompile->at( frompile->count() - m.card_index - 1); + + if ( m.totype == O_Type ) + { + PatPile *target = nullptr; + PatPile *empty = nullptr; + for (int i = 0; i < 4; ++i) { + 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 < 13 ) + target = deal->store[m.to]; + + return MoveHint( card, target, m.pri ); + } + } +} + + +void BakersDozenSolver::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 < 13; ++w ) { + int i = translate_pile(deal->store[w], W[w], 52); + Wp[w] = &W[w][i - 1]; + Wlen[w] = i; + total += i; + if (w == Nwpiles) { + break; + } + } + + /* Output piles, if any. */ + for (int i = 0; i < 4; ++i) { + O[i] = NONE; + } + if (total != 52) { + for (int i = 0; i < 4; ++i) { + KCard *c = deal->target[i]->topCard(); + if (c) { + O[translateSuit( c->suit() ) >> 4] = c->rank(); + total += c->rank(); + } + } + } + +} + diff --git a/src/patsolve/bakersdozensolver.h b/src/patsolve/bakersdozensolver.h new file mode 100644 index 0000000..3ca8f39 --- /dev/null +++ b/src/patsolve/bakersdozensolver.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 1998-2002 Tom Holroyd <t...@kurage.nimh.nih.gov> + * Copyright (C) 2006-2009 Stephan Kulow <co...@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 BAKERSDOZENSOLVER_H +#define BAKERSDOZENSOLVER_H + +// own +#include "abstract_fc_solve_solver.h" + +constexpr auto Nwpiles = 13; +constexpr auto Ntpiles = 0; +class BakersDozen; + +class BakersDozenSolver : public FcSolveSolver +{ +public: + explicit BakersDozenSolver(const BakersDozen *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[4]; /* output piles store only the rank or NONE */ + + const BakersDozen *deal; +}; + +#endif diff --git a/src/patsolve/patsolve.cpp b/src/patsolve/patsolve.cpp index f5fd17c..f649adf 100644 --- a/src/patsolve/patsolve.cpp +++ b/src/patsolve/patsolve.cpp @@ -989,4 +989,5 @@ template class Solver<6>; template class Solver<34>; template class Solver<15>; template class Solver<7>; +template class Solver<13>; template class Solver<Nwpiles + Ntpiles>; diff --git a/src/pileutils.cpp b/src/pileutils.cpp index 468083a..259e053 100644 --- a/src/pileutils.cpp +++ b/src/pileutils.cpp @@ -115,6 +115,23 @@ bool isAlternateColorDescending( const QList<KCard*> & cards ) } +bool isRankDescending( const QList<KCard*> & cards ) +{ + if ( cards.size() <= 1 ) + return true; + + int lastRank = cards.first()->rank(); + + for( int i = 1; i < cards.size(); ++i ) + { + --lastRank; + if ( cards[i]->rank() != lastRank ) + return false; + } + return true; +} + + bool checkAddSameSuitAscendingFromAce( const QList<KCard*> & oldCards, const QList<KCard*> & newCards ) { if ( !isSameSuitAscending( newCards ) ) diff --git a/src/pileutils.h b/src/pileutils.h index 54bcc42..ebdc82b 100644 --- a/src/pileutils.h +++ b/src/pileutils.h @@ -28,6 +28,7 @@ class KCard; bool isSameSuitAscending( const QList<KCard*> & cards ); bool isSameSuitDescending( const QList<KCard*> & cards ); bool isAlternateColorDescending( const QList<KCard*> & cards ); +bool isRankDescending( const QList<KCard*> & cards ); int countSameSuitDescendingSequences( const QList<KCard*> & cards ); bool checkAddSameSuitAscendingFromAce( const QList<KCard*> & oldCards, const QList<KCard*> & newCards );