File indexing completed on 2024-05-12 04:04:48
0001 /* 0002 * Copyright (C) 1997 Rodolfo Borges <barrett@labma.ufrj.br> 0003 * Copyright (C) 1998-2009 Stephan Kulow <coolo@kde.org> 0004 * Copyright (C) 2010 Parker Coates <coates@kde.org> 0005 * 0006 * License of original code: 0007 * ------------------------------------------------------------------------- 0008 * Permission to use, copy, modify, and distribute this software and its 0009 * documentation for any purpose and without fee is hereby granted, 0010 * provided that the above copyright notice appear in all copies and that 0011 * both that copyright notice and this permission notice appear in 0012 * supporting documentation. 0013 * 0014 * This file is provided AS IS with no warranties of any kind. The author 0015 * shall have no liability with respect to the infringement of copyrights, 0016 * trade secrets or any patents by this file or any part thereof. In no 0017 * event will the author be liable for any lost revenue or profits or 0018 * other special, indirect and consequential damages. 0019 * ------------------------------------------------------------------------- 0020 * 0021 * License of modifications/additions made after 2009-01-01: 0022 * ------------------------------------------------------------------------- 0023 * This program is free software; you can redistribute it and/or 0024 * modify it under the terms of the GNU General Public License as 0025 * published by the Free Software Foundation; either version 2 of 0026 * the License, or (at your option) any later version. 0027 * 0028 * This program is distributed in the hope that it will be useful, 0029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0031 * GNU General Public License for more details. 0032 * 0033 * You should have received a copy of the GNU General Public License 0034 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0035 * ------------------------------------------------------------------------- 0036 */ 0037 0038 #include "freecell.h" 0039 0040 // own 0041 #include "dealerinfo.h" 0042 #include "kpat_debug.h" 0043 #include "patsolve/freecellsolver.h" 0044 #include "pileutils.h" 0045 #include "settings.h" 0046 #include "speeds.h" 0047 // KF 0048 #include <KLocalizedString> 0049 #include <KSelectAction> 0050 0051 Freecell::Freecell(const DealerInfo *di) 0052 : DealerScene(di) 0053 { 0054 configOptions(); 0055 getSavedOptions(); 0056 } 0057 0058 void Freecell::initialize() 0059 { 0060 setDeckContents(m_decks + 1); 0061 0062 const bool isRightFoundation = m_reserves + 4 * (m_decks + 1) > (m_stacks + 6); 0063 const qreal topRowDist = isRightFoundation ? 1.13 : 1.08; 0064 const qreal bottomRowDist = 1.13; 0065 const qreal targetOffsetDist = ((m_stacks + 5) * bottomRowDist + 1) - (3 * topRowDist + 1) * (m_decks + 1); 0066 0067 for (int i = 0; i < m_reserves; ++i) { 0068 freecell[i] = new PatPile(this, 1 + i, QStringLiteral("freecell%1").arg(i)); 0069 freecell[i]->setPileRole(PatPile::Cell); 0070 freecell[i]->setLayoutPos(topRowDist * i, 0); 0071 freecell[i]->setKeyboardSelectHint(KCardPile::AutoFocusTop); 0072 freecell[i]->setKeyboardDropHint(KCardPile::AutoFocusTop); 0073 } 0074 0075 for (int i = 0; i < (m_stacks + 6); ++i) { 0076 store[i] = new PatPile(this, 1 + i, QStringLiteral("store%1").arg(i)); 0077 store[i]->setPileRole(PatPile::Tableau); 0078 store[i]->setLayoutPos(bottomRowDist * i, 1.3); 0079 store[i]->setBottomPadding(2.5); 0080 store[i]->setHeightPolicy(KCardPile::GrowDown); 0081 store[i]->setKeyboardSelectHint(KCardPile::AutoFocusDeepestRemovable); 0082 store[i]->setKeyboardDropHint(KCardPile::AutoFocusTop); 0083 } 0084 0085 if (isRightFoundation) { 0086 const int columns = std::max(m_reserves, m_stacks + 6); 0087 for (int i = 0; i < 4 * (m_decks + 1); ++i) { 0088 const qreal offsetX = 0.25 + columns * bottomRowDist + i / 4 * bottomRowDist; 0089 const qreal offsetY = bottomRowDist * i - i / 4 * bottomRowDist * 4; 0090 target[i] = new PatPile(this, 1 + i, QStringLiteral("target%1").arg(i)); 0091 target[i]->setPileRole(PatPile::Foundation); 0092 target[i]->setLayoutPos(offsetX, offsetY); 0093 target[i]->setKeyboardSelectHint(KCardPile::NeverFocus); 0094 target[i]->setKeyboardDropHint(KCardPile::ForceFocusTop); 0095 } 0096 } else { 0097 for (int i = 0; i < 4 * (m_decks + 1); ++i) { 0098 target[i] = new PatPile(this, 1 + i, QStringLiteral("target%1").arg(i)); 0099 target[i]->setPileRole(PatPile::Foundation); 0100 target[i]->setLayoutPos(targetOffsetDist + topRowDist * i, 0); 0101 target[i]->setSpread(0, 0); 0102 target[i]->setKeyboardSelectHint(KCardPile::NeverFocus); 0103 target[i]->setKeyboardDropHint(KCardPile::ForceFocusTop); 0104 } 0105 } 0106 0107 setActions(DealerScene::Demo | DealerScene::Hint); 0108 auto solver = new FreecellSolver(this); 0109 solver->default_max_positions = Settings::freecellSolverIterationsLimit(); 0110 setSolver(solver); 0111 setNeededFutureMoves(4); // reserve some 0112 } 0113 0114 QList<QAction *> Freecell::configActions() const 0115 { 0116 return QList<QAction *>() << options << m_emptyStackFillOption << m_sequenceBuiltByOption << m_reservesOption << m_stacksOption; 0117 } 0118 0119 void Freecell::gameTypeChanged() 0120 { 0121 stopDemo(); 0122 0123 if (allowedToStartNewGame()) { 0124 if (m_variation != options->currentItem()) { 0125 setOptions(options->currentItem()); 0126 } else { 0127 // update option selections 0128 if (m_emptyStackFill != m_emptyStackFillOption->currentItem()) 0129 m_emptyStackFill = m_emptyStackFillOption->currentItem(); 0130 else if (m_sequenceBuiltBy != m_sequenceBuiltByOption->currentItem()) 0131 m_sequenceBuiltBy = m_sequenceBuiltByOption->currentItem(); 0132 else if (m_reserves != m_reservesOption->currentItem()) 0133 m_reserves = m_reservesOption->currentItem(); 0134 else if (m_stacks != m_stacksOption->currentItem()) 0135 m_stacks = m_stacksOption->currentItem(); 0136 else if (m_decks != m_decksOption->currentItem()) 0137 m_decks = m_decksOption->currentItem(); 0138 0139 matchVariant(); 0140 } 0141 0142 // remove existing piles 0143 clearPatPiles(); 0144 0145 initialize(); 0146 relayoutScene(); 0147 startNew(gameNumber()); 0148 setSavedOptions(); 0149 } else { 0150 // If we're not allowed, reset the options 0151 getSavedOptions(); 0152 } 0153 } 0154 0155 void Freecell::restart(const QList<KCard *> &cards) 0156 { 0157 QList<KCard *> cardList = cards; 0158 0159 // Prefill reserves for select game types 0160 if (m_variation == 4) 0161 for (int i = 0; i < 2; ++i) 0162 addCardForDeal(freecell[i], cardList.takeLast(), true, freecell[0]->pos()); 0163 else if (m_variation == 1 || m_variation == 2) 0164 for (int i = 0; i < 4; ++i) 0165 addCardForDeal(freecell[i], cardList.takeLast(), true, freecell[0]->pos()); 0166 0167 int column = 0; 0168 while (!cardList.isEmpty()) { 0169 addCardForDeal(store[column], cardList.takeLast(), true, store[0]->pos()); 0170 column = (column + 1) % (m_stacks + 6); 0171 } 0172 0173 startDealAnimation(); 0174 } 0175 0176 QString Freecell::solverFormat() const 0177 { 0178 QString output; 0179 QString tmp; 0180 for (int i = 0; i < 4 * (m_decks + 1); i++) { 0181 if (target[i]->isEmpty()) 0182 continue; 0183 tmp += suitToString(target[i]->topCard()->suit()) + QLatin1Char('-') + rankToString(target[i]->topCard()->rank()) + QLatin1Char(' '); 0184 } 0185 if (!tmp.isEmpty()) 0186 output += QStringLiteral("Foundations: %1\n").arg(tmp); 0187 0188 tmp.truncate(0); 0189 for (int i = 0; i < m_reserves; i++) { 0190 const auto fc = freecell[i]; 0191 tmp += (fc->isEmpty() ? QStringLiteral("-") : cardToRankSuitString(fc->topCard())) + QLatin1Char(' '); 0192 } 0193 if (!tmp.isEmpty()) { 0194 QString a = QStringLiteral("Freecells: %1\n"); 0195 output += a.arg(tmp); 0196 } 0197 0198 for (int i = 0; i < (m_stacks + 6); i++) 0199 cardsListToLine(output, store[i]->cards()); 0200 return output; 0201 } 0202 0203 void Freecell::cardsDroppedOnPile(const QList<KCard *> &cards, KCardPile *pile) 0204 { 0205 if (cards.size() <= 1) { 0206 DealerScene::moveCardsToPile(cards, pile, DURATION_MOVE); 0207 return; 0208 } 0209 0210 QList<KCardPile *> freeCells; 0211 for (int i = 0; i < m_reserves; ++i) 0212 if (freecell[i]->isEmpty()) 0213 freeCells << freecell[i]; 0214 0215 QList<KCardPile *> freeStores; 0216 for (int i = 0; i < (m_stacks + 6); ++i) 0217 if (store[i]->isEmpty() && store[i] != pile) 0218 freeStores << store[i]; 0219 0220 multiStepMove(cards, pile, freeStores, freeCells, DURATION_MOVE); 0221 } 0222 0223 bool Freecell::tryAutomaticMove(KCard *c) 0224 { 0225 // target move 0226 if (DealerScene::tryAutomaticMove(c)) 0227 return true; 0228 0229 if (c->isAnimated()) 0230 return false; 0231 0232 if (allowedToRemove(c->pile(), c) && c == c->pile()->topCard()) { 0233 for (int i = 0; i < m_reserves; i++) { 0234 if (allowedToAdd(freecell[i], {c})) { 0235 moveCardToPile(c, freecell[i], DURATION_MOVE); 0236 return true; 0237 } 0238 } 0239 } 0240 return false; 0241 } 0242 0243 bool Freecell::canPutStore(const KCardPile *pile, const QList<KCard *> &cards) const 0244 { 0245 int freeCells = 0; 0246 for (int i = 0; i < m_reserves; ++i) 0247 if (freecell[i]->isEmpty()) 0248 ++freeCells; 0249 0250 int freeStores = 0; 0251 if (m_emptyStackFill == 0) { 0252 for (int i = 0; i < (m_stacks + 6); ++i) 0253 if (store[i]->isEmpty() && store[i] != pile) 0254 ++freeStores; 0255 } 0256 0257 if (cards.size() <= (freeCells + 1) << freeStores) { 0258 if (pile->isEmpty()) 0259 return m_emptyStackFill == 0 || (m_emptyStackFill == 1 && cards.first()->rank() == KCardDeck::King); 0260 else if (m_sequenceBuiltBy == 1) 0261 return cards.first()->rank() == pile->topCard()->rank() - 1 && cards.first()->suit() == pile->topCard()->suit(); 0262 else if (m_sequenceBuiltBy == 0) 0263 return cards.first()->rank() == pile->topCard()->rank() - 1 && pile->topCard()->color() != cards.first()->color(); 0264 else 0265 return cards.first()->rank() == pile->topCard()->rank() - 1; 0266 } else { 0267 return false; 0268 } 0269 } 0270 0271 bool Freecell::checkAdd(const PatPile *pile, const QList<KCard *> &oldCards, const QList<KCard *> &newCards) const 0272 { 0273 switch (pile->pileRole()) { 0274 case PatPile::Tableau: 0275 return canPutStore(pile, newCards); 0276 case PatPile::Cell: 0277 return oldCards.isEmpty() && newCards.size() == 1; 0278 case PatPile::Foundation: 0279 return checkAddSameSuitAscendingFromAce(oldCards, newCards); 0280 default: 0281 return false; 0282 } 0283 } 0284 0285 bool Freecell::checkRemove(const PatPile *pile, const QList<KCard *> &cards) const 0286 { 0287 switch (pile->pileRole()) { 0288 case PatPile::Tableau: 0289 if (m_sequenceBuiltBy == 1) 0290 return isSameSuitDescending(cards); 0291 else if (m_sequenceBuiltBy == 0) 0292 return isAlternateColorDescending(cards); 0293 else 0294 return isRankDescending(cards); 0295 case PatPile::Cell: 0296 return cards.first() == pile->topCard(); 0297 case PatPile::Foundation: 0298 default: 0299 return false; 0300 } 0301 } 0302 0303 static class FreecellDealerInfo : public DealerInfo 0304 { 0305 public: 0306 FreecellDealerInfo() 0307 : DealerInfo(kli18n("Freecell"), FreecellGeneralId) 0308 { 0309 addSubtype(FreecellBakersId, kli18n("Baker's Game")); 0310 addSubtype(FreecellEightOffId, kli18n("Eight Off")); 0311 addSubtype(FreecellForeId, kli18n("Forecell")); 0312 addSubtype(FreecellId, kli18n("Freecell")); 0313 addSubtype(FreecellSeahavenId, kli18n("Seahaven Towers")); 0314 addSubtype(FreecellCustomId, kli18n("Freecell (Custom)")); 0315 } 0316 0317 DealerScene *createGame() const override 0318 { 0319 return new Freecell(this); 0320 } 0321 } freecellDealerInfo; 0322 0323 void Freecell::matchVariant() 0324 { 0325 if (m_emptyStackFill == 0 && m_sequenceBuiltBy == 1 && m_reserves == 4 && m_stacks == 2) 0326 m_variation = 0; 0327 else if (m_emptyStackFill == 1 && m_sequenceBuiltBy == 1 && m_reserves == 8 && m_stacks == 2) 0328 m_variation = 1; 0329 else if (m_emptyStackFill == 1 && m_sequenceBuiltBy == 0 && m_reserves == 4 && m_stacks == 2) 0330 m_variation = 2; 0331 else if (m_emptyStackFill == 0 && m_sequenceBuiltBy == 0 && m_reserves == 4 && m_stacks == 2) 0332 m_variation = 3; 0333 else if (m_emptyStackFill == 1 && m_sequenceBuiltBy == 1 && m_reserves == 4 && m_stacks == 4) 0334 m_variation = 4; 0335 else 0336 m_variation = 5; 0337 0338 options->setCurrentItem(m_variation); 0339 } 0340 0341 void Freecell::configOptions() 0342 { 0343 options = new KSelectAction(i18n("Popular Variant Presets"), this); 0344 options->addAction(i18n("Baker's Game")); 0345 options->addAction(i18n("Eight Off")); 0346 options->addAction(i18n("Forecell")); 0347 options->addAction(i18n("Freecell")); 0348 options->addAction(i18n("Seahaven Towers")); 0349 options->addAction(i18n("Custom")); 0350 0351 m_emptyStackFillOption = new KSelectAction(i18n("Empty Stack Fill"), this); 0352 m_emptyStackFillOption->addAction(i18n("Any (Easy)")); 0353 m_emptyStackFillOption->addAction(i18n("Kings only (Medium)")); 0354 m_emptyStackFillOption->addAction(i18n("None (Hard)")); 0355 0356 m_sequenceBuiltByOption = new KSelectAction(i18n("Build Sequence"), this); 0357 m_sequenceBuiltByOption->addAction(i18n("Alternating Color")); 0358 m_sequenceBuiltByOption->addAction(i18n("Matching Suit")); 0359 m_sequenceBuiltByOption->addAction(i18n("Rank")); 0360 0361 m_reservesOption = new KSelectAction(i18n("Free cells"), this); 0362 m_reservesOption->addAction(i18n("0")); 0363 m_reservesOption->addAction(i18n("1")); 0364 m_reservesOption->addAction(i18n("2")); 0365 m_reservesOption->addAction(i18n("3")); 0366 m_reservesOption->addAction(i18n("4")); 0367 m_reservesOption->addAction(i18n("5")); 0368 m_reservesOption->addAction(i18n("6")); 0369 m_reservesOption->addAction(i18n("7")); 0370 m_reservesOption->addAction(i18n("8")); 0371 0372 m_stacksOption = new KSelectAction(i18n("Stacks"), this); 0373 m_stacksOption->addAction(i18n("6")); 0374 m_stacksOption->addAction(i18n("7")); 0375 m_stacksOption->addAction(i18n("8")); 0376 m_stacksOption->addAction(i18n("9")); 0377 m_stacksOption->addAction(i18n("10")); 0378 m_stacksOption->addAction(i18n("11")); 0379 m_stacksOption->addAction(i18n("12")); 0380 0381 m_decksOption = new KSelectAction(i18n("Decks"), this); 0382 m_decksOption->addAction(i18n("1")); 0383 m_decksOption->addAction(i18n("2")); 0384 m_decksOption->addAction(i18n("3")); 0385 0386 connect(options, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged); 0387 connect(m_emptyStackFillOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged); 0388 connect(m_reservesOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged); 0389 connect(m_sequenceBuiltByOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged); 0390 connect(m_stacksOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged); 0391 connect(m_decksOption, &KSelectAction::indexTriggered, this, &Freecell::gameTypeChanged); 0392 } 0393 0394 void Freecell::setSavedOptions() 0395 { 0396 Settings::setFreecellEmptyStackFill(m_emptyStackFill); 0397 Settings::setFreecellSequenceBuiltBy(m_sequenceBuiltBy); 0398 Settings::setFreecellReserves(m_reserves); 0399 Settings::setFreecellStacks(m_stacks); 0400 Settings::setFreecellDecks(m_decks); 0401 } 0402 0403 void Freecell::getSavedOptions() 0404 { 0405 m_emptyStackFill = Settings::freecellEmptyStackFill(); 0406 m_sequenceBuiltBy = Settings::freecellSequenceBuiltBy(); 0407 m_reserves = Settings::freecellReserves(); 0408 m_stacks = Settings::freecellStacks(); 0409 m_decks = Settings::freecellDecks(); 0410 0411 matchVariant(); 0412 0413 m_emptyStackFillOption->setCurrentItem(m_emptyStackFill); 0414 m_sequenceBuiltByOption->setCurrentItem(m_sequenceBuiltBy); 0415 m_reservesOption->setCurrentItem(m_reserves); 0416 m_stacksOption->setCurrentItem(m_stacks); 0417 m_decksOption->setCurrentItem(m_decks); 0418 } 0419 0420 void Freecell::mapOldId(int id) 0421 { 0422 switch (id) { 0423 case DealerInfo::FreecellBakersId: 0424 setOptions(0); 0425 break; 0426 case DealerInfo::FreecellEightOffId: 0427 setOptions(1); 0428 break; 0429 case DealerInfo::FreecellForeId: 0430 setOptions(2); 0431 break; 0432 case DealerInfo::FreecellId: 0433 setOptions(3); 0434 break; 0435 case DealerInfo::FreecellSeahavenId: 0436 setOptions(4); 0437 break; 0438 case DealerInfo::FreecellCustomId: 0439 setOptions(5); 0440 break; 0441 default: 0442 // Do nothing. 0443 break; 0444 } 0445 } 0446 0447 int Freecell::oldId() const 0448 { 0449 switch (m_variation) { 0450 case 0: 0451 return DealerInfo::FreecellBakersId; 0452 case 1: 0453 return DealerInfo::FreecellEightOffId; 0454 case 2: 0455 return DealerInfo::FreecellForeId; 0456 case 3: 0457 return DealerInfo::FreecellId; 0458 case 4: 0459 return DealerInfo::FreecellSeahavenId; 0460 default: 0461 return DealerInfo::FreecellCustomId; 0462 } 0463 } 0464 0465 void Freecell::setOptions(int variation) 0466 { 0467 if (variation != m_variation) { 0468 m_variation = variation; 0469 m_emptyStackFill = 0; 0470 m_sequenceBuiltBy = 0; 0471 m_reserves = 4; 0472 m_stacks = 2; 0473 m_decks = 0; 0474 0475 switch (m_variation) { 0476 case 0: 0477 m_sequenceBuiltBy = 1; 0478 break; 0479 case 1: 0480 m_emptyStackFill = 1; 0481 m_sequenceBuiltBy = 1; 0482 m_reserves = 8; 0483 break; 0484 case 2: 0485 m_emptyStackFill = 1; 0486 break; 0487 case 3: 0488 break; 0489 case 4: 0490 m_emptyStackFill = 1; 0491 m_sequenceBuiltBy = 1; 0492 m_stacks = 4; 0493 break; 0494 case 5: 0495 m_emptyStackFill = 2; 0496 m_sequenceBuiltBy = 2; 0497 break; 0498 } 0499 0500 m_emptyStackFillOption->setCurrentItem(m_emptyStackFill); 0501 m_sequenceBuiltByOption->setCurrentItem(m_sequenceBuiltBy); 0502 m_reservesOption->setCurrentItem(m_reserves); 0503 m_stacksOption->setCurrentItem(m_stacks); 0504 m_decksOption->setCurrentItem(m_decks); 0505 } 0506 } 0507 0508 #include "moc_freecell.cpp"