/*--------------------------------------------------------------------------- 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, TDEMainWindow* 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], TQ_SIGNAL(clicked(Card*)), TQ_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(TDEMainWindow *parent) { return new Spider(1, parent); } } ldi15; static class LocalDealerInfo16 : public DealerInfo { public: LocalDealerInfo16() : DealerInfo(I18N_NOOP("Spider (&Medium)"), 15) {} virtual Dealer *createGame(TDEMainWindow *parent) { return new Spider(2, parent); } } ldi16; static class LocalDealerInfo17 : public DealerInfo { public: LocalDealerInfo17() : DealerInfo(I18N_NOOP("Spider (&Hard)"), 16) {} virtual Dealer *createGame(TDEMainWindow *parent) { return new Spider(4, parent); } } ldi17; //-------------------------------------------------------------------------// #include "spider.moc" //-------------------------------------------------------------------------//