File indexing completed on 2024-11-24 03:44:58

0001 /*
0002  * Copyright (C) 1998-2002 Tom Holroyd <tomh@kurage.nimh.nih.gov>
0003  * Copyright (C) 2006-2009 Stephan Kulow <coolo@kde.org>
0004  *
0005  * This program is free software; you can redistribute it and/or
0006  * modify it under the terms of the GNU General Public License as
0007  * published by the Free Software Foundation; either version 2 of
0008  * the License, or (at your option) any later version.
0009  *
0010  * This program is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013  * GNU General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU General Public License
0016  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017  */
0018 
0019 #include "freecellsolver.h"
0020 
0021 // own
0022 #include "../freecell.h"
0023 #include "../settings.h"
0024 #include "patsolve-config.h"
0025 // freecell-solver
0026 #include "freecell-solver/fcs_cl.h"
0027 #include "freecell-solver/fcs_user.h"
0028 // St
0029 #include <cstdlib>
0030 #include <cstring>
0031 
0032 int m_reserves = Settings::freecellReserves();
0033 int m_stacks = Settings::freecellStacks() + 6;
0034 int m_decks = Settings::freecellDecks() + 1;
0035 int m_emptyStackFill = Settings::freecellEmptyStackFill();
0036 int m_sequenceBuiltBy = Settings::freecellSequenceBuiltBy();
0037 
0038 const int CHUNKSIZE = 100;
0039 const long int MAX_ITERS_LIMIT = 200000;
0040 
0041 /* The following function implements
0042     (Same_suit ? (suit(a) == suit(b)) : (color(a) != color(b)))
0043 */
0044 namespace
0045 {
0046 constexpr bool suitable(const card_t a, const card_t b)
0047 {
0048     return ((a ^ b) & PS_COLOR) == PS_COLOR;
0049 }
0050 }
0051 
0052 /* Statistics. */
0053 
0054 #if 0
0055 int FreecellSolver::Xparam[] = { 4, 1, 8, -1, 7, 11, 4, 2, 2, 1, 2 };
0056 #endif
0057 
0058 /* These two routines make and unmake moves. */
0059 
0060 #if 0
0061 void FreecellSolver::make_move(MOVE *m)
0062 {
0063     int from, to;
0064     card_t card;
0065 
0066     from = m->from;
0067     to = m->to;
0068 
0069     /* Remove from pile. */
0070 
0071         card = *Wp[from]--;
0072         Wlen[from]--;
0073         hashpile(from);
0074 
0075     /* Add to pile. */
0076 
0077     if (m->totype == O_Type) {
0078             O[to]++;
0079         } else {
0080             *++Wp[to] = card;
0081             Wlen[to]++;
0082             hashpile(to);
0083     }
0084 }
0085 
0086 void FreecellSolver::undo_move(MOVE *m)
0087 {
0088     int from, to;
0089     card_t card;
0090 
0091     from = m->from;
0092     to = m->to;
0093 
0094     /* Remove from 'to' pile. */
0095 
0096     if (m->totype == O_Type) {
0097             card = O[to] + Osuit[to];
0098             O[to]--;
0099         } else {
0100             card = *Wp[to]--;
0101             Wlen[to]--;
0102             hashpile(to);
0103     }
0104 
0105     /* Add to 'from' pile. */
0106         *++Wp[from] = card;
0107         Wlen[from]++;
0108         hashpile(from);
0109 }
0110 #endif
0111 
0112 #if 0
0113 /* Move prioritization.  Given legal, pruned moves, there are still some
0114 that are a waste of time, especially in the endgame where there are lots of
0115 possible moves, but few productive ones.  Note that we also prioritize
0116 positions when they are added to the queue. */
0117 
0118 namespace {
0119     constexpr auto NNEED = 8;
0120 }
0121 
0122 void FreecellSolver::prioritize(MOVE *mp0, int n)
0123 {
0124     int i, j, s, w, pile[NNEED], npile;
0125     card_t card, need[4];
0126     MOVE *mp;
0127 
0128     /* There are 4 cards that we "need": the next cards to go out.  We
0129     give higher priority to the moves that remove cards from the piles
0130     containing these cards. */
0131 
0132     for (i = 0; i < NNEED; ++i) {
0133         pile[i] = -1;
0134     }
0135     npile = 0;
0136 
0137     for (s = 0; s < 4; ++s) {
0138         need[s] = NONE;
0139         if (O[s] == NONE) {
0140             need[s] = Osuit[s] + PS_ACE;
0141         } else if (O[s] != PS_KING) {
0142             need[s] = Osuit[s] + O[s] + 1;
0143         }
0144     }
0145 
0146     /* Locate the needed cards.  There's room for optimization here,
0147     like maybe an array that keeps track of every card; if maintaining
0148     such an array is not too expensive. */
0149 
0150     for (w = 0; w < Nwpiles; ++w) {
0151         j = Wlen[w];
0152         for (i = 0; i < j; ++i) {
0153             card = W[w][i];
0154             s = SUIT(card);
0155 
0156             /* Save the locations of the piles containing
0157             not only the card we need next, but the card
0158             after that as well. */
0159 
0160             if (need[s] != NONE &&
0161                 (card == need[s] || card == need[s] + 1)) {
0162                 pile[npile++] = w;
0163                 if (npile == NNEED) {
0164                     break;
0165                 }
0166             }
0167         }
0168         if (npile == NNEED) {
0169             break;
0170         }
0171     }
0172 
0173     /* Now if any of the moves remove a card from any of the piles
0174     listed in pile[], bump their priority.  Likewise, if a move
0175     covers a card we need, decrease its priority.  These priority
0176     increments and decrements were determined empirically. */
0177 
0178     for (i = 0, mp = mp0; i < n; ++i, ++mp) {
0179         if (mp->card_index != -1) {
0180             w = mp->from;
0181             for (j = 0; j < npile; ++j) {
0182                 if (w == pile[j]) {
0183                     mp->pri += Xparam[0];
0184                 }
0185             }
0186             if (Wlen[w] > 1) {
0187                 card = W[w][Wlen[w] - 2];
0188                 for (s = 0; s < 4; ++s) {
0189                     if (card == need[s]) {
0190                         mp->pri += Xparam[1];
0191                         break;
0192                     }
0193                 }
0194             }
0195             if (mp->totype == W_Type) {
0196                 for (j = 0; j < npile; ++j) {
0197                     if (mp->to == pile[j]) {
0198                         mp->pri -= Xparam[2];
0199                     }
0200                 }
0201             }
0202         }
0203     }
0204 }
0205 #endif
0206 
0207 /* Automove logic.  Freecell games must avoid certain types of automoves. */
0208 
0209 #if 1
0210 int FreecellSolver::good_automove(int o, int r)
0211 {
0212     if (r <= 2) {
0213         return true;
0214     }
0215 
0216     if (m_sequenceBuiltBy == 1) {
0217         return true;
0218     }
0219 
0220     for (int foundation_idx = 0; foundation_idx < 4 * m_decks; ++foundation_idx) {
0221         KCard *c = deal->target[foundation_idx]->topCard();
0222         if (c) {
0223             O[translateSuit(c->suit()) >> 4] = c->rank();
0224         }
0225     }
0226     /* Check the Out piles of opposite color. */
0227 
0228     for (int i = 1 - (o & 1); i < 4 * m_decks; i += 2) {
0229         if (O[i] < r - 1) {
0230 #if 1 /* Raymond's Rule */
0231             /* Not all the N-1's of opposite color are out
0232             yet.  We can still make an automove if either
0233             both N-2's are out or the other same color N-3
0234             is out (Raymond's rule).  Note the re-use of
0235             the loop variable i.  We return here and never
0236             make it back to the outer loop. */
0237 
0238             for (i = 1 - (o & 1); i < 4 * m_decks; i += 2) {
0239                 if (O[i] < r - 2) {
0240                     return false;
0241                 }
0242             }
0243             if (O[(o + 2) & 3] < r - 3) {
0244                 return false;
0245             }
0246 
0247             return true;
0248 #else /* Horne's Rule */
0249             return false;
0250 #endif
0251         }
0252     }
0253 
0254     return true;
0255 }
0256 
0257 int FreecellSolver::get_possible_moves(int *a, int *numout)
0258 {
0259     int w;
0260     card_t card;
0261     MOVE *mp;
0262 
0263     /* Check for moves from W to O. */
0264 
0265     int n = 0;
0266     mp = Possible;
0267     for (w = 0; w < m_stacks + m_reserves; ++w) {
0268         if (Wlen[w] > 0) {
0269             card = *Wp[w];
0270             int out_suit = SUIT(card);
0271             const bool empty = (O[out_suit] == NONE);
0272             if ((empty && (RANK(card) == PS_ACE)) || (!empty && (RANK(card) == O[out_suit] + 1))) {
0273                 mp->is_fcs = false;
0274                 mp->card_index = 0;
0275                 mp->from = w;
0276                 mp->to = out_suit;
0277                 mp->totype = O_Type;
0278                 mp->turn_index = -1;
0279                 mp->pri = 0; /* unused */
0280                 n++;
0281                 mp++;
0282 
0283                 /* If it's an automove, just do it. */
0284 
0285                 if (good_automove(out_suit, RANK(card))) {
0286                     *a = true;
0287                     mp[-1].pri = 127;
0288                     if (n != 1) {
0289                         Possible[0] = mp[-1];
0290                         return (*numout = 1);
0291                     }
0292                     return (*numout = n);
0293                 }
0294             }
0295         }
0296     }
0297     return (*numout = 0);
0298 }
0299 #endif
0300 
0301 #define CMD_LINE_ARGS_NUM 2
0302 
0303 static const char *freecell_solver_cmd_line_args[CMD_LINE_ARGS_NUM] = {
0304 #if WITH_FCS_SOFT_SUSPEND
0305     "--load-config",
0306     "video-editing"
0307 #else
0308     "--load-config",
0309     "slick-rock"
0310 #endif
0311 };
0312 
0313 int FreecellSolver::get_cmd_line_arg_count()
0314 {
0315     return CMD_LINE_ARGS_NUM;
0316 }
0317 
0318 const char **FreecellSolver::get_cmd_line_args()
0319 {
0320     return freecell_solver_cmd_line_args;
0321 }
0322 
0323 void FreecellSolver::setFcSolverGameParams()
0324 {
0325     /*
0326      * I'm using the more standard interface instead of the depracated
0327      * user_set_game one. I'd like that each function will have its
0328      * own dedicated purpose.
0329      *
0330      *     Shlomi Fish
0331      * */
0332     freecell_solver_user_set_sequence_move(solver_instance, 0);
0333 
0334     m_reserves = Settings::freecellReserves();
0335     freecell_solver_user_set_num_freecells(solver_instance, m_reserves);
0336 
0337     m_stacks = Settings::freecellStacks() + 6;
0338     freecell_solver_user_set_num_stacks(solver_instance, m_stacks);
0339 
0340     m_decks = Settings::freecellDecks() + 1;
0341     freecell_solver_user_set_num_decks(solver_instance, m_decks);
0342 
0343     // FCS_ES_FILLED_BY_ANY_CARD = 0, FCS_ES_FILLED_BY_KINGS_ONLY = 1,FCS_ES_FILLED_BY_NONE = 2
0344     m_emptyStackFill = Settings::freecellEmptyStackFill();
0345     freecell_solver_user_set_empty_stacks_filled_by(solver_instance, m_emptyStackFill);
0346 
0347     // FCS_SEQ_BUILT_BY_ALTERNATE_COLOR = 0, FCS_SEQ_BUILT_BY_SUIT = 1, FCS_SEQ_BUILT_BY_RANK = 2
0348     m_sequenceBuiltBy = Settings::freecellSequenceBuiltBy();
0349     freecell_solver_user_set_sequences_are_built_by_type(solver_instance, m_sequenceBuiltBy);
0350 }
0351 #if 0
0352 void FreecellSolver::unpack_cluster( unsigned int k )
0353 {
0354     /* Get the Out cells from the cluster number. */
0355     O[0] = k & 0xF;
0356     k >>= 4;
0357     O[1] = k & 0xF;
0358     k >>= 4;
0359     O[2] = k & 0xF;
0360     k >>= 4;
0361     O[3] = k & 0xF;
0362 }
0363 #endif
0364 
0365 FreecellSolver::FreecellSolver(const Freecell *dealer)
0366     : FcSolveSolver()
0367 {
0368 #if 0
0369     Osuit[0] = PS_DIAMOND;
0370     Osuit[1] = PS_CLUB;
0371     Osuit[2] = PS_HEART;
0372     Osuit[3] = PS_SPADE;
0373 
0374     Nwpiles = 8;
0375     Ntpiles = 4;
0376 
0377 #endif
0378 
0379     deal = dealer;
0380 }
0381 
0382 MoveHint FreecellSolver::translateMove(const MOVE &m)
0383 {
0384     if (m.is_fcs) {
0385         fcs_move_t move = m.fcs;
0386         int cards = fcs_move_get_num_cards_in_seq(move);
0387         PatPile *from = nullptr;
0388         PatPile *to = nullptr;
0389 
0390         switch (fcs_move_get_type(move)) {
0391         case FCS_MOVE_TYPE_STACK_TO_STACK:
0392             from = deal->store[fcs_move_get_src_stack(move)];
0393             to = deal->store[fcs_move_get_dest_stack(move)];
0394             break;
0395 
0396         case FCS_MOVE_TYPE_FREECELL_TO_STACK:
0397             from = deal->freecell[fcs_move_get_src_freecell(move)];
0398             to = deal->store[fcs_move_get_dest_stack(move)];
0399             cards = 1;
0400             break;
0401 
0402         case FCS_MOVE_TYPE_FREECELL_TO_FREECELL:
0403             from = deal->freecell[fcs_move_get_src_freecell(move)];
0404             to = deal->freecell[fcs_move_get_dest_freecell(move)];
0405             cards = 1;
0406             break;
0407 
0408         case FCS_MOVE_TYPE_STACK_TO_FREECELL:
0409             from = deal->store[fcs_move_get_src_stack(move)];
0410             to = deal->freecell[fcs_move_get_dest_freecell(move)];
0411             cards = 1;
0412             break;
0413 
0414         case FCS_MOVE_TYPE_STACK_TO_FOUNDATION:
0415             from = deal->store[fcs_move_get_src_stack(move)];
0416             cards = 1;
0417             to = nullptr;
0418             break;
0419 
0420         case FCS_MOVE_TYPE_FREECELL_TO_FOUNDATION:
0421             from = deal->freecell[fcs_move_get_src_freecell(move)];
0422             cards = 1;
0423             to = nullptr;
0424         }
0425         Q_ASSERT(from);
0426         Q_ASSERT(cards <= from->cards().count());
0427         Q_ASSERT(to || cards == 1);
0428         KCard *card = from->cards()[from->cards().count() - cards];
0429 
0430         if (!to) {
0431             PatPile *target = nullptr;
0432             PatPile *empty = nullptr;
0433             for (int i = 0; i < 4 * m_decks; ++i) {
0434                 KCard *c = deal->target[i]->topCard();
0435                 if (c) {
0436                     if (c->suit() == card->suit()) {
0437                         target = deal->target[i];
0438                         break;
0439                     }
0440                 } else if (!empty)
0441                     empty = deal->target[i];
0442             }
0443             to = target ? target : empty;
0444         }
0445         Q_ASSERT(to);
0446 
0447         return MoveHint(card, to, 0);
0448     } else {
0449         // this is tricky as we need to want to build the "meta moves"
0450 
0451         PatPile *frompile = nullptr;
0452         if (m.from < m_stacks)
0453             frompile = deal->store[m.from];
0454         else
0455             frompile = deal->freecell[m.from - m_stacks];
0456         KCard *card = frompile->at(frompile->count() - m.card_index - 1);
0457 
0458         if (m.totype == O_Type) {
0459             PatPile *target = nullptr;
0460             PatPile *empty = nullptr;
0461             for (int i = 0; i < 4 * m_decks; ++i) {
0462                 KCard *c = deal->target[i]->topCard();
0463                 if (c) {
0464                     if (c->suit() == card->suit()) {
0465                         target = deal->target[i];
0466                         break;
0467                     }
0468                 } else if (!empty)
0469                     empty = deal->target[i];
0470             }
0471             if (!target)
0472                 target = empty;
0473             return MoveHint(card, target, m.pri);
0474         } else {
0475             PatPile *target = nullptr;
0476             if (m.to < m_stacks)
0477                 target = deal->store[m.to];
0478             else
0479                 target = deal->freecell[m.to - m_stacks];
0480 
0481             return MoveHint(card, target, m.pri);
0482         }
0483     }
0484 }
0485 
0486 void FreecellSolver::translate_layout()
0487 {
0488     strcpy(board_as_string, deal->solverFormat().toLatin1().constData());
0489 
0490     make_solver_instance_ready();
0491 #if 0
0492     /* Read the workspace. */
0493     int total = 0;
0494 
0495     for ( int w = 0; w < 10; ++w ) {
0496         int i = translate_pile(deal->store[w], W[w], 52);
0497         Wp[w] = &W[w][i - 1];
0498         Wlen[w] = i;
0499         total += i;
0500     }
0501 
0502     for (int i = 0; i < 4; ++i) {
0503         O[i] = -1;
0504         KCard *c = deal->target[i]->top();
0505         if (c) {
0506             total += 13;
0507             O[i] = translateSuit( c->suit() );
0508         }
0509     }
0510 #endif
0511     /* Read the workspace. */
0512 
0513     int total = 0;
0514     for (int w = 0; w < m_stacks; ++w) {
0515         int i = translate_pile(deal->store[w], W[w], 52 * m_decks);
0516         Wp[w] = &W[w][i - 1];
0517         Wlen[w] = i;
0518         total += i;
0519         if (w == m_stacks) {
0520             break;
0521         }
0522     }
0523 
0524     /* Temp cells may have some cards too. */
0525 
0526     for (int w = 0; w < m_reserves; ++w) {
0527         int i = translate_pile(deal->freecell[w], W[w + m_stacks], 52 * m_decks);
0528         Wp[w + m_stacks] = &W[w + m_stacks][i - 1];
0529         Wlen[w + m_stacks] = i;
0530         total += i;
0531     }
0532 
0533     /* Output piles, if any. */
0534     for (int i = 0; i < 4 * m_decks; ++i) {
0535         O[i] = NONE;
0536     }
0537     if (total != 52 * m_decks) {
0538         for (int i = 0; i < 4 * m_decks; ++i) {
0539             KCard *c = deal->target[i]->topCard();
0540             if (c) {
0541                 O[translateSuit(c->suit()) >> 4] = c->rank();
0542                 total += c->rank();
0543             }
0544         }
0545     }
0546 }
0547 
0548 #if 0
0549 unsigned int FreecellSolver::getClusterNumber()
0550 {
0551     int i = O[0] + (O[1] << 4);
0552     unsigned int k = i;
0553     i = O[2] + (O[3] << 4);
0554     k |= i << 8;
0555     return k;
0556 }
0557 #endif
0558 
0559 #if 0
0560 void FreecellSolver::print_layout()
0561 {
0562        int i, t, w, o;
0563 
0564        fprintf(stderr, "print-layout-begin\n");
0565        for (w = 0; w < Nwpiles; ++w) {
0566      fprintf(stderr, "W-Pile%d: ", w);
0567                for (i = 0; i < Wlen[w]; ++i) {
0568                        printcard(W[w][i], stderr);
0569                }
0570                fputc('\n', stderr);
0571        }
0572        for (t = 0; t < Ntpiles; ++t) {
0573      fprintf(stderr, "T-Pile%d: ", t+Nwpiles);
0574            printcard(W[t+Nwpiles][Wlen[t+Nwpiles]], stderr);
0575        }
0576        fprintf( stderr, "\n" );
0577        for (o = 0; o < 4; ++o) {
0578                printcard(O[o] + Osuit[o], stderr);
0579        }
0580        fprintf(stderr, "\nprint-layout-end\n");
0581 }
0582 #endif