RetroShare/plugins/qsolocards_plugin/KlondikeBoard.cpp
defnax 082d5732b0 added game plugin solocards
git-svn-id: http://svn.code.sf.net/p/retroshare/code/trunk@2346 b45a01b8-16f6-495d-af2f-9b41ad6348cc
2010-02-17 01:14:52 +00:00

771 lines
23 KiB
C++

/*
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 <http://www.gnu.org/licenses/>.
*/
#include "KlondikeBoard.h"
#include <QtGui/QMessageBox>
#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;i<this->m_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;j<this->m_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;i<this->m_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;j<this->m_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;i<this->m_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;i<this->m_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;i<this->m_stackVector.size();i++)
{
dealItemVector.push_back(DealItem(this->m_stackVector[i],m_pDeck));
unsigned int j;
for (j=0;j<i+1;j++)
{
// add the items to tell how to deal the cards to the stack
// we want to flip the last card in each stack.
if (i==j)
{
dealItemVector[i].addCard(true);
}
else
{
dealItemVector[i].addCard(false);
}
}
}
// ok now start the deal. We don't need a move record for this item.
m_dealAni.dealCards(dealItemVector,false);
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void KlondikeBoard::addGameMenuItems(QMenu & menu)
{
QActionGroup * pFlipCardsGroup=new QActionGroup(&menu);
QAction * pFlipOneAction=new QAction(tr("Flip one").trimmed(),pFlipCardsGroup);
pFlipOneAction->setShortcut(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;i<this->m_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;i<this->m_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;i<this->m_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;i<this->m_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;i<m_homeVector.size();i++)
{
m_homeVector[i]->setPos(currPos);
currPos.rx()+=cardSize.width()+GameBoard::LayoutSpacing;
}
currPos.setY(GameBoard::LayoutSpacing*2+cardSize.height());
currPos.setX(GameBoard::LayoutSpacing*2+cardSize.width());
for (i=0;i<m_stackVector.size();i++)
{
m_stackVector[i]->setPos(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;i<this->m_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;i<this->m_stackVector.size();i++)
{
if (!(this->m_stackVector[i]->cardsAscendingTopToBottom() &&
this->m_stackVector[i]->allCardsFaceUp()))
{
rc=false;
break;
}
}
}
return rc;
}