Commit https://cgit.kde.org/kpat.git/patch/?id=fc1d54ced6a727382599d767e55879b6843c3456 Introduces a hard dependency on fc-solver which in turn has new dependencies So, we revert this commit to avoid dropping kpat altogether From ed0e53e0888da7123f4a0d2097f8da7fb105ca18 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale <0inkane@googlemail.com> Date: Sun, 13 May 2018 15:14:53 +0200 Subject: Use Freecell Solver for FreeCell and Simple Simon Summary: This uses http://fc-solve.shlomifish.org/ and prevents the looping in the existing solvers. Test Plan: Test that the solvers are working. Reviewers: #kde_games, fabiank Subscribers: kde-games-devel, aacid, #kde_games Tags: #kde_games Differential Revision: https://phabricator.kde.org/D12415 --- CMakeLists.txt | 6 +- dealer.cpp | 4 + freecell.cpp | 35 ++++ freecell.h | 1 + patsolve/abstract_fc_solve_solver.cpp | 239 ++++++++++++++++++++++++++ patsolve/abstract_fc_solve_solver.h | 52 ++++++ patsolve/freecellsolver.cpp | 310 +++++++++++++++++----------------- patsolve/freecellsolver.h | 23 ++- patsolve/patsolve.h | 12 +- patsolve/simonsolver.cpp | 129 +++++++++++++- patsolve/simonsolver.h | 20 ++- patsolve/solverinterface.h | 2 + pileutils.cpp | 61 +++++++ pileutils.h | 4 + simon.cpp | 56 +++++- simon.h | 1 + 16 files changed, 779 insertions(+), 176 deletions(-) create mode 100644 patsolve/abstract_fc_solve_solver.cpp create mode 100644 patsolve/abstract_fc_solve_solver.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f738bf..c043c45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,8 @@ 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}) @@ -45,7 +47,7 @@ add_subdirectory(sounds) add_subdirectory(themes) add_subdirectory(doc) -set(kpat_SRCS +set(kpat_SRCS ${libfcs_SRCS} main.cpp dealer.cpp dealerinfo.cpp @@ -59,6 +61,7 @@ set(kpat_SRCS soundengine.cpp statisticsdialog.cpp view.cpp + patsolve/abstract_fc_solve_solver.cpp patsolve/memory.cpp patsolve/patsolve.cpp @@ -101,6 +104,7 @@ target_link_libraries(kpat KF5::KIOCore KF5KDEGames kcardgame + ${FC_SOLVE_LIBRARIES} ) install(TARGETS kpat ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/dealer.cpp b/dealer.cpp index 7c03ebf..a2558fc 100644 --- a/dealer.cpp +++ b/dealer.cpp @@ -1724,6 +1724,10 @@ void DealerScene::startSolver() bool DealerScene::isGameLost() const { + if (! m_winningMoves.isEmpty()) + { + return false; + } if ( solver() ) { if ( m_solverThread && m_solverThread->isRunning() ) diff --git a/freecell.cpp b/freecell.cpp index f870cdb..9a7c278 100644 --- a/freecell.cpp +++ b/freecell.cpp @@ -111,6 +111,41 @@ void Freecell::restart( const QList & cards ) } +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 ) diff --git a/freecell.h b/freecell.h index 7b0b2cb..9f7d84b 100644 --- a/freecell.h +++ b/freecell.h @@ -62,6 +62,7 @@ protected slots: private: bool canPutStore( const KCardPile * pile, const QList & cards ) const; + virtual QString solverFormat() const; PatPile* store[8]; PatPile* freecell[4]; PatPile* target[4]; 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 @@ -16,12 +16,18 @@ * 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))) @@ -32,10 +38,13 @@ namespace { /* 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; @@ -85,7 +94,9 @@ void FreecellSolver::undo_move(MOVE *m) 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 @@ -178,9 +189,11 @@ void FreecellSolver::prioritize(MOVE *mp0, int n) } } } +#endif /* Automove logic. Freecell games must avoid certain types of automoves. */ +#if 0 int FreecellSolver::good_automove(int o, int r) { int i; @@ -220,148 +233,43 @@ int FreecellSolver::good_automove(int o, int r) 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. */ @@ -373,27 +281,13 @@ void FreecellSolver::unpack_cluster( unsigned int k ) 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; @@ -402,12 +296,15 @@ FreecellSolver::FreecellSolver(const Freecell *dealer) 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. */ @@ -447,9 +344,78 @@ void FreecellSolver::translate_layout() } } } +#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; @@ -486,8 +452,43 @@ MoveHint FreecellSolver::translateMove( const MOVE &m ) 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); @@ -496,7 +497,9 @@ unsigned int FreecellSolver::getClusterNumber() k |= i << 8; return k; } +#endif +#if 0 void FreecellSolver::print_layout() { int i, t, w, o; @@ -519,3 +522,4 @@ void FreecellSolver::print_layout() } 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 @@ -19,16 +19,17 @@ #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; @@ -40,8 +41,17 @@ public: 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; @@ -51,10 +61,11 @@ public: 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 @@ -33,6 +33,10 @@ #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 */ @@ -48,14 +52,15 @@ 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; @@ -126,8 +131,7 @@ protected: POSITION *Stack = nullptr; QMap recu_pos; int max_positions; - -private: +protected: QList m_firstMoves; QList m_winMoves; std::atomic_bool m_shouldEnd; diff --git a/patsolve/simonsolver.cpp b/patsolve/simonsolver.cpp index a9d640c..e75dcaa 100644 --- a/patsolve/simonsolver.cpp +++ b/patsolve/simonsolver.cpp @@ -15,17 +15,26 @@ * 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 @@ -136,13 +145,62 @@ void SimonSolver::undo_move(MOVE *m) 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; @@ -301,8 +359,11 @@ int SimonSolver::get_possible_moves(int *a, int *numout) } return n; +#endif } +#endif +#if 0 void SimonSolver::unpack_cluster( unsigned int k ) { // TODO: this only works for easy @@ -314,7 +375,9 @@ void SimonSolver::unpack_cluster( unsigned int k ) O[i] = -1; } } +#endif +#if 0 bool SimonSolver::isWon() { // maybe won? @@ -324,7 +387,9 @@ bool SimonSolver::isWon() return true; } +#endif +#if 0 int SimonSolver::getOuts() { int k = 0; @@ -334,9 +399,10 @@ int SimonSolver::getOuts() return k; } +#endif SimonSolver::SimonSolver(const Simon *dealer) - : Solver() + : FcSolveSolver() { deal = dealer; } @@ -346,6 +412,14 @@ 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; @@ -364,8 +438,10 @@ void SimonSolver::translate_layout() O[i] = translateSuit( c->suit() ); } } +#endif } +#if 0 unsigned int SimonSolver::getClusterNumber() { unsigned int k = 0; @@ -376,7 +452,9 @@ unsigned int SimonSolver::getClusterNumber() } return k; } +#endif +#if 0 void SimonSolver::print_layout() { int i, w, o; @@ -397,9 +475,57 @@ void SimonSolver::print_layout() } 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]; @@ -414,4 +540,5 @@ MoveHint SimonSolver::translateMove( const MOVE &m ) 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 @@ -18,29 +18,37 @@ #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; }; diff --git a/patsolve/solverinterface.h b/patsolve/solverinterface.h index d99d3b8..77fd410 100644 --- a/patsolve/solverinterface.h +++ b/patsolve/solverinterface.h @@ -4,6 +4,7 @@ #include #include "../hint.h" +#include "freecell-solver/fcs_user.h" /* A card is represented as ( down << 6 ) + (suit << 4) + rank. */ @@ -22,6 +23,7 @@ public: 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 { diff --git a/pileutils.cpp b/pileutils.cpp index 1e3da3e..609c716 100644 --- a/pileutils.cpp +++ b/pileutils.cpp @@ -48,6 +48,33 @@ bool isSameSuitAscending( const QList & cards ) 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 ) { @@ -121,3 +148,37 @@ bool checkAddAlternateColorDescendingFromKing( const QList & oldCards, c && 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 @@ -26,9 +26,13 @@ class KCard; 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 @@ -111,24 +111,70 @@ bool Simon::checkAdd(const PatPile * pile, const QList & oldCards, 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 { diff --git a/simon.h b/simon.h index 83d10ab..d816f27 100644 --- a/simon.h +++ b/simon.h @@ -57,6 +57,7 @@ private: PatPile* store[10]; PatPile* target[4]; + virtual QString solverFormat() const; friend class SimonSolver; }; -- cgit v0.11.2