diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f738bf..c043c45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,115 +1,119 @@ project(kpat) cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) set (QT_MIN_VERSION "5.7.0") set (KF5_MIN_VERSION "5.30.0") +include(FindPkgConfig) +pkg_check_modules(FC_SOLVE REQUIRED libfreecell-solver) find_package(ECM ${KF5_MIN_VERSION} REQUIRED CONFIG) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Widgets Svg) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Completion Config ConfigWidgets CoreAddons Crash DBusAddons DocTools GuiAddons I18n KIO NewStuff WidgetsAddons XmlGui ) find_package(KF5KDEGames 4.9.0 REQUIRED) include(FeatureSummary) include(ECMAddAppIcon) include(ECMInstallIcons) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/libkcardgame/include) add_subdirectory(icons) add_subdirectory(libkcardgame) add_subdirectory(mimetypes) add_subdirectory(previews) add_subdirectory(sounds) add_subdirectory(themes) add_subdirectory(doc) -set(kpat_SRCS +set(kpat_SRCS ${libfcs_SRCS} main.cpp dealer.cpp dealerinfo.cpp gameselectionscene.cpp mainwindow.cpp messagebox.cpp numbereddealdialog.cpp patpile.cpp pileutils.cpp renderer.cpp soundengine.cpp statisticsdialog.cpp view.cpp + patsolve/abstract_fc_solve_solver.cpp patsolve/memory.cpp patsolve/patsolve.cpp clock.cpp patsolve/clocksolver.cpp fortyeight.cpp patsolve/fortyeightsolver.cpp freecell.cpp patsolve/freecellsolver.cpp golf.cpp patsolve/golfsolver.cpp grandf.cpp patsolve/grandfsolver.cpp gypsy.cpp patsolve/gypsysolver.cpp idiot.cpp patsolve/idiotsolver.cpp klondike.cpp patsolve/klondikesolver.cpp mod3.cpp patsolve/mod3solver.cpp simon.cpp patsolve/simonsolver.cpp spider.cpp patsolve/spidersolver.cpp yukon.cpp patsolve/yukonsolver.cpp ) ki18n_wrap_ui( kpat_SRCS statisticsdialog.ui ) kconfig_add_kcfg_files( kpat_SRCS settings.kcfgc ) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*-apps-kpat.png") ecm_add_app_icon(kpat_SRCS ICONS ${ICONS_SRCS}) add_executable(kpat ${kpat_SRCS}) target_link_libraries(kpat KF5::Crash KF5::DBusAddons KF5::I18n KF5::KIOCore KF5KDEGames kcardgame + ${FC_SOLVE_LIBRARIES} ) install(TARGETS kpat ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install(PROGRAMS org.kde.kpat.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES kpatui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kpat) install(FILES kpat.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/dealer.cpp b/dealer.cpp index 7c03ebf..a2558fc 100644 --- a/dealer.cpp +++ b/dealer.cpp @@ -1,2116 +1,2120 @@ /* * Copyright (C) 1995 Paul Olav Tvete * Copyright (C) 2000-2009 Stephan Kulow * Copyright (C) 2009-2010 Parker Coates * * 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 . * ------------------------------------------------------------------------- */ #include "dealer.h" #include "dealerinfo.h" #include "messagebox.h" #include "renderer.h" #include "speeds.h" #include "patsolve/solverinterface.h" #include "version.h" #include "view.h" #include "KCardTheme" #include #include #include #include #include #include #include #include #include #include #include #include namespace { const qreal wonBoxToSceneSizeRatio = 0.7; QList shuffled( const QList & cards, int seed ) { Q_ASSERT( seed > 0 ); QList result = cards; for ( int i = result.size(); i > 1; --i ) { // We use the same pseudorandom number generation algorithm as Windows // Freecell, so that game numbers are the same between the two applications. // For more inforation, see // http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q28150 seed = 214013 * seed + 2531011; int rand = ( seed >> 16 ) & 0x7fff; result.swap( i - 1, rand % i ); } return result; } QString solverStatusMessage( int status, bool everWinnable ) { switch ( status ) { case SolverInterface::SolutionExists: return i18n("Solver: This game is winnable."); case SolverInterface::NoSolutionExists: return everWinnable ? i18n("Solver: This game is no longer winnable.") : i18n("Solver: This game cannot be won."); case SolverInterface::UnableToDetermineSolvability: return i18n("Solver: Unable to determine if this game is winnable."); case SolverInterface::SearchAborted: case SolverInterface::MemoryLimitReached: default: return QString(); } } int readIntAttribute( const QXmlStreamReader & xml, const QString & key, bool * ok = nullptr ) { QStringRef value = xml.attributes().value( key ); return QString::fromRawData( value.data(), value.length() ).toInt( ok ); } QString suitToString( int suit ) { switch ( suit ) { case KCardDeck::Clubs: return QStringLiteral("clubs"); case KCardDeck::Diamonds: return QStringLiteral("diamonds"); case KCardDeck::Hearts: return QStringLiteral("hearts"); case KCardDeck::Spades: return QStringLiteral("spades"); default: return QString(); } } QString rankToString( int rank ) { switch ( rank ) { case KCardDeck::Ace: return QStringLiteral("ace"); case KCardDeck::Two: return QStringLiteral("two"); case KCardDeck::Three: return QStringLiteral("three"); case KCardDeck::Four: return QStringLiteral("four"); case KCardDeck::Five: return QStringLiteral("five"); case KCardDeck::Six: return QStringLiteral("six"); case KCardDeck::Seven: return QStringLiteral("seven"); case KCardDeck::Eight: return QStringLiteral("eight"); case KCardDeck::Nine: return QStringLiteral("nine"); case KCardDeck::Ten: return QStringLiteral("ten"); case KCardDeck::Jack: return QStringLiteral("jack"); case KCardDeck::Queen: return QStringLiteral("queen"); case KCardDeck::King: return QStringLiteral("king"); default: return QString(); } } } class SolverThread : public QThread { Q_OBJECT public: SolverThread( SolverInterface * solver ) : m_solver( solver ) { } void run() Q_DECL_OVERRIDE { SolverInterface::ExitStatus result = m_solver->patsolve(); emit finished( result ); } void abort() { m_solver->stopExecution(); wait(); } signals: void finished( int result ); private: SolverInterface * m_solver; }; int DealerScene::moveCount() const { return m_loadedMoveCount + m_undoStack.size(); } void DealerScene::saveLegacyFile( QIODevice * io ) { QXmlStreamWriter xml( io ); xml.setCodec( "UTF-8" ); xml.setAutoFormatting( true ); xml.setAutoFormattingIndent( -1 ); xml.writeStartDocument(); xml.writeDTD( QStringLiteral("") ); xml.writeStartElement( QStringLiteral("dealer") ); xml.writeAttribute( QStringLiteral("id"), QString::number( gameId() ) ); xml.writeAttribute( QStringLiteral("options"), getGameOptions() ); xml.writeAttribute( QStringLiteral("number"), QString::number( gameNumber() ) ); xml.writeAttribute( QStringLiteral("moves"), QString::number( moveCount() ) ); xml.writeAttribute( QStringLiteral("started"), QString::number( m_dealStarted ) ); xml.writeAttribute( QStringLiteral("data"), getGameState() ); foreach( const PatPile * p, patPiles() ) { xml.writeStartElement( QStringLiteral("pile") ); xml.writeAttribute( QStringLiteral("index"), QString::number( p->index() ) ); xml.writeAttribute( QStringLiteral("z"), QString::number( p->zValue() ) ); foreach( const KCard * c, p->cards() ) { xml.writeStartElement( QStringLiteral("card") ); xml.writeAttribute( QStringLiteral("suit"), QString::number( c->suit() ) ); xml.writeAttribute( QStringLiteral("value"), QString::number( c->rank() ) ); xml.writeAttribute( QStringLiteral("faceup"), QString::number( c->isFaceUp() ) ); xml.writeAttribute( QStringLiteral("z"), QString::number( c->zValue() ) ); xml.writeEndElement(); } xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndDocument(); m_dealWasJustSaved = true; } bool DealerScene::loadLegacyFile( QIODevice * io ) { resetInternals(); QXmlStreamReader xml( io ); xml.readNextStartElement(); // Before KDE4.3, KPat didn't store game specific options in the save // file. This could cause crashes when loading a Spider game with a // different number of suits than the current setting. Similarly, in // Klondike the number of cards drawn from the deck was forgotten, but // this never caused crashes. Fortunately, in Spider we can count the // number of suits ourselves. For Klondike, there is no way to recover // that information. QString options = xml.attributes().value( QStringLiteral("options") ).toString(); if ( gameId() == 17 && options.isEmpty() ) { QSet suits; while ( !xml.atEnd() ) if ( xml.readNextStartElement() && xml.name() == "card" ) suits << readIntAttribute( xml, QStringLiteral("suit") ); options = QString::number( suits.size() ); // "Rewind" back to the tag. Yes, this is ugly. xml.clear(); io->reset(); xml.setDevice( io ); xml.readNextStartElement(); } setGameOptions( options ); m_dealNumber = readIntAttribute( xml, QStringLiteral("number") ); m_loadedMoveCount = readIntAttribute( xml, QStringLiteral("moves") ); m_dealStarted = readIntAttribute( xml, QStringLiteral("started") ); QString gameStateData = xml.attributes().value( QStringLiteral("data") ).toString(); QMultiHash cards; foreach ( KCard * c, deck()->cards() ) cards.insert( (c->id() & 0xffff), c ); QHash piles; foreach ( PatPile * p, patPiles() ) piles.insert( p->index(), p ); // Loop through s. while ( xml.readNextStartElement() ) { if ( xml.name() != "pile" ) { qWarning() << "Expected a \"pile\" tag. Found a" << xml.name() << "tag."; return false; } bool ok; int index = readIntAttribute( xml, QStringLiteral("index"), &ok ); QHash::const_iterator it = piles.constFind( index ); if ( !ok || it == piles.constEnd() ) { qWarning() << "Unrecognized pile index:" << xml.attributes().value( QStringLiteral("index") ); return false; } PatPile * p = it.value(); p->clear(); // Loop through s. while ( xml.readNextStartElement() ) { if ( xml.name() != "card" ) { qWarning() << "Expected a \"card\" tag. Found a" << xml.name() << "tag."; return false; } bool suitOk, rankOk, faceUpOk; int suit = readIntAttribute( xml, QStringLiteral("suit"), &suitOk ); int rank = readIntAttribute( xml, QStringLiteral("value"), &rankOk ); bool faceUp = readIntAttribute( xml, QStringLiteral("faceup"), &faceUpOk ); quint32 id = KCardDeck::getId( KCardDeck::Suit( suit ), KCardDeck::Rank( rank ), 0 ); KCard * card = cards.take( id ); if ( !suitOk || !rankOk || !faceUpOk || !card ) { qWarning() << "Unrecognized card: suit=" << xml.attributes().value(QStringLiteral("suit")) << " value=" << xml.attributes().value(QStringLiteral("value")) << " faceup=" << xml.attributes().value(QStringLiteral("faceup")); return false; } card->setFaceUp( faceUp ); p->add( card ); xml.skipCurrentElement(); } updatePileLayout( p, 0 ); } setGameState( gameStateData ); emit updateMoves( moveCount() ); takeState(); return true; } void DealerScene::saveFile( QIODevice * io ) { QXmlStreamWriter xml( io ); xml.setCodec( "UTF-8" ); xml.setAutoFormatting( true ); xml.setAutoFormattingIndent( -1 ); xml.writeStartDocument(); xml.writeStartElement( QStringLiteral("kpat-game") ); xml.writeAttribute( QStringLiteral("game-type"), m_di->baseIdString() ); if ( !getGameOptions().isEmpty() ) xml.writeAttribute( QStringLiteral("game-type-options"), getGameOptions() ); xml.writeAttribute( QStringLiteral("deal-number"), QString::number( gameNumber() ) ); QList allStates; for ( int i = 0; i < m_undoStack.size(); ++i ) allStates << m_undoStack.at( i ); allStates << m_currentState; for ( int i = m_redoStack.size() - 1; i >= 0; --i ) allStates << m_redoStack.at( i ); QString lastGameSpecificState; for ( int i = 0; i < allStates.size(); ++i ) { const GameState * state = allStates.at( i ); xml.writeStartElement( QStringLiteral("state") ); if ( state->stateData != lastGameSpecificState ) { xml.writeAttribute( QStringLiteral("game-specific-state"), state->stateData ); lastGameSpecificState = state->stateData; } if ( i == m_undoStack.size() ) xml.writeAttribute( QStringLiteral("current"), QStringLiteral("true") ); foreach ( const CardStateChange & change, state->changes ) { xml.writeStartElement( QStringLiteral("move") ); xml.writeAttribute( QStringLiteral("pile"), change.newState.pile->objectName() ); xml.writeAttribute( QStringLiteral("position"), QString::number( change.newState.index ) ); bool faceChanged = !change.oldState.pile || change.oldState.faceUp != change.newState.faceUp; foreach ( const KCard * card, change.cards ) { xml.writeStartElement( QStringLiteral("card") ); xml.writeAttribute( QStringLiteral("id"), QStringLiteral("%1").arg( card->id(), 7, 10, QChar('0') ) ); xml.writeAttribute( QStringLiteral("suit"), suitToString( card->suit() ) ); xml.writeAttribute( QStringLiteral("rank"), rankToString( card->rank() ) ); if ( faceChanged ) xml.writeAttribute( QStringLiteral("turn"), change.newState.faceUp ? "face-up" : "face-down" ); xml.writeEndElement(); } xml.writeEndElement(); } xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndDocument(); m_dealWasJustSaved = true; } bool DealerScene::loadFile( QIODevice * io ) { resetInternals(); bool reenableAutoDrop = autoDropEnabled(); setAutoDropEnabled( false ); QXmlStreamReader xml( io ); xml.readNextStartElement(); if ( xml.name() != "kpat-game" ) { qWarning() << "First tag is not \"kpat-game\""; return false; } m_dealNumber = readIntAttribute( xml, QStringLiteral("deal-number") ); setGameOptions( xml.attributes().value( QStringLiteral("game-type-options") ).toString() ); QMultiHash cardHash; foreach ( KCard * c, deck()->cards() ) cardHash.insert( c->id(), c ); QHash pileHash; foreach ( KCardPile * p, piles() ) pileHash.insert( p->objectName(), p ); int undosToDo = -1; while( xml.readNextStartElement() ) { if ( xml.name() != "state" ) { qWarning() << "Expected a \"state\" tag. Found a" << xml.name() << "tag."; return false; } if ( xml.attributes().hasAttribute( QStringLiteral("game-specific-state") ) ) setGameState( xml.attributes().value( QStringLiteral("game-specific-state") ).toString() ); if ( undosToDo > -1 ) ++undosToDo; else if ( xml.attributes().value( QStringLiteral("current") ) == "true" ) undosToDo = 0; while( xml.readNextStartElement() ) { if ( xml.name() != "move" ) { qWarning() << "Expected a \"move\" tag. Found a" << xml.name() << "tag."; return false; } QString pileName = xml.attributes().value( QStringLiteral("pile") ).toString(); KCardPile * pile = pileHash.value( pileName ); bool indexOk; int index = readIntAttribute( xml, QStringLiteral("position"), &indexOk ); if ( !pile || !indexOk ) { qWarning() << "Unrecognized pile or index."; return false; } while ( xml.readNextStartElement() ) { if ( xml.name() != "card" ) { qWarning() << "Expected a \"card\" tag. Found a" << xml.name() << "tag."; return false; } KCard * card = cardHash.value( readIntAttribute( xml, QStringLiteral("id") ) ); if ( !card ) { qWarning() << "Unrecognized card."; return false; } if ( xml.attributes().value(QStringLiteral("turn")) == "face-up" ) card->setFaceUp( true ); else if ( xml.attributes().value(QStringLiteral("turn")) == "face-down" ) card->setFaceUp( false ); pile->insert( index, card ); ++index; xml.skipCurrentElement(); } } takeState(); } m_loadedMoveCount = 0; m_dealStarted = moveCount() > 0; emit updateMoves( moveCount() ); while ( undosToDo > 0 ) { undo(); --undosToDo; } foreach ( KCardPile * p, piles() ) updatePileLayout( p, 0 ); setAutoDropEnabled( reenableAutoDrop ); return true; } DealerScene::DealerScene( const DealerInfo * di ) : m_di( di ), m_solver( nullptr ), m_solverThread( nullptr ), m_peekedCard( nullptr ), m_dealNumber( 0 ), m_loadedMoveCount( 0 ), m_neededFutureMoves( 1 ), m_supportedActions( 0 ), m_autoDropEnabled( false ), m_solverEnabled( false ), m_dealStarted( false ), m_dealWasEverWinnable( false ), m_dealHasBeenWon( false ), m_dealWasJustSaved( false ), m_statisticsRecorded( false ), m_playerReceivedHelp( false ), m_toldAboutWonGame( false ), m_toldAboutLostGame( false ), m_dropSpeedFactor( 1 ), m_interruptAutoDrop( false ), m_dealInProgress( false ), m_hintInProgress( false ), m_demoInProgress( false ), m_dropInProgress( false ), m_hintQueued( false ), m_demoQueued( false ), m_dropQueued( false ), m_newCardsQueued( false ), m_takeStateQueued( false ), m_currentState( nullptr ) { setItemIndexMethod(QGraphicsScene::NoIndex); m_solverUpdateTimer.setInterval( 250 ); m_solverUpdateTimer.setSingleShot( true ); connect(&m_solverUpdateTimer, &QTimer::timeout, this, &DealerScene::stopAndRestartSolver); m_demoTimer.setSingleShot( true ); connect(&m_demoTimer, &QTimer::timeout, this, &DealerScene::demo); m_dropTimer.setSingleShot( true ); connect(&m_dropTimer, &QTimer::timeout, this, &DealerScene::drop); m_wonItem = new MessageBox(); m_wonItem->setZValue( 2000 ); m_wonItem->hide(); addItem( m_wonItem ); connect(this, &DealerScene::cardAnimationDone, this, &DealerScene::animationDone); connect(this, &DealerScene::cardDoubleClicked, this, &DealerScene::tryAutomaticMove); // Make rightClick == doubleClick. See bug #151921 connect(this, &DealerScene::cardRightClicked, this, &DealerScene::tryAutomaticMove); } DealerScene::~DealerScene() { stop(); disconnect(); if ( m_solverThread ) m_solverThread->abort(); delete m_solverThread; m_solverThread = nullptr; delete m_solver; m_solver = nullptr; qDeleteAll( m_undoStack ); delete m_currentState; qDeleteAll( m_redoStack ); delete m_wonItem; } void DealerScene::addPatPile( PatPile * pile ) { if ( !m_patPiles.contains( pile ) ) m_patPiles.append( pile ); } void DealerScene::removePatPile( PatPile * pile ) { m_patPiles.removeAll( pile ); } QList DealerScene::patPiles() const { return m_patPiles; } void DealerScene::cardsMoved( const QList & cards, KCardPile * oldPile, KCardPile * newPile ) { PatPile * newPatPile = dynamic_cast( newPile ); PatPile * oldPatPile = dynamic_cast( oldPile ); if ( oldPatPile && oldPatPile->isFoundation() && newPatPile && !newPatPile->isFoundation() ) { foreach ( KCard * c, cards ) m_cardsRemovedFromFoundations.insert( c ); } else { foreach ( KCard * c, cards ) m_cardsRemovedFromFoundations.remove( c ); } if ( !m_dropInProgress && !m_dealInProgress ) { m_dealStarted = true; takeState(); } } void DealerScene::startHint() { stopDemo(); stopDrop(); if ( isCardAnimationRunning() ) { m_hintQueued = true; return; } if ( isKeyboardModeActive() ) setKeyboardModeActive( false ); QList toHighlight; foreach ( const MoveHint & h, getHints() ) toHighlight << h.card(); if ( !m_winningMoves.isEmpty() ) { MOVE m = m_winningMoves.first(); MoveHint mh = solver()->translateMove( m ); if ( mh.isValid() ) toHighlight << mh.card(); } m_hintInProgress = !toHighlight.isEmpty(); setHighlightedItems( toHighlight ); emit hintActive( m_hintInProgress ); } void DealerScene::stopHint() { if ( m_hintInProgress ) { m_hintInProgress = false; clearHighlightedItems(); emit hintActive( false ); } } bool DealerScene::isHintActive() const { return m_hintInProgress; } QList DealerScene::getSolverHints() { QList hintList; if ( m_solverThread && m_solverThread->isRunning() ) m_solverThread->abort(); solver()->translate_layout(); solver()->patsolve( 1 ); foreach ( const MOVE & m, solver()->firstMoves() ) { MoveHint mh = solver()->translateMove( m ); hintList << mh; } return hintList; } QList DealerScene::getHints() { if ( solver() ) return getSolverHints(); QList hintList; foreach (PatPile * store, patPiles()) { if (store->isFoundation() || store->isEmpty()) continue; QList cards = store->cards(); while (cards.count() && !cards.first()->isFaceUp()) cards.erase(cards.begin()); QList::Iterator iti = cards.begin(); while (iti != cards.end()) { if (allowedToRemove(store, (*iti))) { foreach (PatPile * dest, patPiles()) { int cardIndex = store->indexOf(*iti); if (cardIndex == 0 && dest->isEmpty() && !dest->isFoundation()) continue; if (!checkAdd(dest, dest->cards(), cards)) continue; if (dest->isFoundation()) { hintList << MoveHint( *iti, dest, 127 ); } else { QList cardsBelow = cards.mid(0, cardIndex); // if it could be here as well, then it's no use if ((cardsBelow.isEmpty() && !dest->isEmpty()) || !checkAdd(store, cardsBelow, cards)) { hintList << MoveHint( *iti, dest, 0 ); } else if (checkPrefering(dest, dest->cards(), cards) && !checkPrefering(store, cardsBelow, cards)) { // if checkPrefers says so, we add it nonetheless hintList << MoveHint( *iti, dest, 10 ); } } } } cards.erase(iti); iti = cards.begin(); } } return hintList; } static bool prioSort(const MoveHint &c1, const MoveHint &c2) { return c1.priority() < c2.priority(); } MoveHint DealerScene::chooseHint() { if ( !m_winningMoves.isEmpty() ) { MOVE m = m_winningMoves.takeFirst(); MoveHint mh = solver()->translateMove( m ); #ifndef NDEBUG if ( m.totype == O_Type ) fprintf( stderr, "move from %d out (at %d) Prio: %d\n", m.from, m.turn_index, m.pri ); else fprintf( stderr, "move from %d to %d (%d) Prio: %d\n", m.from, m.to, m.turn_index, m.pri ); #endif return mh; } QList hintList = getHints(); if ( hintList.isEmpty() ) { return MoveHint(); } else { // Generate a random number with an exponentional distribution averaging 1/4. qreal randomExp = qMin( -log( 1 - qreal( KRandom::random() ) / RAND_MAX ) / 4, 1 ); int randomIndex = randomExp * ( hintList.size() - 1 ); qSort(hintList.begin(), hintList.end(), prioSort); return hintList.at( randomIndex ); } } void DealerScene::startNew( int dealNumber ) { if ( dealNumber != -1 ) m_dealNumber = qBound( 1, dealNumber, INT_MAX ); // Only record the statistics and reset gameStarted if we're starting a // new game number or we're restarting a game we've already won. if ( dealNumber != -1 || m_dealHasBeenWon ) { recordGameStatistics(); m_statisticsRecorded = false; m_dealStarted = false; } if ( isCardAnimationRunning() ) { QTimer::singleShot( 100, this, SLOT(startNew()) ); return; } if ( m_solverThread && m_solverThread->isRunning() ) m_solverThread->abort(); resetInternals(); emit updateMoves( 0 ); foreach( KCardPile * p, piles() ) p->clear(); m_dealInProgress = true; restart( shuffled( deck()->cards(), m_dealNumber ) ); m_dealInProgress = false; takeState(); update(); } void DealerScene::resetInternals() { stop(); setKeyboardModeActive( false ); m_dealHasBeenWon = false; m_wonItem->hide(); qDeleteAll( m_undoStack ); m_undoStack.clear(); delete m_currentState; m_currentState = nullptr; qDeleteAll( m_redoStack ); m_redoStack.clear(); m_lastKnownCardStates.clear(); m_dealWasJustSaved = false; m_dealWasEverWinnable = false; m_toldAboutLostGame = false; m_toldAboutWonGame = false; m_loadedMoveCount = 0; m_playerReceivedHelp = false; m_dealInProgress = false; m_dropInProgress = false; m_dropSpeedFactor = 1; m_cardsRemovedFromFoundations.clear(); foreach (KCard * c, deck()->cards()) { c->disconnect( this ); c->stopAnimation(); } emit solverStateChanged( QString() ); emit gameInProgress( true ); } QPointF posAlongRect( qreal distOnRect, const QRectF & rect ) { if ( distOnRect < rect.width() ) return rect.topLeft() + QPointF( distOnRect, 0 ); distOnRect -= rect.width(); if ( distOnRect < rect.height() ) return rect.topRight() + QPointF( 0, distOnRect ); distOnRect -= rect.height(); if ( distOnRect < rect.width() ) return rect.bottomRight() + QPointF( -distOnRect, 0 ); distOnRect -= rect.width(); return rect.bottomLeft() + QPointF( 0, -distOnRect ); } void DealerScene::won() { if ( m_dealHasBeenWon ) return; m_dealHasBeenWon = true; m_toldAboutWonGame = true; stopDemo(); recordGameStatistics(); emit solverStateChanged( QString() ); emit newCardsPossible( false ); emit undoPossible( false ); emit redoPossible( false ); emit gameInProgress( false ); setKeyboardModeActive( false ); qreal speed = sqrt( width() * width() + height() * height() ) / ( DURATION_WON ); QRectF justOffScreen = sceneRect().adjusted( -deck()->cardWidth(), -deck()->cardHeight(), 0, 0 ); qreal spacing = 2 * ( justOffScreen.width() + justOffScreen.height() ) / deck()->cards().size(); qreal distOnRect = 0; foreach ( KCard *c, deck()->cards() ) { distOnRect += spacing; QPointF pos2 = posAlongRect( distOnRect, justOffScreen ); QPointF delta = c->pos() - pos2; qreal dist = sqrt( delta.x() * delta.x() + delta.y() * delta.y() ); c->setFaceUp( true ); c->animate( pos2, c->zValue(), 0, true, false, dist / speed ); } connect(deck(), &KAbstractCardDeck::cardAnimationDone, this, &DealerScene::showWonMessage); } void DealerScene::showWonMessage() { disconnect(deck(), &KAbstractCardDeck::cardAnimationDone, this, &DealerScene::showWonMessage); // It shouldn't be necessary to stop the demo yet again here, but we // get crashes if we don't. Will have to look into this further. stopDemo(); // Hide all cards to prevent them from showing up accidentally if the // window is resized. foreach ( KCard * c, deck()->cards() ) c->hide(); updateWonItem(); m_wonItem->show(); } void DealerScene::updateWonItem() { const qreal aspectRatio = Renderer::self()->aspectRatioOfElement(QStringLiteral("message_frame")); int boxWidth; int boxHeight; if ( width() < aspectRatio * height() ) { boxWidth = width() * wonBoxToSceneSizeRatio; boxHeight = boxWidth / aspectRatio; } else { boxHeight = height() * wonBoxToSceneSizeRatio; boxWidth = boxHeight * aspectRatio; } m_wonItem->setSize( QSize( boxWidth, boxHeight ) ); if ( m_playerReceivedHelp ) m_wonItem->setMessage( i18n( "Congratulations! We have won." ) ); else m_wonItem->setMessage( i18n( "Congratulations! You have won." ) ); m_wonItem->setPos( QPointF( (width() - boxWidth) / 2, (height() - boxHeight) / 2 ) + sceneRect().topLeft() ); } bool DealerScene::allowedToAdd( const KCardPile * pile, const QList & cards ) const { if ( !pile->isEmpty() && !pile->topCard()->isFaceUp() ) return false; const PatPile * p = dynamic_cast( pile ); return p && checkAdd( p, p->cards(), cards ); } bool DealerScene::allowedToRemove( const KCardPile * pile, const KCard * card ) const { const PatPile * p = dynamic_cast( pile ); QList cards = pile->topCardsDownTo( card ); return p && card->isFaceUp() && !cards.isEmpty() && checkRemove( p, cards ); } bool DealerScene::checkAdd( const PatPile * pile, const QList & oldCards, const QList & newCards ) const { Q_UNUSED( pile ) Q_UNUSED( oldCards ) Q_UNUSED( newCards ) return false; } bool DealerScene::checkRemove(const PatPile * pile, const QList & cards) const { Q_UNUSED( pile ) Q_UNUSED( cards ) return false; } bool DealerScene::checkPrefering( const PatPile * pile, const QList & oldCards, const QList & newCards ) const { Q_UNUSED( pile ) Q_UNUSED( oldCards ) Q_UNUSED( newCards ) return false; } void DealerScene::mousePressEvent( QGraphicsSceneMouseEvent * e ) { stop(); const QList itemsAtPoint = items( e->scenePos() ); KCard * card = qgraphicsitem_cast( itemsAtPoint.isEmpty() ? 0 : itemsAtPoint.first() ); if ( m_peekedCard ) { e->accept(); } else if ( e->button() == Qt::RightButton && card && card->pile() && card != card->pile()->topCard() && cardsBeingDragged().isEmpty() && !isCardAnimationRunning() ) { e->accept(); m_peekedCard = card; QPointF pos2( card->x() + deck()->cardWidth() / 3.0, card->y() - deck()->cardHeight() / 3.0 ); card->setZValue( card->zValue() + 0.1 ); card->animate( pos2, card->zValue(), 20, card->isFaceUp(), false, DURATION_FANCYSHOW ); } else { KCardScene::mousePressEvent( e ); if ( !cardsBeingDragged().isEmpty() ) emit cardsPickedUp(); } } void DealerScene::mouseReleaseEvent( QGraphicsSceneMouseEvent * e ) { stop(); if ( e->button() == Qt::RightButton && m_peekedCard && m_peekedCard->pile() ) { e->accept(); updatePileLayout( m_peekedCard->pile(), DURATION_FANCYSHOW ); m_peekedCard = nullptr; } else { bool hadCards = !cardsBeingDragged().isEmpty(); KCardScene::mouseReleaseEvent( e ); if ( hadCards && cardsBeingDragged().isEmpty() ) emit cardsPutDown(); } } void DealerScene::mouseDoubleClickEvent( QGraphicsSceneMouseEvent * e ) { stop(); KCardScene::mouseDoubleClickEvent( e ); } bool DealerScene::tryAutomaticMove( KCard * card ) { if ( !isCardAnimationRunning() && card && card->pile() && card == card->pile()->topCard() && card->isFaceUp() && allowedToRemove( card->pile(), card ) ) { QList cardList = QList() << card; foreach ( PatPile * p, patPiles() ) { if ( p->isFoundation() && allowedToAdd( p, cardList ) ) { moveCardToPile( card , p, DURATION_MOVE ); return true; } } } return false; } void DealerScene::undo() { undoOrRedo( true ); } void DealerScene::redo() { undoOrRedo( false ); } void DealerScene::undoOrRedo( bool undo ) { stop(); if ( isCardAnimationRunning() ) return; // The undo and redo actions are almost identical, except for where states // are pulled from and pushed to, so to keep things generic, we use // direction dependent const references throughout this code. QStack & fromStack = undo ? m_undoStack : m_redoStack; QStack & toStack = undo ? m_redoStack : m_undoStack; if ( !fromStack.isEmpty() && m_currentState ) { // If we're undoing, we use the oldStates of the changes of the current // state. If we're redoing, we use the newStates of the changes of the // nextState. const QList & changes = undo ? m_currentState->changes : fromStack.top()->changes; // Update the currentState pointer and undo/redo stacks. toStack.push( m_currentState ); m_currentState = fromStack.pop(); setGameState( m_currentState->stateData ); QSet pilesAffected; foreach ( const CardStateChange & change, changes ) { CardState sourceState = undo ? change.newState : change.oldState; CardState destState = undo ? change.oldState : change.newState; PatPile * sourcePile = dynamic_cast( sourceState.pile ); PatPile * destPile = dynamic_cast( destState.pile ); bool notDroppable = destState.takenDown || ((sourcePile && sourcePile->isFoundation()) && !(destPile && destPile->isFoundation())); pilesAffected << sourceState.pile << destState.pile; foreach ( KCard * c, change.cards ) { m_lastKnownCardStates.insert( c, destState ); c->setFaceUp( destState.faceUp ); destState.pile->insert( destState.index, c ); if ( notDroppable ) m_cardsRemovedFromFoundations.insert( c ); else m_cardsRemovedFromFoundations.remove( c ); ++sourceState.index; ++destState.index; } } // At this point all cards should be in the right piles, but not // necessarily at the right positions within those piles. So we // run through the piles involved and swap card positions until // everything is back in its place, then relayout the piles. foreach( KCardPile * p, pilesAffected ) { int i = 0; while ( i < p->count() ) { int index = m_lastKnownCardStates.value( p->at( i ) ).index; if ( i == index ) ++i; else p->swapCards( i, index ); } updatePileLayout( p, 0 ); } emit updateMoves( moveCount() ); emit undoPossible( !m_undoStack.isEmpty() ); emit redoPossible( !m_redoStack.isEmpty() ); if ( m_toldAboutLostGame ) // everything's possible again { gameInProgress( true ); m_toldAboutLostGame = false; m_toldAboutWonGame = false; } int solvability = m_currentState->solvability; m_winningMoves = m_currentState->winningMoves; emit solverStateChanged( solverStatusMessage( solvability, m_dealWasEverWinnable ) ); if ( m_solver && ( solvability == SolverInterface::SearchAborted || solvability == SolverInterface::MemoryLimitReached ) ) { startSolver(); } } } void DealerScene::takeState() { if ( isCardAnimationRunning() ) { m_takeStateQueued = true; return; } if ( !isDemoActive() ) m_winningMoves.clear(); QList changes; foreach ( KCardPile * p, piles() ) { QList currentRun; CardState oldRunState; CardState newRunState; for ( int i = 0; i < p->count(); ++i ) { KCard * c = p->at( i ); const CardState & oldState = m_lastKnownCardStates.value( c ); CardState newState( p, i, c->isFaceUp(), m_cardsRemovedFromFoundations.contains( c ) ); // The card has changed. if ( newState != oldState ) { // There's a run in progress, but this card isn't part of it. if ( !currentRun.isEmpty() && (oldState.pile != oldRunState.pile || (oldState.index != -1 && oldState.index != oldRunState.index + currentRun.size()) || oldState.faceUp != oldRunState.faceUp || newState.faceUp != newRunState.faceUp || oldState.takenDown != oldRunState.takenDown || newState.takenDown != newRunState.takenDown) ) { changes << CardStateChange( oldRunState, newRunState, currentRun ); currentRun.clear(); } // This card is the start of a new run. if ( currentRun.isEmpty() ) { oldRunState = oldState; newRunState = newState; } currentRun << c; m_lastKnownCardStates.insert( c, newState ); } } // Add the last run, if any. if ( !currentRun.isEmpty() ) { changes << CardStateChange( oldRunState, newRunState, currentRun ); } } // If nothing has changed, we're done. if ( changes.isEmpty() && m_currentState && m_currentState->stateData == getGameState() ) { return; } if ( m_currentState ) { m_undoStack.push( m_currentState ); qDeleteAll( m_redoStack ); m_redoStack.clear(); } m_currentState = new GameState( changes, getGameState() ); emit redoPossible( false ); emit undoPossible( !m_undoStack.isEmpty() ); emit updateMoves( moveCount() ); m_dealWasJustSaved = false; if ( isGameWon() ) { won(); return; } if ( !m_toldAboutWonGame && !m_toldAboutLostGame && isGameLost() ) { emit gameInProgress( false ); emit solverStateChanged( i18n( "Solver: This game is lost." ) ); m_toldAboutLostGame = true; stopDemo(); return; } if ( !isDemoActive() && !isCardAnimationRunning() && m_solver ) startSolver(); if ( autoDropEnabled() && !isDropActive() && !isDemoActive() && m_redoStack.isEmpty() ) { if ( m_interruptAutoDrop ) m_interruptAutoDrop = false; else startDrop(); } } void DealerScene::setSolverEnabled(bool a) { m_solverEnabled = a; } void DealerScene::setAutoDropEnabled( bool enabled ) { m_autoDropEnabled = enabled; } bool DealerScene::autoDropEnabled() const { return m_autoDropEnabled; } void DealerScene::startDrop() { stopHint(); stopDemo(); if ( isCardAnimationRunning() ) { m_dropQueued = true; return; } m_dropInProgress = true; m_interruptAutoDrop = false; m_dropSpeedFactor = 1; emit dropActive( true ); drop(); } void DealerScene::stopDrop() { if ( m_dropInProgress ) { m_dropTimer.stop(); m_dropInProgress = false; emit dropActive( false ); if ( autoDropEnabled() && m_takeStateQueued ) m_interruptAutoDrop = true; } } bool DealerScene::isDropActive() const { return m_dropInProgress; } bool DealerScene::drop() { foreach ( const MoveHint & mh, getHints() ) { if ( mh.pile() && mh.pile()->isFoundation() && mh.priority() > 120 && !m_cardsRemovedFromFoundations.contains( mh.card() ) ) { QList cards = mh.card()->pile()->topCardsDownTo( mh.card() ); QMap oldPositions; foreach ( KCard * c, cards ) oldPositions.insert( c, c->pos() ); moveCardsToPile( cards, mh.pile(), DURATION_MOVE ); int count = 0; foreach ( KCard * c, cards ) { c->completeAnimation(); QPointF destPos = c->pos(); c->setPos( oldPositions.value( c ) ); int duration = speedUpTime( DURATION_AUTODROP + count * DURATION_AUTODROP / 10 ); c->animate( destPos, c->zValue(), 0, c->isFaceUp(), true, duration ); ++count; } m_dropSpeedFactor *= AUTODROP_SPEEDUP_FACTOR; takeState(); return true; } } m_dropInProgress = false; emit dropActive( false ); return false; } int DealerScene::speedUpTime( int delay ) const { if ( delay < DURATION_AUTODROP_MINIMUM ) return delay; else return qMax( delay * m_dropSpeedFactor, DURATION_AUTODROP_MINIMUM ); } void DealerScene::stopAndRestartSolver() { if ( m_toldAboutLostGame || m_toldAboutWonGame ) // who cares? return; if ( m_solverThread && m_solverThread->isRunning() ) { m_solverThread->abort(); } if ( isCardAnimationRunning() ) { startSolver(); return; } slotSolverEnded(); } void DealerScene::slotSolverEnded() { if ( m_solverThread && m_solverThread->isRunning() ) return; m_solver->translate_layout(); m_winningMoves.clear(); emit solverStateChanged( i18n("Solver: Calculating...") ); if ( !m_solverThread ) { m_solverThread = new SolverThread( m_solver ); connect(m_solverThread, &SolverThread::finished, this, &DealerScene::slotSolverFinished); } m_solverThread->start( m_solverEnabled ? QThread::IdlePriority : QThread::NormalPriority ); } void DealerScene::slotSolverFinished( int result ) { if ( result == SolverInterface::SolutionExists ) { m_winningMoves = m_solver->winMoves(); m_dealWasEverWinnable = true; } emit solverStateChanged( solverStatusMessage( result, m_dealWasEverWinnable ) ); if ( m_currentState ) { m_currentState->solvability = static_cast( result ); m_currentState->winningMoves = m_winningMoves; } if ( result == SolverInterface::SearchAborted ) startSolver(); } int DealerScene::gameNumber() const { return m_dealNumber; } void DealerScene::stop() { stopHint(); stopDemo(); stopDrop(); } void DealerScene::animationDone() { Q_ASSERT( !isCardAnimationRunning() ); if ( !m_multiStepMoves.isEmpty() ) { continueMultiStepMove(); return; } if ( m_takeStateQueued ) { m_takeStateQueued = false; takeState(); } if ( m_demoInProgress ) { m_demoTimer.start( TIME_BETWEEN_MOVES ); } else if ( m_dropInProgress ) { m_dropTimer.start( speedUpTime( TIME_BETWEEN_MOVES ) ); } else if ( m_newCardsQueued ) { m_newCardsQueued = false; newCards(); } else if ( m_hintQueued ) { m_hintQueued = false; startHint(); } else if ( m_demoQueued ) { m_demoQueued = false; startDemo(); } else if ( m_dropQueued ) { m_dropQueued = false; startDrop(); } } void DealerScene::startDemo() { stopHint(); stopDrop(); if ( isCardAnimationRunning() ) { m_demoQueued = true; return; } m_demoInProgress = true; m_playerReceivedHelp = true; m_dealStarted = true; demo(); } void DealerScene::stopDemo() { if ( m_demoInProgress ) { m_demoTimer.stop(); m_demoInProgress = false; emit demoActive( false ); } } bool DealerScene::isDemoActive() const { return m_demoInProgress; } void DealerScene::demo() { if ( isCardAnimationRunning() ) { m_demoQueued = true; return; } m_demoInProgress = true; m_playerReceivedHelp = true; m_dealStarted = true; clearHighlightedItems(); m_demoTimer.stop(); MoveHint mh = chooseHint(); if ( mh.isValid() ) { KCard * card = mh.card(); Q_ASSERT( card ); KCardPile * sourcePile = mh.card()->pile(); Q_ASSERT( sourcePile ); Q_ASSERT( allowedToRemove( sourcePile, card ) ); PatPile * destPile = mh.pile(); Q_ASSERT( destPile ); Q_ASSERT( sourcePile != destPile ); QList cards = sourcePile->topCardsDownTo( card ); Q_ASSERT( allowedToAdd( destPile, cards ) ); if ( destPile->isEmpty() ) { qDebug() << "Moving" << card->objectName() << "from the" << sourcePile->objectName() << "pile to the" << destPile->objectName() << "pile, which is empty"; } else { qDebug() << "Moving" << card->objectName() << "from the" << sourcePile->objectName() << "pile to the" << destPile->objectName() << "pile, putting it on top of" << destPile->topCard()->objectName(); } moveCardsToPile( cards, destPile, DURATION_DEMO ); } else if ( !newCards() ) { if (isGameWon()) { won(); } else { stopDemo(); slotSolverEnded(); } return; } emit demoActive( true ); takeState(); } void DealerScene::drawDealRowOrRedeal() { stop(); if ( isCardAnimationRunning() ) { m_newCardsQueued = true; return; } m_newCardsQueued = false; newCards(); } bool DealerScene::newCards() { return false; } void DealerScene::setSolver( SolverInterface *s) { delete m_solver; delete m_solverThread; m_solver = s; m_solverThread = nullptr; } bool DealerScene::isGameWon() const { foreach (PatPile *p, patPiles()) { if (!p->isFoundation() && !p->isEmpty()) return false; } return true; } void DealerScene::startSolver() { if( m_solverEnabled ) m_solverUpdateTimer.start(); } bool DealerScene::isGameLost() const { + if (! m_winningMoves.isEmpty()) + { + return false; + } if ( solver() ) { if ( m_solverThread && m_solverThread->isRunning() ) m_solverThread->abort(); solver()->translate_layout(); return solver()->patsolve( neededFutureMoves() ) == SolverInterface::NoSolutionExists; } return false; } void DealerScene::recordGameStatistics() { // Don't record the game if it was never started, if it is unchanged since // it was last saved (allowing the user to close KPat after saving without // it recording a loss) or if it has already been recorded.// takeState(); // copying it again if ( m_dealStarted && !m_dealWasJustSaved && !m_statisticsRecorded ) { int id = oldId(); QString totalPlayedKey = QStringLiteral("total%1").arg( id ); QString wonKey = QStringLiteral("won%1").arg( id ); QString winStreakKey = QStringLiteral("winstreak%1").arg( id ); QString maxWinStreakKey = QStringLiteral("maxwinstreak%1").arg( id ); QString loseStreakKey = QStringLiteral("loosestreak%1").arg( id ); QString maxLoseStreakKey = QStringLiteral("maxloosestreak%1").arg( id ); KConfigGroup config(KSharedConfig::openConfig(), scores_group); int totalPlayed = config.readEntry( totalPlayedKey, 0 ); int won = config.readEntry( wonKey, 0 ); int winStreak = config.readEntry( winStreakKey, 0 ); int maxWinStreak = config.readEntry( maxWinStreakKey, 0 ); int loseStreak = config.readEntry( loseStreakKey, 0 ); int maxLoseStreak = config.readEntry( maxLoseStreakKey, 0 ); ++totalPlayed; if ( m_dealHasBeenWon ) { ++won; ++winStreak; maxWinStreak = qMax( winStreak, maxWinStreak ); loseStreak = 0; } else { ++loseStreak; maxLoseStreak = qMax( loseStreak, maxLoseStreak ); winStreak = 0; } config.writeEntry( totalPlayedKey, totalPlayed ); config.writeEntry( wonKey, won ); config.writeEntry( winStreakKey, winStreak ); config.writeEntry( maxWinStreakKey, maxWinStreak ); config.writeEntry( loseStreakKey, loseStreak ); config.writeEntry( maxLoseStreakKey, maxLoseStreak ); m_statisticsRecorded = true; } } void DealerScene::relayoutScene() { KCardScene::relayoutScene(); if ( m_wonItem->isVisible() ) updateWonItem(); } int DealerScene::gameId() const { return m_di->baseId(); } void DealerScene::setActions( int actions ) { m_supportedActions = actions; } int DealerScene::actions() const { return m_supportedActions; } QList DealerScene::configActions() const { return QList(); } SolverInterface * DealerScene::solver() const { return m_solver; } int DealerScene::neededFutureMoves() const { return m_neededFutureMoves; } void DealerScene::setNeededFutureMoves( int i ) { m_neededFutureMoves = i; } void DealerScene::setDeckContents( int copies, const QList & suits ) { Q_ASSERT( copies >= 1 ); Q_ASSERT( !suits.isEmpty() ); // Note that the order in which the cards are created can not be changed // without breaking the game numbering. For historical reasons, KPat // generates card by rank and then by suit, rather than the more common // suit then rank ordering. QList ids; unsigned int number = 0; for ( int i = 0; i < copies; ++i ) foreach ( const KCardDeck::Rank & r, KCardDeck::standardRanks() ) foreach ( const KCardDeck::Suit & s, suits ) ids << KCardDeck::getId( s, r, number++ ); deck()->setDeckContents( ids ); } QImage DealerScene::createDump() const { const QSize previewSize( 480, 320 ); foreach ( KCard * c, deck()->cards() ) c->completeAnimation(); QMultiMap itemsByZ; foreach ( QGraphicsItem * item, items() ) { Q_ASSERT( item->zValue() >= 0 ); itemsByZ.insert( item->zValue(), item ); } QImage img( contentArea().size().toSize(), QImage::Format_ARGB32 ); img.fill( Qt::transparent ); QPainter p( &img ); foreach ( QGraphicsItem * item, itemsByZ ) { if ( item->isVisible() ) { p.save(); p.setTransform( item->deviceTransform( p.worldTransform() ), false ); item->paint( &p, nullptr ); p.restore(); } } p.end(); img = img.scaled( previewSize, Qt::KeepAspectRatio, Qt::SmoothTransformation ); QImage img2( previewSize, QImage::Format_ARGB32 ); img2.fill( Qt::transparent ); QPainter p2( &img2 ); p2.drawImage( (img2.width() - img.width()) / 2, (img2.height() - img.height()) / 2, img ); p2.end(); return img2; } void DealerScene::mapOldId( int id ) { Q_UNUSED( id ); } int DealerScene::oldId() const { return gameId(); } QString DealerScene::getGameState() const { return QString(); } void DealerScene::setGameState( const QString & state ) { Q_UNUSED( state ); } QString DealerScene::getGameOptions() const { return QString(); } void DealerScene::setGameOptions( const QString & options ) { Q_UNUSED( options ); } bool DealerScene::allowedToStartNewGame() { // Check if the user is already running a game, and if she is, // then ask if she wants to abort it. return !m_dealStarted || m_dealWasJustSaved || m_toldAboutWonGame || m_toldAboutLostGame || KMessageBox::warningContinueCancel(nullptr, i18n("A new game has been requested, but there is already a game in progress.\n\n" "A loss will be recorded in the statistics if the current game is abandoned."), i18n("Abandon Current Game?"), KGuiItem(i18n("Abandon Current Game")), KStandardGuiItem::cancel(), QStringLiteral("careaboutstats") ) == KMessageBox::Continue; } void DealerScene::addCardForDeal( KCardPile * pile, KCard * card, bool faceUp, QPointF startPos ) { Q_ASSERT( card ); Q_ASSERT( pile ); card->setFaceUp( faceUp ); pile->add( card ); m_initDealPositions.insert( card, startPos ); } void DealerScene::startDealAnimation() { qreal speed = sqrt( width() * width() + height() * height() ) / ( DURATION_DEAL ); foreach ( PatPile * p, patPiles() ) { updatePileLayout( p, 0 ); foreach ( KCard * c, p->cards() ) { if ( !m_initDealPositions.contains( c ) ) continue; QPointF pos2 = c->pos(); c->setPos( m_initDealPositions.value( c ) ); QPointF delta = c->pos() - pos2; qreal dist = sqrt( delta.x() * delta.x() + delta.y() * delta.y() ); int duration = qRound( dist / speed ); c->animate( pos2, c->zValue(), 0, c->isFaceUp(), false, duration ); } } m_initDealPositions.clear(); } void DealerScene::multiStepMove( const QList & cards, KCardPile * pile, const QList & freePiles, const QList & freeCells, int duration ) { Q_ASSERT( cards.size() == 1 || !freePiles.isEmpty() || !freeCells.isEmpty() ); m_multiStepMoves.clear(); m_multiStepDuration = duration; multiStepSubMove( cards, pile, freePiles, freeCells ); continueMultiStepMove(); } void DealerScene::multiStepSubMove( QList cards, KCardPile * pile, QList freePiles, const QList & freeCells ) { // Note that cards and freePiles are passed by value, as we need to make a // local copy anyway. // Using n free cells, we can move a run of n+1 cards. If we want to move // more than that, we have to recursively move some of our cards to one of // the free piles temporarily. const int freeCellsPlusOne = freeCells.size() + 1; int cardsToSubMove = cards.size() - freeCellsPlusOne; QList > > tempMoves; while ( cardsToSubMove > 0 ) { int tempMoveSize; if ( cardsToSubMove <= freePiles.size() * freeCellsPlusOne ) { // If the cards that have to be submoved can be spread across the // the free piles without putting more than freeCellsPlusOne cards // on each one, we do so. This means that none of our submoves will // need further submoves, which keeps the total move count down. We // Just to a simple rounding up integer division. tempMoveSize = (cardsToSubMove + freePiles.size() - 1) / freePiles.size(); } else { // Otherwise, we use the space optimal method that gets the cards // moved using a minimal number of piles, but uses more submoves. tempMoveSize = freeCellsPlusOne; while ( tempMoveSize * 2 < cardsToSubMove ) tempMoveSize *= 2; } QList subCards; for ( int i = 0; i < tempMoveSize; ++i ) subCards.prepend( cards.takeLast() ); Q_ASSERT( !freePiles.isEmpty() ); KCardPile * nextPile = freePiles.takeFirst(); tempMoves << qMakePair( nextPile, subCards ); multiStepSubMove( subCards, nextPile, freePiles, freeCells ); cardsToSubMove -= tempMoveSize; } // Move cards to free cells. for ( int i = 0; i < cards.size() - 1; ++i ) { KCard * c = cards.at( cards.size() - 1 - i ); m_multiStepMoves << qMakePair( c, freeCells[i] ); } // Move bottom card to destination pile. m_multiStepMoves << qMakePair( cards.first(), pile ); // Move cards from free cells to destination pile. for ( int i = 1; i < cards.size(); ++i ) m_multiStepMoves << qMakePair( cards.at( i ), pile ); // If we just moved the bottomost card of the source pile, it must now be // empty and we won't need it any more. So we return it to the list of free // piles. KCardPile * sourcePile = cards.first()->pile(); if ( sourcePile->at( 0 ) == cards.first() ) freePiles << sourcePile; // If we had to do any submoves, we now move those cards from their // temporary pile to the destination pile and free up their temporary pile. while ( !tempMoves.isEmpty() ) { QPair > m = tempMoves.takeLast(); multiStepSubMove( m.second, pile, freePiles, freeCells ); freePiles << m.first; } } void DealerScene::continueMultiStepMove() { Q_ASSERT( !m_multiStepMoves.isEmpty() ); Q_ASSERT( !isCardAnimationRunning() ); QPair m = m_multiStepMoves.takeFirst(); KCard * card = m.first; KCardPile * dest = m.second; KCardPile * source = card->pile(); Q_ASSERT( card == source->topCard() ); Q_ASSERT( allowedToAdd( dest, QList() << card ) ); m_multiStepDuration = qMax( m_multiStepDuration * 0.9, 50 ); dest->add( card ); card->raise(); updatePileLayout( dest, m_multiStepDuration ); updatePileLayout( source, m_multiStepDuration ); if ( m_multiStepMoves.isEmpty() ) takeState(); } #include "dealer.moc" #include "moc_dealer.cpp" diff --git a/freecell.cpp b/freecell.cpp index f870cdb..9a7c278 100644 --- a/freecell.cpp +++ b/freecell.cpp @@ -1,275 +1,310 @@ /* * Copyright (C) 1997 Rodolfo Borges * Copyright (C) 1998-2009 Stephan Kulow * Copyright (C) 2010 Parker Coates * * 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 . * ------------------------------------------------------------------------- */ #include "freecell.h" #include "dealerinfo.h" #include "pileutils.h" #include "speeds.h" #include "patsolve/freecellsolver.h" #include #include Freecell::Freecell( const DealerInfo * di ) : DealerScene( di ) { } void Freecell::initialize() { setDeckContents(); const qreal topRowDist = 1.08; const qreal bottomRowDist = 1.13; const qreal targetOffsetDist = ( 7 * bottomRowDist + 1 ) - ( 3 * topRowDist + 1 ); for ( int i = 0; i < 4; ++i ) { freecell[i] = new PatPile ( this, 1 + 8 + i, QStringLiteral( "freecell%1" ).arg( i ) ); freecell[i]->setPileRole(PatPile::Cell); freecell[i]->setLayoutPos(topRowDist * i, 0); freecell[i]->setKeyboardSelectHint( KCardPile::AutoFocusTop ); freecell[i]->setKeyboardDropHint( KCardPile::AutoFocusTop ); } for ( int i = 0; i < 8; ++i ) { store[i] = new PatPile( this, 1 + i, QStringLiteral( "store%1" ).arg( i ) ); store[i]->setPileRole(PatPile::Tableau); store[i]->setLayoutPos( bottomRowDist * i, 1.3 ); store[i]->setBottomPadding( 2.5 ); store[i]->setHeightPolicy( KCardPile::GrowDown ); store[i]->setKeyboardSelectHint( KCardPile::AutoFocusDeepestRemovable ); store[i]->setKeyboardDropHint( KCardPile::AutoFocusTop ); } for ( int i = 0; i < 4; ++i ) { target[i] = new PatPile(this, 1 + 8 + 4 + i, QStringLiteral( "target%1" ).arg( i )); target[i]->setPileRole(PatPile::Foundation); target[i]->setLayoutPos(targetOffsetDist + topRowDist * i, 0); target[i]->setSpread(0, 0); target[i]->setKeyboardSelectHint( KCardPile::NeverFocus ); target[i]->setKeyboardDropHint( KCardPile::ForceFocusTop ); } setActions(DealerScene::Demo | DealerScene::Hint); setSolver( new FreecellSolver( this ) ); setNeededFutureMoves( 4 ); // reserve some } void Freecell::restart( const QList & cards ) { QList cardList = cards; int column = 0; while ( !cardList.isEmpty() ) { addCardForDeal( store[column], cardList.takeLast(), true, store[0]->pos() ); column = (column + 1) % 8; } startDealAnimation(); } +QString Freecell::solverFormat() const +{ + QString output; + QString tmp; + for (int i = 0; i < 4 ; i++) { + if (target[i]->isEmpty()) + continue; + tmp += suitToString(target[i]->topCard()->suit()) + '-' + rankToString(target[i]->topCard()->rank()) + ' '; + } + if (!tmp.isEmpty()) + output += QString::fromLatin1("Foundations: %1\n").arg(tmp); + + tmp.truncate(0); + for (int i = 0; i < 4 ; i++) { + if (freecell[i]->isEmpty()) + tmp += "- "; + else + tmp += rankToString(freecell[i]->topCard()->rank()) + suitToString(freecell[i]->topCard()->suit()) + ' '; + } + if (!tmp.isEmpty()) + { + QString a = QString::fromLatin1("Freecells: %1\n"); + output += a.arg(tmp); + } + + for (int i = 0; i < 8 ; i++) + { + QList cards = store[i]->cards(); + for (QList::ConstIterator it = cards.begin(); it != cards.end(); ++it) + output += rankToString((*it)->rank()) + suitToString((*it)->suit()) + ' '; + output += '\n'; + } + return output; +} + void Freecell::cardsDroppedOnPile( const QList & cards, KCardPile * pile ) { if ( cards.size() <= 1 ) { DealerScene::moveCardsToPile( cards, pile, DURATION_MOVE ); return; } QList freeCells; for ( int i = 0; i < 4; ++i ) if ( freecell[i]->isEmpty() ) freeCells << freecell[i]; QList freeStores; for ( int i = 0; i < 8; ++i ) if ( store[i]->isEmpty() && store[i] != pile ) freeStores << store[i]; multiStepMove( cards, pile, freeStores, freeCells, DURATION_MOVE ); } bool Freecell::tryAutomaticMove(KCard *c) { // target move if (DealerScene::tryAutomaticMove(c)) return true; if (c->isAnimated()) return false; if (c == c->pile()->topCard() && c->isFaceUp()) { for (int i = 0; i < 4; i++) { if (freecell[i]->isEmpty()) { moveCardToPile( c, freecell[i], DURATION_MOVE ); return true; } } } return false; } bool Freecell::canPutStore( const KCardPile * pile, const QList & cards ) const { int freeCells = 0; for ( int i = 0; i < 4; ++i ) if ( freecell[i]->isEmpty() ) ++freeCells; int freeStores = 0; for ( int i = 0; i < 8; ++i ) if ( store[i]->isEmpty() && store[i] != pile ) ++freeStores; return cards.size() <= (freeCells + 1) << freeStores && ( pile->isEmpty() || ( pile->topCard()->rank() == cards.first()->rank() + 1 && pile->topCard()->color() != cards.first()->color() ) ); } bool Freecell::checkAdd(const PatPile * pile, const QList & oldCards, const QList & newCards) const { switch (pile->pileRole()) { case PatPile::Tableau: return canPutStore(pile, newCards); case PatPile::Cell: return oldCards.isEmpty() && newCards.size() == 1; case PatPile::Foundation: return checkAddSameSuitAscendingFromAce(oldCards, newCards); default: return false; } } bool Freecell::checkRemove(const PatPile * pile, const QList & cards) const { switch (pile->pileRole()) { case PatPile::Tableau: return isAlternateColorDescending(cards); case PatPile::Cell: return cards.first() == pile->topCard(); case PatPile::Foundation: default: return false; } } QList Freecell::getHints() { QList hintList = getSolverHints(); if ( isDemoActive() ) return hintList; foreach (PatPile * store, patPiles()) { if (store->isEmpty()) continue; QList cards = store->cards(); while (cards.count() && !cards.first()->isFaceUp()) cards.erase(cards.begin()); QList::Iterator iti = cards.begin(); while (iti != cards.end()) { if (allowedToRemove(store, (*iti))) { foreach (PatPile * dest, patPiles()) { int cardIndex = store->indexOf(*iti); if (cardIndex == 0 && dest->isEmpty() && !dest->isFoundation()) continue; if (!checkAdd(dest, dest->cards(), cards)) continue; if ( dest->isFoundation() ) // taken care by solver continue; QList cardsBelow = cards.mid(0, cardIndex); // if it could be here as well, then it's no use if ((cardsBelow.isEmpty() && !dest->isEmpty()) || !checkAdd(store, cardsBelow, cards)) { hintList << MoveHint( *iti, dest, 0 ); } else if (checkPrefering( dest, dest->cards(), cards ) && !checkPrefering( store, cardsBelow, cards )) { // if checkPrefers says so, we add it nonetheless hintList << MoveHint( *iti, dest, 0 ); } } } cards.erase(iti); iti = cards.begin(); } } return hintList; } static class FreecellDealerInfo : public DealerInfo { public: FreecellDealerInfo() : DealerInfo(I18N_NOOP("Freecell"), FreecellId) {} DealerScene *createGame() const Q_DECL_OVERRIDE { return new Freecell( this ); } } freecellDealerInfo; diff --git a/freecell.h b/freecell.h index 7b0b2cb..9f7d84b 100644 --- a/freecell.h +++ b/freecell.h @@ -1,72 +1,73 @@ /* * Copyright (C) 1997 Rodolfo Borges * Copyright (C) 1998-2009 Stephan Kulow * * 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 . * ------------------------------------------------------------------------- */ #ifndef FREECELL_H #define FREECELL_H #include "dealer.h" #include "hint.h" class Freecell : public DealerScene { Q_OBJECT public: explicit Freecell( const DealerInfo * di ); void initialize() Q_DECL_OVERRIDE; protected: bool checkAdd(const PatPile * pile, const QList & oldCards, const QList & newCards) const Q_DECL_OVERRIDE; bool checkRemove(const PatPile * pile, const QList & cards) const Q_DECL_OVERRIDE; void cardsDroppedOnPile( const QList & cards, KCardPile * pile ) Q_DECL_OVERRIDE; void restart( const QList & cards ) Q_DECL_OVERRIDE; QList getHints() Q_DECL_OVERRIDE; protected slots: bool tryAutomaticMove( KCard * c ) Q_DECL_OVERRIDE; private: bool canPutStore( const KCardPile * pile, const QList & cards ) const; + virtual QString solverFormat() const; PatPile* store[8]; PatPile* freecell[4]; PatPile* target[4]; friend class FreecellSolver; }; #endif diff --git a/patsolve/abstract_fc_solve_solver.cpp b/patsolve/abstract_fc_solve_solver.cpp new file mode 100644 index 0000000..11e5baa --- /dev/null +++ b/patsolve/abstract_fc_solve_solver.cpp @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2006-2009 Stephan Kulow + * + * 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 . + */ + +#include +#include + +#include "freecell-solver/fcs_user.h" +#include "freecell-solver/fcs_cl.h" + +#include "abstract_fc_solve_solver.h" + +const int CHUNKSIZE = 100; +const long int MAX_ITERS_LIMIT = 200000; + +#define PRINT 0 + +/* These two routines make and unmake moves. */ + +void FcSolveSolver::make_move(MOVE *) +{ + return; +} + +void FcSolveSolver::undo_move(MOVE *) +{ + return; +} + +/* Get the possible moves from a position, and store them in Possible[]. */ +SolverInterface::ExitStatus FcSolveSolver::patsolve( int _max_positions ) +{ + int current_iters_count; + max_positions = (_max_positions < 0) ? MAX_ITERS_LIMIT : _max_positions; + + init(); + + if (!solver_instance) + { + { + solver_instance = freecell_solver_user_alloc(); + + solver_ret = FCS_STATE_NOT_BEGAN_YET; + + char * error_string; + int error_arg; + const char * known_parameters[1] = {NULL}; + /* A "char *" copy instead of "const char *". */ + + int parse_args_ret_code = freecell_solver_user_cmd_line_parse_args( + solver_instance, + get_cmd_line_arg_count() , + get_cmd_line_args(), + 0, + known_parameters, + NULL, + NULL, + &error_string, + &error_arg + ); + + Q_ASSERT(!parse_args_ret_code); + } + + /* Not needed for Simple Simon because it's already specified in + * freecell_solver_cmd_line_args. TODO : abstract . + * + * Shlomi Fish + * */ + setFcSolverGameParams(); + + current_iters_count = CHUNKSIZE; + freecell_solver_user_limit_iterations(solver_instance, current_iters_count); + } + + if (solver_instance) + { + bool continue_loop = true; + while (continue_loop && + ( (solver_ret == FCS_STATE_NOT_BEGAN_YET) + || (solver_ret == FCS_STATE_SUSPEND_PROCESS)) + && + (current_iters_count < MAX_ITERS_LIMIT) + ) + { + current_iters_count += CHUNKSIZE; + freecell_solver_user_limit_iterations(solver_instance, current_iters_count); + + if (solver_ret == FCS_STATE_NOT_BEGAN_YET) + { + solver_ret = + freecell_solver_user_solve_board( + solver_instance, + board_as_string + ); + } + else + { + solver_ret = freecell_solver_user_resume_solution(solver_instance); + } + { + // QMutexLocker lock( &endMutex ); + if ( m_shouldEnd ) + { + continue_loop = false; + } + } + } + } + + switch (solver_ret) + { + case FCS_STATE_IS_NOT_SOLVEABLE: + if (solver_instance) + { + freecell_solver_user_free(solver_instance); + solver_instance = NULL; + } + return Solver::NoSolutionExists; + + case FCS_STATE_WAS_SOLVED: + { + if (solver_instance) + { + m_winMoves.clear(); + while (freecell_solver_user_get_moves_left(solver_instance)) + { + fcs_move_t move; + MOVE new_move; + const int verdict = !freecell_solver_user_get_next_move( + solver_instance, &move) + ; + + Q_ASSERT (verdict); + + new_move.fcs = move; + + m_winMoves.append( new_move ); + } + + freecell_solver_user_free(solver_instance); + solver_instance = NULL; + } + return Solver::SolutionExists; + } + + case FCS_STATE_SUSPEND_PROCESS: + return Solver::UnableToDetermineSolvability; + + default: + if (solver_instance) + { + freecell_solver_user_free(solver_instance); + solver_instance = NULL; + } + return Solver::NoSolutionExists; + } +} + +/* Get the possible moves from a position, and store them in Possible[]. */ + +int FcSolveSolver::get_possible_moves(int *, int *) +{ + return 0; +} + +bool FcSolveSolver::isWon() +{ + return true; +} + +int FcSolveSolver::getOuts() +{ + return 0; +} + +FcSolveSolver::FcSolveSolver() + : Solver() + , solver_instance(NULL) + , solver_ret(FCS_STATE_NOT_BEGAN_YET) + , board_as_string("") +{ +} + +unsigned int FcSolveSolver::getClusterNumber() +{ + return 0; +} + +void FcSolveSolver::print_layout() +{ +#if 0 + int i, w, o; + + fprintf(stderr, "print-layout-begin\n"); + for (w = 0; w < 10; ++w) { + Q_ASSERT( Wp[w] == &W[w][Wlen[w]-1] ); + fprintf( stderr, "Play%d: ", w ); + for (i = 0; i < Wlen[w]; ++i) { + printcard(W[w][i], stderr); + } + fputc('\n', stderr); + } + fprintf( stderr, "Off: " ); + for (o = 0; o < 4; ++o) { + if ( O[o] != -1 ) + printcard( O[o] + PS_KING, stderr); + } + fprintf(stderr, "\nprint-layout-end\n"); +#endif +} + +void FcSolveSolver::unpack_cluster( unsigned int) +{ + return; +} + +FcSolveSolver::~FcSolveSolver() +{ + if (solver_instance) + { + freecell_solver_user_free(solver_instance); + solver_instance = NULL; + } +} + diff --git a/patsolve/abstract_fc_solve_solver.h b/patsolve/abstract_fc_solve_solver.h new file mode 100644 index 0000000..d2d072d --- /dev/null +++ b/patsolve/abstract_fc_solve_solver.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2006-2009 Stephan Kulow + * + * 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 . + */ + +#ifndef ABSTRACT_FC_SOLVE_SOLVER_H +#define ABSTRACT_FC_SOLVE_SOLVER_H + +#include "patsolve.h" + +struct FcSolveSolver : public Solver<10> +{ +public: + FcSolveSolver(); + virtual ~FcSolveSolver(); + virtual int get_possible_moves(int *a, int *numout); + virtual bool isWon(); + virtual void make_move(MOVE *m); + virtual void undo_move(MOVE *m); + virtual int getOuts(); + virtual unsigned int getClusterNumber(); + virtual void translate_layout() = 0; + virtual void unpack_cluster( unsigned int k ); + virtual MoveHint translateMove(const MOVE &m) = 0; + virtual SolverInterface::ExitStatus patsolve( int _max_positions = -1); + virtual void setFcSolverGameParams() = 0; + + virtual void print_layout(); + + virtual int get_cmd_line_arg_count() = 0; + virtual const char * * get_cmd_line_args() = 0; +/* Names of the cards. The ordering is defined in pat.h. */ + + void * solver_instance; + int solver_ret; + // More than enough space for two decks. + char board_as_string[4 * 13 * 2 * 4 * 3]; +}; + +#endif // ABSTRACT_FC_SOLVE_SOLVER_H diff --git a/patsolve/freecellsolver.cpp b/patsolve/freecellsolver.cpp index 39eff50..e92000f 100644 --- a/patsolve/freecellsolver.cpp +++ b/patsolve/freecellsolver.cpp @@ -1,521 +1,525 @@ /* * Copyright (C) 1998-2002 Tom Holroyd * Copyright (C) 2006-2009 Stephan Kulow * * 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 . */ +#include +#include + +#include "freecell-solver/fcs_user.h" +#include "freecell-solver/fcs_cl.h" + #include "freecellsolver.h" #include "../freecell.h" - -/* Some macros used in get_possible_moves(). */ +const int CHUNKSIZE = 100; +const long int MAX_ITERS_LIMIT = 200000; /* The following function implements (Same_suit ? (suit(a) == suit(b)) : (color(a) != color(b))) */ namespace { constexpr bool suitable(const card_t a, const card_t b) {return ((a ^ b) & PS_COLOR) == PS_COLOR; } } /* Statistics. */ +#if 0 int FreecellSolver::Xparam[] = { 4, 1, 8, -1, 7, 11, 4, 2, 2, 1, 2 }; +#endif /* These two routines make and unmake moves. */ +#if 0 void FreecellSolver::make_move(MOVE *m) { int from, to; card_t card; from = m->from; to = m->to; /* Remove from pile. */ card = *Wp[from]--; Wlen[from]--; hashpile(from); /* Add to pile. */ if (m->totype == O_Type) { O[to]++; } else { *++Wp[to] = card; Wlen[to]++; hashpile(to); } } void FreecellSolver::undo_move(MOVE *m) { int from, to; card_t card; from = m->from; to = m->to; /* Remove from 'to' pile. */ if (m->totype == O_Type) { card = O[to] + Osuit[to]; O[to]--; } else { card = *Wp[to]--; Wlen[to]--; hashpile(to); } /* Add to 'from' pile. */ *++Wp[from] = card; Wlen[from]++; hashpile(from); } +#endif +#if 0 /* Move prioritization. Given legal, pruned moves, there are still some that are a waste of time, especially in the endgame where there are lots of possible moves, but few productive ones. Note that we also prioritize positions when they are added to the queue. */ namespace { constexpr auto NNEED = 8; } void FreecellSolver::prioritize(MOVE *mp0, int n) { int i, j, s, w, pile[NNEED], npile; card_t card, need[4]; MOVE *mp; /* There are 4 cards that we "need": the next cards to go out. We give higher priority to the moves that remove cards from the piles containing these cards. */ for (i = 0; i < NNEED; ++i) { pile[i] = -1; } npile = 0; for (s = 0; s < 4; ++s) { need[s] = NONE; if (O[s] == NONE) { need[s] = Osuit[s] + PS_ACE; } else if (O[s] != PS_KING) { need[s] = Osuit[s] + O[s] + 1; } } /* Locate the needed cards. There's room for optimization here, like maybe an array that keeps track of every card; if maintaining such an array is not too expensive. */ for (w = 0; w < Nwpiles; ++w) { j = Wlen[w]; for (i = 0; i < j; ++i) { card = W[w][i]; s = SUIT(card); /* Save the locations of the piles containing not only the card we need next, but the card after that as well. */ if (need[s] != NONE && (card == need[s] || card == need[s] + 1)) { pile[npile++] = w; if (npile == NNEED) { break; } } } if (npile == NNEED) { break; } } /* Now if any of the moves remove a card from any of the piles listed in pile[], bump their priority. Likewise, if a move covers a card we need, decrease its priority. These priority increments and decrements were determined empirically. */ for (i = 0, mp = mp0; i < n; ++i, ++mp) { if (mp->card_index != -1) { w = mp->from; for (j = 0; j < npile; ++j) { if (w == pile[j]) { mp->pri += Xparam[0]; } } if (Wlen[w] > 1) { card = W[w][Wlen[w] - 2]; for (s = 0; s < 4; ++s) { if (card == need[s]) { mp->pri += Xparam[1]; break; } } } if (mp->totype == W_Type) { for (j = 0; j < npile; ++j) { if (mp->to == pile[j]) { mp->pri -= Xparam[2]; } } } } } } +#endif /* Automove logic. Freecell games must avoid certain types of automoves. */ +#if 0 int FreecellSolver::good_automove(int o, int r) { int i; if (r <= 2) { return true; } /* Check the Out piles of opposite color. */ for (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; } +#endif -/* Get the possible moves from a position, and store them in Possible[]. */ +#define CMD_LINE_ARGS_NUM 2 -int FreecellSolver::get_possible_moves(int *a, int *numout) +static const char * freecell_solver_cmd_line_args[CMD_LINE_ARGS_NUM] = { - int i, n, t, w, o, empty, emptyw; - card_t card; - MOVE *mp; - - /* Check for moves from W to O. */ - - n = 0; - mp = Possible; - for (w = 0; w < Nwpiles + Ntpiles; ++w) { - if (Wlen[w] > 0) { - card = *Wp[w]; - o = SUIT(card); - empty = (O[o] == NONE); - if ((empty && (RANK(card) == PS_ACE)) || - (!empty && (RANK(card) == O[o] + 1))) { - mp->card_index = 0; - mp->from = w; - mp->to = o; - 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(o, RANK(card))) { - *a = true; - mp[-1].pri = 127; - if (n != 1) { - Possible[0] = mp[-1]; - return 1; - } - return n; - } - } - } - } - - /* No more automoves, but remember if there were any moves out. */ - - *a = false; - *numout = n; - - /* Check for moves from non-singleton W cells to one of any - empty W cells. */ + "--load-config", "video-editing" +}; - emptyw = -1; - for (w = 0; w < Nwpiles; ++w) { - if (Wlen[w] == 0) { - emptyw = w; - break; - } - } - if (emptyw >= 0) { - for (i = 0; i < Nwpiles + Ntpiles; ++i) { - if (i == emptyw || Wlen[i] == 0) { - continue; - } - bool allowed = false; - if ( i < Nwpiles) - allowed = true; - if ( i >= Nwpiles ) - allowed = true; - if ( allowed ) { - card = *Wp[i]; - mp->card_index = 0; - mp->from = i; - mp->to = emptyw; - mp->totype = W_Type; - mp->turn_index = -1; - if ( i >= Nwpiles ) - mp->pri = Xparam[6]; - else - mp->pri = Xparam[3]; - n++; - mp++; - } - } - } - - /* Check for moves from W to non-empty W cells. */ - - for (i = 0; i < Nwpiles + Ntpiles; ++i) { - if (Wlen[i] > 0) { - card = *Wp[i]; - for (w = 0; w < Nwpiles; ++w) { - if (i == w) { - continue; - } - if (Wlen[w] > 0 && - (RANK(card) == RANK(*Wp[w]) - 1 && - suitable(card, *Wp[w]))) { - mp->card_index = 0; - mp->from = i; - mp->to = w; - mp->totype = W_Type; - mp->turn_index = -1; - if ( i >= Nwpiles ) - mp->pri = Xparam[5]; - else - mp->pri = Xparam[4]; - n++; - mp++; - } - } - } - } - - /* Check for moves from W to one of any empty T cells. */ - - for (t = 0; t < Ntpiles; ++t) { - if (!Wlen[t+Nwpiles]) { - break; - } - } +int FreecellSolver::get_cmd_line_arg_count() +{ + return CMD_LINE_ARGS_NUM; +} - if (t < Ntpiles) { - for (w = 0; w < Nwpiles; ++w) { - if (Wlen[w] > 0) { - card = *Wp[w]; - mp->card_index = 0; - mp->from = w; - mp->turn_index = -1; - mp->to = t+Nwpiles; - mp->totype = W_Type; - mp->pri = Xparam[7]; - n++; - mp++; - } - } - } +const char * * FreecellSolver::get_cmd_line_args() +{ + return freecell_solver_cmd_line_args; +} - return n; +void FreecellSolver::setFcSolverGameParams() +{ + /* + * I'm using the more standard interface instead of the depracated + * user_set_game one. I'd like that each function will have its + * own dedicated purpose. + * + * Shlomi Fish + * */ + freecell_solver_user_set_num_freecells(solver_instance,4); + freecell_solver_user_set_num_stacks(solver_instance,8); + freecell_solver_user_set_num_decks(solver_instance,1); + freecell_solver_user_set_sequences_are_built_by_type(solver_instance, FCS_SEQ_BUILT_BY_ALTERNATE_COLOR); + freecell_solver_user_set_sequence_move(solver_instance, 0); + freecell_solver_user_set_empty_stacks_filled_by(solver_instance, FCS_ES_FILLED_BY_ANY_CARD); } - +#if 0 void FreecellSolver::unpack_cluster( unsigned int k ) { /* Get the Out cells from the cluster number. */ O[0] = k & 0xF; k >>= 4; O[1] = k & 0xF; k >>= 4; O[2] = k & 0xF; k >>= 4; O[3] = k & 0xF; } +#endif -bool FreecellSolver::isWon() -{ - // maybe won? - for (int o = 0; o < 4; ++o) { - if (O[o] != PS_KING) { - return false; - } - } - - return true; -} - -int FreecellSolver::getOuts() -{ - return O[0] + O[1] + O[2] + O[3]; -} FreecellSolver::FreecellSolver(const Freecell *dealer) - : Solver() + : FcSolveSolver() { +#if 0 Osuit[0] = PS_DIAMOND; Osuit[1] = PS_CLUB; Osuit[2] = PS_HEART; Osuit[3] = PS_SPADE; Nwpiles = 8; Ntpiles = 4; +#endif + deal = dealer; } /* Read a layout file. Format is one pile per line, bottom to top (visible card). Temp cells and Out on the last two lines, if any. */ +#if 0 void FreecellSolver::translate_layout() { /* Read the workspace. */ int total = 0; for ( int w = 0; w < 8; ++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; } } /* Temp cells may have some cards too. */ for (int w = 0; w < Ntpiles; ++w) { int i = translate_pile( deal->freecell[w], W[w+Nwpiles], 52 ); Wp[w+Nwpiles] = &W[w+Nwpiles][i-1]; Wlen[w+Nwpiles] = i; total += i; } /* 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(); } } } } +#endif MoveHint FreecellSolver::translateMove( const MOVE &m ) { + fcs_move_t move = m.fcs; + int cards = fcs_move_get_num_cards_in_seq(move); + PatPile *from = 0; + PatPile *to = 0; + + switch(fcs_move_get_type(move)) + { + case FCS_MOVE_TYPE_STACK_TO_STACK: + from = deal->store[fcs_move_get_src_stack(move)]; + to = deal->store[fcs_move_get_dest_stack(move)]; + break; + + case FCS_MOVE_TYPE_FREECELL_TO_STACK: + from = deal->freecell[fcs_move_get_src_freecell(move)]; + to = deal->store[fcs_move_get_dest_stack(move)]; + cards = 1; + break; + + case FCS_MOVE_TYPE_FREECELL_TO_FREECELL: + from = deal->freecell[fcs_move_get_src_freecell(move)]; + to = deal->freecell[fcs_move_get_dest_freecell(move)]; + cards = 1; + break; + + case FCS_MOVE_TYPE_STACK_TO_FREECELL: + from = deal->store[fcs_move_get_src_stack(move)]; + to = deal->freecell[fcs_move_get_dest_freecell(move)]; + cards = 1; + break; + + case FCS_MOVE_TYPE_STACK_TO_FOUNDATION: + from = deal->store[fcs_move_get_src_stack(move)]; + cards = 1; + to = 0; + break; + + case FCS_MOVE_TYPE_FREECELL_TO_FOUNDATION: + from = deal->freecell[fcs_move_get_src_freecell(move)]; + cards = 1; + to = 0; + } + 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 = 0; + PatPile *empty = 0; + 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); + +#if 0 // this is tricky as we need to want to build the "meta moves" PatPile *frompile = nullptr; if ( m.from < 8 ) frompile = deal->store[m.from]; else frompile = deal->freecell[m.from-8]; 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 < 8 ) target = deal->store[m.to]; else target = deal->freecell[m.to-8]; return MoveHint( card, target, m.pri ); } +#endif } +void FreecellSolver::translate_layout() +{ + strcpy(board_as_string, deal->solverFormat().toLatin1()); + + if (solver_instance) + { + freecell_solver_user_recycle(solver_instance); + solver_ret = FCS_STATE_NOT_BEGAN_YET; + } +#if 0 + /* Read the workspace. */ + int total = 0; + + for ( int w = 0; w < 10; ++w ) { + int i = translate_pile(deal->store[w], W[w], 52); + Wp[w] = &W[w][i - 1]; + Wlen[w] = i; + total += i; + } + + for (int i = 0; i < 4; ++i) { + O[i] = -1; + KCard *c = deal->target[i]->top(); + if (c) { + total += 13; + O[i] = translateSuit( c->suit() ); + } + } +#endif +} + + + +#if 0 unsigned int FreecellSolver::getClusterNumber() { int i = O[0] + (O[1] << 4); unsigned int k = i; i = O[2] + (O[3] << 4); k |= i << 8; return k; } +#endif +#if 0 void FreecellSolver::print_layout() { int i, t, w, o; fprintf(stderr, "print-layout-begin\n"); for (w = 0; w < Nwpiles; ++w) { fprintf(stderr, "W-Pile%d: ", w); for (i = 0; i < Wlen[w]; ++i) { printcard(W[w][i], stderr); } fputc('\n', stderr); } for (t = 0; t < Ntpiles; ++t) { fprintf(stderr, "T-Pile%d: ", t+Nwpiles); printcard(W[t+Nwpiles][Wlen[t+Nwpiles]], stderr); } fprintf( stderr, "\n" ); for (o = 0; o < 4; ++o) { printcard(O[o] + Osuit[o], stderr); } fprintf(stderr, "\nprint-layout-end\n"); } +#endif diff --git a/patsolve/freecellsolver.h b/patsolve/freecellsolver.h index 45ca063..99d1dbb 100644 --- a/patsolve/freecellsolver.h +++ b/patsolve/freecellsolver.h @@ -1,60 +1,71 @@ /* * Copyright (C) 1998-2002 Tom Holroyd * Copyright (C) 2006-2009 Stephan Kulow * * 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 . */ #ifndef FREECELLSOLVER_H #define FREECELLSOLVER_H -class Freecell; -#include "patsolve.h" +#include "abstract_fc_solve_solver.h" constexpr auto Nwpiles = 8; constexpr auto Ntpiles = 4; +class Freecell; -class FreecellSolver : public Solver +class FreecellSolver : public FcSolveSolver { public: explicit FreecellSolver(const Freecell *dealer); +#if 0 int good_automove(int o, int r); int get_possible_moves(int *a, int *numout) Q_DECL_OVERRIDE; bool isWon() Q_DECL_OVERRIDE; void make_move(MOVE *m) Q_DECL_OVERRIDE; void undo_move(MOVE *m) Q_DECL_OVERRIDE; void prioritize(MOVE *mp0, int n) Q_DECL_OVERRIDE; int getOuts() Q_DECL_OVERRIDE; unsigned int getClusterNumber() Q_DECL_OVERRIDE; void translate_layout() Q_DECL_OVERRIDE; void unpack_cluster( unsigned int k ) Q_DECL_OVERRIDE; MoveHint translateMove(const MOVE &m) Q_DECL_OVERRIDE; - - void print_layout() Q_DECL_OVERRIDE; +#endif + virtual void translate_layout(); +#if 0 + virtual void unpack_cluster( unsigned int k ); +#endif + virtual MoveHint translateMove(const MOVE &m); + virtual void setFcSolverGameParams(); + virtual int get_cmd_line_arg_count(); + virtual const char * * get_cmd_line_args(); +#if 0 + virtual void print_layout(); int Nwpiles; /* the numbers we're actually using */ int Ntpiles; /* Names of the cards. The ordering is defined in pat.h. */ card_t O[4]; /* output piles store only the rank or NONE */ card_t Osuit[4]; - const Freecell *deal; static int Xparam[]; +#endif + const Freecell *deal; }; #endif // FREECELLSOLVER_H diff --git a/patsolve/patsolve.h b/patsolve/patsolve.h index 03285d4..1c3a7c6 100644 --- a/patsolve/patsolve.h +++ b/patsolve/patsolve.h @@ -1,156 +1,160 @@ /* * Copyright (C) 1998-2002 Tom Holroyd * Copyright (C) 2006-2009 Stephan Kulow * * 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 . */ #ifndef PATSOLVE_H #define PATSOLVE_H #include "solverinterface.h" #include "../hint.h" #include "memory.h" #include "KCardPile" #include #include #include #include #include +/* A card is represented as ( down << 6 ) + (suit << 4) + rank. */ + +typedef quint8 card_t; + struct POSITION { POSITION *queue; /* next position in the queue */ POSITION *parent; /* point back up the move stack */ TREE *node; /* compact position rep.'s tree node */ MOVE move; /* move that got us here from the parent */ quint32 cluster; /* the cluster this node is in */ short depth; /* number of moves so far */ quint8 nchild; /* number of child nodes left */ }; class MemoryManager; template class Solver : public SolverInterface { + public: Solver(); virtual ~Solver(); - ExitStatus patsolve( int max_positions = -1) final; + virtual ExitStatus patsolve( int max_positions = -1); + bool recursive(POSITION *pos = nullptr); virtual void translate_layout() = 0; virtual MoveHint translateMove(const MOVE &m ) = 0; - void stopExecution() final; QList firstMoves() const final; QList winMoves() const final; protected: MOVE *get_moves(int *nmoves); bool solve(POSITION *parent); void doit(); void win(POSITION *pos); virtual int get_possible_moves(int *a, int *numout) = 0; int translateSuit( int s ); void queue_position(POSITION *pos, int pri); void free_position(POSITION *pos, int); POSITION *dequeue_position(); void hashpile(int w); POSITION *new_position(POSITION *parent, MOVE *m); TREE *pack_position(void); void unpack_position(POSITION *pos); void init_buckets(void); int get_pilenum(int w); MemoryManager::inscode insert(unsigned int *cluster, int d, TREE **node); void free_buckets(void); void printcard(card_t card, FILE *outfile); int translate_pile(const KCardPile *pile, card_t *w, int size); virtual void print_layout(); void pilesort(void); void hash_layout( void ); virtual void make_move(MOVE *m) = 0; virtual void undo_move(MOVE *m) = 0; virtual void prioritize(MOVE *mp0, int n); virtual bool isWon() = 0; virtual int getOuts() = 0; virtual unsigned int getClusterNumber() { return 0; } virtual void unpack_cluster( unsigned int ) {} void init(); void free(); /* Work arrays. */ std::array W; /* the workspace */ std::array Wp; /* point to the top card of each work pile */ std::array Wlen; /* the number of cards in each pile */ /* Every different pile has a hash and a unique id. */ std::array Whash; std::array Wpilenum = {}; // = {} for zero initialization /* Position freelist. */ POSITION *Freepos = nullptr; static constexpr auto MAXMOVES = 64; /* > max # moves from any position */ MOVE Possible[MAXMOVES]; std::unique_ptr mm; ExitStatus Status; /* win, lose, or fail */ static constexpr auto NQUEUES = 127; POSITION *Qhead[NQUEUES]; /* separate queue for each priority */ int Maxq; unsigned long Total_generated, Total_positions; qreal depth_sum; POSITION *Stack = nullptr; QMap recu_pos; int max_positions; - -private: +protected: QList m_firstMoves; QList m_winMoves; std::atomic_bool m_shouldEnd; }; /* Misc. */ constexpr card_t PS_DIAMOND = 0x00; /* red */ constexpr card_t PS_CLUB = 0x10; /* black */ constexpr card_t PS_HEART = 0x20; /* red */ constexpr card_t PS_SPADE = 0x30; /* black */ constexpr card_t PS_BLACK = 0x10; constexpr card_t PS_COLOR = 0x10; /* black if set */ constexpr card_t PS_SUIT = 0x30; /* mask both suit bits */ constexpr card_t NONE = 0; constexpr card_t PS_ACE = 1; constexpr card_t PS_KING = 13; constexpr card_t RANK(card_t card) {return card & 0xF;} constexpr card_t SUIT(card_t card) {return (card >> 4 ) & 3;} constexpr card_t COLOR(card_t card) {return card & PS_COLOR;} constexpr card_t DOWN(card_t card) {return (card) & ( 1 << 7 );} #endif // PATSOLVE_H diff --git a/patsolve/simonsolver.cpp b/patsolve/simonsolver.cpp index a9d640c..e75dcaa 100644 --- a/patsolve/simonsolver.cpp +++ b/patsolve/simonsolver.cpp @@ -1,417 +1,544 @@ /* * Copyright (C) 2006-2009 Stephan Kulow * * 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 . */ +#include +#include + +#include "freecell-solver/fcs_user.h" +#include "freecell-solver/fcs_cl.h" + #include "simonsolver.h" #include "../simon.h" #include +const int CHUNKSIZE = 100; +const long int MAX_ITERS_LIMIT = 200000; #define PRINT 0 /* These two routines make and unmake moves. */ +#if 0 void SimonSolver::make_move(MOVE *m) { #if PRINT //qDebug() << "\n\nmake_move\n"; if ( m->totype == O_Type ) fprintf( stderr, "move %d from %d out (at %d) Prio: %d\n\n", m->card_index, m->from, m->turn_index, m->pri ); else fprintf( stderr, "move %d from %d to %d (%d) Prio: %d\n\n", m->card_index, m->from, m->to, m->turn_index, m->pri ); print_layout(); #else //print_layout(); #endif int from, to; card_t card = NONE; from = m->from; to = m->to; if (m->totype == O_Type) { O[to] = SUIT( *Wp[from] ); Wlen[from] -= 13; Wp[from] -= 13; hashpile( from ); if ( Wlen[from] && DOWN( *Wp[from] ) ) { *Wp[from] = ( SUIT( *Wp[from] ) << 4 ) + RANK( *Wp[from] ); } #if PRINT print_layout(); #endif return; } for ( int l = m->card_index; l >= 0; --l ) { card = W[from][Wlen[from]-l-1]; Wp[from]--; if ( m->totype != O_Type ) { Wp[to]++; *Wp[to] = card; Wlen[to]++; } } Wlen[from] -= m->card_index + 1; hashpile(from); hashpile(to); #if PRINT print_layout(); #endif } void SimonSolver::undo_move(MOVE *m) { #if PRINT //qDebug() << "\n\nundo_move\n"; if ( m->totype == O_Type ) fprintf( stderr, "move %d from %d out (at %d)\n\n", m->card_index, m->from, m->turn_index ); else fprintf( stderr, "move %d from %d to %d (%d)\n\n", m->card_index, m->from, m->to, m->turn_index ); print_layout(); #endif int from, to; card_t card; from = m->from; to = m->to; if (m->totype == O_Type) { for ( int j = PS_KING; j >= PS_ACE; --j ) { Wp[from]++; *Wp[from] = O[to] + j; Wlen[from]++; } O[to] = -1; hashpile( from ); #if PRINT print_layout(); #endif return; } /* Add to 'from' pile. */ if ( m->turn_index > 0 ) { card_t card2 = *Wp[from]; if ( !DOWN( card2 ) ) card2 = ( SUIT( card2 ) << 4 ) + RANK( card2 ) + ( 1 << 7 ); *Wp[from] = card2; } for ( int l = m->card_index; l >= 0; --l ) { card = W[to][Wlen[to]-l-1]; Wp[from]++; *Wp[from] = card; Wlen[from]++; Wp[to]--; } Wlen[to] -= m->card_index + 1; hashpile(to); hashpile(from); #if PRINT print_layout(); #endif } +#endif + +#define CMD_LINE_ARGS_NUM 4 +static const char * freecell_solver_cmd_line_args[CMD_LINE_ARGS_NUM] = +{ + "-g", "simple_simon", "--load-config", "the-last-mohican" +}; + +int SimonSolver::get_cmd_line_arg_count() +{ + return CMD_LINE_ARGS_NUM; +} + +const char * * SimonSolver::get_cmd_line_args() +{ + return freecell_solver_cmd_line_args; +} + +void SimonSolver::setFcSolverGameParams() +{ + freecell_solver_user_apply_preset(solver_instance, "simple_simon"); +} +#if 0 /* Get the possible moves from a position, and store them in Possible[]. */ int SimonSolver::get_possible_moves(int *a, int *numout) { MOVE *mp; + int n; + + mp = Possible; + n = 0; + *a = 1; + + while (freecell_solver_user_get_moves_left(solver_instance)) + { + fcs_move_t move; + fcs_move_t * move_ptr; + if (!freecell_solver_user_get_next_move(solver_instance, &move)) { + move_ptr = new fcs_move_t; + *move_ptr = move; + mp->ptr = (void *)move_ptr; + mp++; + n++; + } + else + { + Q_ASSERT(0); + } + } + + *numout = n; + return n; +#if 0 /* Check for moves from W to O. */ int n = 0; mp = Possible; for (int w = 0; w < 10; ++w) { if (Wlen[w] >= 13 && RANK( *Wp[w] ) == PS_ACE ) { int ace_suit = SUIT( *Wp[w] ); bool stroke = true; for ( int l = 0; l < 13; ++l ) { if ( RANK( W[w][Wlen[w]-l-1] ) != l+1 || SUIT( W[w][Wlen[w]-l-1] ) != ace_suit ) { stroke = false; break; } } if ( !stroke ) continue; mp->card_index = 12; mp->from = w; int o = 0; while ( O[o] != -1) o++; // TODO I need a way to tell spades off from heart off mp->to = o; mp->totype = O_Type; mp->pri = 0; /* unused */ mp->turn_index = -1; n++; mp++; return 1; } } /* No more automoves, but remember if there were any moves out. */ *a = false; *numout = n; int conti[10]; for ( int j = 0; j < 10; ++j ) { conti[j] = 0; for ( ; conti[j] < Wlen[j]-1; ++conti[j] ) { if ( SUIT( *Wp[j] ) != SUIT( W[j][Wlen[j]-conti[j]-2] ) || DOWN( W[j][Wlen[j]-conti[j]-2] )) break; if ( RANK( W[j][Wlen[j]-conti[j]-1] ) != RANK( W[j][Wlen[j]-conti[j]-2] ) - 1) break; } conti[j]++; } for(int i=0; i<10; ++i) { int len = Wlen[i]; for (int l=0; l < len; ++l ) { card_t card = W[i][Wlen[i]-1-l]; if ( DOWN( card ) ) break; if ( l > 0 ) { card_t card_on_top = W[i][Wlen[i]-l]; if ( RANK( card ) != RANK( card_on_top ) + 1 ) break; if ( SUIT( card ) != SUIT( card_on_top ) ) break; } bool wasempty = false; for (int j = 0; j < 10; ++j) { if (i == j) continue; bool allowed = false; if ( Wlen[j] > 0 && RANK(card) == RANK(*Wp[j]) - 1 ) { allowed = true; } if ( Wlen[j] == 0 && !wasempty ) { if ( l != Wlen[i]-1 ) { allowed = true; wasempty = true; } } if ( allowed && Wlen[i] >= l+2 && Wlen[i] > 1 ) { Q_ASSERT( Wlen[i]-l-2 >= 0 ); card_t card_below = W[i][Wlen[i]-l-2]; if ( SUIT( card ) == SUIT( card_below ) && !DOWN( card_below ) && RANK( card_below ) == RANK( card ) + 1 ) { if ( conti[j]+l != 13 || conti[i]>conti[j]+l || SUIT( card ) != SUIT( *Wp[j] ) ) { // fprintf( stderr, "continue\n" ); continue; } } } if ( allowed ) { mp->card_index = l; mp->from = i; mp->to = j; mp->totype = W_Type; mp->turn_index = -1; int cont = conti[j]; if ( Wlen[j] ) cont++; if ( cont ) cont += l; mp->pri = 8 * cont + qMax( 0, 10 - Wlen[i] ); if ( Wlen[j] ) { if ( SUIT( card ) != SUIT( *Wp[j] ) ) { mp->pri /= 2; } } else { mp->pri = 2; // TODO: it should depend on the actual stack's order } if ( Wlen[i] == l+1 ) mp->pri = qMin( 127, mp->pri + 20 ); else mp->pri = qMin( 127, mp->pri + 2 ); /* and now check what sequence we open */ int conti_pos = l+1; for ( ; conti_pos < Wlen[i]-1; ++conti_pos ) { card_t top = W[i][Wlen[i]-l-2]; card_t theone = W[i][Wlen[i]-conti_pos-1]; card_t below = W[i][Wlen[i]-conti_pos-2]; if ( SUIT( top ) != SUIT( below ) || DOWN( below ) ) break; if ( RANK( theone ) != RANK( below ) - 1) break; } mp->pri = qMin( 127, mp->pri + 5 * ( conti_pos - l - 1 ) ); n++; mp++; } } } } return n; +#endif } +#endif +#if 0 void SimonSolver::unpack_cluster( unsigned int k ) { // TODO: this only works for easy for ( unsigned int i = 0; i < 4; ++i ) { if ( i < k ) O[i] = PS_SPADE; else O[i] = -1; } } +#endif +#if 0 bool SimonSolver::isWon() { // maybe won? for (int o = 0; o < 4; ++o) if (O[o] == -1) return false; return true; } +#endif +#if 0 int SimonSolver::getOuts() { int k = 0; for (int o = 0; o < 4; ++o) if (O[o]) k += 13; return k; } +#endif SimonSolver::SimonSolver(const Simon *dealer) - : Solver() + : FcSolveSolver() { deal = dealer; } /* Read a layout file. Format is one pile per line, bottom to top (visible card). Temp cells and Out on the last two lines, if any. */ void SimonSolver::translate_layout() { + strcpy(board_as_string, deal->solverFormat().toLatin1()); + + if (solver_instance) + { + freecell_solver_user_recycle(solver_instance); + solver_ret = FCS_STATE_NOT_BEGAN_YET; + } +#if 0 /* Read the workspace. */ int total = 0; for ( int w = 0; w < 10; ++w ) { int i = translate_pile(deal->store[w], W[w], 52); Wp[w] = &W[w][i - 1]; Wlen[w] = i; total += i; } for (int i = 0; i < 4; ++i) { O[i] = -1; KCard *c = deal->target[i]->topCard(); if (c) { total += 13; O[i] = translateSuit( c->suit() ); } } +#endif } +#if 0 unsigned int SimonSolver::getClusterNumber() { unsigned int k = 0; for ( int i = 0; i < 4; ++i ) { if ( O[i] != -1 ) k++; } return k; } +#endif +#if 0 void SimonSolver::print_layout() { int i, w, o; fprintf(stderr, "print-layout-begin\n"); for (w = 0; w < 10; ++w) { Q_ASSERT( Wp[w] == &W[w][Wlen[w]-1] ); fprintf( stderr, "Play%d: ", w ); for (i = 0; i < Wlen[w]; ++i) { printcard(W[w][i], stderr); } fputc('\n', stderr); } fprintf( stderr, "Off: " ); for (o = 0; o < 4; ++o) { if ( O[o] != -1 ) printcard( O[o] + PS_KING, stderr); } fprintf(stderr, "\nprint-layout-end\n"); } +#endif MoveHint SimonSolver::translateMove( const MOVE &m ) { + fcs_move_t move = m.fcs; + int cards = fcs_move_get_num_cards_in_seq(move); + PatPile *from = 0; + PatPile *to = 0; + + 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_SEQ_TO_FOUNDATION: + from = deal->store[fcs_move_get_src_stack(move)]; + cards = 13; + to = deal->target[fcs_move_get_foundation(move)]; + break; + + } + 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 = 0; + PatPile *empty = 0; + 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); + +#if 0 Q_ASSERT( m.from < 10 && m.to < 10 ); PatPile *frompile = deal->store[m.from]; KCard *card = frompile->at( frompile->count() - m.card_index - 1); if ( m.totype == O_Type ) { for ( int i = 0; i < 4; ++i ) if ( deal->target[i]->isEmpty() ) return MoveHint( card, deal->target[i], 127 ); } Q_ASSERT( m.to < 10 ); return MoveHint( card, deal->store[m.to], m.pri ); +#endif } diff --git a/patsolve/simonsolver.h b/patsolve/simonsolver.h index 2d57dda..4a417b1 100644 --- a/patsolve/simonsolver.h +++ b/patsolve/simonsolver.h @@ -1,47 +1,55 @@ /* * Copyright (C) 2006-2009 Stephan Kulow * * 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 . */ #ifndef SIMONSOLVER_H #define SIMONSOLVER_H -#include "patsolve.h" +#include "abstract_fc_solve_solver.h" +#include "simon.h" class Simon; -class SimonSolver : public Solver<10> +class SimonSolver : public FcSolveSolver { public: explicit SimonSolver(const Simon *dealer); +#if 0 int get_possible_moves(int *a, int *numout) Q_DECL_OVERRIDE; bool isWon() Q_DECL_OVERRIDE; void make_move(MOVE *m) Q_DECL_OVERRIDE; void undo_move(MOVE *m) Q_DECL_OVERRIDE; int getOuts() Q_DECL_OVERRIDE; unsigned int getClusterNumber() Q_DECL_OVERRIDE; - void translate_layout() Q_DECL_OVERRIDE; +#endif + virtual void translate_layout() Q_DECL_OVERRIDE; + virtual MoveHint translateMove(const MOVE &m) Q_DECL_OVERRIDE; +#if 0 void unpack_cluster( unsigned int k ) Q_DECL_OVERRIDE; - MoveHint translateMove(const MOVE &m) Q_DECL_OVERRIDE; - void print_layout() Q_DECL_OVERRIDE; +#endif + virtual void setFcSolverGameParams(); + virtual int get_cmd_line_arg_count(); + virtual const char * * get_cmd_line_args(); +#if 0 /* Names of the cards. The ordering is defined in pat.h. */ - int O[4]; +#endif const Simon *deal; }; #endif // SIMONSOLVER_H diff --git a/patsolve/solverinterface.h b/patsolve/solverinterface.h index d99d3b8..77fd410 100644 --- a/patsolve/solverinterface.h +++ b/patsolve/solverinterface.h @@ -1,55 +1,57 @@ #ifndef SolverInterface_H #define SolverInterface_H #include #include "../hint.h" +#include "freecell-solver/fcs_user.h" /* A card is represented as ( down << 6 ) + (suit << 4) + rank. */ typedef quint8 card_t; /* Represent a move. */ enum PileType { O_Type = 1, W_Type }; class MOVE { public: int card_index; /* the card we're moving (0 is the top)*/ quint8 from; /* from pile number */ quint8 to; /* to pile number */ PileType totype; signed char pri; /* move priority (low priority == low value) */ int turn_index; /* turn the card index */ + fcs_move_t fcs; /* A Freecell Solver move. */ bool operator<( const MOVE &m) const { return pri < m.pri; } }; class SolverInterface { public: enum ExitStatus { MemoryLimitReached = -3, SearchAborted = -2, UnableToDetermineSolvability = -1, NoSolutionExists = 0, SolutionExists = 1 }; virtual ~SolverInterface() {}; virtual ExitStatus patsolve( int max_positions = -1) = 0; virtual void translate_layout() = 0; virtual MoveHint translateMove(const MOVE &m ) = 0; virtual void stopExecution() = 0; virtual QList firstMoves() const = 0; virtual QList winMoves() const = 0; }; extern long all_moves; #endif diff --git a/pileutils.cpp b/pileutils.cpp index 1e3da3e..609c716 100644 --- a/pileutils.cpp +++ b/pileutils.cpp @@ -1,123 +1,184 @@ /* * Copyright (C) 2010 Parker Coates * * 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 . * */ #include "pileutils.h" #include "KCard" #include "KCardDeck" namespace { inline int alternateColor( int color ) { return color == KCardDeck::Red ? KCardDeck::Black : KCardDeck::Red; } } bool isSameSuitAscending( const QList & cards ) { if ( cards.size() <= 1 ) return true; int suit = cards.first()->suit(); int lastRank = cards.first()->rank(); for( int i = 1; i < cards.size(); ++i ) { ++lastRank; if ( cards[i]->suit() != suit || cards[i]->rank() != lastRank ) return false; } return true; } +int countSameSuitDescendingSequences( const QList & cards ) +{ + if ( cards.size() <= 1 ) + return 0; + + int suit = cards.first()->suit(); + int lastRank = cards.first()->rank(); + + int count = 1; + + for( int i = 1; i < cards.size(); ++i ) + { + --lastRank; + + if ( cards[i]->rank() != lastRank ) + return -1; + + if ( cards[i]->suit() != suit ) + { + count++; + suit = cards[i]->suit(); + } + } + return count; +} + + bool isSameSuitDescending( const QList & cards ) { if ( cards.size() <= 1 ) return true; int suit = cards.first()->suit(); int lastRank = cards.first()->rank(); for( int i = 1; i < cards.size(); ++i ) { --lastRank; if ( cards[i]->suit() != suit || cards[i]->rank() != lastRank ) return false; } return true; } bool isAlternateColorDescending( const QList & cards ) { if ( cards.size() <= 1 ) return true; int lastColor = cards.first()->color(); int lastRank = cards.first()->rank(); for( int i = 1; i < cards.size(); ++i ) { lastColor = alternateColor( lastColor ); --lastRank; if ( cards[i]->color() != lastColor || cards[i]->rank() != lastRank ) return false; } return true; } bool checkAddSameSuitAscendingFromAce( const QList & oldCards, const QList & newCards ) { if ( !isSameSuitAscending( newCards ) ) return false; if ( oldCards.isEmpty() ) return newCards.first()->rank() == KCardDeck::Ace; else return newCards.first()->suit() == oldCards.last()->suit() && newCards.first()->rank() == oldCards.last()->rank() + 1; } bool checkAddAlternateColorDescending( const QList & oldCards, const QList & newCards ) { return isAlternateColorDescending( newCards ) && ( oldCards.isEmpty() || ( newCards.first()->color() == alternateColor( oldCards.last()->color() ) && newCards.first()->rank() == oldCards.last()->rank() - 1 ) ); } bool checkAddAlternateColorDescendingFromKing( const QList & oldCards, const QList & newCards ) { if ( !isAlternateColorDescending( newCards ) ) return false; if ( oldCards.isEmpty() ) return newCards.first()->rank() == KCardDeck::King; else return newCards.first()->color() == alternateColor( oldCards.last()->color() ) && newCards.first()->rank() == oldCards.last()->rank() - 1; } +QString suitToString(int s) { + switch (s) { + case KCardDeck::Clubs: + return "C"; + case KCardDeck::Hearts: + return "H"; + case KCardDeck::Diamonds: + return "D"; + case KCardDeck::Spades: + return "S"; + default: + exit(-1); + } + return QString(); +} + +QString rankToString(int r) +{ + switch (r) { + case KCardDeck::King: + return "K"; + case KCardDeck::Ace: + return "A"; + case KCardDeck::Jack: + return "J"; + case KCardDeck::Queen: + return "Q"; + case KCardDeck::Ten: + return "T"; + default: + return QString::number(r); + } +} + diff --git a/pileutils.h b/pileutils.h index 2fa1657..faa8c40 100644 --- a/pileutils.h +++ b/pileutils.h @@ -1,34 +1,38 @@ /* * Copyright (C) 2010 Parker Coates * * 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 . * */ #ifndef PILEUTILS_H #define PILEUTILS_H class KCard; #include bool isSameSuitAscending( const QList & cards ); bool isSameSuitDescending( const QList & cards ); bool isAlternateColorDescending( const QList & cards ); +int countSameSuitDescendingSequences( const QList & cards ); bool checkAddSameSuitAscendingFromAce( const QList & oldCards, const QList & newCards ); bool checkAddAlternateColorDescending( const QList & oldCards, const QList & newCards ); bool checkAddAlternateColorDescendingFromKing( const QList & oldCards, const QList & newCards ); +extern QString suitToString(int s); +extern QString rankToString(int r); + #endif diff --git a/simon.cpp b/simon.cpp index 8e3ef10..834dd55 100644 --- a/simon.cpp +++ b/simon.cpp @@ -1,146 +1,192 @@ /* * Copyright (C) 2000-2009 Stephan Kulow * Copyright (C) 2010 Parker Coates * * 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 . * ------------------------------------------------------------------------- */ #include "simon.h" #include "dealerinfo.h" #include "pileutils.h" #include "patsolve/simonsolver.h" #include Simon::Simon( const DealerInfo * di ) : DealerScene( di ) { } void Simon::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+3)*dist_x, 0); target[i]->setSpread(0, 0); target[i]->setKeyboardSelectHint( KCardPile::NeverFocus ); target[i]->setKeyboardDropHint( KCardPile::AutoFocusTop ); } for ( int i = 0; i < 10; ++i ) { store[i] = new PatPile( this, 5 + i, QStringLiteral( "store%1" ).arg( i ) ); store[i]->setPileRole(PatPile::Tableau); store[i]->setLayoutPos(dist_x*i, 1.2); store[i]->setBottomPadding( 2.5 ); store[i]->setHeightPolicy( KCardPile::GrowDown ); store[i]->setZValue( 0.01 * i ); store[i]->setKeyboardSelectHint( KCardPile::AutoFocusDeepestRemovable ); store[i]->setKeyboardDropHint( KCardPile::AutoFocusTop ); } setActions(DealerScene::Hint | DealerScene::Demo); setSolver( new SimonSolver( this ) ); //setNeededFutureMoves( 1 ); // could be some nonsense moves } void Simon::restart( const QList & cards ) { QList cardList = cards; QPointF initPos( 0, -deck()->cardHeight() ); for ( int piles = 9; piles >= 3; --piles ) for ( int j = 0; j < piles; ++j ) addCardForDeal( store[j], cardList.takeLast(), true, initPos ); for ( int j = 0; j < 10; ++j ) addCardForDeal( store[j], cardList.takeLast(), true, initPos ); Q_ASSERT( cardList.isEmpty() ); startDealAnimation(); } bool Simon::checkPrefering(const PatPile * pile, const QList & oldCards, const QList & newCards) const { return pile->pileRole() == PatPile::Tableau && !oldCards.isEmpty() && oldCards.last()->suit() == newCards.first()->suit(); } bool Simon::checkAdd(const PatPile * pile, const QList & oldCards, const QList & newCards) const { if (pile->pileRole() == PatPile::Tableau) { - return oldCards.isEmpty() - || oldCards.last()->rank() == newCards.first()->rank() + 1; + if (! (oldCards.isEmpty() + || oldCards.last()->rank() == newCards.first()->rank() + 1 )) + { + return false; + } + + int seqs_count = countSameSuitDescendingSequences(newCards); + + if (seqs_count < 0) + return false; + + // This is similar to the supermoves of Freecell - we can use empty + // columns to temporarily hold intermediate sub-sequences which are + // not the same suit - only a "false" parent. + // Shlomi Fish + + int empty_piles_count = 0; + + for (int i = 0; i < 10; ++i ) + if (store[i]->isEmpty() && ( store[i]->index() != pile->index() )) + empty_piles_count++; + + return (seqs_count <= (1 << empty_piles_count)); } else { return oldCards.isEmpty() && newCards.first()->rank() == KCardDeck::King - && newCards.last()->rank() == KCardDeck::Ace; + && newCards.last()->rank() == KCardDeck::Ace + && isSameSuitDescending(newCards); } } bool Simon::checkRemove(const PatPile * pile, const QList & cards) const { - return pile->pileRole() == PatPile::Tableau - && isSameSuitDescending(cards); + if (pile->pileRole() != PatPile::Tableau) + return false; + + int seqs_count = countSameSuitDescendingSequences(cards); + + return (seqs_count >= 0); } +QString Simon::solverFormat() const +{ + QString output; + QString tmp; + for (int i = 0; i < 4 ; i++) { + if (target[i]->isEmpty()) + continue; + tmp += suitToString(target[i]->topCard()->suit()) + "-K "; + } + if (!tmp.isEmpty()) + output += QString::fromLatin1("Foundations: %1\n").arg(tmp); + for (int i = 0; i < 10 ; i++) + { + QList cards = store[i]->cards(); + for (QList::ConstIterator it = cards.begin(); it != cards.end(); ++it) + output += rankToString((*it)->rank()) + suitToString((*it)->suit()) + ' '; + output += '\n'; + } + return output; +} static class SimonDealerInfo : public DealerInfo { public: SimonDealerInfo() : DealerInfo(I18N_NOOP("Simple Simon"), SimpleSimonId) {} DealerScene *createGame() const Q_DECL_OVERRIDE { return new Simon( this ); } } simonDealerInfo; diff --git a/simon.h b/simon.h index 83d10ab..d816f27 100644 --- a/simon.h +++ b/simon.h @@ -1,63 +1,64 @@ /* * Copyright (C) 2000-2009 Stephan Kulow * * 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 . * ------------------------------------------------------------------------- */ #ifndef SIMON_H #define SIMON_H #include "dealer.h" class Simon : public DealerScene { Q_OBJECT public: explicit Simon( const DealerInfo * di ); void initialize() Q_DECL_OVERRIDE; protected: bool checkAdd(const PatPile * pile, const QList & oldCards, const QList & newCards) const Q_DECL_OVERRIDE; bool checkPrefering(const PatPile * pile, const QList & oldCards, const QList & newCards) const Q_DECL_OVERRIDE; bool checkRemove(const PatPile * pile, const QList & cards) const Q_DECL_OVERRIDE; void restart( const QList & cards ) Q_DECL_OVERRIDE; private: PatPile* store[10]; PatPile* target[4]; + virtual QString solverFormat() const; friend class SimonSolver; }; #endif