1 | /* |
2 | * Copyright (C) 1995 Paul Olav Tvete <paul@troll.no> |
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 "klondike.h" |
39 | |
40 | #include "dealerinfo.h" |
41 | #include "pileutils.h" |
42 | #include "settings.h" |
43 | #include "speeds.h" |
44 | #include "patsolve/klondikesolver.h" |
45 | |
46 | #include <KLocale> |
47 | #include <KSelectAction> |
48 | |
49 | |
50 | KlondikePile::KlondikePile( DealerScene * scene, int index, const QString & objectName ) |
51 | : PatPile( scene, index, objectName ), |
52 | m_cardsToShow( 1 ) |
53 | { |
54 | } |
55 | |
56 | |
57 | void KlondikePile::setCardsToShow( int draw ) |
58 | { |
59 | m_cardsToShow = draw; |
60 | } |
61 | |
62 | |
63 | int KlondikePile::cardsToShow() const |
64 | { |
65 | return m_cardsToShow; |
66 | } |
67 | |
68 | |
69 | QList<QPointF> KlondikePile::cardPositions() const |
70 | { |
71 | QList<QPointF> positions; |
72 | QPointF currentPos( 0, 0 ); |
73 | for ( int i = 0; i < count(); ++i ) |
74 | { |
75 | positions << currentPos; |
76 | if ( i >= count() - m_cardsToShow ) |
77 | currentPos += spread(); |
78 | } |
79 | return positions; |
80 | } |
81 | |
82 | |
83 | Klondike::Klondike( const DealerInfo * di ) |
84 | : DealerScene( di ) |
85 | { |
86 | } |
87 | |
88 | |
89 | void Klondike::initialize() |
90 | { |
91 | // The units of the follwoing constants are pixels |
92 | const qreal hspacing = 1.0 / 6 + 0.02; // horizontal spacing between card piles |
93 | const qreal vspacing = 1.0 / 4; // vertical spacing between card piles |
94 | |
95 | setDeckContents(); |
96 | |
97 | easyRules = Settings::klondikeIsDrawOne(); |
98 | |
99 | talon = new PatPile( this, 0, "talon" ); |
100 | talon->setPileRole(PatPile::Stock); |
101 | talon->setLayoutPos(0, 0); |
102 | // Give the talon a low Z value to keep it out of the way during there |
103 | // deal animation. |
104 | talon->setZValue( -52 ); |
105 | talon->setKeyboardSelectHint( KCardPile::NeverFocus ); |
106 | talon->setKeyboardDropHint( KCardPile::NeverFocus ); |
107 | connect( talon, SIGNAL(clicked(KCard*)), SLOT(drawDealRowOrRedeal()) ); |
108 | |
109 | pile = new KlondikePile( this, 13, "pile" ); |
110 | pile->setCardsToShow( easyRules ? 1 : 3 ); |
111 | pile->setPileRole( PatPile::Waste ); |
112 | pile->setRightPadding( 1.1 ); |
113 | pile->setLayoutPos( 1 + hspacing, 0 ); |
114 | pile->setSpread( 0.33, 0 ); |
115 | pile->setKeyboardSelectHint( KCardPile::ForceFocusTop ); |
116 | pile->setKeyboardDropHint( KCardPile::NeverFocus ); |
117 | |
118 | for( int i = 0; i < 7; ++i ) |
119 | { |
120 | play[i] = new PatPile( this, i + 5, QString( "play%1" ).arg( i )); |
121 | play[i]->setPileRole(PatPile::Tableau); |
122 | play[i]->setLayoutPos((1.0 + hspacing) * i, 1.0 + vspacing); |
123 | play[i]->setAutoTurnTop(true); |
124 | play[i]->setBottomPadding( play[i]->spread().y() * 7 ); |
125 | play[i]->setHeightPolicy( KCardPile::GrowDown ); |
126 | play[i]->setKeyboardSelectHint( KCardPile::AutoFocusDeepestFaceUp ); |
127 | play[i]->setKeyboardDropHint( KCardPile::AutoFocusTop ); |
128 | } |
129 | |
130 | for( int i = 0; i < 4; ++i ) |
131 | { |
132 | target[i] = new PatPile( this, i + 1, QString( "target%1" ).arg( i ) ); |
133 | target[i]->setPileRole(PatPile::Foundation); |
134 | target[i]->setLayoutPos((3 + i) * (1.0 + hspacing), 0); |
135 | target[i]->setKeyboardSelectHint( KCardPile::ForceFocusTop ); |
136 | target[i]->setKeyboardDropHint( KCardPile::ForceFocusTop ); |
137 | } |
138 | |
139 | setActions(DealerScene::Hint | DealerScene::Demo | DealerScene::Draw); |
140 | setSolver( new KlondikeSolver( this, pile->cardsToShow() ) ); |
141 | redealt = false; |
142 | |
143 | options = new KSelectAction(i18n("Klondike &Options" ), this ); |
144 | options->addAction( i18n("Draw 1" )); |
145 | options->addAction( i18n("Draw 3" )); |
146 | options->setCurrentItem( easyRules ? 0 : 1 ); |
147 | connect( options, SIGNAL(triggered(int)), SLOT(gameTypeChanged()) ); |
148 | } |
149 | |
150 | bool Klondike::checkAdd(const PatPile * pile, const QList<KCard*> & oldCards, const QList<KCard*> & newCards) const |
151 | { |
152 | switch (pile->pileRole()) |
153 | { |
154 | case PatPile::Tableau: |
155 | return checkAddAlternateColorDescendingFromKing(oldCards, newCards); |
156 | case PatPile::Foundation: |
157 | return checkAddSameSuitAscendingFromAce(oldCards, newCards); |
158 | case PatPile::Waste: |
159 | case PatPile::Stock: |
160 | default: |
161 | return false; |
162 | } |
163 | } |
164 | |
165 | bool Klondike::checkRemove(const PatPile * pile, const QList<KCard*> & cards) const |
166 | { |
167 | switch (pile->pileRole()) |
168 | { |
169 | case PatPile::Tableau: |
170 | return isAlternateColorDescending(cards); |
171 | case PatPile::Foundation: |
172 | return easyRules && cards.first() == pile->topCard(); |
173 | case PatPile::Waste: |
174 | return cards.first() == pile->topCard(); |
175 | case PatPile::Stock: |
176 | default: |
177 | return false; |
178 | } |
179 | } |
180 | |
181 | |
182 | void Klondike::cardsMoved( const QList<KCard*> & cards, KCardPile * oldPile, KCardPile * newPile ) |
183 | { |
184 | DealerScene::cardsMoved( cards, oldPile, newPile ); |
185 | |
186 | emit newCardsPossible( !talon->isEmpty() || pile->count() > 1 ); |
187 | } |
188 | |
189 | |
190 | QList<QAction*> Klondike::configActions() const |
191 | { |
192 | return QList<QAction*>() << options; |
193 | } |
194 | |
195 | |
196 | bool Klondike::newCards() |
197 | { |
198 | if ( talon->isEmpty() && pile->count() <= 1 ) |
199 | return false; |
200 | |
201 | if ( talon->isEmpty() ) |
202 | { |
203 | // Move the cards from the pile back to the deck |
204 | flipCardsToPile( pile->cards(), talon, DURATION_MOVE ); |
205 | |
206 | redealt = true; |
207 | } |
208 | else |
209 | { |
210 | QList<KCard*> cards = talon->topCards( pile->cardsToShow() ); |
211 | flipCardsToPile( cards, pile, DURATION_MOVE ); |
212 | setKeyboardFocus( pile->topCard() ); |
213 | } |
214 | |
215 | if ( talon->isEmpty() && pile->count() <= 1 ) |
216 | emit newCardsPossible( false ); |
217 | |
218 | // we need to look that many steps in the future to see if we can lose |
219 | setNeededFutureMoves( talon->count() + pile->count() ); |
220 | |
221 | return true; |
222 | } |
223 | |
224 | |
225 | void Klondike::restart( const QList<KCard*> & cards ) |
226 | { |
227 | redealt = false; |
228 | |
229 | QList<KCard*> cardList = cards; |
230 | |
231 | for( int round = 0; round < 7; ++round ) |
232 | for ( int i = round; i < 7; ++i ) |
233 | addCardForDeal( play[i], cardList.takeLast(), (i == round), talon->pos()); |
234 | |
235 | while ( !cardList.isEmpty() ) |
236 | { |
237 | KCard * c = cardList.takeFirst(); |
238 | c->setPos( talon->pos() ); |
239 | c->setFaceUp( false ); |
240 | talon->add( c ); |
241 | } |
242 | |
243 | startDealAnimation(); |
244 | } |
245 | |
246 | void Klondike::gameTypeChanged() |
247 | { |
248 | stopDemo(); |
249 | |
250 | if ( allowedToStartNewGame() ) |
251 | { |
252 | setEasy( options->currentItem() == 0 ); |
253 | startNew( gameNumber() ); |
254 | } |
255 | else |
256 | { |
257 | // If we're not allowed, reset the option to |
258 | // the current number of suits. |
259 | options->setCurrentItem( easyRules ? 0 : 1 ); |
260 | } |
261 | } |
262 | |
263 | |
264 | void Klondike::setGameState( const QString & state ) |
265 | { |
266 | Q_UNUSED( state ); |
267 | |
268 | emit newCardsPossible( !talon->isEmpty() || pile->count() > 1 ); |
269 | } |
270 | |
271 | |
272 | QString Klondike::getGameOptions() const |
273 | { |
274 | return QString::number( pile->cardsToShow() ); |
275 | } |
276 | |
277 | void Klondike::setGameOptions( const QString & options ) |
278 | { |
279 | setEasy( options.toInt() == 1 ); |
280 | } |
281 | |
282 | void Klondike::setEasy( bool _EasyRules ) |
283 | { |
284 | if ( _EasyRules != easyRules ) |
285 | { |
286 | easyRules = _EasyRules; |
287 | options->setCurrentItem( easyRules ? 0 : 1 ); |
288 | |
289 | int drawNumber = easyRules ? 1 : 3; |
290 | pile->setCardsToShow( drawNumber ); |
291 | |
292 | KCardPile::KeyboardFocusHint hint = easyRules |
293 | ? KCardPile::ForceFocusTop |
294 | : KCardPile::NeverFocus; |
295 | for( int i = 0; i < 4; ++i ) |
296 | target[i]->setKeyboardSelectHint( hint ); |
297 | |
298 | setSolver( new KlondikeSolver( this, drawNumber ) ); |
299 | |
300 | Settings::setKlondikeIsDrawOne( easyRules ); |
301 | } |
302 | } |
303 | |
304 | |
305 | bool Klondike::drop() |
306 | { |
307 | bool pileempty = pile->isEmpty(); |
308 | if (!DealerScene::drop()) |
309 | return false; |
310 | if (pile->isEmpty() && !pileempty) |
311 | newCards(); |
312 | return true; |
313 | } |
314 | |
315 | void Klondike::mapOldId(int id) |
316 | { |
317 | switch ( id ) |
318 | { |
319 | case DealerInfo::KlondikeDrawOneId: |
320 | setEasy( true ); |
321 | break; |
322 | case DealerInfo::KlondikeDrawThreeId: |
323 | setEasy( false ); |
324 | break; |
325 | case DealerInfo::KlondikeGeneralId: |
326 | default: |
327 | // Do nothing. |
328 | break; |
329 | } |
330 | } |
331 | |
332 | |
333 | int Klondike::oldId() const |
334 | { |
335 | if ( easyRules ) |
336 | return DealerInfo::KlondikeDrawOneId; |
337 | else |
338 | return DealerInfo::KlondikeDrawThreeId; |
339 | } |
340 | |
341 | |
342 | |
343 | |
344 | static class KlondikeDealerInfo : public DealerInfo |
345 | { |
346 | public: |
347 | KlondikeDealerInfo() |
348 | : DealerInfo(I18N_NOOP("Klondike" ), DealerInfo::KlondikeGeneralId) |
349 | { |
350 | addSubtype( KlondikeDrawOneId, I18N_NOOP( "Klondike (Draw 1)" ) ); |
351 | addSubtype( KlondikeDrawThreeId, I18N_NOOP( "Klondike (Draw 3)" ) ); |
352 | } |
353 | |
354 | virtual DealerScene *createGame() const |
355 | { |
356 | return new Klondike( this ); |
357 | } |
358 | } klondikeDealerInfo; |
359 | |
360 | |
361 | #include "klondike.moc" |
362 | |