1 | /* |
2 | * Copyright (C) 2003 Josh Metzler <joshdeb@metzlers.org> |
3 | * Copyright (C) 2000-2009 Stephan Kulow <coolo@kde.org> |
4 | * Copyright (C) 2010 Parker Coates <coates@kde.org> |
5 | * |
6 | * License of original code: |
7 | * ------------------------------------------------------------------------- |
8 | * Permission to use, copy, modify, and distribute this software and its |
9 | * documentation for any purpose and without fee is hereby granted, |
10 | * provided that the above copyright notice appear in all copies and that |
11 | * both that copyright notice and this permission notice appear in |
12 | * supporting documentation. |
13 | * |
14 | * This file is provided AS IS with no warranties of any kind. The author |
15 | * shall have no liability with respect to the infringement of copyrights, |
16 | * trade secrets or any patents by this file or any part thereof. In no |
17 | * event will the author be liable for any lost revenue or profits or |
18 | * other special, indirect and consequential damages. |
19 | * ------------------------------------------------------------------------- |
20 | * |
21 | * License of modifications/additions made after 2009-01-01: |
22 | * ------------------------------------------------------------------------- |
23 | * This program is free software; you can redistribute it and/or |
24 | * modify it under the terms of the GNU General Public License as |
25 | * published by the Free Software Foundation; either version 2 of |
26 | * the License, or (at your option) any later version. |
27 | * |
28 | * This program is distributed in the hope that it will be useful, |
29 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
30 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
31 | * GNU General Public License for more details. |
32 | * |
33 | * You should have received a copy of the GNU General Public License |
34 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
35 | * ------------------------------------------------------------------------- |
36 | */ |
37 | |
38 | #include "spider.h" |
39 | |
40 | #include "dealerinfo.h" |
41 | #include "pileutils.h" |
42 | #include "settings.h" |
43 | #include "speeds.h" |
44 | #include "patsolve/spidersolver.h" |
45 | |
46 | #include <KLocale> |
47 | #include <KRandom> |
48 | #include <KSelectAction> |
49 | |
50 | |
51 | class InvisiblePile : public PatPile |
52 | { |
53 | public: |
54 | InvisiblePile( DealerScene * scene, int index, const QString & objectName = QString() ) |
55 | : PatPile( scene, index, objectName ) |
56 | { |
57 | }; |
58 | |
59 | protected: |
60 | virtual void paintGraphic( QPainter * painter, qreal highlightedness ) |
61 | { |
62 | Q_UNUSED( painter ); |
63 | Q_UNUSED( highlightedness ); |
64 | }; |
65 | }; |
66 | |
67 | |
68 | Spider::Spider( const DealerInfo * di ) |
69 | : DealerScene( di ) |
70 | { |
71 | } |
72 | |
73 | |
74 | void Spider::initialize() |
75 | { |
76 | m_leg = 0; |
77 | m_redeal = 0; |
78 | |
79 | const qreal dist_x = 1.12; |
80 | const qreal smallNeg = -1e-6; |
81 | |
82 | m_suits = Settings::spiderSuitCount(); |
83 | |
84 | createDeck(); |
85 | |
86 | // Dealing the cards out into 5 piles so the user can see how many |
87 | // sets of 10 cards are left to be dealt out |
88 | for( int column = 0; column < 5; ++column ) |
89 | { |
90 | redeals[column] = new InvisiblePile( this, column + 1, QString( "redeals%1" ).arg( column ) ); |
91 | redeals[column]->setPileRole(PatPile::Stock); |
92 | redeals[column]->setLayoutPos( dist_x * (9 - (4.0 - column) / 3), smallNeg ); |
93 | redeals[column]->setZValue(12 * ( 5-column )); |
94 | redeals[column]->setSpread(0, 0); |
95 | redeals[column]->setKeyboardSelectHint( KCardPile::NeverFocus ); |
96 | redeals[column]->setKeyboardDropHint( KCardPile::NeverFocus ); |
97 | connect( redeals[column], SIGNAL(clicked(KCard*)), SLOT(drawDealRowOrRedeal()) ); |
98 | } |
99 | |
100 | // The 10 playing piles |
101 | for( int column = 0; column < 10; ++column ) |
102 | { |
103 | stack[column] = new PatPile( this, column + 6, QString( "stack%1" ).arg( column ) ); |
104 | stack[column]->setPileRole(PatPile::Tableau); |
105 | stack[column]->setLayoutPos(dist_x * column, 0); |
106 | stack[column]->setZValue(20); |
107 | stack[column]->setAutoTurnTop(true); |
108 | stack[column]->setBottomPadding( 1.5 ); |
109 | stack[column]->setHeightPolicy( KCardPile::GrowDown ); |
110 | stack[column]->setKeyboardSelectHint( KCardPile::AutoFocusDeepestRemovable ); |
111 | stack[column]->setKeyboardDropHint( KCardPile::AutoFocusTop ); |
112 | } |
113 | |
114 | // The 8 'legs' so named by me because spiders have 8 legs - why |
115 | // else the name Spider? |
116 | for( int column = 0; column < 8; ++column ) |
117 | { |
118 | legs[column] = new InvisiblePile( this, column + 16, QString( "legs%1" ).arg( column ) ); |
119 | legs[column]->setPileRole(PatPile::Foundation); |
120 | legs[column]->setLayoutPos(dist_x / 3 * column, smallNeg); |
121 | legs[column]->setZValue(column+1); |
122 | legs[column]->setSpread(0, 0); |
123 | legs[column]->setZValue(14 * column); |
124 | legs[column]->setVisible( false ); |
125 | legs[column]->setKeyboardSelectHint( KCardPile::NeverFocus ); |
126 | legs[column]->setKeyboardDropHint( KCardPile::NeverFocus ); |
127 | } |
128 | |
129 | // Moving an A-K run to a leg is not really an autoDrop - the |
130 | // user should have no choice. |
131 | setAutoDropEnabled(false); |
132 | setActions(DealerScene::Hint | DealerScene::Demo | DealerScene::Deal); |
133 | setSolver( new SpiderSolver( this ) ); |
134 | |
135 | options = new KSelectAction(i18n("Spider &Options" ), this ); |
136 | options->addAction( i18n("1 Suit (Easy)" ) ); |
137 | options->addAction( i18n("2 Suits (Medium)" ) ); |
138 | options->addAction( i18n("4 Suits (Hard)" ) ); |
139 | if ( m_suits == 1 ) |
140 | options->setCurrentItem( 0 ); |
141 | else if ( m_suits == 2 ) |
142 | options->setCurrentItem( 1 ); |
143 | else |
144 | options->setCurrentItem( 2 ); |
145 | connect( options, SIGNAL(triggered(int)), SLOT(gameTypeChanged()) ); |
146 | } |
147 | |
148 | |
149 | QList<QAction*> Spider::configActions() const |
150 | { |
151 | return QList<QAction*>() << options; |
152 | } |
153 | |
154 | |
155 | void Spider::gameTypeChanged() |
156 | { |
157 | stopDemo(); |
158 | |
159 | if ( allowedToStartNewGame() ) |
160 | { |
161 | if ( options->currentItem() == 0 ) |
162 | setSuits( 1 ); |
163 | else if ( options->currentItem() == 1 ) |
164 | setSuits( 2 ); |
165 | else |
166 | setSuits( 4 ); |
167 | startNew( gameNumber() ); |
168 | } |
169 | else |
170 | { |
171 | // If we're not allowed, reset the option to |
172 | // the current number of suits. |
173 | if ( m_suits == 1 ) |
174 | options->setCurrentItem( 0 ); |
175 | else if ( m_suits == 2 ) |
176 | options->setCurrentItem( 1 ); |
177 | else |
178 | options->setCurrentItem( 2 ); |
179 | } |
180 | } |
181 | |
182 | |
183 | void Spider::setSuits(int suits) |
184 | { |
185 | if ( suits != m_suits ) |
186 | { |
187 | m_suits = suits; |
188 | |
189 | stopDemo(); |
190 | clearHighlightedItems(); |
191 | setKeyboardModeActive( false ); |
192 | |
193 | int cardWidth = deck()->cardWidth(); |
194 | createDeck(); |
195 | deck()->setCardWidth( cardWidth ); |
196 | |
197 | Settings::setSpiderSuitCount( m_suits ); |
198 | |
199 | if ( m_suits == 1 ) |
200 | options->setCurrentItem( 0 ); |
201 | else if ( m_suits == 2 ) |
202 | options->setCurrentItem( 1 ); |
203 | else |
204 | options->setCurrentItem( 2 ); |
205 | } |
206 | } |
207 | |
208 | |
209 | void Spider::createDeck() |
210 | { |
211 | // These look a bit weird, but are needed to keep the game numbering |
212 | // from breaking. The original logic always created groupings of 4 |
213 | // suits, while the new logic is more flexible. We maintain the card |
214 | // ordering by always passing a list of 4 suits even if we really only |
215 | // have one or two. |
216 | QList<KCardDeck::Suit> suits; |
217 | if ( m_suits == 1 ) |
218 | suits << KCardDeck::Spades << KCardDeck::Spades << KCardDeck::Spades << KCardDeck::Spades; |
219 | else if ( m_suits == 2 ) |
220 | suits << KCardDeck::Hearts << KCardDeck::Spades << KCardDeck::Hearts << KCardDeck::Spades; |
221 | else |
222 | suits << KCardDeck::Clubs << KCardDeck::Diamonds << KCardDeck::Hearts << KCardDeck::Spades; |
223 | |
224 | setDeckContents( 2, suits ); |
225 | } |
226 | |
227 | |
228 | bool Spider::checkAdd(const PatPile * pile, const QList<KCard*> & oldCards, const QList<KCard*> & newCards) const |
229 | { |
230 | // assuming the cardlist is a valid unit, since I allowed |
231 | // it to be removed - can drop any card on empty pile or |
232 | // on any suit card of one higher rank |
233 | return pile->pileRole() == PatPile::Tableau |
234 | && ( oldCards.isEmpty() |
235 | || oldCards.last()->rank() == newCards.first()->rank() + 1 ); |
236 | } |
237 | |
238 | |
239 | bool Spider::checkRemove(const PatPile * pile, const QList<KCard*> & cards) const |
240 | { |
241 | return pile->pileRole() == PatPile::Tableau |
242 | && isSameSuitDescending(cards); |
243 | } |
244 | |
245 | |
246 | QString Spider::getGameState() const |
247 | { |
248 | return QString::number(m_leg*10 + m_redeal); |
249 | } |
250 | |
251 | |
252 | void Spider::setGameState( const QString & state ) |
253 | { |
254 | int n = state.toInt(); |
255 | int numLegs = n / 10; |
256 | int numRedeals = n % 10; |
257 | |
258 | if ( numRedeals != m_redeal || numLegs != m_leg ) |
259 | { |
260 | m_redeal = numRedeals; |
261 | for ( int i = 0; i < 5; ++i ) |
262 | redeals[i]->setVisible( i >= m_redeal ); |
263 | |
264 | m_leg = numLegs; |
265 | for ( int i = 0; i < 8; ++i ) |
266 | legs[i]->setVisible( i < m_leg ); |
267 | |
268 | recalculatePileLayouts(); |
269 | foreach ( KCardPile * p, piles() ) |
270 | updatePileLayout( p, 0 ); |
271 | |
272 | emit newCardsPossible(m_redeal <= 4); |
273 | } |
274 | } |
275 | |
276 | |
277 | QString Spider::getGameOptions() const |
278 | { |
279 | return QString::number(m_suits); |
280 | } |
281 | |
282 | |
283 | void Spider::setGameOptions( const QString & options ) |
284 | { |
285 | setSuits(options.toInt()); |
286 | } |
287 | |
288 | |
289 | void Spider::restart( const QList<KCard*> & cards ) |
290 | { |
291 | m_pilesWithRuns.clear(); |
292 | |
293 | // make the redeal piles visible |
294 | for (int i = 0; i < 5; ++i ) |
295 | redeals[i]->setVisible( true ); |
296 | |
297 | // make the leg piles invisible |
298 | for (int i = 0; i < 8; ++i ) |
299 | legs[i]->setVisible( false ); |
300 | |
301 | recalculatePileLayouts(); |
302 | |
303 | m_leg = 0; |
304 | m_redeal = 0; |
305 | |
306 | QList<KCard*> cardList = cards; |
307 | |
308 | int column = 0; |
309 | // deal face down cards (5 to first 4 piles, 4 to last 6) |
310 | for ( int i = 0; i < 44; ++i ) |
311 | { |
312 | addCardForDeal( stack[column], cardList.takeLast(), false, randomPos() ); |
313 | column = (column + 1) % 10; |
314 | } |
315 | // deal face up cards, one to each pile |
316 | for ( int i = 0; i < 10; ++i ) |
317 | { |
318 | addCardForDeal( stack[column], cardList.takeLast(), true, randomPos() ); |
319 | column = (column + 1) % 10; |
320 | } |
321 | // deal the remaining cards into 5 'redeal' piles |
322 | for ( int column = 0; column < 5; ++column ) |
323 | for ( int i = 0; i < 10; ++i ) |
324 | addCardForDeal( redeals[column], cardList.takeLast(), false, randomPos() ); |
325 | |
326 | startDealAnimation(); |
327 | |
328 | emit newCardsPossible(true); |
329 | } |
330 | |
331 | |
332 | void Spider::cardsMoved( const QList<KCard*> & cards, KCardPile * oldPile, KCardPile * newPile ) |
333 | { |
334 | PatPile * p = dynamic_cast<PatPile*>( newPile ); |
335 | |
336 | // The solver treats the removal of complete runs from the table as a |
337 | // separate move, so we don't do it automatically when the demo is active. |
338 | if ( !isDemoActive() |
339 | && p |
340 | && p->pileRole() == PatPile::Tableau |
341 | && pileHasFullRun( p ) ) |
342 | { |
343 | m_pilesWithRuns << p; |
344 | } |
345 | |
346 | DealerScene::cardsMoved( cards, oldPile, newPile ); |
347 | } |
348 | |
349 | |
350 | bool Spider::pileHasFullRun( KCardPile * pile ) |
351 | { |
352 | QList<KCard*> potentialRun = pile->topCards( 13 ); |
353 | return pile->count() >= 13 |
354 | && potentialRun.first()->isFaceUp() |
355 | && isSameSuitDescending( potentialRun ); |
356 | } |
357 | |
358 | |
359 | void Spider::moveFullRunToLeg( KCardPile * pile ) |
360 | { |
361 | QList<KCard*> run = pile->topCards( 13 ); |
362 | |
363 | PatPile * leg = legs[m_leg]; |
364 | ++m_leg; |
365 | leg->setVisible( true ); |
366 | |
367 | recalculatePileLayouts(); |
368 | for ( int i = 0; i < 10; ++i ) |
369 | if ( stack[i] != pile ) |
370 | updatePileLayout( stack[i], DURATION_RELAYOUT ); |
371 | |
372 | for ( int i = 0; i < run.size(); ++i ) |
373 | { |
374 | KCard * c = run.at( i ); |
375 | leg->add( c ); |
376 | int duration = DURATION_AUTODROP * (0.7 + i / 10.0); |
377 | c->animate( leg->pos(), leg->zValue() + i, 0, true, true, duration ); |
378 | } |
379 | |
380 | updatePileLayout( pile, DURATION_RELAYOUT ); |
381 | } |
382 | |
383 | |
384 | QPointF Spider::randomPos() |
385 | { |
386 | QRectF rect = sceneRect(); |
387 | qreal x = rect.left() + qreal(KRandom::random()) / RAND_MAX * (rect.width() - deck()->cardWidth()); |
388 | qreal y = rect.top() + qreal(KRandom::random()) / RAND_MAX * (rect.height() - deck()->cardHeight()); |
389 | return QPointF( x, y ); |
390 | } |
391 | |
392 | |
393 | bool Spider::newCards() |
394 | { |
395 | // The solver doesn't distinguish between dealing a new row of cards and |
396 | // removing complete runs from the tableau. So it we're in demo mode and |
397 | // newCards() is called, we should check to see if there are any complete |
398 | // runs to move before dealing a new row. |
399 | if ( isDemoActive() ) |
400 | { |
401 | for ( int i = 0; i < 10; ++i ) |
402 | { |
403 | if ( pileHasFullRun( stack[i] ) ) |
404 | { |
405 | moveFullRunToLeg( stack[i] ); |
406 | return true; |
407 | } |
408 | } |
409 | } |
410 | |
411 | if ( m_redeal > 4 ) |
412 | return false; |
413 | |
414 | redeals[m_redeal]->setVisible(false); |
415 | recalculatePileLayouts(); |
416 | |
417 | for ( int column = 0; column < 10; ++column ) |
418 | { |
419 | KCard * c = redeals[m_redeal]->topCard(); |
420 | if ( !c ) |
421 | break; |
422 | |
423 | flipCardToPileAtSpeed( c, stack[column], DEAL_SPEED ); |
424 | c->setZValue( c->zValue() + 10 - column ); |
425 | } |
426 | |
427 | ++m_redeal; |
428 | |
429 | if (m_redeal > 4) |
430 | emit newCardsPossible(false); |
431 | |
432 | return true; |
433 | } |
434 | |
435 | |
436 | void Spider::animationDone() |
437 | { |
438 | if ( !m_pilesWithRuns.isEmpty() ) |
439 | moveFullRunToLeg( m_pilesWithRuns.takeFirst() ); |
440 | else |
441 | DealerScene::animationDone(); |
442 | } |
443 | |
444 | |
445 | |
446 | void Spider::mapOldId(int id) |
447 | { |
448 | switch (id) { |
449 | case DealerInfo::SpiderOneSuitId : |
450 | setSuits(1); |
451 | break; |
452 | case DealerInfo::SpiderTwoSuitId : |
453 | setSuits(2); |
454 | break; |
455 | case DealerInfo::SpiderFourSuitId : |
456 | setSuits(4); |
457 | break; |
458 | } |
459 | } |
460 | |
461 | |
462 | int Spider::oldId() const |
463 | { |
464 | switch (m_suits) { |
465 | case 1 : |
466 | return DealerInfo::SpiderOneSuitId; |
467 | case 2 : |
468 | return DealerInfo::SpiderTwoSuitId; |
469 | case 4 : |
470 | default : |
471 | return DealerInfo::SpiderFourSuitId; |
472 | } |
473 | } |
474 | |
475 | |
476 | static class SpideDealerInfo : public DealerInfo |
477 | { |
478 | public: |
479 | SpideDealerInfo() |
480 | : DealerInfo(I18N_NOOP("Spider" ), SpiderGeneralId) |
481 | { |
482 | addSubtype( SpiderOneSuitId, I18N_NOOP( "Spider (1 Suit)" ) ); |
483 | addSubtype( SpiderTwoSuitId, I18N_NOOP( "Spider (2 Suit)" ) ); |
484 | addSubtype( SpiderFourSuitId, I18N_NOOP( "Spider (4 Suit)" ) ); |
485 | } |
486 | |
487 | virtual DealerScene *createGame() const |
488 | { |
489 | return new Spider( this ); |
490 | } |
491 | } spideDealerInfo; |
492 | |
493 | |
494 | #include "spider.moc" |
495 | |