1 /*
2 * Copyright 2021 Michael Lang <criticaltemp@protonmail.com>
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of
7 * the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "castle.h"
19
20 // own
21 #include "dealerinfo.h"
22 #include "kpat_debug.h"
23 #include "pileutils.h"
24 #include "settings.h"
25 #include "speeds.h"
26 #include "patsolve/castlesolver.h"
27 // KF
28 #include <KLocalizedString>
29 #include <kwidgetsaddons_version.h>
30 #include <KSelectAction>
31
32
Castle(const DealerInfo * di)33 Castle::Castle( const DealerInfo * di )
34 : DealerScene( di )
35 {
36 configOptions();
37 getSavedOptions();
38 }
39
40
initialize()41 void Castle::initialize()
42 {
43 setDeckContents( m_decks );
44
45 const qreal topRowDist = 1.125;
46 const qreal bottomRowDist = 1.125;
47 const qreal offsetY = m_reserves > 0 ? 1.3 : 0;
48
49 for ( int i = 0; i < m_reserves; ++i )
50 {
51 freecell[i] = new PatPile ( this, 1 + i, QStringLiteral( "freecell%1" ).arg( i ) );
52 freecell[i]->setPileRole(PatPile::Cell);
53 freecell[i]->setLayoutPos(3.25 - topRowDist * (m_reserves - 1) / 2 + topRowDist * i, 0);
54 freecell[i]->setKeyboardSelectHint( KCardPile::AutoFocusTop );
55 freecell[i]->setKeyboardDropHint( KCardPile::AutoFocusTop );
56 }
57
58 for ( int i = 0; i < m_stacks; ++i )
59 {
60 store[i] = new PatPile( this, 1 + i, QStringLiteral( "store%1" ).arg( i ) );
61 store[i]->setPileRole(PatPile::Tableau);
62 store[i]->setAutoTurnTop(true);
63 if (m_layout == 1) {
64 store[i]->setLayoutPos( 4.5 * (i % 2) + 0, offsetY + bottomRowDist * static_cast<int>((i + 0) / 2 ));
65 } else {
66 store[i]->setLayoutPos( 2.5 * (i % 2) + 2, offsetY + bottomRowDist * static_cast<int>((i + 0) / 2 ));
67 }
68
69
70 if (i % 2 || m_layout == 1) {
71 store[i]->setSpread(0.21, 0);
72 store[i]->setRightPadding( 2 );
73 store[i]->setWidthPolicy( KCardPile::GrowRight );
74 } else {
75 store[i]->setSpread(-0.21, 0);
76 store[i]->setLeftPadding( 3 );
77 store[i]->setWidthPolicy( KCardPile::GrowLeft );
78 }
79
80 store[i]->setKeyboardSelectHint( KCardPile::AutoFocusDeepestRemovable );
81 store[i]->setKeyboardDropHint( KCardPile::AutoFocusTop );
82 }
83
84 for ( int i = 0; i < 4 * m_decks; ++i )
85 {
86 target[i] = new PatPile( this, 1 + i, QStringLiteral("target%1").arg(i) );
87 target[i]->setPileRole(PatPile::Foundation);
88 target[i]->setLayoutPos(3.25, offsetY + bottomRowDist * i);
89 target[i]->setKeyboardSelectHint( KCardPile::NeverFocus );
90 target[i]->setKeyboardDropHint( KCardPile::ForceFocusTop );
91 }
92
93 setActions(DealerScene::Demo | DealerScene::Hint);
94 auto solver = new CastleSolver( this );
95 solver->default_max_positions = Settings::castleSolverIterationsLimit();
96 setSolver( solver );
97 setNeededFutureMoves( 4 ); // reserve some
98 }
99
100
configActions() const101 QList<QAction*> Castle::configActions() const
102 {
103 return QList<QAction*>() << options << m_emptyStackFillOption << m_sequenceBuiltByOption << m_reservesOption << m_stacksOption << m_stackFaceupOption << m_foundationOption << m_layoutOption;
104 }
105
106
gameTypeChanged()107 void Castle::gameTypeChanged()
108 {
109 stopDemo();
110
111 if ( allowedToStartNewGame() )
112 {
113 if ( m_variation != options->currentItem() )
114 {
115 setOptions(options->currentItem());
116 }
117 else
118 {
119 // update option selections
120 if ( m_emptyStackFill != m_emptyStackFillOption->currentItem() )
121 m_emptyStackFill = m_emptyStackFillOption->currentItem();
122 else if ( m_sequenceBuiltBy != m_sequenceBuiltByOption->currentItem() )
123 m_sequenceBuiltBy = m_sequenceBuiltByOption->currentItem();
124 else if ( m_reserves != m_reservesOption->currentItem() )
125 m_reserves = m_reservesOption->currentItem();
126 else if ( m_stacks - 6 != m_stacksOption->currentItem() )
127 m_stacks = m_stacksOption->currentItem() + 6;
128 else if ( m_stackFaceup != m_stackFaceupOption->currentItem() )
129 m_stackFaceup = m_stackFaceupOption->currentItem();
130 else if ( m_foundation != m_foundationOption->currentItem() )
131 m_foundation = m_foundationOption->currentItem();
132 else if ( m_layout != m_layoutOption->currentItem() )
133 m_layout = m_layoutOption->currentItem();
134
135 matchVariant();
136 }
137
138 // remove existing piles
139 clearPatPiles();
140
141 initialize();
142 relayoutScene();
143 startNew( gameNumber() );
144 setSavedOptions();
145 }
146 else
147 {
148 // If we're not allowed, reset the options
149 getSavedOptions();
150 }
151 }
152
153
restart(const QList<KCard * > & cards)154 void Castle::restart( const QList<KCard*> & cards )
155 {
156 QList<KCard*> cardList = cards;
157
158 int column = 0;
159 int fdx = 0;
160
161 // Prefill aces to foundation for select game types
162 if ( m_foundation >= 1 )
163 {
164 for ( int i = 0; i < cardList.size(); ++i )
165 {
166 if (cardList.at(i)->rank() == KCardDeck::Ace)
167 {
168 addCardForDeal( target[fdx], cardList.takeAt(i), true, target[fdx]->pos() );
169 --i;
170 fdx++;
171 }
172 }
173 }
174
175 bool alternate = false;
176 while ( !cardList.isEmpty() )
177 {
178 // Add matching cards to foundation during deal for select game types
179 if ( m_foundation == 2 && fdx > 0)
180 {
181 KCard * card = cardList.last();
182 int i = 0;
183 for ( i = 0; i < fdx; ++i )
184 {
185 if ( card->rank() == target[i]->topCard()->rank() + 1 && card->suit() == target[i]->topCard()->suit() )
186 {
187 addCardForDeal( target[i], cardList.takeLast(), true, target[i]->pos() );
188 column = (column + 1) % m_stacks;
189 break;
190 }
191 }
192
193 // Skip if card already added
194 if ( i < fdx ) continue;
195 }
196
197 bool faceUp = m_stackFaceup == 1 || cardList.length() <= m_stacks || ( m_stackFaceup == 2 && alternate );
198 addCardForDeal( store[column], cardList.takeLast(), faceUp, store[0]->pos() );
199 column = (column + 1) % m_stacks;
200 if ( column % m_stacks == 0)
201 alternate = !alternate;
202 }
203
204 startDealAnimation();
205 }
206
207
solverFormat() const208 QString Castle::solverFormat() const
209 {
210 QString output;
211 QString tmp;
212 for (int i = 0; i < 4 * m_decks ; i++) {
213 if (target[i]->isEmpty())
214 continue;
215 tmp += suitToString(target[i]->topCard()->suit()) + QLatin1Char('-') + rankToString(target[i]->topCard()->rank()) + QLatin1Char(' ');
216 }
217 if (!tmp.isEmpty())
218 output += QStringLiteral("Foundations: %1\n").arg(tmp);
219
220 tmp.truncate(0);
221 for (int i = 0; i < m_reserves ; i++) {
222 const auto fc = freecell[i];
223 tmp += (fc->isEmpty() ? QStringLiteral("-") : cardToRankSuitString(fc->topCard())) + QLatin1Char(' ');
224 }
225 if (!tmp.isEmpty())
226 {
227 QString a = QStringLiteral("Freecells: %1\n");
228 output += a.arg(tmp);
229 }
230
231 for (int i = 0; i < m_stacks ; i++)
232 cardsListToLine(output, store[i]->cards());
233 return output;
234 }
235
236
cardsDroppedOnPile(const QList<KCard * > & cards,KCardPile * pile)237 void Castle::cardsDroppedOnPile( const QList<KCard*> & cards, KCardPile * pile )
238 {
239 if ( cards.size() <= 1 )
240 {
241 DealerScene::moveCardsToPile( cards, pile, DURATION_MOVE );
242 return;
243 }
244
245 QList<KCardPile*> freeCells;
246 for ( int i = 0; i < m_reserves; ++i )
247 if ( freecell[i]->isEmpty() )
248 freeCells << freecell[i];
249
250 QList<KCardPile*> freeStores;
251 for ( int i = 0; i < m_stacks; ++i )
252 if ( store[i]->isEmpty() && store[i] != pile )
253 freeStores << store[i];
254
255 multiStepMove( cards, pile, freeStores, freeCells, DURATION_MOVE );
256 }
257
258
tryAutomaticMove(KCard * c)259 bool Castle::tryAutomaticMove(KCard *c)
260 {
261 // target move
262 if (DealerScene::tryAutomaticMove(c))
263 return true;
264
265 if (c->isAnimated())
266 return false;
267
268 if (allowedToRemove(c->pile(), c)
269 && c == c->pile()->topCard())
270 {
271 for (int i = 0; i < m_reserves; i++)
272 {
273 if (allowedToAdd( freecell[i], {c} ))
274 {
275 moveCardToPile( c, freecell[i], DURATION_MOVE );
276 return true;
277 }
278 }
279 }
280 return false;
281 }
282
283
canPutStore(const KCardPile * pile,const QList<KCard * > & cards) const284 bool Castle::canPutStore( const KCardPile * pile, const QList<KCard*> & cards ) const
285 {
286 int freeCells = 0;
287 for ( int i = 0; i < m_reserves; ++i )
288 if ( freecell[i]->isEmpty() )
289 ++freeCells;
290
291 int freeStores = 0;
292 if (m_emptyStackFill == 0)
293 {
294 for ( int i = 0; i < m_stacks; ++i )
295 if ( store[i]->isEmpty() && store[i] != pile)
296 ++freeStores;
297 }
298
299 if (cards.size() <= (freeCells + 1) << freeStores)
300 {
301 if (pile->isEmpty())
302 return m_emptyStackFill == 0 || (m_emptyStackFill == 1 && cards.first()->rank() == KCardDeck::King);
303 else
304 if (m_sequenceBuiltBy == 1)
305 return cards.first()->rank() == pile->topCard()->rank() - 1
306 && cards.first()->suit() == pile->topCard()->suit();
307 else if (m_sequenceBuiltBy == 0)
308 return cards.first()->rank() == pile->topCard()->rank() - 1
309 && pile->topCard()->color() != cards.first()->color();
310 else
311 return cards.first()->rank() == pile->topCard()->rank() - 1;
312 }
313 else
314 {
315 return false;
316 }
317
318 }
319
320
checkAdd(const PatPile * pile,const QList<KCard * > & oldCards,const QList<KCard * > & newCards) const321 bool Castle::checkAdd(const PatPile * pile, const QList<KCard*> & oldCards, const QList<KCard*> & newCards) const
322 {
323 switch (pile->pileRole())
324 {
325 case PatPile::Tableau:
326 return canPutStore(pile, newCards);
327 case PatPile::Cell:
328 return oldCards.isEmpty() && newCards.size() == 1;
329 case PatPile::Foundation:
330 return checkAddSameSuitAscendingFromAce(oldCards, newCards);
331 default:
332 return false;
333 }
334 }
335
336
checkRemove(const PatPile * pile,const QList<KCard * > & cards) const337 bool Castle::checkRemove(const PatPile * pile, const QList<KCard*> & cards) const
338 {
339 switch (pile->pileRole())
340 {
341 case PatPile::Tableau:
342 if (m_sequenceBuiltBy == 1)
343 return isSameSuitDescending(cards);
344 else if (m_sequenceBuiltBy == 0)
345 return isAlternateColorDescending(cards);
346 else
347 return isRankDescending(cards);
348 case PatPile::Cell:
349 return cards.first() == pile->topCard();
350 case PatPile::Foundation:
351 return true;
352 default:
353 return false;
354 }
355 }
356
357
358 static class CastleDealerInfo : public DealerInfo
359 {
360 public:
CastleDealerInfo()361 CastleDealerInfo()
362 : DealerInfo(I18N_NOOP("Castle"), CastleGeneralId)
363 {
364 addSubtype( CastleBeleagueredId, I18N_NOOP( "Beleaguered Castle" ) );
365 addSubtype( CastleCitadelId, I18N_NOOP( "Citadel" ) );
366 addSubtype( CastleExiledKingsId, I18N_NOOP( "Exiled Kings" ) );
367 addSubtype( CastleStreetAlleyId, I18N_NOOP( "Streets and Alleys" ) );
368 addSubtype( CastleSiegecraftId, I18N_NOOP( "Siegecraft" ) );
369 addSubtype( CastleStrongholdId, I18N_NOOP( "Stronghold" ) );
370 addSubtype( CastleCustomId, I18N_NOOP( "Castle (Custom)" ) );
371 }
372
createGame() const373 DealerScene *createGame() const override
374 {
375 return new Castle( this );
376 }
377 } castleDealerInfo;
378
379
matchVariant()380 void Castle::matchVariant()
381 {
382 if ( m_emptyStackFill == 0 && m_sequenceBuiltBy == 2 && m_reserves == 0 && m_stacks == 8 && m_foundation == 1 )
383 m_variation = 0;
384 else if ( m_emptyStackFill == 0 && m_sequenceBuiltBy == 2 && m_reserves == 0 && m_stacks == 8 && m_foundation == 2 )
385 m_variation = 1;
386 else if ( m_emptyStackFill == 1 && m_sequenceBuiltBy == 2 && m_reserves == 0 && m_stacks == 8 && m_foundation == 2 )
387 m_variation = 2;
388 else if ( m_emptyStackFill == 0 && m_sequenceBuiltBy == 2 && m_reserves == 0 && m_stacks == 8 && m_foundation == 0 )
389 m_variation = 3;
390 else if ( m_emptyStackFill == 0 && m_sequenceBuiltBy == 2 && m_reserves == 1 && m_stacks == 8 && m_foundation == 1 )
391 m_variation = 4;
392 else if ( m_emptyStackFill == 0 && m_sequenceBuiltBy == 2 && m_reserves == 1 && m_stacks == 8 && m_foundation == 0 )
393 m_variation = 5;
394 else
395 m_variation = 6;
396
397 options->setCurrentItem( m_variation );
398 }
399
400
configOptions()401 void Castle::configOptions()
402 {
403 options = new KSelectAction(i18n("Popular Variant Presets"), this );
404 options->addAction( i18n("Beleaguered Castle") );
405 options->addAction( i18n("Citadel") );
406 options->addAction( i18n("Exiled Kings") );
407 options->addAction( i18n("Streets and Alleys") );
408 options->addAction( i18n("Siegecraft") );
409 options->addAction( i18n("Stronghold") );
410 options->addAction( i18n("Custom") );
411
412 m_emptyStackFillOption = new KSelectAction(i18n("Empty Stack Fill"), this );
413 m_emptyStackFillOption->addAction( i18n("Any (Easy)") );
414 m_emptyStackFillOption->addAction( i18n("Kings only (Medium)") );
415 m_emptyStackFillOption->addAction( i18n("None (Hard)") );
416
417 m_sequenceBuiltByOption = new KSelectAction(i18n("Build Sequence"), this );
418 m_sequenceBuiltByOption->addAction( i18n("Alternating Color") );
419 m_sequenceBuiltByOption->addAction( i18n("Matching Suit") );
420 m_sequenceBuiltByOption->addAction( i18n("Rank") );
421
422 m_reservesOption = new KSelectAction(i18n("Free Cells"), this );
423 m_reservesOption->addAction( i18n("0") );
424 m_reservesOption->addAction( i18n("1") );
425 m_reservesOption->addAction( i18n("2") );
426 m_reservesOption->addAction( i18n("3") );
427 m_reservesOption->addAction( i18n("4") );
428
429 m_stacksOption = new KSelectAction(i18n("Stacks"), this );
430 m_stacksOption->addAction( i18n("6") );
431 m_stacksOption->addAction( i18n("7") );
432 m_stacksOption->addAction( i18n("8") );
433 m_stacksOption->addAction( i18n("9") );
434 m_stacksOption->addAction( i18n("10") );
435
436 m_stackFaceupOption = new KSelectAction(i18n("S&tack Options"), this );
437 m_stackFaceupOption->addAction( i18n("Face &Down") );
438 m_stackFaceupOption->addAction( i18n("Face &Up") );
439 m_stackFaceupOption->addAction( i18n("Alternating Face &Up") );
440
441 m_foundationOption = new KSelectAction(i18n("Foundation Deal"), this );
442 m_foundationOption->addAction( i18n("None") );
443 m_foundationOption->addAction( i18n("Aces") );
444 m_foundationOption->addAction( i18n("Any") );
445
446 m_layoutOption = new KSelectAction(i18n("Layout"), this );
447 m_layoutOption->addAction( i18n("Classic") );
448 m_layoutOption->addAction( i18n("Modern") );
449
450 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 78, 0)
451 connect(options, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
452 connect(m_emptyStackFillOption, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
453 connect(m_reservesOption, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
454 connect(m_sequenceBuiltByOption, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
455 connect(m_stacksOption, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
456 connect(m_stackFaceupOption, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
457 connect(m_foundationOption, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
458 connect(m_layoutOption, &KSelectAction::indexTriggered, this, &Castle::gameTypeChanged);
459 #else
460 connect(options, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
461 connect(m_emptyStackFillOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
462 connect(m_reservesOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
463 connect(m_sequenceBuiltByOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
464 connect(m_stacksOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
465 connect(m_stackFaceupOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
466 connect(m_foundationOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
467 connect(m_layoutOption, static_cast<void (KSelectAction::*)(int)>(&KSelectAction::triggered), this, &Castle::gameTypeChanged);
468 #endif
469 }
470
471
setSavedOptions()472 void Castle::setSavedOptions()
473 {
474 Settings::setCastleEmptyStackFill( m_emptyStackFill );
475 Settings::setCastleSequenceBuiltBy( m_sequenceBuiltBy );
476 Settings::setCastleReserves( m_reserves );
477 Settings::setCastleStacks( m_stacks );
478 Settings::setCastleStackFaceup( m_stackFaceup );
479 Settings::setCastleFoundation( m_foundation );
480 Settings::setCastleLayout( m_layout );
481 }
482
483
getSavedOptions()484 void Castle::getSavedOptions()
485 {
486 m_emptyStackFill = Settings::castleEmptyStackFill();
487 m_sequenceBuiltBy = Settings::castleSequenceBuiltBy();
488 m_reserves = Settings::castleReserves();
489 m_stacks = Settings::castleStacks();
490 m_stackFaceup = Settings::castleStackFaceup();
491 m_foundation = Settings::castleFoundation();
492 m_layout = Settings::castleLayout();
493 m_decks = 1;
494
495 if ( m_stacks < 6) m_stacks = 6;
496
497 matchVariant();
498
499 m_emptyStackFillOption->setCurrentItem( m_emptyStackFill );
500 m_sequenceBuiltByOption->setCurrentItem( m_sequenceBuiltBy );
501 m_reservesOption->setCurrentItem( m_reserves );
502 m_stacksOption->setCurrentItem( m_stacks - 6 );
503 m_stackFaceupOption->setCurrentItem( m_stackFaceup );
504 m_foundationOption->setCurrentItem( m_foundation );
505 m_layoutOption->setCurrentItem( m_layout );
506 }
507
508
mapOldId(int id)509 void Castle::mapOldId(int id)
510 {
511 switch (id) {
512
513 case DealerInfo::CastleBeleagueredId :
514 setOptions(0);
515 break;
516 case DealerInfo::CastleCitadelId :
517 setOptions(1);
518 break;
519 case DealerInfo::CastleExiledKingsId :
520 setOptions(2);
521 break;
522 case DealerInfo::CastleStreetAlleyId :
523 setOptions(3);
524 break;
525 case DealerInfo::CastleSiegecraftId :
526 setOptions(4);
527 break;
528 case DealerInfo::CastleStrongholdId :
529 setOptions(5);
530 break;
531 case DealerInfo::CastleCustomId :
532 setOptions(6);
533 break;
534 default:
535 // Do nothing.
536 break;
537 }
538 }
539
540
oldId() const541 int Castle::oldId() const
542 {
543 switch (m_variation) {
544 case 0 :
545 return DealerInfo::CastleBeleagueredId;
546 case 1 :
547 return DealerInfo::CastleCitadelId;
548 case 2 :
549 return DealerInfo::CastleExiledKingsId;
550 case 3 :
551 return DealerInfo::CastleStreetAlleyId;
552 case 4 :
553 return DealerInfo::CastleSiegecraftId;
554 case 5 :
555 return DealerInfo::CastleStrongholdId;
556 default :
557 return DealerInfo::CastleCustomId;
558 }
559 }
560
561
setOptions(int variation)562 void Castle::setOptions(int variation)
563 {
564 if ( variation != m_variation )
565 {
566 m_variation = variation;
567 m_emptyStackFill = 0;
568 m_sequenceBuiltBy = 2;
569 m_reserves = 0;
570 m_stacks = 8;
571 m_stackFaceup = 1;
572 m_decks = 1;
573 m_foundation = 1;
574
575 switch (m_variation) {
576 case 0 :
577 break;
578 case 1 :
579 m_foundation = 2;
580 break;
581 case 2 :
582 m_foundation = 2;
583 m_emptyStackFill = 1;
584 break;
585 case 3 :
586 m_foundation = 0;
587 break;
588 case 4 :
589 m_reserves = 1;
590 break;
591 case 5 :
592 m_foundation = 0;
593 m_reserves = 1;
594 break;
595 case 6 :
596 m_sequenceBuiltBy = 0;
597 break;
598 }
599
600 m_emptyStackFillOption->setCurrentItem( m_emptyStackFill );
601 m_sequenceBuiltByOption->setCurrentItem( m_sequenceBuiltBy );
602 m_reservesOption->setCurrentItem( m_reserves );
603 m_stacksOption->setCurrentItem( m_stacks - 6 );
604 m_stackFaceupOption->setCurrentItem( m_stackFaceup );
605 m_foundationOption->setCurrentItem( m_foundation );
606 }
607 }
608
609