[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