File indexing completed on 2024-05-12 08:00:30
0001 /* 0002 * Copyright 2021 Michael Lang <criticaltemp@protonmail.com> 0003 * 0004 * This program is free software; you can redistribute it and/or 0005 * modify it under the terms of the GNU General Public License as 0006 * published by the Free Software Foundation; either version 2 of 0007 * the License, or (at your option) any later version. 0008 * 0009 * This program is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 * GNU General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU General Public License 0015 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0016 */ 0017 0018 #include "bakersdozen.h" 0019 0020 // own 0021 #include "dealerinfo.h" 0022 #include "patsolve/bakersdozensolver.h" 0023 #include "pileutils.h" 0024 #include "settings.h" 0025 #include "speeds.h" 0026 // KF 0027 #include <KLocalizedString> 0028 #include <KSelectAction> 0029 0030 BakersDozen::BakersDozen(const DealerInfo *di) 0031 : DealerScene(di) 0032 { 0033 } 0034 0035 void BakersDozen::initialize() 0036 { 0037 setDeckContents(); 0038 0039 const qreal dist_x = 1.11; 0040 0041 for (int i = 0; i < 4; ++i) { 0042 target[i] = new PatPile(this, i + 1, QStringLiteral("target%1").arg(i)); 0043 target[i]->setPileRole(PatPile::Foundation); 0044 target[i]->setLayoutPos((i * 2 + 3) * dist_x, 0); 0045 target[i]->setSpread(0, 0); 0046 target[i]->setKeyboardSelectHint(KCardPile::NeverFocus); 0047 target[i]->setKeyboardDropHint(KCardPile::AutoFocusTop); 0048 } 0049 0050 for (int i = 0; i < 13; ++i) { 0051 store[i] = new PatPile(this, 0 + i, QStringLiteral("store%1").arg(i)); 0052 store[i]->setPileRole(PatPile::Tableau); 0053 store[i]->setLayoutPos(dist_x * i, 1.2); 0054 store[i]->setAutoTurnTop(true); 0055 store[i]->setBottomPadding(2.0); 0056 store[i]->setHeightPolicy(KCardPile::GrowDown); 0057 store[i]->setZValue(0.01 * i); 0058 store[i]->setKeyboardSelectHint(KCardPile::FreeFocus); 0059 store[i]->setKeyboardDropHint(KCardPile::AutoFocusTop); 0060 } 0061 0062 setActions(DealerScene::Hint | DealerScene::Demo); 0063 auto solver = new BakersDozenSolver(this); 0064 solver->default_max_positions = Settings::bakersDozenSolverIterationsLimit(); 0065 setSolver(solver); 0066 setNeededFutureMoves(4); // reserve some 0067 0068 options = new KSelectAction(i18n("Popular Variant Presets"), this); 0069 options->addAction(i18n("Baker's Dozen")); 0070 options->addAction(i18n("Spanish Patience")); 0071 options->addAction(i18n("Castles in Spain")); 0072 options->addAction(i18n("Portuguese Solitaire")); 0073 options->addAction(i18n("Custom")); 0074 0075 m_emptyStackFillOption = new KSelectAction(i18n("Empty Stack Fill"), this); 0076 m_emptyStackFillOption->addAction(i18n("Any (Easy)")); 0077 m_emptyStackFillOption->addAction(i18n("Kings only (Medium)")); 0078 m_emptyStackFillOption->addAction(i18n("None (Hard)")); 0079 0080 m_stackFacedownOption = new KSelectAction(i18n("Stack Options"), this); 0081 m_stackFacedownOption->addAction(i18n("Face &Up (Easier)")); 0082 m_stackFacedownOption->addAction(i18n("Face &Down (Harder)")); 0083 0084 m_sequenceBuiltByOption = new KSelectAction(i18n("Build Sequence"), this); 0085 m_sequenceBuiltByOption->addAction(i18n("Alternating Color")); 0086 m_sequenceBuiltByOption->addAction(i18n("Matching Suit")); 0087 m_sequenceBuiltByOption->addAction(i18n("Rank")); 0088 0089 connect(options, &KSelectAction::indexTriggered, this, &BakersDozen::gameTypeChanged); 0090 connect(m_emptyStackFillOption, &KSelectAction::indexTriggered, this, &BakersDozen::gameTypeChanged); 0091 connect(m_stackFacedownOption, &KSelectAction::indexTriggered, this, &BakersDozen::gameTypeChanged); 0092 connect(m_sequenceBuiltByOption, &KSelectAction::indexTriggered, this, &BakersDozen::gameTypeChanged); 0093 0094 getSavedOptions(); 0095 } 0096 0097 QList<QAction *> BakersDozen::configActions() const 0098 { 0099 return QList<QAction *>() << options << m_emptyStackFillOption << m_stackFacedownOption << m_sequenceBuiltByOption; 0100 } 0101 0102 void BakersDozen::gameTypeChanged() 0103 { 0104 stopDemo(); 0105 0106 if (allowedToStartNewGame()) { 0107 if (m_variation != options->currentItem()) { 0108 setOptions(options->currentItem()); 0109 } else { 0110 // update option selections 0111 if (m_emptyStackFill != m_emptyStackFillOption->currentItem()) 0112 m_emptyStackFill = m_emptyStackFillOption->currentItem(); 0113 else if (m_stackFacedown != m_stackFacedownOption->currentItem()) 0114 m_stackFacedown = m_stackFacedownOption->currentItem(); 0115 else if (m_sequenceBuiltBy != m_sequenceBuiltByOption->currentItem()) 0116 m_sequenceBuiltBy = m_sequenceBuiltByOption->currentItem(); 0117 0118 matchVariant(); 0119 } 0120 0121 auto solver = new BakersDozenSolver(this); 0122 solver->default_max_positions = Settings::bakersDozenSolverIterationsLimit(); 0123 setSolver(solver); 0124 startNew(gameNumber()); 0125 setSavedOptions(); 0126 } else { 0127 // If we're not allowed, reset the options 0128 getSavedOptions(); 0129 } 0130 } 0131 0132 void BakersDozen::restart(const QList<KCard *> &cards) 0133 { 0134 QList<KCard *> cardList = cards; 0135 0136 QPointF initPos(0, -deck()->cardHeight()); 0137 0138 for (int row = 0; row < 4; ++row) { 0139 bool isFaceUp = m_stackFacedown == 1 && row < 3 ? false : true; 0140 0141 for (int column = 0; column < 13; ++column) { 0142 addCardForDeal(store[column], cardList.takeLast(), isFaceUp, initPos); 0143 } 0144 } 0145 0146 // Move kings to bottom of pile without changing relative order 0147 for (int column = 0; column < 13; ++column) { 0148 int counter = 0; 0149 0150 const auto cards = store[column]->cards(); 0151 for (KCard *c : cards) { 0152 if (c->rank() == KCardDeck::King) { 0153 int index = store[column]->indexOf(c); 0154 store[column]->swapCards(index, counter); 0155 counter++; 0156 0157 if (m_stackFacedown == 1) 0158 c->setFaceUp(false); 0159 } 0160 } 0161 } 0162 0163 startDealAnimation(); 0164 } 0165 0166 QString BakersDozen::solverFormat() const 0167 { 0168 QString output; 0169 QString tmp; 0170 for (int i = 0; i < 4; i++) { 0171 if (target[i]->isEmpty()) 0172 continue; 0173 tmp += suitToString(target[i]->topCard()->suit()) + QLatin1Char('-') + rankToString(target[i]->topCard()->rank()) + QLatin1Char(' '); 0174 } 0175 if (!tmp.isEmpty()) 0176 output += QStringLiteral("Foundations: %1\n").arg(tmp); 0177 0178 for (int i = 0; i < 13; i++) 0179 cardsListToLine(output, store[i]->cards()); 0180 return output; 0181 } 0182 0183 bool BakersDozen::checkAdd(const PatPile *pile, const QList<KCard *> &oldCards, const QList<KCard *> &newCards) const 0184 { 0185 if (pile->pileRole() == PatPile::Tableau) { 0186 int freeStores = 0; 0187 if (m_emptyStackFill == 0) { 0188 for (int i = 0; i < 13; ++i) 0189 if (store[i]->isEmpty() && store[i] != pile) 0190 ++freeStores; 0191 } 0192 0193 if (newCards.size() <= 1 << freeStores) { 0194 if (oldCards.isEmpty()) 0195 return m_emptyStackFill == 0 || (m_emptyStackFill == 1 && newCards.first()->rank() == KCardDeck::King); 0196 else if (m_sequenceBuiltBy == 1) 0197 return newCards.first()->suit() == oldCards.last()->suit() && newCards.first()->rank() == oldCards.last()->rank() - 1; 0198 else if (m_sequenceBuiltBy == 0) 0199 return checkAddAlternateColorDescending(oldCards, newCards); 0200 else 0201 return newCards.first()->rank() == oldCards.last()->rank() - 1; 0202 } else { 0203 return false; 0204 } 0205 } else { 0206 return checkAddSameSuitAscendingFromAce(oldCards, newCards); 0207 } 0208 } 0209 0210 bool BakersDozen::checkRemove(const PatPile *pile, const QList<KCard *> &cards) const 0211 { 0212 switch (pile->pileRole()) { 0213 case PatPile::Tableau: 0214 if (m_emptyStackFill == 2) { 0215 return cards.size() == 1; 0216 } else { 0217 if (m_sequenceBuiltBy == 1) 0218 return isSameSuitDescending(cards); 0219 else if (m_sequenceBuiltBy == 0) 0220 return isAlternateColorDescending(cards); 0221 else 0222 return isRankDescending(cards); 0223 } 0224 case PatPile::Cell: 0225 return cards.first() == pile->topCard(); 0226 case PatPile::Foundation: 0227 return true; 0228 default: 0229 return false; 0230 } 0231 } 0232 0233 static class BakersDozenDealerInfo : public DealerInfo 0234 { 0235 public: 0236 BakersDozenDealerInfo() 0237 : DealerInfo(kli18n("Baker's Dozen"), BakersDozenGeneralId) 0238 { 0239 addSubtype(BakersDozenId, kli18n("Baker's Dozen")); 0240 addSubtype(BakersDozenSpanishId, kli18n("Spanish Patience")); 0241 addSubtype(BakersDozenCastlesId, kli18n("Castles in Spain")); 0242 addSubtype(BakersDozenPortugueseId, kli18n("Portuguese Solitaire")); 0243 addSubtype(BakersDozenCustomId, kli18n("Baker's Dozen (Custom)")); 0244 } 0245 0246 DealerScene *createGame() const override 0247 { 0248 return new BakersDozen(this); 0249 } 0250 } BakersDozenDealerInfo; 0251 0252 void BakersDozen::matchVariant() 0253 { 0254 if (m_emptyStackFill == 2 && m_stackFacedown == 0 && m_sequenceBuiltBy == 2) 0255 m_variation = 0; 0256 else if (m_emptyStackFill == 0 && m_stackFacedown == 0 && m_sequenceBuiltBy == 2) 0257 m_variation = 1; 0258 else if (m_emptyStackFill == 0 && m_stackFacedown == 1 && m_sequenceBuiltBy == 0) 0259 m_variation = 2; 0260 else if (m_emptyStackFill == 1 && m_stackFacedown == 0 && m_sequenceBuiltBy == 2) 0261 m_variation = 3; 0262 else 0263 m_variation = 4; 0264 0265 options->setCurrentItem(m_variation); 0266 } 0267 0268 void BakersDozen::setSavedOptions() 0269 { 0270 Settings::setBakersDozenEmptyStackFill(m_emptyStackFill); 0271 Settings::setBakersDozenStackFacedown(m_stackFacedown); 0272 Settings::setBakersDozenSequenceBuiltBy(m_sequenceBuiltBy); 0273 } 0274 0275 void BakersDozen::getSavedOptions() 0276 { 0277 m_emptyStackFill = Settings::bakersDozenEmptyStackFill(); 0278 m_stackFacedown = Settings::bakersDozenStackFacedown(); 0279 m_sequenceBuiltBy = Settings::bakersDozenSequenceBuiltBy(); 0280 0281 matchVariant(); 0282 0283 m_emptyStackFillOption->setCurrentItem(m_emptyStackFill); 0284 m_stackFacedownOption->setCurrentItem(m_stackFacedown); 0285 m_sequenceBuiltByOption->setCurrentItem(m_sequenceBuiltBy); 0286 } 0287 0288 void BakersDozen::mapOldId(int id) 0289 { 0290 switch (id) { 0291 case DealerInfo::BakersDozenId: 0292 setOptions(0); 0293 break; 0294 case DealerInfo::BakersDozenSpanishId: 0295 setOptions(1); 0296 break; 0297 case DealerInfo::BakersDozenCastlesId: 0298 setOptions(2); 0299 break; 0300 case DealerInfo::BakersDozenPortugueseId: 0301 setOptions(3); 0302 break; 0303 case DealerInfo::BakersDozenCustomId: 0304 setOptions(4); 0305 break; 0306 default: 0307 // Do nothing. 0308 break; 0309 } 0310 } 0311 0312 int BakersDozen::oldId() const 0313 { 0314 switch (m_variation) { 0315 case 0: 0316 return DealerInfo::BakersDozenId; 0317 case 1: 0318 return DealerInfo::BakersDozenSpanishId; 0319 case 2: 0320 return DealerInfo::BakersDozenCastlesId; 0321 case 3: 0322 return DealerInfo::BakersDozenPortugueseId; 0323 default: 0324 return DealerInfo::BakersDozenCustomId; 0325 } 0326 } 0327 0328 void BakersDozen::setOptions(int variation) 0329 { 0330 if (variation != m_variation) { 0331 m_variation = variation; 0332 0333 switch (m_variation) { 0334 case 0: 0335 m_emptyStackFill = 2; 0336 m_stackFacedown = 0; 0337 m_sequenceBuiltBy = 2; 0338 break; 0339 case 1: 0340 m_emptyStackFill = 0; 0341 m_stackFacedown = 0; 0342 m_sequenceBuiltBy = 2; 0343 break; 0344 case 2: 0345 m_emptyStackFill = 0; 0346 m_stackFacedown = 1; 0347 m_sequenceBuiltBy = 0; 0348 break; 0349 case 3: 0350 m_emptyStackFill = 1; 0351 m_stackFacedown = 0; 0352 m_sequenceBuiltBy = 2; 0353 break; 0354 case 4: 0355 m_emptyStackFill = 0; 0356 m_stackFacedown = 1; 0357 m_sequenceBuiltBy = 1; 0358 break; 0359 } 0360 0361 m_emptyStackFillOption->setCurrentItem(m_emptyStackFill); 0362 m_stackFacedownOption->setCurrentItem(m_stackFacedown); 0363 m_sequenceBuiltByOption->setCurrentItem(m_sequenceBuiltBy); 0364 } 0365 } 0366 0367 #include "moc_bakersdozen.cpp"