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.
tdegames/kpat/klondike.cpp

495 lines
16 KiB

/*
klondike.cpp implements a patience card game
Copyright (C) 1995 Paul Olav Tvete
* 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.
//
// 7 positions, alternating red and black
//
****************************************/
#include "klondike.h"
#include <tdelocale.h>
#include "deck.h"
#include <kdebug.h>
#include <assert.h>
#include "cardmaps.h"
class KlondikePile : public Pile
{
public:
KlondikePile( int _index, Dealer* parent)
: Pile(_index, parent) {}
void clearSpread() { cardlist.clear(); }
void addSpread(Card *c) {
cardlist.append(c);
}
virtual TQSize cardOffset( bool _spread, bool, const Card *c) const {
kdDebug(11111) << "cardOffset " << _spread << " " << (c? c->name() : "(null)") << endl;
if (cardlist.contains(const_cast<Card * const>(c)))
return TQSize(+dspread(), 0);
return TQSize(0, 0);
}
private:
CardList cardlist;
};
Klondike::Klondike( bool easy, TDEMainWindow* parent, const char* _name )
: Dealer( parent, _name )
{
// The units of the follwoing constants are pixels
const int margin = 10; // between card piles and board edge
const int hspacing = cardMap::CARDX() / 6 + 1; // horizontal spacing between card piles
const int vspacing = cardMap::CARDY() / 4; // vertical spacing between card piles
deck = Deck::new_deck(this);
deck->move(margin, margin);
EasyRules = easy;
pile = new KlondikePile( 13, this);
pile->move(margin + cardMap::CARDX() + cardMap::CARDX() / 4, margin);
// Move the visual representation of the pile to the intended position
// on the game board.
pile->setAddFlags(Pile::disallow);
pile->setRemoveFlags(Pile::Default);
for( int i = 0; i < 7; i++ ) {
play[ i ] = new Pile( i + 5, this);
play[i]->move(margin + (cardMap::CARDX() + hspacing) * i, margin + cardMap::CARDY() + vspacing);
play[i]->setAddType(Pile::KlondikeStore);
play[i]->setRemoveFlags(Pile::several | Pile::autoTurnTop | Pile::wholeColumn);
}
for( int i = 0; i < 4; i++ ) {
target[ i ] = new Pile( i + 1, this );
target[i]->move(margin + (3 + i) * (cardMap::CARDX()+ hspacing), margin);
target[i]->setAddType(Pile::KlondikeTarget);
if (EasyRules) // change default
target[i]->setRemoveFlags(Pile::Default);
else
target[i]->setRemoveType(Pile::KlondikeTarget);
}
setActions(Dealer::Hint | Dealer::Demo);
redealt = false;
}
// This function returns true when it is certain that the card t is no longer
// needed on any of the play piles. This function is recursive but the
// recursion will not get deep.
//
// To determine wether a card is no longer needed on any of the play piles we
// obviously must know what a card can be used for there. According to the
// rules a card can be used to store another card with 1 less unit of value
// and opposite color. This is the only thing that a card can be used for
// there. Therefore the cards with lowest value (1) are useless there (base
// case). The other cards each have 2 cards that can be stored on them, let us
// call those 2 cards *depending cards*.
//
// The object of the game is to put all cards on the target piles. Therefore
// cards that are no longer needed on any of the play piles should be put on
// the target piles if possible. Cards on the target piles can not be moved
// and they can not store any of its depending cards. Let us call this that
// the cards on the target piles are *out of play*.
//
// The simple and obvious rule is:
// A card is no longer needed when both of its depending cards are out of
// play.
//
// But using only the simplest rule to determine if a card is no longer
// needed on any of the play piles is not ambitios enough. Therefore, if a
// depending card is not out of play, we test if it could become out of play.
// The requirement for getting a card out of play is that it can be placed on
// a target pile and that it is no longer needed on any of the play piles
// (this is why this function is recursive). This more ambitious rule lets
// us extend the base case with the second lowest value (2).
bool Klondike::noLongerNeeded(Card::Rank r, Card::Suit s) {
if (r <= Card::Two) return true; // Base case.
// Find the 2 suits of opposite color. "- 1" is used here because the
// siuts are ranged 1 .. 4 but target_tops is indexed 0 .. 3. (Of course
// the subtraction of 1 does not affect performance because it is a
// constant expression that is calculated at compile time).
unsigned char a = Card::Clubs - 1, b = Card::Spades - 1;
if (s == Card::Clubs || s == Card::Spades)
a = Card::Diamonds - 1, b = Card::Hearts - 1;
const Card::Rank depending_rank = static_cast<Card::Rank>(r - 1);
return
(((target_tops[a] >= depending_rank)
||
((target_tops[a] >= depending_rank - 1)
&&
(noLongerNeeded
(depending_rank, static_cast<Card::Suit>(a + 1)))))
&&
((target_tops[b] >= depending_rank)
||
((target_tops[b] >= depending_rank - 1)
&&
(noLongerNeeded
(depending_rank, static_cast<Card::Suit>(b + 1))))));
}
bool Klondike::tryToDrop(Card *t)
{
if (!t || !t->realFace() || t->takenDown())
return false;
// kdDebug(11111) << "tryToDrop " << t->name() << endl;
Pile *tgt = findTarget(t);
if (tgt) {
newHint
(new MoveHint(t, tgt, noLongerNeeded(t->rank(), t->suit())));
return true;
}
return false;
}
void Klondike::getHints() {
target_tops[0] = target_tops[1] = target_tops[2] = target_tops[3]
= Card::None;
for( int i = 0; i < 4; i++ )
{
Card *c = target[i]->top();
if (!c) continue;
target_tops[c->suit() - 1] = c->rank();
}
Card* t[7];
for(int i=0; i<7;i++)
t[i] = play[i]->top();
for(int i=0; i<7; i++)
{
CardList list = play[i]->cards();
for (CardList::ConstIterator it = list.begin(); it != list.end(); ++it)
{
if (!(*it)->isFaceUp())
continue;
CardList empty;
empty.append(*it);
for (int j = 0; j < 7; j++)
{
if (i == j)
continue;
if (play[j]->legalAdd(empty)) {
if (((*it)->rank() != Card::King) || it != list.begin()) {
newHint(new MoveHint(*it, play[j]));
break;
}
}
}
break; // the first face up
}
tryToDrop(play[i]->top());
}
if (!pile->isEmpty())
{
Card *t = pile->top();
if (!tryToDrop(t))
{
for (int j = 0; j < 7; j++)
{
CardList empty;
empty.append(t);
if (play[j]->legalAdd(empty)) {
newHint(new MoveHint(t, play[j]));
break;
}
}
}
}
}
Card *Klondike::demoNewCards() {
deal3();
if (!deck->isEmpty() && pile->isEmpty())
deal3(); // again
return pile->top();
}
void Klondike::restart() {
kdDebug(11111) << "restart\n";
deck->collectAndShuffle();
redealt = false;
deal();
}
void Klondike::deal3()
{
int draw;
if ( EasyRules ) {
draw = 1;
} else {
draw = 3;
}
pile->clearSpread();
if (deck->isEmpty())
{
redeal();
return;
}
// move the cards back on the deck, so we can have three new
for (int i = 0; i < pile->cardsLeft(); ++i) {
pile->at(i)->move(pile->x(), pile->y());
}
for (int flipped = 0; flipped < draw ; ++flipped) {
Card *item = deck->nextCard();
if (!item) {
kdDebug(11111) << "deck empty!!!\n";
return;
}
pile->add(item, true, true); // facedown, nospread
if (flipped < draw - 1)
pile->addSpread(item);
// move back to flip
item->move(deck->x(), deck->y());
item->flipTo( int(pile->x()) + pile->dspread() * (flipped), int(pile->y()), 8 * (flipped + 1) );
}
}
// Add cards from pile to deck, in reverse direction
void Klondike::redeal() {
CardList pilecards = pile->cards();
if (EasyRules)
// the remaining cards in deck should be on top
// of the new deck
pilecards += deck->cards();
for (int count = pilecards.count() - 1; count >= 0; --count)
{
Card *card = pilecards[count];
card->setAnimated(false);
deck->add(card, true, false); // facedown, nospread
}
redealt = true;
}
void Klondike::deal() {
for(int round=0; round < 7; round++)
for (int i = round; i < 7; i++ )
play[i]->add(deck->nextCard(), i != round, true);
}
bool Klondike::cardClicked(Card *c) {
kdDebug(11111) << "card clicked " << c->name() << endl;
if (Dealer::cardClicked(c))
return true;
if (c->source() == deck) {
pileClicked(deck);
return true;
}
return false;
}
void Klondike::pileClicked(Pile *c) {
kdDebug(11111) << "pile clicked " << endl;
Dealer::pileClicked(c);
if (c == deck) {
deal3();
}
}
bool Klondike::startAutoDrop()
{
bool pileempty = pile->isEmpty();
if (!Dealer::startAutoDrop())
return false;
if (pile->isEmpty() && !pileempty)
deal3();
return true;
}
bool Klondike::isGameLost() const
{
kdDebug( 11111 ) << "Is the game lost?" << endl;
if (!deck->isEmpty()) {
kdDebug( 11111 ) << "We should only check this when the deck is exhausted." << endl;
return false;
}
// Check whether top of the pile can be added to any of the target piles.
if ( !pile->isEmpty() ) {
for ( int i = 0; i < 4; ++i ) {
if ( target[ i ]->isEmpty() ) {
continue;
}
if ( pile->top()->suit() == target[ i ]->top()->suit() &&
pile->top()->rank() - 1 == target[ i ]->top()->rank() ) {
kdDebug( 11111 ) << "No, the source pile's top card could be added to target pile " << i << endl;
return false;
}
}
}
// Create a card list - srcPileCards - that contains all accessible
// cards in the pile and the deck.
CardList srcPileCards;
if ( EasyRules ) {
srcPileCards = pile->cards();
} else {
/* In the draw3 mode, not every card in the source pile is
* accessible, but only every third one.
*/
for ( unsigned int i = 2; i < pile->cards().count(); i += 3 ) {
kdDebug( 11111 ) << "Found card "<< pile->cards()[i]->name()<< endl;
srcPileCards += pile->cards()[ i ];
}
if ( !pile->cards().isEmpty() && pile->cards().count() % 3 != 0 ) {
kdDebug( 11111 ) << "Found last card "<< pile->cards()[pile->cards().count() - 1]->name()<< endl;
srcPileCards += pile->cards()[ pile->cards().count() - 1 ];
}
}
// Check all seven stores
for ( int i = 0; i < 7; ++i ) {
// If this store is empty...
if ( play[ i ]->isEmpty() ) {
// ...check whether the pile contains a king we could move here.
CardList::ConstIterator it = srcPileCards.begin();
CardList::ConstIterator end = srcPileCards.end();
for ( ; it != end; ++it ) {
if ( ( *it )->rank() == Card::King ) {
kdDebug( 11111 ) << "No, the pile contains a king which we could move onto store " << i << endl;
return false;
}
}
// ...check whether any of the other stores contains a (visible)
// king we could move here.
for ( int j = 0; j < 7; ++j ) {
if ( j == i || play[ j ]->isEmpty() ) {
continue;
}
const CardList cards = play[ j ]->cards();
CardList::ConstIterator it = ++cards.begin();
CardList::ConstIterator end = cards.end();
for ( ; it != end; ++it ) {
if ( ( *it )->realFace() && ( *it )->rank() == Card::King ) {
kdDebug( 11111 ) << "No, store " << j << " contains a visible king which we could move onto store " << i << endl;
return false;
}
}
}
} else { // This store is not empty...
Card *topCard = play[ i ]->top();
// ...check whether the top card is an Ace (we can start a target)
if ( topCard->rank() == Card::Ace ) {
kdDebug( 11111 ) << "No, store " << i << " has an Ace, we could start a target pile." << endl;
return false;
}
// ...check whether the top card can be added to any target pile
for ( int targetIdx = 0; targetIdx < 4; ++targetIdx ) {
if ( target[ targetIdx ]->isEmpty() ) {
continue;
}
if ( target[ targetIdx ]->top()->suit() == topCard->suit() &&
target[ targetIdx ]->top()->rank() == topCard->rank() - 1 ) {
kdDebug( 11111 ) << "No, store " << i << "'s top card could be added to target pile " << targetIdx << endl;
return false;
}
}
// ...check whether the source pile contains a card which can be
// put onto this store.
CardList::ConstIterator it = srcPileCards.begin();
CardList::ConstIterator end = srcPileCards.end();
for ( ; it != end; ++it ) {
if ( ( *it )->isRed() != topCard->isRed() &&
( *it )->rank() == topCard->rank() - 1 ) {
kdDebug( 11111 ) << "No, the pile contains a card which we could add to store " << i << endl;
return false;
}
}
// ...check whether any of the other stores contains a visible card
// which can be put onto this store, and which is on top of an
// uncovered card.
for ( int j = 0; j < 7; ++j ) {
if ( j == i ) {
continue;
}
const CardList cards = play[ j ]->cards();
CardList::ConstIterator it = cards.begin();
CardList::ConstIterator end = cards.end();
for ( ; it != end; ++it ) {
if ( ( *it )->realFace() &&
( *it )->isRed() != topCard->isRed() &&
( *it )->rank() == topCard->rank() - 1 ) {
kdDebug( 11111 ) << "No, store " << j << " contains a card which we could add to store " << i << endl;
return false;
}
}
}
}
}
kdDebug( 11111 ) << "Yep, all hope is lost." << endl;
return true;
}
static class LocalDealerInfo0 : public DealerInfo
{
public:
LocalDealerInfo0() : DealerInfo(I18N_NOOP("&Klondike"), 0) {}
virtual Dealer *createGame(TDEMainWindow *parent) { return new Klondike(true, parent); }
} ldi0;
static class LocalDealerInfo14 : public DealerInfo
{
public:
LocalDealerInfo14() : DealerInfo(I18N_NOOP("Klondike (&draw 3)"), 13) {}
virtual Dealer *createGame(TDEMainWindow *parent) { return new Klondike(false, parent); }
} ldi14;
#include "klondike.moc"