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
68namespace
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
170class SolverThread : public QThread
171{
172 Q_OBJECT
173
174public:
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
195signals:
196 void finished( int result );
197
198private:
199 Solver * m_solver;
200};
201
202
203int DealerScene::moveCount() const
204{
205 return m_loadedMoveCount + m_undoStack.size();
206}
207
208
209void 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
250bool 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
359void 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
426bool 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
540DealerScene::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
596DealerScene::~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
614void DealerScene::addPatPile( PatPile * pile )
615{
616 if ( !m_patPiles.contains( pile ) )
617 m_patPiles.append( pile );
618}
619
620
621void DealerScene::removePatPile( PatPile * pile )
622{
623 m_patPiles.removeAll( pile );
624}
625
626
627QList<PatPile*> DealerScene::patPiles() const
628{
629 return m_patPiles;
630}
631
632
633void 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
657void 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
689void DealerScene::stopHint()
690{
691 if ( m_hintInProgress )
692 {
693 m_hintInProgress = false;
694 clearHighlightedItems();
695 emit hintActive( false );
696 }
697}
698
699
700bool DealerScene::isHintActive() const
701{
702 return m_hintInProgress;
703}
704
705QList<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
727QList<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
784static bool prioSort(const MoveHint &c1, const MoveHint &c2)
785{
786 return c1.priority() < c2.priority();
787}
788
789
790MoveHint 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
827void 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
865void 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
906QPointF 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
920void 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
960void 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
977void 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
1004bool 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
1014bool 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
1025bool 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
1034bool 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
1042bool 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
1051void 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
1083void 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
1103void DealerScene::mouseDoubleClickEvent( QGraphicsSceneMouseEvent * e )
1104{
1105 stop();
1106
1107 KCardScene::mouseDoubleClickEvent( e );
1108}
1109
1110
1111bool 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
1136void DealerScene::undo()
1137{
1138 undoOrRedo( true );
1139}
1140
1141
1142void DealerScene::redo()
1143{
1144 undoOrRedo( false );
1145}
1146
1147
1148void 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
1249void 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
1359void DealerScene::setSolverEnabled(bool a)
1360{
1361 m_solverEnabled = a;
1362}
1363
1364
1365void DealerScene::setAutoDropEnabled( bool enabled )
1366{
1367 m_autoDropEnabled = enabled;
1368}
1369
1370
1371bool DealerScene::autoDropEnabled() const
1372{
1373 return m_autoDropEnabled;
1374}
1375
1376
1377void 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
1397void 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
1411bool DealerScene::isDropActive() const
1412{
1413 return m_dropInProgress;
1414}
1415
1416
1417bool 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
1461int 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
1469void 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
1488void 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
1505void 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
1526int DealerScene::gameNumber() const
1527{
1528 return m_dealNumber;
1529}
1530
1531
1532void DealerScene::stop()
1533{
1534 stopHint();
1535 stopDemo();
1536 stopDrop();
1537}
1538
1539
1540void 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
1587void 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
1606void DealerScene::stopDemo()
1607{
1608 if ( m_demoInProgress )
1609 {
1610 m_demoTimer.stop();
1611 m_demoInProgress = false;
1612 emit demoActive( false );
1613 }
1614}
1615
1616
1617bool DealerScene::isDemoActive() const
1618{
1619 return m_demoInProgress;
1620}
1621
1622
1623void 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
1689void 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
1704bool DealerScene::newCards()
1705{
1706 return false;
1707}
1708
1709
1710void DealerScene::setSolver( Solver *s) {
1711 delete m_solver;
1712 delete m_solverThread;
1713 m_solver = s;
1714 m_solverThread = 0;
1715}
1716
1717bool 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
1727void DealerScene::startSolver()
1728{
1729 if( m_solverEnabled )
1730 m_solverUpdateTimer.start();
1731}
1732
1733
1734bool 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
1747void 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
1799void DealerScene::relayoutScene()
1800{
1801 KCardScene::relayoutScene();
1802
1803 if ( m_wonItem->isVisible() )
1804 updateWonItem();
1805}
1806
1807
1808int DealerScene::gameId() const
1809{
1810 return m_di->baseId();
1811}
1812
1813
1814void DealerScene::setActions( int actions )
1815{
1816 m_supportedActions = actions;
1817}
1818
1819
1820int DealerScene::actions() const
1821{
1822 return m_supportedActions;
1823}
1824
1825
1826QList<QAction*> DealerScene::configActions() const
1827{
1828 return QList<QAction*>();
1829}
1830
1831
1832Solver * DealerScene::solver() const
1833{
1834 return m_solver;
1835}
1836
1837
1838int DealerScene::neededFutureMoves() const
1839{
1840 return m_neededFutureMoves;
1841}
1842
1843
1844void DealerScene::setNeededFutureMoves( int i )
1845{
1846 m_neededFutureMoves = i;
1847}
1848
1849
1850void 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
1870QImage 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
1913void DealerScene::mapOldId( int id )
1914{
1915 Q_UNUSED( id );
1916}
1917
1918
1919int DealerScene::oldId() const
1920{
1921 return gameId();
1922}
1923
1924
1925QString DealerScene::getGameState() const
1926{
1927 return QString();
1928}
1929
1930
1931void DealerScene::setGameState( const QString & state )
1932{
1933 Q_UNUSED( state );
1934}
1935
1936
1937QString DealerScene::getGameOptions() const
1938{
1939 return QString();
1940}
1941
1942
1943void DealerScene::setGameOptions( const QString & options )
1944{
1945 Q_UNUSED( options );
1946}
1947
1948
1949bool 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
1967void 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
1978void 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
2002void 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
2018void 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
2099void 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