1 | /* |
2 | * Copyright (C) 1995 Paul Olav Tvete <paul@troll.no> |
3 | * Copyright (C) 2000-2009 Stephan Kulow <coolo@kde.org> |
4 | * Copyright (C) 2009-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 "dealer.h" |
39 | |
40 | #include "dealerinfo.h" |
41 | #include "messagebox.h" |
42 | #include "renderer.h" |
43 | #include "speeds.h" |
44 | #include "version.h" |
45 | #include "view.h" |
46 | |
47 | #include "KCardTheme" |
48 | |
49 | #include <KConfigGroup> |
50 | #include <KDebug> |
51 | #include <KLocalizedString> |
52 | #include <KMessageBox> |
53 | #include <KRandom> |
54 | #include <KSharedConfig> |
55 | |
56 | #include <QtCore/QCoreApplication> |
57 | #include <QtCore/QMutex> |
58 | #include <QtCore/QThread> |
59 | #include <QtCore/QXmlStreamReader> |
60 | #include <QtCore/QXmlStreamWriter> |
61 | #include <QtGui/QGraphicsSceneMouseEvent> |
62 | |
63 | #include <cmath> |
64 | |
65 | #define DEBUG_HINTS 0 |
66 | |
67 | |
68 | namespace |
69 | { |
70 | const qreal wonBoxToSceneSizeRatio = 0.7; |
71 | |
72 | QList<KCard*> shuffled( const QList<KCard*> & cards, int seed ) |
73 | { |
74 | Q_ASSERT( seed > 0 ); |
75 | |
76 | QList<KCard*> result = cards; |
77 | for ( int i = result.size(); i > 1; --i ) |
78 | { |
79 | // We use the same pseudorandom number generation algorithm as Windows |
80 | // Freecell, so that game numbers are the same between the two applications. |
81 | // For more inforation, see |
82 | // http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q28150 |
83 | seed = 214013 * seed + 2531011; |
84 | int rand = ( seed >> 16 ) & 0x7fff; |
85 | |
86 | result.swap( i - 1, rand % i ); |
87 | } |
88 | |
89 | return result; |
90 | } |
91 | |
92 | QString solverStatusMessage( int status, bool everWinnable ) |
93 | { |
94 | switch ( status ) |
95 | { |
96 | case Solver::SolutionExists: |
97 | return i18n("Solver: This game is winnable." ); |
98 | case Solver::NoSolutionExists: |
99 | return everWinnable ? i18n("Solver: This game is no longer winnable." ) |
100 | : i18n("Solver: This game cannot be won." ); |
101 | case Solver::UnableToDetermineSolvability: |
102 | return i18n("Solver: Unable to determine if this game is winnable." ); |
103 | case Solver::SearchAborted: |
104 | case Solver::MemoryLimitReached: |
105 | default: |
106 | return QString(); |
107 | } |
108 | } |
109 | |
110 | int readIntAttribute( const QXmlStreamReader & xml, const QString & key, bool * ok = 0 ) |
111 | { |
112 | QStringRef value = xml.attributes().value( key ); |
113 | return QString::fromRawData( value.data(), value.length() ).toInt( ok ); |
114 | } |
115 | |
116 | QString suitToString( int suit ) |
117 | { |
118 | switch ( suit ) |
119 | { |
120 | case KCardDeck::Clubs: |
121 | return "clubs" ; |
122 | case KCardDeck::Diamonds: |
123 | return "diamonds" ; |
124 | case KCardDeck::Hearts: |
125 | return "hearts" ; |
126 | case KCardDeck::Spades: |
127 | return "spades" ; |
128 | default: |
129 | return QString(); |
130 | } |
131 | } |
132 | |
133 | QString rankToString( int rank ) |
134 | { |
135 | switch ( rank ) |
136 | { |
137 | case KCardDeck::Ace: |
138 | return "ace" ; |
139 | case KCardDeck::Two: |
140 | return "two" ; |
141 | case KCardDeck::Three: |
142 | return "three" ; |
143 | case KCardDeck::Four: |
144 | return "four" ; |
145 | case KCardDeck::Five: |
146 | return "five" ; |
147 | case KCardDeck::Six: |
148 | return "six" ; |
149 | case KCardDeck::Seven: |
150 | return "seven" ; |
151 | case KCardDeck::Eight: |
152 | return "eight" ; |
153 | case KCardDeck::Nine: |
154 | return "nine" ; |
155 | case KCardDeck::Ten: |
156 | return "ten" ; |
157 | case KCardDeck::Jack: |
158 | return "jack" ; |
159 | case KCardDeck::Queen: |
160 | return "queen" ; |
161 | case KCardDeck::King: |
162 | return "king" ; |
163 | default: |
164 | return QString(); |
165 | } |
166 | } |
167 | } |
168 | |
169 | |
170 | class SolverThread : public QThread |
171 | { |
172 | Q_OBJECT |
173 | |
174 | public: |
175 | SolverThread( Solver * solver ) |
176 | : m_solver( solver ) |
177 | { |
178 | } |
179 | |
180 | virtual void run() |
181 | { |
182 | Solver::ExitStatus result = m_solver->patsolve(); |
183 | emit finished( result ); |
184 | } |
185 | |
186 | void abort() |
187 | { |
188 | { |
189 | QMutexLocker lock( &m_solver->endMutex ); |
190 | m_solver->m_shouldEnd = true; |
191 | } |
192 | wait(); |
193 | } |
194 | |
195 | signals: |
196 | void finished( int result ); |
197 | |
198 | private: |
199 | Solver * m_solver; |
200 | }; |
201 | |
202 | |
203 | int DealerScene::moveCount() const |
204 | { |
205 | return m_loadedMoveCount + m_undoStack.size(); |
206 | } |
207 | |
208 | |
209 | void DealerScene::saveLegacyFile( QIODevice * io ) |
210 | { |
211 | QXmlStreamWriter xml( io ); |
212 | xml.setCodec( "UTF-8" ); |
213 | xml.setAutoFormatting( true ); |
214 | xml.setAutoFormattingIndent( -1 ); |
215 | xml.writeStartDocument(); |
216 | |
217 | xml.writeDTD( "<!DOCTYPE kpat>" ); |
218 | |
219 | xml.writeStartElement( "dealer" ); |
220 | xml.writeAttribute( "id" , QString::number( gameId() ) ); |
221 | xml.writeAttribute( "options" , getGameOptions() ); |
222 | xml.writeAttribute( "number" , QString::number( gameNumber() ) ); |
223 | xml.writeAttribute( "moves" , QString::number( moveCount() ) ); |
224 | xml.writeAttribute( "started" , QString::number( m_dealStarted ) ); |
225 | xml.writeAttribute( "data" , getGameState() ); |
226 | |
227 | foreach( const PatPile * p, patPiles() ) |
228 | { |
229 | xml.writeStartElement( "pile" ); |
230 | xml.writeAttribute( "index" , QString::number( p->index() ) ); |
231 | xml.writeAttribute( "z" , QString::number( p->zValue() ) ); |
232 | |
233 | foreach( const KCard * c, p->cards() ) |
234 | { |
235 | xml.writeStartElement( "card" ); |
236 | xml.writeAttribute( "suit" , QString::number( c->suit() ) ); |
237 | xml.writeAttribute( "value" , QString::number( c->rank() ) ); |
238 | xml.writeAttribute( "faceup" , QString::number( c->isFaceUp() ) ); |
239 | xml.writeAttribute( "z" , QString::number( c->zValue() ) ); |
240 | xml.writeEndElement(); |
241 | } |
242 | xml.writeEndElement(); |
243 | } |
244 | xml.writeEndElement(); |
245 | xml.writeEndDocument(); |
246 | |
247 | m_dealWasJustSaved = true; |
248 | } |
249 | |
250 | bool DealerScene::loadLegacyFile( QIODevice * io ) |
251 | { |
252 | resetInternals(); |
253 | |
254 | QXmlStreamReader xml( io ); |
255 | |
256 | xml.readNextStartElement(); |
257 | |
258 | // Before KDE4.3, KPat didn't store game specific options in the save |
259 | // file. This could cause crashes when loading a Spider game with a |
260 | // different number of suits than the current setting. Similarly, in |
261 | // Klondike the number of cards drawn from the deck was forgotten, but |
262 | // this never caused crashes. Fortunately, in Spider we can count the |
263 | // number of suits ourselves. For Klondike, there is no way to recover |
264 | // that information. |
265 | QString options = xml.attributes().value( "options" ).toString(); |
266 | if ( gameId() == 17 && options.isEmpty() ) |
267 | { |
268 | QSet<int> suits; |
269 | while ( !xml.atEnd() ) |
270 | if ( xml.readNextStartElement() && xml.name() == "card" ) |
271 | suits << readIntAttribute( xml, "suit" ); |
272 | options = QString::number( suits.size() ); |
273 | |
274 | // "Rewind" back to the <dealer> tag. Yes, this is ugly. |
275 | xml.clear(); |
276 | io->reset(); |
277 | xml.setDevice( io ); |
278 | xml.readNextStartElement(); |
279 | } |
280 | setGameOptions( options ); |
281 | |
282 | m_dealNumber = readIntAttribute( xml, "number" ); |
283 | m_loadedMoveCount = readIntAttribute( xml, "moves" ); |
284 | m_dealStarted = readIntAttribute( xml, "started" ); |
285 | QString gameStateData = xml.attributes().value( "data" ).toString(); |
286 | |
287 | QMultiHash<quint32,KCard*> cards; |
288 | foreach ( KCard * c, deck()->cards() ) |
289 | cards.insert( (c->id() & 0xffff), c ); |
290 | |
291 | QHash<int,PatPile*> piles; |
292 | foreach ( PatPile * p, patPiles() ) |
293 | piles.insert( p->index(), p ); |
294 | |
295 | // Loop through <pile>s. |
296 | while ( xml.readNextStartElement() ) |
297 | { |
298 | if ( xml.name() != "pile" ) |
299 | { |
300 | kWarning() << "Expected a \"pile\" tag. Found a" << xml.name() << "tag." ; |
301 | return false; |
302 | } |
303 | |
304 | bool ok; |
305 | int index = readIntAttribute( xml, "index" , &ok ); |
306 | QHash<int,PatPile*>::const_iterator it = piles.constFind( index ); |
307 | if ( !ok || it == piles.constEnd() ) |
308 | { |
309 | kWarning() << "Unrecognized pile index:" << xml.attributes().value( "index" ); |
310 | return false; |
311 | } |
312 | |
313 | PatPile * p = it.value(); |
314 | p->clear(); |
315 | |
316 | // Loop through <card>s. |
317 | while ( xml.readNextStartElement() ) |
318 | { |
319 | if ( xml.name() != "card" ) |
320 | { |
321 | kWarning() << "Expected a \"card\" tag. Found a" << xml.name() << "tag." ; |
322 | return false; |
323 | } |
324 | |
325 | bool suitOk, rankOk, faceUpOk; |
326 | int suit = readIntAttribute( xml, "suit" , &suitOk ); |
327 | int rank = readIntAttribute( xml, "value" , &rankOk ); |
328 | bool faceUp = readIntAttribute( xml, "faceup" , &faceUpOk ); |
329 | |
330 | quint32 id = KCardDeck::getId( KCardDeck::Suit( suit ), KCardDeck::Rank( rank ), 0 ); |
331 | KCard * card = cards.take( id ); |
332 | |
333 | if ( !suitOk || !rankOk || !faceUpOk || !card ) |
334 | { |
335 | kWarning() << "Unrecognized card: suit=" << xml.attributes().value("suit" ) |
336 | << " value=" << xml.attributes().value("value" ) |
337 | << " faceup=" << xml.attributes().value("faceup" ); |
338 | return false; |
339 | } |
340 | |
341 | card->setFaceUp( faceUp ); |
342 | p->add( card ); |
343 | |
344 | xml.skipCurrentElement(); |
345 | } |
346 | updatePileLayout( p, 0 ); |
347 | } |
348 | |
349 | setGameState( gameStateData ); |
350 | |
351 | |
352 | emit updateMoves( moveCount() ); |
353 | takeState(); |
354 | |
355 | return true; |
356 | } |
357 | |
358 | |
359 | void DealerScene::saveFile( QIODevice * io ) |
360 | { |
361 | QXmlStreamWriter xml( io ); |
362 | xml.setCodec( "UTF-8" ); |
363 | xml.setAutoFormatting( true ); |
364 | xml.setAutoFormattingIndent( -1 ); |
365 | xml.writeStartDocument(); |
366 | |
367 | xml.writeStartElement( "kpat-game" ); |
368 | xml.writeAttribute( "game-type" , m_di->baseIdString() ); |
369 | if ( !getGameOptions().isEmpty() ) |
370 | xml.writeAttribute( "game-type-options" , getGameOptions() ); |
371 | xml.writeAttribute( "deal-number" , QString::number( gameNumber() ) ); |
372 | |
373 | QList<GameState*> allStates; |
374 | for ( int i = 0; i < m_undoStack.size(); ++i ) |
375 | allStates << m_undoStack.at( i ); |
376 | allStates << m_currentState; |
377 | for ( int i = m_redoStack.size() - 1; i >= 0; --i ) |
378 | allStates << m_redoStack.at( i ); |
379 | |
380 | QString lastGameSpecificState; |
381 | |
382 | for ( int i = 0; i < allStates.size(); ++i ) |
383 | { |
384 | const GameState * state = allStates.at( i ); |
385 | xml.writeStartElement( "state" ); |
386 | if ( state->stateData != lastGameSpecificState ) |
387 | { |
388 | xml.writeAttribute( "game-specific-state" , state->stateData ); |
389 | lastGameSpecificState = state->stateData; |
390 | } |
391 | if ( i == m_undoStack.size() ) |
392 | xml.writeAttribute( "current" , "true" ); |
393 | |
394 | foreach ( const CardStateChange & change, state->changes ) |
395 | { |
396 | xml.writeStartElement( "move" ); |
397 | xml.writeAttribute( "pile" , change.newState.pile->objectName() ); |
398 | xml.writeAttribute( "position" , QString::number( change.newState.index ) ); |
399 | |
400 | bool faceChanged = !change.oldState.pile |
401 | || change.oldState.faceUp != change.newState.faceUp; |
402 | |
403 | foreach ( const KCard * card, change.cards ) |
404 | { |
405 | xml.writeStartElement( "card" ); |
406 | xml.writeAttribute( "id" , QString("%1" ).arg( card->id(), 7, 10, QChar('0') ) ); |
407 | xml.writeAttribute( "suit" , suitToString( card->suit() ) ); |
408 | xml.writeAttribute( "rank" , rankToString( card->rank() ) ); |
409 | if ( faceChanged ) |
410 | xml.writeAttribute( "turn" , change.newState.faceUp ? "face-up" : "face-down" ); |
411 | xml.writeEndElement(); |
412 | } |
413 | |
414 | xml.writeEndElement(); |
415 | } |
416 | xml.writeEndElement(); |
417 | } |
418 | |
419 | xml.writeEndElement(); |
420 | xml.writeEndDocument(); |
421 | |
422 | m_dealWasJustSaved = true; |
423 | } |
424 | |
425 | |
426 | bool DealerScene::loadFile( QIODevice * io ) |
427 | { |
428 | resetInternals(); |
429 | |
430 | bool reenableAutoDrop = autoDropEnabled(); |
431 | setAutoDropEnabled( false ); |
432 | |
433 | QXmlStreamReader xml( io ); |
434 | |
435 | xml.readNextStartElement(); |
436 | |
437 | if ( xml.name() != "kpat-game" ) |
438 | { |
439 | kWarning() << "First tag is not \"kpat-game\"" ; |
440 | return false; |
441 | } |
442 | |
443 | m_dealNumber = readIntAttribute( xml, "deal-number" ); |
444 | setGameOptions( xml.attributes().value( "game-type-options" ).toString() ); |
445 | |
446 | QMultiHash<quint32,KCard*> cardHash; |
447 | foreach ( KCard * c, deck()->cards() ) |
448 | cardHash.insert( c->id(), c ); |
449 | |
450 | QHash<QString,KCardPile*> pileHash; |
451 | foreach ( KCardPile * p, piles() ) |
452 | pileHash.insert( p->objectName(), p ); |
453 | |
454 | int undosToDo = -1; |
455 | |
456 | while( xml.readNextStartElement() ) |
457 | { |
458 | if ( xml.name() != "state" ) |
459 | { |
460 | kWarning() << "Expected a \"state\" tag. Found a" << xml.name() << "tag." ; |
461 | return false; |
462 | } |
463 | |
464 | if ( xml.attributes().hasAttribute( "game-specific-state" ) ) |
465 | setGameState( xml.attributes().value( "game-specific-state" ).toString() ); |
466 | |
467 | if ( undosToDo > -1 ) |
468 | ++undosToDo; |
469 | else if ( xml.attributes().value( "current" ) == "true" ) |
470 | undosToDo = 0; |
471 | |
472 | while( xml.readNextStartElement() ) |
473 | { |
474 | if ( xml.name() != "move" ) |
475 | { |
476 | kWarning() << "Expected a \"move\" tag. Found a" << xml.name() << "tag." ; |
477 | return false; |
478 | } |
479 | |
480 | QString pileName = xml.attributes().value( "pile" ).toString(); |
481 | KCardPile * pile = pileHash.value( pileName ); |
482 | |
483 | bool indexOk; |
484 | int index = readIntAttribute( xml, "position" , &indexOk ); |
485 | |
486 | if ( !pile || !indexOk ) |
487 | { |
488 | kWarning() << "Unrecognized pile or index." ; |
489 | return false; |
490 | } |
491 | |
492 | while ( xml.readNextStartElement() ) |
493 | { |
494 | if ( xml.name() != "card" ) |
495 | { |
496 | kWarning() << "Expected a \"card\" tag. Found a" << xml.name() << "tag." ; |
497 | return false; |
498 | } |
499 | |
500 | KCard * card = cardHash.value( readIntAttribute( xml, "id" ) ); |
501 | if ( !card ) |
502 | { |
503 | kWarning() << "Unrecognized card." ; |
504 | return false; |
505 | } |
506 | |
507 | if ( xml.attributes().value("turn" ) == "face-up" ) |
508 | card->setFaceUp( true ); |
509 | else if ( xml.attributes().value("turn" ) == "face-down" ) |
510 | card->setFaceUp( false ); |
511 | |
512 | pile->insert( index, card ); |
513 | |
514 | ++index; |
515 | xml.skipCurrentElement(); |
516 | } |
517 | } |
518 | takeState(); |
519 | } |
520 | |
521 | m_loadedMoveCount = 0; |
522 | m_dealStarted = moveCount() > 0; |
523 | emit updateMoves( moveCount() ); |
524 | |
525 | while ( undosToDo > 0 ) |
526 | { |
527 | undo(); |
528 | --undosToDo; |
529 | } |
530 | |
531 | foreach ( KCardPile * p, piles() ) |
532 | updatePileLayout( p, 0 ); |
533 | |
534 | setAutoDropEnabled( reenableAutoDrop ); |
535 | |
536 | return true; |
537 | } |
538 | |
539 | |
540 | DealerScene::DealerScene( const DealerInfo * di ) |
541 | : m_di( di ), |
542 | m_solver( 0 ), |
543 | m_solverThread( 0 ), |
544 | m_peekedCard( 0 ), |
545 | m_dealNumber( 0 ), |
546 | m_loadedMoveCount( 0 ), |
547 | m_neededFutureMoves( 1 ), |
548 | m_supportedActions( 0 ), |
549 | m_autoDropEnabled( false ), |
550 | m_solverEnabled( false ), |
551 | m_dealStarted( false ), |
552 | m_dealWasEverWinnable( false ), |
553 | m_dealHasBeenWon( false ), |
554 | m_dealWasJustSaved( false ), |
555 | m_statisticsRecorded( false ), |
556 | m_playerReceivedHelp( false ), |
557 | m_toldAboutWonGame( false ), |
558 | m_toldAboutLostGame( false ), |
559 | m_dropSpeedFactor( 1 ), |
560 | m_interruptAutoDrop( false ), |
561 | m_dealInProgress( false ), |
562 | m_hintInProgress( false ), |
563 | m_demoInProgress( false ), |
564 | m_dropInProgress( false ), |
565 | m_hintQueued( false ), |
566 | m_demoQueued( false ), |
567 | m_dropQueued( false ), |
568 | m_newCardsQueued( false ), |
569 | m_takeStateQueued( false ), |
570 | m_currentState( 0 ) |
571 | { |
572 | setItemIndexMethod(QGraphicsScene::NoIndex); |
573 | |
574 | m_solverUpdateTimer.setInterval( 250 ); |
575 | m_solverUpdateTimer.setSingleShot( true ); |
576 | connect( &m_solverUpdateTimer, SIGNAL(timeout()), SLOT(stopAndRestartSolver()) ); |
577 | |
578 | m_demoTimer.setSingleShot( true ); |
579 | connect( &m_demoTimer, SIGNAL(timeout()), SLOT(demo()) ); |
580 | |
581 | m_dropTimer.setSingleShot( true ); |
582 | connect( &m_dropTimer, SIGNAL(timeout()), this, SLOT(drop()) ); |
583 | |
584 | m_wonItem = new MessageBox(); |
585 | m_wonItem->setZValue( 2000 ); |
586 | m_wonItem->hide(); |
587 | addItem( m_wonItem ); |
588 | |
589 | connect( this, SIGNAL(cardAnimationDone()), this, SLOT(animationDone()) ); |
590 | |
591 | connect( this, SIGNAL(cardDoubleClicked(KCard*)), this, SLOT(tryAutomaticMove(KCard*)) ); |
592 | // Make rightClick == doubleClick. See bug #151921 |
593 | connect( this, SIGNAL(cardRightClicked(KCard*)), this, SLOT(tryAutomaticMove(KCard*)) ); |
594 | } |
595 | |
596 | DealerScene::~DealerScene() |
597 | { |
598 | stop(); |
599 | |
600 | disconnect(); |
601 | if ( m_solverThread ) |
602 | m_solverThread->abort(); |
603 | delete m_solverThread; |
604 | m_solverThread = 0; |
605 | delete m_solver; |
606 | m_solver = 0; |
607 | qDeleteAll( m_undoStack ); |
608 | delete m_currentState; |
609 | qDeleteAll( m_redoStack ); |
610 | delete m_wonItem; |
611 | } |
612 | |
613 | |
614 | void DealerScene::addPatPile( PatPile * pile ) |
615 | { |
616 | if ( !m_patPiles.contains( pile ) ) |
617 | m_patPiles.append( pile ); |
618 | } |
619 | |
620 | |
621 | void DealerScene::removePatPile( PatPile * pile ) |
622 | { |
623 | m_patPiles.removeAll( pile ); |
624 | } |
625 | |
626 | |
627 | QList<PatPile*> DealerScene::patPiles() const |
628 | { |
629 | return m_patPiles; |
630 | } |
631 | |
632 | |
633 | void DealerScene::cardsMoved( const QList<KCard*> & cards, KCardPile * oldPile, KCardPile * newPile ) |
634 | { |
635 | PatPile * newPatPile = dynamic_cast<PatPile*>( newPile ); |
636 | PatPile * oldPatPile = dynamic_cast<PatPile*>( oldPile ); |
637 | |
638 | if ( oldPatPile && oldPatPile->isFoundation() && newPatPile && !newPatPile->isFoundation() ) |
639 | { |
640 | foreach ( KCard * c, cards ) |
641 | m_cardsRemovedFromFoundations.insert( c ); |
642 | } |
643 | else |
644 | { |
645 | foreach ( KCard * c, cards ) |
646 | m_cardsRemovedFromFoundations.remove( c ); |
647 | } |
648 | |
649 | if ( !m_dropInProgress && !m_dealInProgress ) |
650 | { |
651 | m_dealStarted = true; |
652 | takeState(); |
653 | } |
654 | } |
655 | |
656 | |
657 | void DealerScene::startHint() |
658 | { |
659 | stopDemo(); |
660 | stopDrop(); |
661 | |
662 | if ( isCardAnimationRunning() ) |
663 | { |
664 | m_hintQueued = true; |
665 | return; |
666 | } |
667 | |
668 | if ( isKeyboardModeActive() ) |
669 | setKeyboardModeActive( false ); |
670 | |
671 | QList<QGraphicsItem*> toHighlight; |
672 | foreach ( const MoveHint & h, getHints() ) |
673 | toHighlight << h.card(); |
674 | |
675 | if ( !m_winningMoves.isEmpty() ) |
676 | { |
677 | MOVE m = m_winningMoves.first(); |
678 | MoveHint mh = solver()->translateMove( m ); |
679 | if ( mh.isValid() ) |
680 | toHighlight << mh.card(); |
681 | } |
682 | |
683 | m_hintInProgress = !toHighlight.isEmpty(); |
684 | setHighlightedItems( toHighlight ); |
685 | emit hintActive( m_hintInProgress ); |
686 | } |
687 | |
688 | |
689 | void DealerScene::stopHint() |
690 | { |
691 | if ( m_hintInProgress ) |
692 | { |
693 | m_hintInProgress = false; |
694 | clearHighlightedItems(); |
695 | emit hintActive( false ); |
696 | } |
697 | } |
698 | |
699 | |
700 | bool DealerScene::isHintActive() const |
701 | { |
702 | return m_hintInProgress; |
703 | } |
704 | |
705 | QList<MoveHint> DealerScene::getSolverHints() |
706 | { |
707 | QList<MoveHint> hintList; |
708 | |
709 | if ( m_solverThread && m_solverThread->isRunning() ) |
710 | m_solverThread->abort(); |
711 | |
712 | solver()->translate_layout(); |
713 | bool debug = false; |
714 | #if DEBUG_HINTS |
715 | debug = true; |
716 | #endif |
717 | solver()->patsolve( 1, debug ); |
718 | |
719 | foreach ( const MOVE & m, solver()->firstMoves ) |
720 | { |
721 | MoveHint mh = solver()->translateMove( m ); |
722 | hintList << mh; |
723 | } |
724 | return hintList; |
725 | } |
726 | |
727 | QList<MoveHint> DealerScene::getHints() |
728 | { |
729 | if ( solver() ) |
730 | return getSolverHints(); |
731 | |
732 | QList<MoveHint> hintList; |
733 | foreach (PatPile * store, patPiles()) |
734 | { |
735 | if (store->isFoundation() || store->isEmpty()) |
736 | continue; |
737 | |
738 | QList<KCard*> cards = store->cards(); |
739 | while (cards.count() && !cards.first()->isFaceUp()) |
740 | cards.erase(cards.begin()); |
741 | |
742 | QList<KCard*>::Iterator iti = cards.begin(); |
743 | while (iti != cards.end()) |
744 | { |
745 | if (allowedToRemove(store, (*iti))) |
746 | { |
747 | foreach (PatPile * dest, patPiles()) |
748 | { |
749 | int cardIndex = store->indexOf(*iti); |
750 | if (cardIndex == 0 && dest->isEmpty() && !dest->isFoundation()) |
751 | continue; |
752 | |
753 | if (!checkAdd(dest, dest->cards(), cards)) |
754 | continue; |
755 | |
756 | if (dest->isFoundation()) |
757 | { |
758 | hintList << MoveHint( *iti, dest, 127 ); |
759 | } |
760 | else |
761 | { |
762 | QList<KCard*> cardsBelow = cards.mid(0, cardIndex); |
763 | |
764 | // if it could be here as well, then it's no use |
765 | if ((cardsBelow.isEmpty() && !dest->isEmpty()) || !checkAdd(store, cardsBelow, cards)) |
766 | { |
767 | hintList << MoveHint( *iti, dest, 0 ); |
768 | } |
769 | else if (checkPrefering(dest, dest->cards(), cards) |
770 | && !checkPrefering(store, cardsBelow, cards)) |
771 | { // if checkPrefers says so, we add it nonetheless |
772 | hintList << MoveHint( *iti, dest, 10 ); |
773 | } |
774 | } |
775 | } |
776 | } |
777 | cards.erase(iti); |
778 | iti = cards.begin(); |
779 | } |
780 | } |
781 | return hintList; |
782 | } |
783 | |
784 | static bool prioSort(const MoveHint &c1, const MoveHint &c2) |
785 | { |
786 | return c1.priority() < c2.priority(); |
787 | } |
788 | |
789 | |
790 | MoveHint DealerScene::chooseHint() |
791 | { |
792 | if ( !m_winningMoves.isEmpty() ) |
793 | { |
794 | MOVE m = m_winningMoves.takeFirst(); |
795 | MoveHint mh = solver()->translateMove( m ); |
796 | |
797 | #if DEBUG_HINTS |
798 | if ( m.totype == O_Type ) |
799 | fprintf( stderr, "move from %d out (at %d) Prio: %d\n" , m.from, |
800 | m.turn_index, m.pri ); |
801 | else |
802 | fprintf( stderr, "move from %d to %d (%d) Prio: %d\n" , m.from, m.to, |
803 | m.turn_index, m.pri ); |
804 | #endif |
805 | |
806 | return mh; |
807 | } |
808 | |
809 | QList<MoveHint> hintList = getHints(); |
810 | |
811 | if ( hintList.isEmpty() ) |
812 | { |
813 | return MoveHint(); |
814 | } |
815 | else |
816 | { |
817 | // Generate a random number with an exponentional distribution averaging 1/4. |
818 | qreal randomExp = qMin<qreal>( -log( 1 - qreal( KRandom::random() ) / RAND_MAX ) / 4, 1 ); |
819 | int randomIndex = randomExp * ( hintList.size() - 1 ); |
820 | |
821 | qSort(hintList.begin(), hintList.end(), prioSort); |
822 | return hintList.at( randomIndex ); |
823 | } |
824 | } |
825 | |
826 | |
827 | void DealerScene::startNew( int dealNumber ) |
828 | { |
829 | if ( dealNumber != -1 ) |
830 | m_dealNumber = qBound( 1, dealNumber, INT_MAX ); |
831 | |
832 | // Only record the statistics and reset gameStarted if we're starting a |
833 | // new game number or we're restarting a game we've already won. |
834 | if ( dealNumber != -1 || m_dealHasBeenWon ) |
835 | { |
836 | recordGameStatistics(); |
837 | m_statisticsRecorded = false; |
838 | m_dealStarted = false; |
839 | } |
840 | |
841 | if ( isCardAnimationRunning() ) |
842 | { |
843 | QTimer::singleShot( 100, this, SLOT(startNew()) ); |
844 | return; |
845 | } |
846 | |
847 | if ( m_solverThread && m_solverThread->isRunning() ) |
848 | m_solverThread->abort(); |
849 | |
850 | resetInternals(); |
851 | |
852 | emit updateMoves( 0 ); |
853 | |
854 | foreach( KCardPile * p, piles() ) |
855 | p->clear(); |
856 | |
857 | m_dealInProgress = true; |
858 | restart( shuffled( deck()->cards(), m_dealNumber ) ); |
859 | m_dealInProgress = false; |
860 | |
861 | takeState(); |
862 | update(); |
863 | } |
864 | |
865 | void DealerScene::resetInternals() |
866 | { |
867 | stop(); |
868 | |
869 | setKeyboardModeActive( false ); |
870 | |
871 | m_dealHasBeenWon = false; |
872 | m_wonItem->hide(); |
873 | |
874 | qDeleteAll( m_undoStack ); |
875 | m_undoStack.clear(); |
876 | delete m_currentState; |
877 | m_currentState = 0; |
878 | qDeleteAll( m_redoStack ); |
879 | m_redoStack.clear(); |
880 | m_lastKnownCardStates.clear(); |
881 | |
882 | m_dealWasJustSaved = false; |
883 | m_dealWasEverWinnable = false; |
884 | m_toldAboutLostGame = false; |
885 | m_toldAboutWonGame = false; |
886 | m_loadedMoveCount = 0; |
887 | |
888 | m_playerReceivedHelp = false; |
889 | |
890 | m_dealInProgress = false; |
891 | |
892 | m_dropInProgress = false; |
893 | m_dropSpeedFactor = 1; |
894 | m_cardsRemovedFromFoundations.clear(); |
895 | |
896 | foreach (KCard * c, deck()->cards()) |
897 | { |
898 | c->disconnect( this ); |
899 | c->stopAnimation(); |
900 | } |
901 | |
902 | emit solverStateChanged( QString() ); |
903 | emit gameInProgress( true ); |
904 | } |
905 | |
906 | QPointF posAlongRect( qreal distOnRect, const QRectF & rect ) |
907 | { |
908 | if ( distOnRect < rect.width() ) |
909 | return rect.topLeft() + QPointF( distOnRect, 0 ); |
910 | distOnRect -= rect.width(); |
911 | if ( distOnRect < rect.height() ) |
912 | return rect.topRight() + QPointF( 0, distOnRect ); |
913 | distOnRect -= rect.height(); |
914 | if ( distOnRect < rect.width() ) |
915 | return rect.bottomRight() + QPointF( -distOnRect, 0 ); |
916 | distOnRect -= rect.width(); |
917 | return rect.bottomLeft() + QPointF( 0, -distOnRect ); |
918 | } |
919 | |
920 | void DealerScene::won() |
921 | { |
922 | if ( m_dealHasBeenWon ) |
923 | return; |
924 | |
925 | m_dealHasBeenWon = true; |
926 | m_toldAboutWonGame = true; |
927 | |
928 | stopDemo(); |
929 | recordGameStatistics(); |
930 | |
931 | emit solverStateChanged( QString() ); |
932 | |
933 | emit newCardsPossible( false ); |
934 | emit undoPossible( false ); |
935 | emit redoPossible( false ); |
936 | emit gameInProgress( false ); |
937 | |
938 | setKeyboardModeActive( false ); |
939 | |
940 | qreal speed = sqrt( width() * width() + height() * height() ) / ( DURATION_WON ); |
941 | |
942 | QRectF justOffScreen = sceneRect().adjusted( -deck()->cardWidth(), -deck()->cardHeight(), 0, 0 ); |
943 | qreal spacing = 2 * ( justOffScreen.width() + justOffScreen.height() ) / deck()->cards().size(); |
944 | qreal distOnRect = 0; |
945 | |
946 | foreach ( KCard *c, deck()->cards() ) |
947 | { |
948 | distOnRect += spacing; |
949 | QPointF pos2 = posAlongRect( distOnRect, justOffScreen ); |
950 | QPointF delta = c->pos() - pos2; |
951 | qreal dist = sqrt( delta.x() * delta.x() + delta.y() * delta.y() ); |
952 | |
953 | c->setFaceUp( true ); |
954 | c->animate( pos2, c->zValue(), 0, true, false, dist / speed ); |
955 | } |
956 | |
957 | connect(deck(), SIGNAL(cardAnimationDone()), this, SLOT(showWonMessage())); |
958 | } |
959 | |
960 | void DealerScene::showWonMessage() |
961 | { |
962 | disconnect(deck(), SIGNAL(cardAnimationDone()), this, SLOT(showWonMessage())); |
963 | |
964 | // It shouldn't be necessary to stop the demo yet again here, but we |
965 | // get crashes if we don't. Will have to look into this further. |
966 | stopDemo(); |
967 | |
968 | // Hide all cards to prevent them from showing up accidentally if the |
969 | // window is resized. |
970 | foreach ( KCard * c, deck()->cards() ) |
971 | c->hide(); |
972 | |
973 | updateWonItem(); |
974 | m_wonItem->show(); |
975 | } |
976 | |
977 | void DealerScene::updateWonItem() |
978 | { |
979 | const qreal aspectRatio = Renderer::self()->aspectRatioOfElement("message_frame" ); |
980 | int boxWidth; |
981 | int boxHeight; |
982 | if ( width() < aspectRatio * height() ) |
983 | { |
984 | boxWidth = width() * wonBoxToSceneSizeRatio; |
985 | boxHeight = boxWidth / aspectRatio; |
986 | } |
987 | else |
988 | { |
989 | boxHeight = height() * wonBoxToSceneSizeRatio; |
990 | boxWidth = boxHeight * aspectRatio; |
991 | } |
992 | m_wonItem->setSize( QSize( boxWidth, boxHeight ) ); |
993 | |
994 | if ( m_playerReceivedHelp ) |
995 | m_wonItem->setMessage( i18n( "Congratulations! We have won." ) ); |
996 | else |
997 | m_wonItem->setMessage( i18n( "Congratulations! You have won." ) ); |
998 | |
999 | m_wonItem->setPos( QPointF( (width() - boxWidth) / 2, (height() - boxHeight) / 2 ) |
1000 | + sceneRect().topLeft() ); |
1001 | } |
1002 | |
1003 | |
1004 | bool DealerScene::allowedToAdd( const KCardPile * pile, const QList<KCard*> & cards ) const |
1005 | { |
1006 | if ( !pile->isEmpty() && !pile->topCard()->isFaceUp() ) |
1007 | return false; |
1008 | |
1009 | const PatPile * p = dynamic_cast<const PatPile*>( pile ); |
1010 | return p && checkAdd( p, p->cards(), cards ); |
1011 | } |
1012 | |
1013 | |
1014 | bool DealerScene::allowedToRemove( const KCardPile * pile, const KCard * card ) const |
1015 | { |
1016 | const PatPile * p = dynamic_cast<const PatPile*>( pile ); |
1017 | QList<KCard*> cards = pile->topCardsDownTo( card ); |
1018 | return p |
1019 | && card->isFaceUp() |
1020 | && !cards.isEmpty() |
1021 | && checkRemove( p, cards ); |
1022 | } |
1023 | |
1024 | |
1025 | bool DealerScene::checkAdd( const PatPile * pile, const QList<KCard*> & oldCards, const QList<KCard*> & newCards ) const |
1026 | { |
1027 | Q_UNUSED( pile ) |
1028 | Q_UNUSED( oldCards ) |
1029 | Q_UNUSED( newCards ) |
1030 | return false; |
1031 | } |
1032 | |
1033 | |
1034 | bool DealerScene::checkRemove(const PatPile * pile, const QList<KCard*> & cards) const |
1035 | { |
1036 | Q_UNUSED( pile ) |
1037 | Q_UNUSED( cards ) |
1038 | return false; |
1039 | } |
1040 | |
1041 | |
1042 | bool DealerScene::checkPrefering( const PatPile * pile, const QList<KCard*> & oldCards, const QList<KCard*> & newCards ) const |
1043 | { |
1044 | Q_UNUSED( pile ) |
1045 | Q_UNUSED( oldCards ) |
1046 | Q_UNUSED( newCards ) |
1047 | return false; |
1048 | } |
1049 | |
1050 | |
1051 | void DealerScene::mousePressEvent( QGraphicsSceneMouseEvent * e ) |
1052 | { |
1053 | stop(); |
1054 | |
1055 | KCard * card = qgraphicsitem_cast<KCard*>( itemAt( e->scenePos() ) ); |
1056 | |
1057 | if ( m_peekedCard ) |
1058 | { |
1059 | e->accept(); |
1060 | } |
1061 | else if ( e->button() == Qt::RightButton |
1062 | && card |
1063 | && card->pile() |
1064 | && card != card->pile()->topCard() |
1065 | && cardsBeingDragged().isEmpty() |
1066 | && !isCardAnimationRunning() ) |
1067 | { |
1068 | e->accept(); |
1069 | m_peekedCard = card; |
1070 | QPointF pos2( card->x() + deck()->cardWidth() / 3.0, card->y() - deck()->cardHeight() / 3.0 ); |
1071 | card->setZValue( card->zValue() + 0.1 ); |
1072 | card->animate( pos2, card->zValue(), 20, card->isFaceUp(), false, DURATION_FANCYSHOW ); |
1073 | } |
1074 | else |
1075 | { |
1076 | KCardScene::mousePressEvent( e ); |
1077 | if ( !cardsBeingDragged().isEmpty() ) |
1078 | emit cardsPickedUp(); |
1079 | } |
1080 | } |
1081 | |
1082 | |
1083 | void DealerScene::mouseReleaseEvent( QGraphicsSceneMouseEvent * e ) |
1084 | { |
1085 | stop(); |
1086 | |
1087 | if ( e->button() == Qt::RightButton && m_peekedCard && m_peekedCard->pile() ) |
1088 | { |
1089 | e->accept(); |
1090 | updatePileLayout( m_peekedCard->pile(), DURATION_FANCYSHOW ); |
1091 | m_peekedCard = 0; |
1092 | } |
1093 | else |
1094 | { |
1095 | bool hadCards = !cardsBeingDragged().isEmpty(); |
1096 | KCardScene::mouseReleaseEvent( e ); |
1097 | if ( hadCards && cardsBeingDragged().isEmpty() ) |
1098 | emit cardsPutDown(); |
1099 | } |
1100 | } |
1101 | |
1102 | |
1103 | void DealerScene::mouseDoubleClickEvent( QGraphicsSceneMouseEvent * e ) |
1104 | { |
1105 | stop(); |
1106 | |
1107 | KCardScene::mouseDoubleClickEvent( e ); |
1108 | } |
1109 | |
1110 | |
1111 | bool DealerScene::tryAutomaticMove( KCard * card ) |
1112 | { |
1113 | if ( !isCardAnimationRunning() |
1114 | && card |
1115 | && card->pile() |
1116 | && card == card->pile()->topCard() |
1117 | && card->isFaceUp() |
1118 | && allowedToRemove( card->pile(), card ) ) |
1119 | { |
1120 | QList<KCard*> cardList = QList<KCard*>() << card; |
1121 | |
1122 | foreach ( PatPile * p, patPiles() ) |
1123 | { |
1124 | if ( p->isFoundation() && allowedToAdd( p, cardList ) ) |
1125 | { |
1126 | moveCardToPile( card , p, DURATION_MOVE ); |
1127 | return true; |
1128 | } |
1129 | } |
1130 | } |
1131 | |
1132 | return false; |
1133 | } |
1134 | |
1135 | |
1136 | void DealerScene::undo() |
1137 | { |
1138 | undoOrRedo( true ); |
1139 | } |
1140 | |
1141 | |
1142 | void DealerScene::redo() |
1143 | { |
1144 | undoOrRedo( false ); |
1145 | } |
1146 | |
1147 | |
1148 | void DealerScene::undoOrRedo( bool undo ) |
1149 | { |
1150 | stop(); |
1151 | |
1152 | if ( isCardAnimationRunning() ) |
1153 | return; |
1154 | |
1155 | // The undo and redo actions are almost identical, except for where states |
1156 | // are pulled from and pushed to, so to keep things generic, we use |
1157 | // direction dependent const references throughout this code. |
1158 | QStack<GameState*> & fromStack = undo ? m_undoStack : m_redoStack; |
1159 | QStack<GameState*> & toStack = undo ? m_redoStack : m_undoStack; |
1160 | |
1161 | if ( !fromStack.isEmpty() && m_currentState ) |
1162 | { |
1163 | // If we're undoing, we use the oldStates of the changes of the current |
1164 | // state. If we're redoing, we use the newStates of the changes of the |
1165 | // nextState. |
1166 | const QList<CardStateChange> & changes = undo ? m_currentState->changes |
1167 | : fromStack.top()->changes; |
1168 | |
1169 | // Update the currentState pointer and undo/redo stacks. |
1170 | toStack.push( m_currentState ); |
1171 | m_currentState = fromStack.pop(); |
1172 | setGameState( m_currentState->stateData ); |
1173 | |
1174 | QSet<KCardPile*> pilesAffected; |
1175 | foreach ( const CardStateChange & change, changes ) |
1176 | { |
1177 | CardState sourceState = undo ? change.newState : change.oldState; |
1178 | CardState destState = undo ? change.oldState : change.newState; |
1179 | |
1180 | PatPile * sourcePile = dynamic_cast<PatPile*>( sourceState.pile ); |
1181 | PatPile * destPile = dynamic_cast<PatPile*>( destState.pile ); |
1182 | bool notDroppable = destState.takenDown |
1183 | || ((sourcePile && sourcePile->isFoundation()) |
1184 | && !(destPile && destPile->isFoundation())); |
1185 | |
1186 | pilesAffected << sourceState.pile << destState.pile; |
1187 | |
1188 | foreach ( KCard * c, change.cards ) |
1189 | { |
1190 | m_lastKnownCardStates.insert( c, destState ); |
1191 | |
1192 | c->setFaceUp( destState.faceUp ); |
1193 | destState.pile->insert( destState.index, c ); |
1194 | |
1195 | if ( notDroppable ) |
1196 | m_cardsRemovedFromFoundations.insert( c ); |
1197 | else |
1198 | m_cardsRemovedFromFoundations.remove( c ); |
1199 | |
1200 | ++sourceState.index; |
1201 | ++destState.index; |
1202 | } |
1203 | } |
1204 | |
1205 | // At this point all cards should be in the right piles, but not |
1206 | // necessarily at the right positions within those piles. So we |
1207 | // run through the piles involved and swap card positions until |
1208 | // everything is back in its place, then relayout the piles. |
1209 | foreach( KCardPile * p, pilesAffected ) |
1210 | { |
1211 | int i = 0; |
1212 | while ( i < p->count() ) |
1213 | { |
1214 | int index = m_lastKnownCardStates.value( p->at( i ) ).index; |
1215 | if ( i == index ) |
1216 | ++i; |
1217 | else |
1218 | p->swapCards( i, index ); |
1219 | } |
1220 | |
1221 | updatePileLayout( p, 0 ); |
1222 | } |
1223 | |
1224 | emit updateMoves( moveCount() ); |
1225 | emit undoPossible( !m_undoStack.isEmpty() ); |
1226 | emit redoPossible( !m_redoStack.isEmpty() ); |
1227 | |
1228 | if ( m_toldAboutLostGame ) // everything's possible again |
1229 | { |
1230 | gameInProgress( true ); |
1231 | m_toldAboutLostGame = false; |
1232 | m_toldAboutWonGame = false; |
1233 | } |
1234 | |
1235 | int solvability = m_currentState->solvability; |
1236 | m_winningMoves = m_currentState->winningMoves; |
1237 | |
1238 | emit solverStateChanged( solverStatusMessage( solvability, m_dealWasEverWinnable ) ); |
1239 | |
1240 | if ( m_solver && ( solvability == Solver::SearchAborted |
1241 | || solvability == Solver::MemoryLimitReached ) ) |
1242 | { |
1243 | startSolver(); |
1244 | } |
1245 | } |
1246 | } |
1247 | |
1248 | |
1249 | void DealerScene::takeState() |
1250 | { |
1251 | if ( isCardAnimationRunning() ) |
1252 | { |
1253 | m_takeStateQueued = true; |
1254 | return; |
1255 | } |
1256 | |
1257 | if ( !isDemoActive() ) |
1258 | m_winningMoves.clear(); |
1259 | |
1260 | QList<CardStateChange> changes; |
1261 | |
1262 | foreach ( KCardPile * p, piles() ) |
1263 | { |
1264 | QList<KCard*> currentRun; |
1265 | CardState oldRunState; |
1266 | CardState newRunState; |
1267 | |
1268 | for ( int i = 0; i < p->count(); ++i ) |
1269 | { |
1270 | KCard * c = p->at( i ); |
1271 | |
1272 | const CardState & oldState = m_lastKnownCardStates.value( c ); |
1273 | CardState newState( p, i, c->isFaceUp(), m_cardsRemovedFromFoundations.contains( c ) ); |
1274 | |
1275 | // The card has changed. |
1276 | if ( newState != oldState ) |
1277 | { |
1278 | // There's a run in progress, but this card isn't part of it. |
1279 | if ( !currentRun.isEmpty() |
1280 | && (oldState.pile != oldRunState.pile |
1281 | || (oldState.index != -1 && oldState.index != oldRunState.index + currentRun.size()) |
1282 | || oldState.faceUp != oldRunState.faceUp |
1283 | || newState.faceUp != newRunState.faceUp |
1284 | || oldState.takenDown != oldRunState.takenDown |
1285 | || newState.takenDown != newRunState.takenDown) ) |
1286 | { |
1287 | changes << CardStateChange( oldRunState, newRunState, currentRun ); |
1288 | currentRun.clear(); |
1289 | } |
1290 | |
1291 | // This card is the start of a new run. |
1292 | if ( currentRun.isEmpty() ) |
1293 | { |
1294 | oldRunState = oldState; |
1295 | newRunState = newState; |
1296 | } |
1297 | |
1298 | currentRun << c; |
1299 | |
1300 | m_lastKnownCardStates.insert( c, newState ); |
1301 | } |
1302 | } |
1303 | // Add the last run, if any. |
1304 | if ( !currentRun.isEmpty() ) |
1305 | { |
1306 | changes << CardStateChange( oldRunState, newRunState, currentRun ); |
1307 | } |
1308 | } |
1309 | |
1310 | // If nothing has changed, we're done. |
1311 | if ( changes.isEmpty() |
1312 | && m_currentState |
1313 | && m_currentState->stateData == getGameState() ) |
1314 | { |
1315 | return; |
1316 | } |
1317 | |
1318 | if ( m_currentState ) |
1319 | { |
1320 | m_undoStack.push( m_currentState ); |
1321 | qDeleteAll( m_redoStack ); |
1322 | m_redoStack.clear(); |
1323 | } |
1324 | m_currentState = new GameState( changes, getGameState() ); |
1325 | |
1326 | emit redoPossible( false ); |
1327 | emit undoPossible( !m_undoStack.isEmpty() ); |
1328 | emit updateMoves( moveCount() ); |
1329 | |
1330 | m_dealWasJustSaved = false; |
1331 | if ( isGameWon() ) |
1332 | { |
1333 | won(); |
1334 | return; |
1335 | } |
1336 | |
1337 | if ( !m_toldAboutWonGame && !m_toldAboutLostGame && isGameLost() ) |
1338 | { |
1339 | emit gameInProgress( false ); |
1340 | emit solverStateChanged( i18n( "Solver: This game is lost." ) ); |
1341 | m_toldAboutLostGame = true; |
1342 | stopDemo(); |
1343 | return; |
1344 | } |
1345 | |
1346 | if ( !isDemoActive() && !isCardAnimationRunning() && m_solver ) |
1347 | startSolver(); |
1348 | |
1349 | if ( autoDropEnabled() && !isDropActive() && !isDemoActive() && m_redoStack.isEmpty() ) |
1350 | { |
1351 | if ( m_interruptAutoDrop ) |
1352 | m_interruptAutoDrop = false; |
1353 | else |
1354 | startDrop(); |
1355 | } |
1356 | } |
1357 | |
1358 | |
1359 | void DealerScene::setSolverEnabled(bool a) |
1360 | { |
1361 | m_solverEnabled = a; |
1362 | } |
1363 | |
1364 | |
1365 | void DealerScene::setAutoDropEnabled( bool enabled ) |
1366 | { |
1367 | m_autoDropEnabled = enabled; |
1368 | } |
1369 | |
1370 | |
1371 | bool DealerScene::autoDropEnabled() const |
1372 | { |
1373 | return m_autoDropEnabled; |
1374 | } |
1375 | |
1376 | |
1377 | void DealerScene::startDrop() |
1378 | { |
1379 | stopHint(); |
1380 | stopDemo(); |
1381 | |
1382 | if ( isCardAnimationRunning() ) |
1383 | { |
1384 | m_dropQueued = true; |
1385 | return; |
1386 | } |
1387 | |
1388 | m_dropInProgress = true; |
1389 | m_interruptAutoDrop = false; |
1390 | m_dropSpeedFactor = 1; |
1391 | emit dropActive( true ); |
1392 | |
1393 | drop(); |
1394 | } |
1395 | |
1396 | |
1397 | void DealerScene::stopDrop() |
1398 | { |
1399 | if ( m_dropInProgress ) |
1400 | { |
1401 | m_dropTimer.stop(); |
1402 | m_dropInProgress = false; |
1403 | emit dropActive( false ); |
1404 | |
1405 | if ( autoDropEnabled() && m_takeStateQueued ) |
1406 | m_interruptAutoDrop = true; |
1407 | } |
1408 | } |
1409 | |
1410 | |
1411 | bool DealerScene::isDropActive() const |
1412 | { |
1413 | return m_dropInProgress; |
1414 | } |
1415 | |
1416 | |
1417 | bool DealerScene::drop() |
1418 | { |
1419 | foreach ( const MoveHint & mh, getHints() ) |
1420 | { |
1421 | if ( mh.pile() |
1422 | && mh.pile()->isFoundation() |
1423 | && mh.priority() > 120 |
1424 | && !m_cardsRemovedFromFoundations.contains( mh.card() ) ) |
1425 | { |
1426 | QList<KCard*> cards = mh.card()->pile()->topCardsDownTo( mh.card() ); |
1427 | |
1428 | QMap<KCard*,QPointF> oldPositions; |
1429 | foreach ( KCard * c, cards ) |
1430 | oldPositions.insert( c, c->pos() ); |
1431 | |
1432 | moveCardsToPile( cards, mh.pile(), DURATION_MOVE ); |
1433 | |
1434 | int count = 0; |
1435 | foreach ( KCard * c, cards ) |
1436 | { |
1437 | c->completeAnimation(); |
1438 | QPointF destPos = c->pos(); |
1439 | c->setPos( oldPositions.value( c ) ); |
1440 | |
1441 | int duration = speedUpTime( DURATION_AUTODROP + count * DURATION_AUTODROP / 10 ); |
1442 | c->animate( destPos, c->zValue(), 0, c->isFaceUp(), true, duration ); |
1443 | |
1444 | ++count; |
1445 | } |
1446 | |
1447 | m_dropSpeedFactor *= AUTODROP_SPEEDUP_FACTOR; |
1448 | |
1449 | takeState(); |
1450 | |
1451 | return true; |
1452 | } |
1453 | } |
1454 | |
1455 | m_dropInProgress = false; |
1456 | emit dropActive( false ); |
1457 | |
1458 | return false; |
1459 | } |
1460 | |
1461 | int DealerScene::speedUpTime( int delay ) const |
1462 | { |
1463 | if ( delay < DURATION_AUTODROP_MINIMUM ) |
1464 | return delay; |
1465 | else |
1466 | return qMax<int>( delay * m_dropSpeedFactor, DURATION_AUTODROP_MINIMUM ); |
1467 | } |
1468 | |
1469 | void DealerScene::stopAndRestartSolver() |
1470 | { |
1471 | if ( m_toldAboutLostGame || m_toldAboutWonGame ) // who cares? |
1472 | return; |
1473 | |
1474 | if ( m_solverThread && m_solverThread->isRunning() ) |
1475 | { |
1476 | m_solverThread->abort(); |
1477 | } |
1478 | |
1479 | if ( isCardAnimationRunning() ) |
1480 | { |
1481 | startSolver(); |
1482 | return; |
1483 | } |
1484 | |
1485 | slotSolverEnded(); |
1486 | } |
1487 | |
1488 | void DealerScene::slotSolverEnded() |
1489 | { |
1490 | if ( m_solverThread && m_solverThread->isRunning() ) |
1491 | return; |
1492 | |
1493 | m_solver->translate_layout(); |
1494 | m_winningMoves.clear(); |
1495 | emit solverStateChanged( i18n("Solver: Calculating..." ) ); |
1496 | if ( !m_solverThread ) |
1497 | { |
1498 | m_solverThread = new SolverThread( m_solver ); |
1499 | connect( m_solverThread, SIGNAL(finished(int)), this, SLOT(slotSolverFinished(int))); |
1500 | } |
1501 | m_solverThread->start( m_solverEnabled ? QThread::IdlePriority : QThread::NormalPriority ); |
1502 | } |
1503 | |
1504 | |
1505 | void DealerScene::slotSolverFinished( int result ) |
1506 | { |
1507 | if ( result == Solver::SolutionExists ) |
1508 | { |
1509 | m_winningMoves = m_solver->winMoves; |
1510 | m_dealWasEverWinnable = true; |
1511 | } |
1512 | |
1513 | emit solverStateChanged( solverStatusMessage( result, m_dealWasEverWinnable ) ); |
1514 | |
1515 | if ( m_currentState ) |
1516 | { |
1517 | m_currentState->solvability = static_cast<Solver::ExitStatus>( result ); |
1518 | m_currentState->winningMoves = m_winningMoves; |
1519 | } |
1520 | |
1521 | if ( result == Solver::SearchAborted ) |
1522 | startSolver(); |
1523 | } |
1524 | |
1525 | |
1526 | int DealerScene::() const |
1527 | { |
1528 | return m_dealNumber; |
1529 | } |
1530 | |
1531 | |
1532 | void DealerScene::stop() |
1533 | { |
1534 | stopHint(); |
1535 | stopDemo(); |
1536 | stopDrop(); |
1537 | } |
1538 | |
1539 | |
1540 | void DealerScene::animationDone() |
1541 | { |
1542 | Q_ASSERT( !isCardAnimationRunning() ); |
1543 | |
1544 | if ( !m_multiStepMoves.isEmpty() ) |
1545 | { |
1546 | continueMultiStepMove(); |
1547 | return; |
1548 | } |
1549 | |
1550 | if ( m_takeStateQueued ) |
1551 | { |
1552 | m_takeStateQueued = false; |
1553 | takeState(); |
1554 | } |
1555 | |
1556 | if ( m_demoInProgress ) |
1557 | { |
1558 | m_demoTimer.start( TIME_BETWEEN_MOVES ); |
1559 | } |
1560 | else if ( m_dropInProgress ) |
1561 | { |
1562 | m_dropTimer.start( speedUpTime( TIME_BETWEEN_MOVES ) ); |
1563 | } |
1564 | else if ( m_newCardsQueued ) |
1565 | { |
1566 | m_newCardsQueued = false; |
1567 | newCards(); |
1568 | } |
1569 | else if ( m_hintQueued ) |
1570 | { |
1571 | m_hintQueued = false; |
1572 | startHint(); |
1573 | } |
1574 | else if ( m_demoQueued ) |
1575 | { |
1576 | m_demoQueued = false; |
1577 | startDemo(); |
1578 | } |
1579 | else if ( m_dropQueued ) |
1580 | { |
1581 | m_dropQueued = false; |
1582 | startDrop(); |
1583 | } |
1584 | } |
1585 | |
1586 | |
1587 | void DealerScene::startDemo() |
1588 | { |
1589 | stopHint(); |
1590 | stopDrop(); |
1591 | |
1592 | if ( isCardAnimationRunning() ) |
1593 | { |
1594 | m_demoQueued = true; |
1595 | return; |
1596 | } |
1597 | |
1598 | m_demoInProgress = true; |
1599 | m_playerReceivedHelp = true; |
1600 | m_dealStarted = true; |
1601 | |
1602 | demo(); |
1603 | } |
1604 | |
1605 | |
1606 | void DealerScene::stopDemo() |
1607 | { |
1608 | if ( m_demoInProgress ) |
1609 | { |
1610 | m_demoTimer.stop(); |
1611 | m_demoInProgress = false; |
1612 | emit demoActive( false ); |
1613 | } |
1614 | } |
1615 | |
1616 | |
1617 | bool DealerScene::isDemoActive() const |
1618 | { |
1619 | return m_demoInProgress; |
1620 | } |
1621 | |
1622 | |
1623 | void DealerScene::demo() |
1624 | { |
1625 | if ( isCardAnimationRunning() ) |
1626 | { |
1627 | m_demoQueued = true; |
1628 | return; |
1629 | } |
1630 | |
1631 | m_demoInProgress = true; |
1632 | m_playerReceivedHelp = true; |
1633 | m_dealStarted = true; |
1634 | clearHighlightedItems(); |
1635 | |
1636 | m_demoTimer.stop(); |
1637 | |
1638 | MoveHint mh = chooseHint(); |
1639 | if ( mh.isValid() ) |
1640 | { |
1641 | KCard * card = mh.card(); |
1642 | Q_ASSERT( card ); |
1643 | KCardPile * sourcePile = mh.card()->pile(); |
1644 | Q_ASSERT( sourcePile ); |
1645 | Q_ASSERT( allowedToRemove( sourcePile, card ) ); |
1646 | PatPile * destPile = mh.pile(); |
1647 | Q_ASSERT( destPile ); |
1648 | Q_ASSERT( sourcePile != destPile ); |
1649 | QList<KCard*> cards = sourcePile->topCardsDownTo( card ); |
1650 | Q_ASSERT( allowedToAdd( destPile, cards ) ); |
1651 | |
1652 | if ( destPile->isEmpty() ) |
1653 | { |
1654 | kDebug() << "Moving" << card->objectName() |
1655 | << "from the" << sourcePile->objectName() |
1656 | << "pile to the" << destPile->objectName() |
1657 | << "pile, which is empty" ; |
1658 | } |
1659 | else |
1660 | { |
1661 | kDebug() << "Moving" << card->objectName() |
1662 | << "from the" << sourcePile->objectName() |
1663 | << "pile to the" << destPile->objectName() |
1664 | << "pile, putting it on top of" |
1665 | << destPile->topCard()->objectName(); |
1666 | } |
1667 | |
1668 | moveCardsToPile( cards, destPile, DURATION_DEMO ); |
1669 | } |
1670 | else if ( !newCards() ) |
1671 | { |
1672 | if (isGameWon()) |
1673 | { |
1674 | won(); |
1675 | } |
1676 | else |
1677 | { |
1678 | stopDemo(); |
1679 | slotSolverEnded(); |
1680 | } |
1681 | return; |
1682 | } |
1683 | |
1684 | emit demoActive( true ); |
1685 | takeState(); |
1686 | } |
1687 | |
1688 | |
1689 | void DealerScene::drawDealRowOrRedeal() |
1690 | { |
1691 | stop(); |
1692 | |
1693 | if ( isCardAnimationRunning() ) |
1694 | { |
1695 | m_newCardsQueued = true; |
1696 | return; |
1697 | } |
1698 | |
1699 | m_newCardsQueued = false; |
1700 | newCards(); |
1701 | } |
1702 | |
1703 | |
1704 | bool DealerScene::newCards() |
1705 | { |
1706 | return false; |
1707 | } |
1708 | |
1709 | |
1710 | void DealerScene::setSolver( Solver *s) { |
1711 | delete m_solver; |
1712 | delete m_solverThread; |
1713 | m_solver = s; |
1714 | m_solverThread = 0; |
1715 | } |
1716 | |
1717 | bool DealerScene::isGameWon() const |
1718 | { |
1719 | foreach (PatPile *p, patPiles()) |
1720 | { |
1721 | if (!p->isFoundation() && !p->isEmpty()) |
1722 | return false; |
1723 | } |
1724 | return true; |
1725 | } |
1726 | |
1727 | void DealerScene::startSolver() |
1728 | { |
1729 | if( m_solverEnabled ) |
1730 | m_solverUpdateTimer.start(); |
1731 | } |
1732 | |
1733 | |
1734 | bool DealerScene::isGameLost() const |
1735 | { |
1736 | if ( solver() ) |
1737 | { |
1738 | if ( m_solverThread && m_solverThread->isRunning() ) |
1739 | m_solverThread->abort(); |
1740 | |
1741 | solver()->translate_layout(); |
1742 | return solver()->patsolve( neededFutureMoves() ) == Solver::NoSolutionExists; |
1743 | } |
1744 | return false; |
1745 | } |
1746 | |
1747 | void DealerScene::recordGameStatistics() |
1748 | { |
1749 | // Don't record the game if it was never started, if it is unchanged since |
1750 | // it was last saved (allowing the user to close KPat after saving without |
1751 | // it recording a loss) or if it has already been recorded.// takeState(); // copying it again |
1752 | if ( m_dealStarted && !m_dealWasJustSaved && !m_statisticsRecorded ) |
1753 | { |
1754 | int id = oldId(); |
1755 | |
1756 | QString totalPlayedKey = QString("total%1" ).arg( id ); |
1757 | QString wonKey = QString("won%1" ).arg( id ); |
1758 | QString winStreakKey = QString("winstreak%1" ).arg( id ); |
1759 | QString maxWinStreakKey = QString("maxwinstreak%1" ).arg( id ); |
1760 | QString loseStreakKey = QString("loosestreak%1" ).arg( id ); |
1761 | QString maxLoseStreakKey = QString("maxloosestreak%1" ).arg( id ); |
1762 | |
1763 | KConfigGroup config(KGlobal::config(), scores_group); |
1764 | |
1765 | int totalPlayed = config.readEntry( totalPlayedKey, 0 ); |
1766 | int won = config.readEntry( wonKey, 0 ); |
1767 | int winStreak = config.readEntry( winStreakKey, 0 ); |
1768 | int maxWinStreak = config.readEntry( maxWinStreakKey, 0 ); |
1769 | int loseStreak = config.readEntry( loseStreakKey, 0 ); |
1770 | int maxLoseStreak = config.readEntry( maxLoseStreakKey, 0 ); |
1771 | |
1772 | ++totalPlayed; |
1773 | |
1774 | if ( m_dealHasBeenWon ) |
1775 | { |
1776 | ++won; |
1777 | ++winStreak; |
1778 | maxWinStreak = qMax( winStreak, maxWinStreak ); |
1779 | loseStreak = 0; |
1780 | } |
1781 | else |
1782 | { |
1783 | ++loseStreak; |
1784 | maxLoseStreak = qMax( loseStreak, maxLoseStreak ); |
1785 | winStreak = 0; |
1786 | } |
1787 | |
1788 | config.writeEntry( totalPlayedKey, totalPlayed ); |
1789 | config.writeEntry( wonKey, won ); |
1790 | config.writeEntry( winStreakKey, winStreak ); |
1791 | config.writeEntry( maxWinStreakKey, maxWinStreak ); |
1792 | config.writeEntry( loseStreakKey, loseStreak ); |
1793 | config.writeEntry( maxLoseStreakKey, maxLoseStreak ); |
1794 | |
1795 | m_statisticsRecorded = true; |
1796 | } |
1797 | } |
1798 | |
1799 | void DealerScene::relayoutScene() |
1800 | { |
1801 | KCardScene::relayoutScene(); |
1802 | |
1803 | if ( m_wonItem->isVisible() ) |
1804 | updateWonItem(); |
1805 | } |
1806 | |
1807 | |
1808 | int DealerScene::gameId() const |
1809 | { |
1810 | return m_di->baseId(); |
1811 | } |
1812 | |
1813 | |
1814 | void DealerScene::setActions( int actions ) |
1815 | { |
1816 | m_supportedActions = actions; |
1817 | } |
1818 | |
1819 | |
1820 | int DealerScene::actions() const |
1821 | { |
1822 | return m_supportedActions; |
1823 | } |
1824 | |
1825 | |
1826 | QList<QAction*> DealerScene::configActions() const |
1827 | { |
1828 | return QList<QAction*>(); |
1829 | } |
1830 | |
1831 | |
1832 | Solver * DealerScene::solver() const |
1833 | { |
1834 | return m_solver; |
1835 | } |
1836 | |
1837 | |
1838 | int DealerScene::neededFutureMoves() const |
1839 | { |
1840 | return m_neededFutureMoves; |
1841 | } |
1842 | |
1843 | |
1844 | void DealerScene::setNeededFutureMoves( int i ) |
1845 | { |
1846 | m_neededFutureMoves = i; |
1847 | } |
1848 | |
1849 | |
1850 | void DealerScene::setDeckContents( int copies, const QList<KCardDeck::Suit> & suits ) |
1851 | { |
1852 | Q_ASSERT( copies >= 1 ); |
1853 | Q_ASSERT( !suits.isEmpty() ); |
1854 | |
1855 | // Note that the order in which the cards are created can not be changed |
1856 | // without breaking the game numbering. For historical reasons, KPat |
1857 | // generates card by rank and then by suit, rather than the more common |
1858 | // suit then rank ordering. |
1859 | QList<quint32> ids; |
1860 | unsigned int number = 0; |
1861 | for ( int i = 0; i < copies; ++i ) |
1862 | foreach ( const KCardDeck::Rank & r, KCardDeck::standardRanks() ) |
1863 | foreach ( const KCardDeck::Suit & s, suits ) |
1864 | ids << KCardDeck::getId( s, r, number++ ); |
1865 | |
1866 | deck()->setDeckContents( ids ); |
1867 | } |
1868 | |
1869 | |
1870 | QImage DealerScene::createDump() const |
1871 | { |
1872 | const QSize previewSize( 480, 320 ); |
1873 | |
1874 | foreach ( KCard * c, deck()->cards() ) |
1875 | c->completeAnimation(); |
1876 | |
1877 | QMultiMap<qreal,QGraphicsItem*> itemsByZ; |
1878 | foreach ( QGraphicsItem * item, items() ) |
1879 | { |
1880 | Q_ASSERT( item->zValue() >= 0 ); |
1881 | itemsByZ.insert( item->zValue(), item ); |
1882 | } |
1883 | |
1884 | QImage img( contentArea().size().toSize(), QImage::Format_ARGB32 ); |
1885 | img.fill( Qt::transparent ); |
1886 | QPainter p( &img ); |
1887 | |
1888 | foreach ( QGraphicsItem * item, itemsByZ ) |
1889 | { |
1890 | if ( item->isVisible() ) |
1891 | { |
1892 | p.save(); |
1893 | p.setTransform( item->deviceTransform( p.worldTransform() ), false ); |
1894 | item->paint( &p, 0 ); |
1895 | p.restore(); |
1896 | } |
1897 | } |
1898 | |
1899 | p.end(); |
1900 | |
1901 | img = img.scaled( previewSize, Qt::KeepAspectRatio, Qt::SmoothTransformation ); |
1902 | |
1903 | QImage img2( previewSize, QImage::Format_ARGB32 ); |
1904 | img2.fill( Qt::transparent ); |
1905 | QPainter p2( &img2 ); |
1906 | p2.drawImage( (img2.width() - img.width()) / 2, (img2.height() - img.height()) / 2, img ); |
1907 | p2.end(); |
1908 | |
1909 | return img2; |
1910 | } |
1911 | |
1912 | |
1913 | void DealerScene::mapOldId( int id ) |
1914 | { |
1915 | Q_UNUSED( id ); |
1916 | } |
1917 | |
1918 | |
1919 | int DealerScene::oldId() const |
1920 | { |
1921 | return gameId(); |
1922 | } |
1923 | |
1924 | |
1925 | QString DealerScene::getGameState() const |
1926 | { |
1927 | return QString(); |
1928 | } |
1929 | |
1930 | |
1931 | void DealerScene::setGameState( const QString & state ) |
1932 | { |
1933 | Q_UNUSED( state ); |
1934 | } |
1935 | |
1936 | |
1937 | QString DealerScene::getGameOptions() const |
1938 | { |
1939 | return QString(); |
1940 | } |
1941 | |
1942 | |
1943 | void DealerScene::setGameOptions( const QString & options ) |
1944 | { |
1945 | Q_UNUSED( options ); |
1946 | } |
1947 | |
1948 | |
1949 | bool DealerScene::allowedToStartNewGame() |
1950 | { |
1951 | // Check if the user is already running a game, and if she is, |
1952 | // then ask if she wants to abort it. |
1953 | return !m_dealStarted |
1954 | || m_dealWasJustSaved |
1955 | || m_toldAboutWonGame |
1956 | || m_toldAboutLostGame |
1957 | || KMessageBox::warningContinueCancel(0, |
1958 | i18n("A new game has been requested, but there is already a game in progress.\n\n" |
1959 | "A loss will be recorded in the statistics if the current game is abandoned." ), |
1960 | i18n("Abandon Current Game?" ), |
1961 | KGuiItem(i18n("Abandon Current Game" )), |
1962 | KStandardGuiItem::cancel(), |
1963 | "careaboutstats" |
1964 | ) == KMessageBox::Continue; |
1965 | } |
1966 | |
1967 | void DealerScene::addCardForDeal( KCardPile * pile, KCard * card, bool faceUp, QPointF startPos ) |
1968 | { |
1969 | Q_ASSERT( card ); |
1970 | Q_ASSERT( pile ); |
1971 | |
1972 | card->setFaceUp( faceUp ); |
1973 | pile->add( card ); |
1974 | m_initDealPositions.insert( card, startPos ); |
1975 | } |
1976 | |
1977 | |
1978 | void DealerScene::startDealAnimation() |
1979 | { |
1980 | qreal speed = sqrt( width() * width() + height() * height() ) / ( DURATION_DEAL ); |
1981 | foreach ( PatPile * p, patPiles() ) |
1982 | { |
1983 | updatePileLayout( p, 0 ); |
1984 | foreach ( KCard * c, p->cards() ) |
1985 | { |
1986 | if ( !m_initDealPositions.contains( c ) ) |
1987 | continue; |
1988 | |
1989 | QPointF pos2 = c->pos(); |
1990 | c->setPos( m_initDealPositions.value( c ) ); |
1991 | |
1992 | QPointF delta = c->pos() - pos2; |
1993 | qreal dist = sqrt( delta.x() * delta.x() + delta.y() * delta.y() ); |
1994 | int duration = qRound( dist / speed ); |
1995 | c->animate( pos2, c->zValue(), 0, c->isFaceUp(), false, duration ); |
1996 | } |
1997 | } |
1998 | m_initDealPositions.clear(); |
1999 | } |
2000 | |
2001 | |
2002 | void DealerScene::multiStepMove( const QList<KCard*> & cards, |
2003 | KCardPile * pile, |
2004 | const QList<KCardPile*> & freePiles, |
2005 | const QList<KCardPile*> & freeCells, |
2006 | int duration ) |
2007 | { |
2008 | Q_ASSERT( cards.size() == 1 || !freePiles.isEmpty() || !freeCells.isEmpty() ); |
2009 | |
2010 | m_multiStepMoves.clear(); |
2011 | m_multiStepDuration = duration; |
2012 | |
2013 | multiStepSubMove( cards, pile, freePiles, freeCells ); |
2014 | continueMultiStepMove(); |
2015 | } |
2016 | |
2017 | |
2018 | void DealerScene::multiStepSubMove( QList<KCard*> cards, |
2019 | KCardPile * pile, |
2020 | QList<KCardPile*> freePiles, |
2021 | const QList<KCardPile*> & freeCells ) |
2022 | { |
2023 | // Note that cards and freePiles are passed by value, as we need to make a |
2024 | // local copy anyway. |
2025 | |
2026 | // Using n free cells, we can move a run of n+1 cards. If we want to move |
2027 | // more than that, we have to recursively move some of our cards to one of |
2028 | // the free piles temporarily. |
2029 | const int freeCellsPlusOne = freeCells.size() + 1; |
2030 | int cardsToSubMove = cards.size() - freeCellsPlusOne; |
2031 | |
2032 | QList<QPair<KCardPile*,QList<KCard*> > > tempMoves; |
2033 | while ( cardsToSubMove > 0 ) |
2034 | { |
2035 | int tempMoveSize; |
2036 | if ( cardsToSubMove <= freePiles.size() * freeCellsPlusOne ) |
2037 | { |
2038 | // If the cards that have to be submoved can be spread across the |
2039 | // the free piles without putting more than freeCellsPlusOne cards |
2040 | // on each one, we do so. This means that none of our submoves will |
2041 | // need further submoves, which keeps the total move count down. We |
2042 | // Just to a simple rounding up integer division. |
2043 | tempMoveSize = (cardsToSubMove + freePiles.size() - 1) / freePiles.size(); |
2044 | } |
2045 | else |
2046 | { |
2047 | // Otherwise, we use the space optimal method that gets the cards |
2048 | // moved using a minimal number of piles, but uses more submoves. |
2049 | tempMoveSize = freeCellsPlusOne; |
2050 | while ( tempMoveSize * 2 < cardsToSubMove ) |
2051 | tempMoveSize *= 2; |
2052 | } |
2053 | |
2054 | QList<KCard*> subCards; |
2055 | for ( int i = 0; i < tempMoveSize; ++i ) |
2056 | subCards.prepend( cards.takeLast() ); |
2057 | |
2058 | Q_ASSERT( !freePiles.isEmpty() ); |
2059 | KCardPile * nextPile = freePiles.takeFirst(); |
2060 | |
2061 | tempMoves << qMakePair( nextPile, subCards ); |
2062 | multiStepSubMove( subCards, nextPile, freePiles, freeCells ); |
2063 | |
2064 | cardsToSubMove -= tempMoveSize; |
2065 | } |
2066 | |
2067 | // Move cards to free cells. |
2068 | for ( int i = 0; i < cards.size() - 1; ++i ) |
2069 | { |
2070 | KCard * c = cards.at( cards.size() - 1 - i ); |
2071 | m_multiStepMoves << qMakePair( c, freeCells[i] ); |
2072 | } |
2073 | |
2074 | // Move bottom card to destination pile. |
2075 | m_multiStepMoves << qMakePair( cards.first(), pile ); |
2076 | |
2077 | // Move cards from free cells to destination pile. |
2078 | for ( int i = 1; i < cards.size(); ++i ) |
2079 | m_multiStepMoves << qMakePair( cards.at( i ), pile ); |
2080 | |
2081 | // If we just moved the bottomost card of the source pile, it must now be |
2082 | // empty and we won't need it any more. So we return it to the list of free |
2083 | // piles. |
2084 | KCardPile * sourcePile = cards.first()->pile(); |
2085 | if ( sourcePile->at( 0 ) == cards.first() ) |
2086 | freePiles << sourcePile; |
2087 | |
2088 | // If we had to do any submoves, we now move those cards from their |
2089 | // temporary pile to the destination pile and free up their temporary pile. |
2090 | while ( !tempMoves.isEmpty() ) |
2091 | { |
2092 | QPair<KCardPile*, QList<KCard*> > m = tempMoves.takeLast(); |
2093 | multiStepSubMove( m.second, pile, freePiles, freeCells ); |
2094 | freePiles << m.first; |
2095 | } |
2096 | } |
2097 | |
2098 | |
2099 | void DealerScene::continueMultiStepMove() |
2100 | { |
2101 | Q_ASSERT( !m_multiStepMoves.isEmpty() ); |
2102 | Q_ASSERT( !isCardAnimationRunning() ); |
2103 | |
2104 | QPair<KCard*,KCardPile*> m = m_multiStepMoves.takeFirst(); |
2105 | KCard * card = m.first; |
2106 | KCardPile * dest = m.second; |
2107 | KCardPile * source = card->pile(); |
2108 | |
2109 | Q_ASSERT( card == source->topCard() ); |
2110 | Q_ASSERT( allowedToAdd( dest, QList<KCard*>() << card ) ); |
2111 | |
2112 | m_multiStepDuration = qMax<int>( m_multiStepDuration * 0.9, 50 ); |
2113 | |
2114 | dest->add( card ); |
2115 | card->raise(); |
2116 | updatePileLayout( dest, m_multiStepDuration ); |
2117 | updatePileLayout( source, m_multiStepDuration ); |
2118 | |
2119 | if ( m_multiStepMoves.isEmpty() ) |
2120 | takeState(); |
2121 | } |
2122 | |
2123 | |
2124 | #include "dealer.moc" |
2125 | #include "moc_dealer.cpp" |
2126 | |