From c90c389a8a8d9d8661e9772ec4144c5cf2039f23 Mon Sep 17 00:00:00 2001 From: toma Date: Wed, 25 Nov 2009 17:56:58 +0000 Subject: Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features. BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdegames@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- kpat/spider.cpp | 484 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 484 insertions(+) create mode 100644 kpat/spider.cpp (limited to 'kpat/spider.cpp') diff --git a/kpat/spider.cpp b/kpat/spider.cpp new file mode 100644 index 00000000..262c49b9 --- /dev/null +++ b/kpat/spider.cpp @@ -0,0 +1,484 @@ +/*--------------------------------------------------------------------------- + + 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 +#include "deck.h" +#include + +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(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], SIGNAL(clicked(Card*)), 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())]; +} + +//-------------------------------------------------------------------------// + +QString Spider::getGameState() const +{ + return QString::number(m_leg*10 + m_redeal); +} + +void Spider::setGameState(const QString &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" + +//-------------------------------------------------------------------------// + -- cgit v1.2.1