[games/kpat] /: Add new game type called Baker's Dozen
Albert Astals Cid
null at kde.org
Thu Feb 11 23:16:14 GMT 2021
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 <coolo at kde.org>
+ * Copyright (C) 2010 Parker Coates <coates at 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 <paul at troll.no>
* Copyright (C) 2000-2009 Stephan Kulow <coolo at kde.org>
- * Copyright (C) 2009 Parker Coates <coates at 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("shlomif at cpan.org") );
+ aboutData.addAuthor( i18n("Michael Lang"),
+ i18n("New game types"),
+ QStringLiteral("criticaltemp at 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 <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 "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 <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 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 );
More information about the kde-doc-english
mailing list