/* QSoloCards is a collection of Solitaire card games written using Qt Copyright (C) 2009 Steve Moore This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "KlondikeBoard.h" #include #include "CardDeck.h" #include "CardPixmaps.h" #include "CardAnimationLock.h" const QString KlondikeBoard::GameTypeKeyStr("GameType"); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// KlondikeBoard::KlondikeBoard(QWidget * pWidget) :GameBoard(pWidget,QString(tr("Klondike Solitaire")).trimmed(),QString("Klondike")), m_pDeck(NULL), m_pFlipDeck(NULL), m_homeVector(), m_stackVector(), m_cheat(false), m_gameType(KlondikeBoard::FlipOne), m_flipNum(0), m_sToSFlipAni() { this->setHelpFile(":/help/KlondikeHelp.html"); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// KlondikeBoard::~KlondikeBoard() { } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::undoMove() { bool emptyBefore=false; bool emptyAfter=false; emptyBefore=m_pDeck->isEmpty(); GameBoard::undoMove(); emptyAfter=m_pDeck->isEmpty(); // if we undone a redeal decrement the flip count if (!emptyBefore && emptyAfter) { this->m_flipNum--; if (!(KlondikeBoard::FlipOne==this->m_gameType && this->m_flipNum<2) && !(KlondikeBoard::FlipThree==this->m_gameType)) { this->m_pDeck->setShowRedealCircle(false); this->m_pDeck->updateStack(); } else { this->m_pDeck->setShowRedealCircle(true); this->m_pDeck->updateStack(); } } } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::redoMove() { bool emptyBefore=false; bool emptyAfter=false; emptyBefore=m_pDeck->isEmpty(); GameBoard::redoMove(); emptyAfter=m_pDeck->isEmpty(); // if we are redoing a redeal increment the flip count if (emptyBefore && !emptyAfter) { this->m_flipNum++; if (!(KlondikeBoard::FlipOne==this->m_gameType && this->m_flipNum<2) && !(KlondikeBoard::FlipThree==this->m_gameType)) { this->m_pDeck->setShowRedealCircle(false); this->m_pDeck->updateStack(); } else { this->m_pDeck->setShowRedealCircle(true); this->m_pDeck->updateStack(); } } } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::restartGame() { // call the base class to rewind the undo stack GameBoard::restartGame(); // reset the flip count this->m_flipNum=0; // and set the redeal circle depending on game type if (KlondikeBoard::NoRedeals==this->m_gameType) { this->m_pDeck->setShowRedealCircle(false); } else { this->m_pDeck->setShowRedealCircle(true); } } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// bool KlondikeBoard::getHint(CardStack * & pFromStack, unsigned int & index, CardStack * & pToStack) { // first see if we can move anything to one of the home stacks. PlayingCardVector moveCards; unsigned int i; bool matchFound=false; // ok check to see if we have a card from the stacks that can // be added to a home stack for (i=0;im_stackVector.size() && !matchFound;i++) { if (!this->m_stackVector[i]->isEmpty()) { const PlayingCardVector & currCards=this->m_stackVector[i]->getCardVector(); moveCards.clear(); moveCards.push_back(currCards[currCards.size()-1]); // just get the last card since we are moving to the home stack index=currCards.size()-1; for (unsigned int j=0;jm_homeVector.size();j++) { if (this->m_homeVector[j]->canAddCards(moveCards)) { pToStack=this->m_homeVector[j]; pFromStack=this->m_stackVector[i]; matchFound=true; break; } } } } // next check for stack to stack moves. if (!matchFound) { moveCards.clear(); pToStack=NULL; pFromStack=NULL; for (i=0;im_stackVector.size() && !matchFound;i++) { moveCards.clear(); if (!this->m_stackVector[i]->isEmpty() && this->m_stackVector[i]->getMovableCards(moveCards,index)) { // we want to eliminate a move that starts with a stack that // has a faceup king as the first card. We will just end up // suggesting to move it to another empty row. Which is not // really a move. if (PlayingCard::King==moveCards[0].getIndex() && moveCards.size()== this->m_stackVector[i]->getCardVector().size()) { continue; } for (unsigned int j=0;jm_stackVector.size();j++) { if (this->m_stackVector[i]!=this->m_stackVector[j] && this->m_stackVector[j]->canAddCards(moveCards)) { pToStack=this->m_stackVector[j]; pFromStack=this->m_stackVector[i]; matchFound=true; break; } } } } } // check the flip deck. We can just call getMovableCards for it // since, it will always be just one card. if (!matchFound) { moveCards.clear(); pToStack=NULL; pFromStack=NULL; if (!this->m_pFlipDeck->isEmpty() && this->m_pFlipDeck->getMovableCards(moveCards,index)) { // first see if we can send a card to one of the home // stacks for(i=0;im_homeVector.size();i++) { if (this->m_homeVector[i]->canAddCards(moveCards)) { pToStack=this->m_homeVector[i]; pFromStack=this->m_pFlipDeck; matchFound=true; break; } } // if no match now check to see if we can add the card to the // normal stacks if (!matchFound) { for(i=0;im_stackVector.size();i++) { if (this->m_stackVector[i]->canAddCards(moveCards)) { pToStack=this->m_stackVector[i]; pFromStack=this->m_pFlipDeck; matchFound=true; break; } } } } } return matchFound; } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::newGame() { // stop any animation of flipping cards m_sToSFlipAni.stopAni(); // call the base class GameBoard::newGame(); CardDeck deck; unsigned int i; this->m_flipNum=0; // only show the redeal circle in the cases that we can redeal if (KlondikeBoard::NoRedeals==this->m_gameType) { this->m_pDeck->setShowRedealCircle(false); } else { this->m_pDeck->setShowRedealCircle(true); } this->m_pFlipDeck->cardsToShow(((KlondikeBoard::FlipThree==this->m_gameType)?3:1)); while(!deck.isEmpty()) { this->m_pDeck->addCard(deck.next()); } DealItemVector dealItemVector; // Create the dealItemVector to direct the DealAnimation object on // how to deal the cards. for (i=0;im_stackVector.size();i++) { dealItemVector.push_back(DealItem(this->m_stackVector[i],m_pDeck)); unsigned int j; for (j=0;jsetShortcut(QKeySequence(Qt::CTRL + Qt::Key_1)); pFlipOneAction->setCheckable(true); connect(pFlipOneAction,SIGNAL(triggered()), this,SLOT(slotSetFlipOne())); QAction * pFlipThreeAction=new QAction(tr("Flip three").trimmed(),pFlipCardsGroup); pFlipThreeAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_3)); pFlipThreeAction->setCheckable(true); connect(pFlipThreeAction,SIGNAL(triggered()), this,SLOT(slotSetFlipThree())); QAction * pNoRedealsAction=new QAction(tr("No redeals (flip one)").trimmed(),pFlipCardsGroup); pNoRedealsAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_0)); pNoRedealsAction->setCheckable(true); connect(pNoRedealsAction,SIGNAL(triggered()), this,SLOT(slotSetNoRedeals())); // select the correct item in the list switch (this->m_gameType) { case KlondikeBoard::FlipOne: pFlipOneAction->setChecked(true); break; case KlondikeBoard::FlipThree: pFlipThreeAction->setChecked(true); break; case KlondikeBoard::NoRedeals: pNoRedealsAction->setChecked(true); break; }; menu.addAction(pFlipOneAction); menu.addAction(pFlipThreeAction); menu.addAction(pNoRedealsAction); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::loadSettings(const QSettings & settings) { int gameType=settings.value(this->GameTypeKeyStr,KlondikeBoard::FlipOne).toInt(); switch (gameType) { case KlondikeBoard::NoRedeals: this->m_gameType=KlondikeBoard::NoRedeals; break; case KlondikeBoard::FlipThree: this->m_gameType=KlondikeBoard::FlipThree; break; case KlondikeBoard::FlipOne: default: this->m_gameType=KlondikeBoard::FlipOne; break; }; } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::saveSettings(QSettings & settings) { settings.setValue(this->GameTypeKeyStr,this->m_gameType); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::setCheat(bool cheat) { this->m_cheat=cheat; for(unsigned int i=0;im_stackVector.size();i++) { m_stackVector[i]->setCheat(cheat); } } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::slotFlipCards(CardStack * pCardStack, unsigned int index) { Q_UNUSED(pCardStack); Q_UNUSED(index); // just use the game type to indicate the number of flip cards // the enum is assigned the number of cards that will be flipped // the exception is the NoRedeals in that case we are just flipping // one card. unsigned int numFlipCards=this->m_gameType; if (KlondikeBoard::NoRedeals==this->m_gameType) { numFlipCards=KlondikeBoard::FlipOne; } m_sToSFlipAni.moveCards(m_pFlipDeck,m_pDeck,numFlipCards); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::slotRedealCards(CardStack * pCardStack) { Q_UNUSED(pCardStack); // ignore an attempt to redeal if the game type is NoRedeals. // otherwise take all the cards from the Flip stack and put them back // in the deal stack if (KlondikeBoard::NoRedeals!=this->m_gameType && ((KlondikeBoard::FlipOne==this->m_gameType && this->m_flipNum<2) || KlondikeBoard::FlipThree==this->m_gameType)) { unsigned int numFlipCards=this->m_gameType; if (KlondikeBoard::NoRedeals==this->m_gameType) { numFlipCards=KlondikeBoard::FlipOne; } // flip the cards back. Flip the cards back over. Show the bottom 3 if in FlipThree mode. Otherwise // just show one. m_sToSFlipAni.moveCards(m_pDeck,m_pFlipDeck,this->m_pFlipDeck->getCardVector().size(),numFlipCards); this->m_flipNum++; if (!(KlondikeBoard::FlipOne==this->m_gameType && this->m_flipNum<2) && !(KlondikeBoard::FlipThree==this->m_gameType)) { this->m_pDeck->setShowRedealCircle(false); } } else { // if a demo is running stop it. Can't redeal any more. this->stopDemo(); } } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::slotStackCardsClicked(CardStack * pCardStack, const PlayingCardVector & cardVector, const CardMoveRecord & startMoveRecord) { unsigned int i=0; CardStack * pFoundStack=NULL; if (NULL==pCardStack) { return; } // first see if the card can be added to the sent home stack if (cardVector.size()==1) { for(i=0;im_homeVector.size();i++) { if (pCardStack!=this->m_homeVector[i] && this->m_homeVector[i]->canAddCards(cardVector)) { pFoundStack=this->m_homeVector[i]; break; } } } // if we did not find a match look at the stacks. // the home will use the same logic so just call that slot if (NULL==pFoundStack) { this->slotHomeCardsClicked(pCardStack,cardVector,startMoveRecord); } if (pFoundStack) { CardMoveRecord moveRecord(startMoveRecord); pFoundStack->addCards(cardVector,moveRecord,true); // perform the move of the cards and animate it if animations // are enabled m_sToSAniMove.moveCards(moveRecord); } } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::slotHomeCardsClicked(CardStack * pCardStack, const PlayingCardVector & cardVector, const CardMoveRecord & startMoveRecord) { unsigned int i=0; CardStack * pFoundStack=NULL; if (NULL==pCardStack) { return; } for(i=0;im_stackVector.size();i++) { if (pCardStack!=this->m_stackVector[i] && this->m_stackVector[i]->canAddCards(cardVector)) { pFoundStack=this->m_stackVector[i]; break; } } if (pFoundStack) { CardMoveRecord moveRecord(startMoveRecord); pFoundStack->addCards(cardVector,moveRecord,true); // perform the move of the cards and animate it if animations // are enabled m_sToSAniMove.moveCards(moveRecord); } } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::slotSetNoRedeals() { this->m_gameType=KlondikeBoard::NoRedeals; this->newGame(); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::slotSetFlipOne() { this->m_gameType=KlondikeBoard::FlipOne; this->newGame(); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::slotSetFlipThree() { this->m_gameType=KlondikeBoard::FlipThree; this->newGame(); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::calcScore() { int score=0; for(unsigned int i=0;im_homeVector.size();i++) { score+=this->m_homeVector[i]->score(); } emit scoreChanged(score,""); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::resizeEvent (QResizeEvent * event) { unsigned int i; GameBoard::resizeEvent(event); QSize cardSize(CardPixmaps::getInst().getCardSize()); QPointF currPos(GameBoard::LayoutSpacing,GameBoard::LayoutSpacing); m_pDeck->setPos(currPos); currPos.rx()+=GameBoard::LayoutSpacing + cardSize.width(); m_pFlipDeck->setPos(currPos); currPos.rx()+=GameBoard::LayoutSpacing*3 + cardSize.width()*3; for (i=0;isetPos(currPos); currPos.rx()+=cardSize.width()+GameBoard::LayoutSpacing; } currPos.setY(GameBoard::LayoutSpacing*2+cardSize.height()); currPos.setX(GameBoard::LayoutSpacing*2+cardSize.width()); for (i=0;isetPos(currPos); currPos.rx()+=cardSize.width()+GameBoard::LayoutSpacing; } } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// bool KlondikeBoard::runDemo(bool stopWhenNoMore) { bool rc=true; if (!GameBoard::runDemo(false)) { if (!m_pDeck->isEmpty()) { this->slotFlipCards(); } else if (!m_pFlipDeck->isEmpty()) { this->slotRedealCards(); } else if (stopWhenNoMore) { stopDemo(); rc=false; } else { rc=false; } } return rc; } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// void KlondikeBoard::createStacks() { setCardResizeAlg(8,ResizeByWidth); unsigned int i; // first create the home widgets where the cards need to be eventually stacked to // win the game. for(i=0;i<4;i++) { this->m_homeVector.push_back(new KlondikeHomeStack); this->m_scene.addItem(m_homeVector[i]); this->connect(this->m_homeVector[i],SIGNAL(cardsMovedByDragDrop(CardMoveRecord)), this,SLOT(slotCardsMoved(CardMoveRecord))); this->connect(this->m_homeVector[i],SIGNAL(movableCardsClicked(CardStack*,PlayingCardVector,CardMoveRecord)), this,SLOT(slotHomeCardsClicked(CardStack*,PlayingCardVector,CardMoveRecord))); } // now create the 7 rows for the stacks. for (i=0;i<7;i++) { this->m_stackVector.push_back(new KlondikeStack); this->m_scene.addItem(m_stackVector[i]); this->connect(this->m_stackVector[i],SIGNAL(cardsMovedByDragDrop(CardMoveRecord)), this,SLOT(slotCardsMoved(CardMoveRecord))); this->connect(this->m_stackVector[i],SIGNAL(movableCardsClicked(CardStack*,PlayingCardVector,CardMoveRecord)), this,SLOT(slotStackCardsClicked(CardStack*,PlayingCardVector,CardMoveRecord))); } this->m_pDeck=new CardStack; this->m_scene.addItem(this->m_pDeck); this->m_pDeck->setShowRedealCircle(); // if a card is double clicked that means we need to flip over the next set of cards this->connect(this->m_pDeck,SIGNAL(cardClicked(CardStack*,uint)), this,SLOT(slotFlipCards(CardStack*,uint))); // if there are no cards in the deck and the pad is clicked that means we need // to flip the deck back over. If the version of the game is not No redeals. this->connect(this->m_pDeck,SIGNAL(padClicked(CardStack*)), this,SLOT(slotRedealCards(CardStack*))); this->m_pFlipDeck=new KlondikeFlipStack; this->m_scene.addItem(this->m_pFlipDeck); this->connect(this->m_pFlipDeck,SIGNAL(cardsMovedByDragDrop(CardMoveRecord)), this,SLOT(slotCardsMoved(CardMoveRecord))); this->connect(this->m_pFlipDeck,SIGNAL(movableCardsClicked(CardStack*,PlayingCardVector,CardMoveRecord)), this,SLOT(slotStackCardsClicked(CardStack*,PlayingCardVector,CardMoveRecord))); // hook up the animation signal for flipping cards over from the deck to the flip // stack. And then back. this->connect(&m_sToSFlipAni,SIGNAL(cardsMoved(CardMoveRecord)), this,SLOT(slotCardsMoved(CardMoveRecord))); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// bool KlondikeBoard::isGameWon()const { bool rc=true; for (unsigned int i=0;im_homeVector.size();i++) { if (!this->m_homeVector[i]->isStackComplete()) { rc=false; break; } } return rc; } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// bool KlondikeBoard::isGameWonNotComplete()const { bool rc=false; rc=m_pDeck->isEmpty(); if (rc) { rc=this->m_pFlipDeck->cardsAscendingTopToBottom(); } if (rc) { for (unsigned int i=0;im_stackVector.size();i++) { if (!(this->m_stackVector[i]->cardsAscendingTopToBottom() && this->m_stackVector[i]->allCardsFaceUp())) { rc=false; break; } } } return rc; }