RetroShare/plugins/qsolocards_plugin/SpiderBoard.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

755 lines
24 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 "SpiderBoard.h"
#include <QtGui/QMessageBox>
#include <QtGui/QAction>
#include <QtGui/QActionGroup>
#include <QtGui/QResizeEvent>
#include <QtCore/QDateTime>
#include "CardPixmaps.h"
#include "CardDeck.h"
#include <iostream>
const QString SpiderBoard::GameTypeKeyStr("GameType");
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
SpiderBoard::SpiderBoard(QWidget * pWidget)
:GameBoard(pWidget,QString(tr("Spider Solitaire")).trimmed(),QString("Spider")),
m_pDeck(NULL),
m_homeVector(),
m_stackVector(),
m_cheat(false),
m_gameType(SpiderBoard::FourSuits)
{
this->setHelpFile(":/help/SpiderHelp.html");
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
SpiderBoard::~SpiderBoard()
{
}
////////////////////////////////////////////////////////////////////////////////
// ok this is an attempt to offer hint moves. It will prefer same suit moves, then
// consecutive index matches, and then card to empty stack moves. It will try to
// move all cards that are movable in a stack. So, there are possibilities for
// tweaks.
////////////////////////////////////////////////////////////////////////////////
bool SpiderBoard::getHint(CardStack * & pSrcWidget,
unsigned int & srcStackIndex,
CardStack * & pDstWidget)
{
bool moveFound=false;
bool sameSuitFound=false;
bool nonSameSuitFound=false;
CardStack * pCanMoveToEmpty=NULL;
CardStack * pCanMoveTo=NULL;
CardStack * pCanMoveToSameSuit=NULL;
bool newMoveFound=false;
int j;
qsrand(QDateTime::currentDateTime().toTime_t());
bool fromZero=((0==qrand()%2 ^ 1==qrand()%3)?true:false); // this introduces some randomness by selecting whether we are going
// to go through the stacks forward or backwards.
for ((fromZero?(j=0):(j=this->m_stackVector.size()-1));
((fromZero)?(j<(int)this->m_stackVector.size()):(j>=0))&& !sameSuitFound ;((fromZero)?(j++):(j--)))
{
PlayingCardVector cardVector;
unsigned int currSrcStackIndex;
bool fromZeroInner=((0==qrand()%2 ^ 1==qrand()%3)?true:false); // this introduces some randomness by selecting whether we are going
// to go through the stacks forward or backwards.
int i;
newMoveFound=false;
if (this->m_stackVector[j]->getMovableCards(cardVector,currSrcStackIndex))
{
// first see if the cards can be sent home
// the priority will be the same as a move to
// cards of the same suit
if (SpiderHomeStack::canSendHome(cardVector))
{
// find an open home widget
for(unsigned int k=0;k<this->m_homeVector.size();k++)
{
if (this->m_homeVector[k]->isEmpty())
{
pDstWidget=this->m_homeVector[k];
break;
}
}
if (NULL!=pDstWidget)
{
pSrcWidget=this->m_stackVector[j];
srcStackIndex=currSrcStackIndex;
moveFound=true;
break;
}
}
for ((fromZeroInner?(i=0):(i=this->m_stackVector.size()-1));
((fromZeroInner)?(i<(int)this->m_stackVector.size()):(i>=0));
((fromZeroInner)?(i++):(i--)))
{
// ok we are only moving the cards if the hit is not on the same
// stack they are already located in
if (this->m_stackVector[i]!=this->m_stackVector[j])
{
// see if we have a perferred move to a stack that has the same suit
// we will break out if we find a perferred match
if (this->m_stackVector[i]->canAddCardsSameSuit(cardVector))
{
pCanMoveToSameSuit=this->m_stackVector[i];
newMoveFound=true;
break;
}
// otherwise see if we can move the cards to the stack at all
else if (this->m_stackVector[i]->canAddCards(cardVector))
{
if (this->m_stackVector[i]->isEmpty())
{
pCanMoveToEmpty=this->m_stackVector[i];
}
else
{
pCanMoveTo=this->m_stackVector[i];
}
newMoveFound=true;
}
}
}
if (newMoveFound)
{
// ok the best move is to a matching suit and consecutive
// next best is to a consecutive
// and lastly to an empty stack.
if (NULL!=pCanMoveToSameSuit)
{
pDstWidget=pCanMoveToSameSuit;
pSrcWidget=this->m_stackVector[j];
srcStackIndex=currSrcStackIndex;
sameSuitFound=true;
}
else if (NULL!=pCanMoveTo && !nonSameSuitFound)
{
pDstWidget=pCanMoveTo;
pSrcWidget=this->m_stackVector[j];
srcStackIndex=currSrcStackIndex;
nonSameSuitFound=true;
}
else if (NULL!=pCanMoveToEmpty && !nonSameSuitFound)
{
pDstWidget=pCanMoveToEmpty;
pSrcWidget=this->m_stackVector[j];
srcStackIndex=currSrcStackIndex;
}
moveFound=true;
}
}
}
return moveFound;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::newGame()
{
// call the base class to do cleanup for us.
GameBoard::newGame();
CardDeck * pDeck=NULL;
unsigned int i;
unsigned int j;
switch(this->m_gameType)
{
case SpiderBoard::FourSuits:
pDeck=new CardDeck(2);
break;
case SpiderBoard::TwoSuits:
{
PlayingCardVector cardVector;
// use hearts and spades as the two suits
for (i=PlayingCard::Ace;i<PlayingCard::MaxCardIndex;i++)
{
cardVector.push_back(PlayingCard(PlayingCard::Hearts,(PlayingCard::CardIndex)i));
}
for (i=PlayingCard::Ace;i<PlayingCard::MaxCardIndex;i++)
{
cardVector.push_back(PlayingCard(PlayingCard::Spades,(PlayingCard::CardIndex)i));
}
pDeck=new CardDeck(cardVector,4);
}
break;
case SpiderBoard::OneSuit:
{
PlayingCardVector cardVector;
// use spades as the suit
for (i=PlayingCard::Ace;i<PlayingCard::MaxCardIndex;i++)
{
cardVector.push_back(PlayingCard(PlayingCard::Spades,(PlayingCard::CardIndex)i));
}
pDeck=new CardDeck(cardVector,8);
}
break;
};
// if this happens something is very wrong just return
if (NULL==pDeck)
{
return;
}
// Put all cards in the m_pDeck stack. We will deal
// from this stack.
while(!pDeck->isEmpty())
{
this->m_pDeck->addCard(pDeck->next());
}
delete pDeck;
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++)
{
unsigned int cardsInStack=((i<4)?6:5);
dealItemVector.push_back(DealItem(this->m_stackVector[i],m_pDeck));
for (j=0;j<cardsInStack;j++)
{
// add the items to tell how to deal the cards to the stack
// we want to flip the last card.
if (cardsInStack-1==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);
}
////////////////////////////////////////////////////////////////////////////////
// The actions and the groups are create on the stack and then added to the QMenu
// Since, the QMenu is the owner it will clean up the memory.
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::addGameMenuItems(QMenu & menu)
{
QActionGroup * pNumSuitsGroup=new QActionGroup(&menu);
QAction * pFourSuitsAction=new QAction(tr("Four Suits").trimmed(),pNumSuitsGroup);
pFourSuitsAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_4));
pFourSuitsAction->setCheckable(true);
connect(pFourSuitsAction,SIGNAL(triggered()),
this,SLOT(slotSetFourSuits()));
QAction * pTwoSuitsAction=new QAction(tr("Two Suits").trimmed(),pNumSuitsGroup);
pTwoSuitsAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_2));
pTwoSuitsAction->setCheckable(true);
connect(pTwoSuitsAction,SIGNAL(triggered()),
this,SLOT(slotSetTwoSuits()));
QAction * pOneSuitAction=new QAction(tr("One Suit").trimmed(),pNumSuitsGroup);
pOneSuitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_1));
pOneSuitAction->setCheckable(true);
connect(pOneSuitAction,SIGNAL(triggered()),
this,SLOT(slotSetOneSuit()));
// select the correct item in the list
switch (this->m_gameType)
{
case SpiderBoard::FourSuits:
pFourSuitsAction->setChecked(true);
break;
case SpiderBoard::TwoSuits:
pTwoSuitsAction->setChecked(true);
break;
case SpiderBoard::OneSuit:
pOneSuitAction->setChecked(true);
break;
};
menu.addAction(pFourSuitsAction);
menu.addAction(pTwoSuitsAction);
menu.addAction(pOneSuitAction);
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::loadSettings(const QSettings & settings)
{
int gameType=settings.value(this->GameTypeKeyStr,SpiderBoard::FourSuits).toInt();
switch (gameType)
{
case SpiderBoard::OneSuit:
this->m_gameType=SpiderBoard::OneSuit;
break;
case SpiderBoard::TwoSuits:
this->m_gameType=SpiderBoard::TwoSuits;
break;
case SpiderBoard::FourSuits:
default:
this->m_gameType=SpiderBoard::FourSuits;
break;
};
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::saveSettings(QSettings & settings)
{
settings.setValue(this->GameTypeKeyStr,this->m_gameType);
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::setCheat(bool cheat)
{
this->m_cheat=cheat;
for(unsigned int i=0;i<this->m_stackVector.size();i++)
{
this->m_stackVector[i]->setCheat(cheat);
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::slotDealNextCards(CardStack * pCardStackWidget,
unsigned int index)
{
bool aStackIsEmpty=false;
unsigned int i;
Q_UNUSED(pCardStackWidget);
Q_UNUSED(index);
// if the deck has cards to deal make sure all the stacks have cards
// and deal them out
if (!this->m_pDeck->isEmpty())
{
// first make sure none of the 10 stacks is empty. All must have at least one card.
// to be able to deal the cards.
for(i=0;i<this->m_stackVector.size();i++)
{
if (this->m_stackVector[i]->isEmpty())
{
aStackIsEmpty=true;
break;
}
}
if (aStackIsEmpty)
{
QMessageBox::critical(this,this->gameName(),
tr("All stacks must contain at least one card before the next set of cards can be dealt!").trimmed());
this->stopDemo();
return;
}
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));
dealItemVector[i].addCard(true);
}
// ok now start the deal.
m_dealAni.dealCards(dealItemVector);
}
}
////////////////////////////////////////////////////////////////////////////////
// ok we need to perform the move from the stackWidget to the sentHome widget
// and create the CardMoveRecord. Then just call slotCardsMoved and let the
// move be processed as if it was a drag and drop
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::slotSendSuitHome(SpiderStack * pStack,
const PlayingCardVector & cardVector,
const CardMoveRecord & startMoveRecord)
{
if (NULL!=pStack)
{
// ok first find an empty sentHome widget to add the cards too.
SpiderHomeStack * pHome=NULL;
unsigned int i;
for (i=0;i<this->m_homeVector.size();i++)
{
if (this->m_homeVector[i]->isEmpty())
{
pHome=this->m_homeVector[i];
break;
}
}
// we should always find an empty home widget. But check just in case
if (pHome)
{
CardMoveRecord moveRecord(startMoveRecord);
// add the cards to the update record. But don't move the
// cards
pHome->addCards(cardVector,moveRecord,true);
// perform the move of the cards and animate it if animations
// are enabled
m_sToSAniMove.moveCards(moveRecord);
}
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::slotMoveCardsToDiffStack(SpiderStack * pStack,
const PlayingCardVector & cardVector,
const CardMoveRecord & startMoveRecord)
{
if (NULL!=pStack)
{
SpiderStack * pCanMoveToEmpty=NULL;
SpiderStack * pCanMoveTo=NULL;
SpiderStack * pCanMoveToSameSuit=NULL;
bool cardsWillMove=false;
for (unsigned int i=0;i<this->m_stackVector.size();i++)
{
// ok we are only moving the cards if the hit is not on the same
// stack they are already located in
if (this->m_stackVector[i]!=pStack)
{
// see if we have a perferred move to a stack that has the same suit
// we will break out if we find a perferred match
if (this->m_stackVector[i]->canAddCardsSameSuit(cardVector))
{
pCanMoveToSameSuit=this->m_stackVector[i];
cardsWillMove=true;
break;
}
// otherwise see if we can move the cards to the stack at all
else if (this->m_stackVector[i]->canAddCards(cardVector))
{
if (this->m_stackVector[i]->isEmpty())
{
pCanMoveToEmpty=this->m_stackVector[i];
}
else
{
pCanMoveTo=this->m_stackVector[i];
}
cardsWillMove=true;
}
}
}
// if we are going to move the cards
if (cardsWillMove)
{
SpiderStack * pNewStack=NULL;
// ok the best move is to a matching suit and consecutive
// next best is to a consecutive
// and lastly to an empty stack.
if (NULL!=pCanMoveToSameSuit)
{
pNewStack=pCanMoveToSameSuit;
}
else if (NULL!=pCanMoveTo)
{
pNewStack=pCanMoveTo;
}
else
{
pNewStack=pCanMoveToEmpty;
}
CardMoveRecord moveRecord(startMoveRecord);
pNewStack->addCards(cardVector,moveRecord,true);
// perform the move of the cards and animate it if animations
// are enabled
m_sToSAniMove.moveCards(moveRecord);
}
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::slotSetFourSuits()
{
if (SpiderBoard::FourSuits!=this->m_gameType)
{
this->m_gameType=SpiderBoard::FourSuits;
this->newGame();
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::slotSetTwoSuits()
{
if (SpiderBoard::TwoSuits!=this->m_gameType)
{
this->m_gameType=SpiderBoard::TwoSuits;
this->newGame();
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::slotSetOneSuit()
{
if (SpiderBoard::OneSuit!=this->m_gameType)
{
this->m_gameType=SpiderBoard::OneSuit;
this->newGame();
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::calcScore()
{
int score=0;
unsigned int i;
for(i=0;i<this->m_homeVector.size();i++)
{
score+=this->m_homeVector[i]->score();
}
for(i=0;i<this->m_stackVector.size();i++)
{
score+=this->m_stackVector[i]->score();
}
int deals=0;
// The number of deals left is the number of cards in the deck widget divided by the number of stack widgets
if (!this->m_pDeck->isEmpty() && this->m_stackVector.size()>0)
{
const PlayingCardVector & cardVector=this->m_pDeck->getCardVector();
deals=cardVector.size()/this->m_stackVector.size();
if (cardVector.size()%this->m_stackVector.size())
{
deals++;
}
}
QString dealsLeft(tr("Deals remaining: %1").arg(QString::number(deals)).trimmed());
emit scoreChanged(score,dealsLeft);
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::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.setX(GameBoard::LayoutSpacing*3 + cardSize.width()*2);
for (i=0;i<m_homeVector.size();i++)
{
m_homeVector[i]->setPos(currPos);
currPos.setX(currPos.x()+cardSize.width()+GameBoard::LayoutSpacing);
}
currPos.setY(GameBoard::LayoutSpacing*2+cardSize.height());
currPos.setX(GameBoard::LayoutSpacing);
for (i=0;i<m_stackVector.size();i++)
{
m_stackVector[i]->setPos(currPos);
currPos.setX(currPos.x()+cardSize.width()+GameBoard::LayoutSpacing);
}
std::cout<<__FUNCTION__<<std::endl;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
bool SpiderBoard::runDemo(bool stopWhenNoMore)
{
bool rc=true;
if (!GameBoard::runDemo(false))
{
if (!m_pDeck->isEmpty())
{
slotDealNextCards();
}
else if (stopWhenNoMore)
{
stopDemo();
rc=false;
}
else
{
rc=false;
}
}
return rc;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::createStacks()
{
setCardResizeAlg(10,ResizeByWidth);
unsigned int i;
// create all the widgets for the board
for(i=0;i<8;i++)
{
this->m_homeVector.push_back(new SpiderHomeStack);
this->m_scene.addItem(m_homeVector[i]);
// get signals when cards are added to the stack. So, we can add undo info and
// see when the game is over.
this->connect(this->m_homeVector[i],SIGNAL(cardsMovedByDragDrop(CardMoveRecord)),
this,SLOT(slotCardsMoved(CardMoveRecord)));
}
for(i=0;i<10;i++)
{
this->m_stackVector.push_back(new SpiderStack);
this->m_scene.addItem(m_stackVector[i]);
// get signals when cards are added to the stack. So, we can add undo info and
// see when the game is over.
this->connect(this->m_stackVector[i],SIGNAL(cardsMovedByDragDrop(CardMoveRecord)),
this,SLOT(slotCardsMoved(CardMoveRecord)));
this->connect(this->m_stackVector[i],SIGNAL(sendSuitHome(SpiderStack *,PlayingCardVector,CardMoveRecord)),
this,SLOT(slotSendSuitHome(SpiderStack*,PlayingCardVector,CardMoveRecord)));
this->connect(this->m_stackVector[i],SIGNAL(moveCardsToDiffStack(SpiderStack *,PlayingCardVector,CardMoveRecord)),
this,SLOT(slotMoveCardsToDiffStack(SpiderStack*,PlayingCardVector,CardMoveRecord)));
}
m_pDeck=new CardStack;
this->m_scene.addItem(m_pDeck);
// get signals when the deck is clicked on so we can deal the next set of cards
this->connect(this->m_pDeck,SIGNAL(cardClicked(CardStack*,uint)),
this,SLOT(slotDealNextCards(CardStack*,uint)));
std::cout<<__FUNCTION__<<__FILE__<<std::endl;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
bool SpiderBoard::isGameWon()const
{
bool allSuitsSentHome=true;
for(unsigned int i=0;i<this->m_homeVector.size();i++)
{
if (this->m_homeVector[i]->isEmpty())
{
allSuitsSentHome=false;
break;
}
}
return allSuitsSentHome;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
bool SpiderBoard::isGameWonNotComplete()const
{
bool rc=m_pDeck->isEmpty();
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;
}