You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
485 lines
15 KiB
485 lines
15 KiB
/*---------------------------------------------------------------------------
|
|
|
|
spider.cpp implements a patience card game
|
|
|
|
Copyright (C) 2003 Josh Metzler
|
|
|
|
* Permission to use, copy, modify, and distribute this software and its
|
|
* documentation for any purpose and without fee is hereby granted,
|
|
* provided that the above copyright notice appear in all copies and that
|
|
* both that copyright notice and this permission notice appear in
|
|
* supporting documentation.
|
|
*
|
|
* This file is provided AS IS with no warranties of any kind. The author
|
|
* shall have no liability with respect to the infringement of copyrights,
|
|
* trade secrets or any patents by this file or any part thereof. In no
|
|
* event will the author be liable for any lost revenue or profits or
|
|
* other special, indirect and consequential damages.
|
|
|
|
---------------------------------------------------------------------------*/
|
|
|
|
#include "spider.h"
|
|
#include "cardmaps.h"
|
|
#include <klocale.h>
|
|
#include "deck.h"
|
|
#include <kdebug.h>
|
|
|
|
void SpiderPile::moveCards(CardList &c, Pile *to)
|
|
{
|
|
Pile::moveCards(c, to);
|
|
|
|
// if this is a leg pile, don't do anything special
|
|
if ( to->checkIndex() == 0 )
|
|
return;
|
|
|
|
// if the top card of the list I just moved is an Ace,
|
|
// the run I just moved is the same suit as the pile,
|
|
// and the destination pile now has more than 12 cards,
|
|
// then it could have a full deck that needs removed.
|
|
if (c.last()->rank() == Card::Ace &&
|
|
c.first()->suit() == to->top()->suit() &&
|
|
to->cardsLeft() > 12) {
|
|
Spider *b = dynamic_cast<Spider*>(dealer());
|
|
if (b) {
|
|
b->checkPileDeck(to);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------//
|
|
|
|
Spider::Spider(int suits, KMainWindow* parent, const char* _name)
|
|
: Dealer(parent, _name)
|
|
{
|
|
const int dist_x = cardMap::CARDX() * 11 / 10 + 1;
|
|
const int dist_y = cardMap::CARDY() * 11 / 10 + 1;
|
|
|
|
deck = Deck::new_deck(this, 2, suits);
|
|
|
|
// I deal the cards into 'redeal' piles, so hide the deck
|
|
deck->setVisible(false);
|
|
|
|
// Dealing the cards out into 5 piles so the user can see how many
|
|
// sets of 10 cards are left to be dealt out
|
|
for( int column = 0; column < 5; column++ ) {
|
|
redeals[column] = new Pile(column + 1, this);
|
|
redeals[column]->move(8 + dist_x / 3 * (23 + column), 8 + dist_y * 4.5);
|
|
redeals[column]->setZ(5-column);
|
|
redeals[column]->setCheckIndex(0);
|
|
redeals[column]->setAddFlags(Pile::disallow);
|
|
redeals[column]->setRemoveFlags(Pile::disallow);
|
|
connect(redeals[column], TQT_SIGNAL(clicked(Card*)), TQT_SLOT(deckClicked(Card*)));
|
|
}
|
|
|
|
// The 10 playing piles
|
|
for( int column = 0; column < 10; column++ ) {
|
|
stack[column] = new SpiderPile(column + 6, this);
|
|
stack[column]->move(8 + dist_x * column, 8);
|
|
stack[column]->setZ(20);
|
|
stack[column]->setCheckIndex(1);
|
|
stack[column]->setAddFlags(Pile::addSpread | Pile::several);
|
|
stack[column]->setRemoveFlags(Pile::several |
|
|
Pile::autoTurnTop | Pile::wholeColumn);
|
|
}
|
|
|
|
// The 8 'legs' so named by me because spiders have 8 legs - why
|
|
// else the name Spider?
|
|
for( int column = 0; column < 8; column++ ) {
|
|
legs[column] = new Pile(column + 16, this);
|
|
legs[column]->move(8 + dist_x / 3 * column, 8 + dist_y * 4.5);
|
|
legs[column]->setZ(column+1);
|
|
legs[column]->setCheckIndex(0);
|
|
legs[column]->setAddFlags(Pile::disallow);
|
|
legs[column]->setRemoveFlags(Pile::disallow);
|
|
legs[column]->setTarget(true);
|
|
}
|
|
|
|
// Moving an A-K run to a leg is not really an autoDrop - the
|
|
// user should have no choice. Also, it must be moved A first, ...
|
|
// up to K so the King will be on top.
|
|
setAutoDropEnabled(false);
|
|
setActions(Dealer::Hint | Dealer::Demo );
|
|
}
|
|
|
|
//-------------------------------------------------------------------------//
|
|
|
|
bool Spider::checkAdd(int /*checkIndex*/, const Pile *c1, const CardList& c2) const
|
|
{
|
|
// assuming the cardlist is a valid unit, since I allowed
|
|
// it to be removed - can drop any card on empty pile or
|
|
// on any suit card of one higher rank
|
|
if (c1->isEmpty() || c1->top()->rank() == c2.first()->rank()+1)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Spider::checkRemove(int /*checkIndex*/, const Pile *p, const Card *c) const
|
|
{
|
|
// if the pile from c up is decreasing by 1 and all the same suit, ok
|
|
// note that this is true if c is the top card
|
|
const Card *before;
|
|
int index = p->indexOf(c);
|
|
while (c != p->top()) {
|
|
before = c;
|
|
c = p->at(++index);
|
|
if (before->suit() != c->suit() || before->rank() != c->rank()+1)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Spider::getHints()
|
|
{
|
|
kdDebug(11111) << "get hints" << endl;
|
|
// first, get runs from each stack
|
|
CardList cl[10];
|
|
|
|
Pile* empty = NULL;
|
|
for (int column = 0; column < 10; column++) {
|
|
if (stack[column]->isEmpty())
|
|
empty = stack[column];
|
|
else
|
|
cl[column] = getRun(stack[column]->top());
|
|
}
|
|
|
|
// if I can build a run from Ace->King in one suit then
|
|
// hint those moves
|
|
HintList hl;
|
|
for (int s = Card::Clubs; s <= Card::Spades; s++) {
|
|
bool bGrowing = true;
|
|
int vTopNew = 0;
|
|
int colNew = -1;
|
|
while (bGrowing && vTopNew < 13) {
|
|
bGrowing = false;
|
|
int col = colNew;
|
|
int vTop = vTopNew;
|
|
for (int column = 0; column < 10; column++) {
|
|
if (cl[column].isEmpty() || col == column)
|
|
continue;
|
|
if (cl[column].last()->suit() == s &&
|
|
cl[column].last()->rank() <= vTop+1 &&
|
|
cl[column].first()->rank() > vTop)
|
|
{
|
|
bGrowing = true;
|
|
if (cl[column].first()->rank() > vTopNew) {
|
|
colNew = column;
|
|
vTopNew = cl[column].first()->rank();
|
|
}
|
|
}
|
|
}
|
|
if (bGrowing && vTop)
|
|
hl.append(new MoveHint(cl[col][vTop-
|
|
cl[colNew].last()->rank()+1], stack[colNew]));
|
|
}
|
|
if (vTopNew == 13)
|
|
hints += hl;
|
|
else
|
|
for (HintList::Iterator it = hl.begin(); it != hl.end(); ++it)
|
|
delete *it;
|
|
hl.clear();
|
|
}
|
|
|
|
// now check to see if a run from one column can go on the end
|
|
// of a run from another stack
|
|
for (int column = 0; column < 10; column++) {
|
|
if (cl[column].isEmpty())
|
|
continue;
|
|
|
|
// if there is an empty column and this stack is on
|
|
// another card, hint
|
|
if (empty && cl[column].count() < (uint)stack[column]->cardsLeft()) {
|
|
newHint(new MoveHint(cl[column].first(), empty));
|
|
continue;
|
|
}
|
|
|
|
// now see if I can move this stack to any other column
|
|
for (int c2 = 0; c2 < 10; c2++) {
|
|
if (c2 == column || cl[c2].isEmpty())
|
|
continue;
|
|
|
|
if (cl[c2].last()->rank() == cl[column].first()->rank()+1)
|
|
{
|
|
// I can hint this move - should I?
|
|
int index = stack[column]->indexOf(cl[column].first());
|
|
|
|
// if target pile is the same suit as this card,
|
|
// or if there are no cards under this one,
|
|
// or if it couldn't move to where it is now,
|
|
// or if the card under this one is face down, hint
|
|
if (cl[c2].last()->suit() == cl[column].first()->suit() ||
|
|
index == 0 || stack[column]->at(index-1)->rank() !=
|
|
cl[column].first()->rank()+1 ||
|
|
!(stack[column]->at(index-1)->realFace()))
|
|
newHint(new MoveHint(cl[column].first(), stack[c2]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MoveHint *Spider::chooseHint()
|
|
{
|
|
kdDebug(11111) << "choose 1 of " << hints.count() << " hints" << endl;
|
|
if (hints.isEmpty())
|
|
return 0;
|
|
|
|
// first, choose a card that is moving to the same suit
|
|
for (HintList::ConstIterator it = hints.begin(); it != hints.end(); ++it)
|
|
{
|
|
if (!(*it)->pile()->isEmpty() &&
|
|
(*it)->pile()->top()->suit() == (*it)->card()->suit())
|
|
return *it;
|
|
}
|
|
|
|
// second, choose a card that is moving from the base
|
|
for (HintList::ConstIterator it = hints.begin(); it != hints.end(); ++it)
|
|
{
|
|
if ((*it)->card()->source() &&
|
|
(*it)->card()->source()->at(0) == (*it)->card())
|
|
return *it;
|
|
}
|
|
|
|
// otherwise, go with a random hint
|
|
return hints[randseq.getLong(hints.count())];
|
|
}
|
|
|
|
//-------------------------------------------------------------------------//
|
|
|
|
TQString Spider::getGameState() const
|
|
{
|
|
return TQString::number(m_leg*10 + m_redeal);
|
|
}
|
|
|
|
void Spider::setGameState(const TQString &stream)
|
|
{
|
|
int i = stream.toInt();
|
|
|
|
if (m_leg > i/10) {
|
|
for (m_leg--; m_leg > i/10; m_leg--)
|
|
legs[m_leg]->setVisible(false);
|
|
legs[m_leg]->setVisible(false);
|
|
} else
|
|
for (; m_leg < i/10; m_leg++)
|
|
legs[m_leg]->setVisible(true);
|
|
|
|
if (m_redeal > i%10) {
|
|
for (m_redeal--; m_redeal > i%10; m_redeal--)
|
|
redeals[m_redeal]->setVisible(true);
|
|
redeals[m_redeal]->setVisible(true);
|
|
} else
|
|
for (; m_redeal < i%10; m_redeal++)
|
|
redeals[m_redeal]->setVisible(false);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------//
|
|
|
|
void Spider::restart()
|
|
{
|
|
deck->collectAndShuffle();
|
|
deal();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------//
|
|
|
|
CardList Spider::getRun(Card *c) const
|
|
{
|
|
CardList result;
|
|
|
|
Pile *p = c->source();
|
|
if (!p || p->isEmpty())
|
|
return result;
|
|
|
|
result.append(c);
|
|
|
|
Card::Suit s = c->suit();
|
|
int v = c->rank();
|
|
|
|
int index = p->indexOf(c);
|
|
c = p->at(--index);
|
|
while (index >= 0 && c->realFace() &&
|
|
c->suit() == s && c->rank() == ++v)
|
|
{
|
|
result.prepend(c);
|
|
c = p->at(--index);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Spider::checkPileDeck(Pile *check)
|
|
{
|
|
kdDebug(11111) << "check for run" << endl;
|
|
if (check->isEmpty())
|
|
return;
|
|
|
|
if (check->top()->rank() == Card::Ace) {
|
|
// just using the CardList to see if this goes to King
|
|
CardList run = getRun(check->top());
|
|
if (run.first()->rank() == Card::King) {
|
|
legs[m_leg]->setVisible(true);
|
|
|
|
// remove this full deck from this pile
|
|
CardList cl;
|
|
for (int i = 0; i < 13; i++ ) {
|
|
cl.append(check->cards().last());
|
|
check->moveCards(cl, legs[m_leg]);
|
|
cl.clear();
|
|
}
|
|
m_leg++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Spider::dealRow()
|
|
{
|
|
if (m_redeal > 4)
|
|
return;
|
|
|
|
for (int column = 0; column < 10; column++) {
|
|
stack[column]->add(redeals[m_redeal]->top(), false, true);
|
|
|
|
// I may put an Ace on a K->2 pile so it could need cleared.
|
|
if (stack[column]->top()->rank() == Card::Ace)
|
|
checkPileDeck(stack[column]);
|
|
}
|
|
|
|
redeals[m_redeal++]->setVisible(false);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------//
|
|
|
|
void Spider::deal()
|
|
{
|
|
unmarkAll();
|
|
|
|
m_leg = 0;
|
|
m_redeal = 0;
|
|
|
|
int column = 0;
|
|
// deal face down cards (5 to first 4 piles, 4 to last 6)
|
|
for (int i = 0; i < 44; i++ ) {
|
|
stack[column]->add(deck->nextCard(), true, true);
|
|
column = (column + 1) % 10;
|
|
}
|
|
// deal face up cards, one to each pile
|
|
for (int i = 0; i < 10; i++ ) {
|
|
stack[column]->add(deck->nextCard(), false, true);
|
|
column = (column + 1) % 10;
|
|
}
|
|
// deal the remaining cards into 5 'redeal' piles
|
|
for (int column = 0; column < 5; column++ )
|
|
for (int i = 0; i < 10; i++ )
|
|
redeals[column]->add(deck->nextCard(), true, false);
|
|
|
|
// make the leg piles invisible
|
|
for (int i = 0; i < 8; i++ )
|
|
legs[i]->setVisible(false);
|
|
// make the redeal piles visible
|
|
for (int i = 0; i < 5; i++ )
|
|
redeals[i]->setVisible(true);
|
|
}
|
|
|
|
Card *Spider::demoNewCards()
|
|
{
|
|
if (m_leg > 4)
|
|
return 0;
|
|
deckClicked(0);
|
|
return stack[0]->top();
|
|
}
|
|
|
|
void Spider::deckClicked(Card*)
|
|
{
|
|
kdDebug(11111) << "deck clicked " << m_redeal << endl;
|
|
if (m_redeal > 4)
|
|
return;
|
|
|
|
unmarkAll();
|
|
dealRow();
|
|
takeState();
|
|
}
|
|
|
|
bool Spider::isGameLost() const
|
|
{
|
|
kdDebug(11111) << "isGameLost ?"<< endl;
|
|
|
|
// if there are still cards to deal out, you have not lost
|
|
if (m_redeal < 5)
|
|
return false;
|
|
|
|
// first, get runs from each stack - returning if empty
|
|
CardList cl[10];
|
|
|
|
for (int column = 0; column < 10; column++) {
|
|
if (stack[column]->isEmpty())
|
|
return false;
|
|
cl[column] = getRun(stack[column]->top());
|
|
}
|
|
|
|
// from this point on, I know that none of the columns is empty
|
|
// now check to see if a run from one column can go on the end
|
|
// of a run from another stack
|
|
for (int column = 0; column < 10; column++)
|
|
for (int c2 = 0; c2 < 10; c2++) {
|
|
if (c2 == column)
|
|
continue;
|
|
|
|
// if I can move this run to another pile, I'm not done
|
|
if (cl[c2].last()->rank() == cl[column].first()->rank()+1)
|
|
return false;
|
|
}
|
|
|
|
// if you can build a run from Ace->King in one suit then
|
|
// you can clear it and keep playing
|
|
for (int s = Card::Clubs; s <= Card::Spades; s++) {
|
|
bool bGrowing = true;
|
|
int vTop = 0;
|
|
while (bGrowing && vTop < 13) {
|
|
bGrowing = false;
|
|
int column = 0;
|
|
while (column < 10 && !bGrowing) {
|
|
if (cl[column].last()->suit() == s &&
|
|
cl[column].last()->rank() <= vTop+1 &&
|
|
cl[column].first()->rank() > vTop)
|
|
{
|
|
bGrowing = true;
|
|
vTop = cl[column].first()->rank();
|
|
}
|
|
column++;
|
|
}
|
|
}
|
|
// if you can build such a pile, you can continue
|
|
if (vTop == 13)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static class LocalDealerInfo15 : public DealerInfo
|
|
{
|
|
public:
|
|
LocalDealerInfo15() : DealerInfo(I18N_NOOP("S&pider (Easy)"), 14) {}
|
|
virtual Dealer *createGame(KMainWindow *parent) { return new Spider(1, parent); }
|
|
} ldi15;
|
|
|
|
static class LocalDealerInfo16 : public DealerInfo
|
|
{
|
|
public:
|
|
LocalDealerInfo16() : DealerInfo(I18N_NOOP("Spider (&Medium)"), 15) {}
|
|
virtual Dealer *createGame(KMainWindow *parent) { return new Spider(2, parent); }
|
|
} ldi16;
|
|
|
|
static class LocalDealerInfo17 : public DealerInfo
|
|
{
|
|
public:
|
|
LocalDealerInfo17() : DealerInfo(I18N_NOOP("Spider (&Hard)"), 16) {}
|
|
virtual Dealer *createGame(KMainWindow *parent) { return new Spider(4, parent); }
|
|
} ldi17;
|
|
|
|
//-------------------------------------------------------------------------//
|
|
|
|
#include "spider.moc"
|
|
|
|
//-------------------------------------------------------------------------//
|
|
|