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