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