2024-12-01 21:40:42 +00:00
|
|
|
#include "gamestate.h"
|
|
|
|
#include <random>
|
2024-12-02 15:10:51 +00:00
|
|
|
#include <QDebug>
|
2024-12-01 21:40:42 +00:00
|
|
|
|
|
|
|
GameState::GameState(QObject *parent)
|
|
|
|
: QObject{parent}
|
|
|
|
{
|
|
|
|
// Initialize the foundation piles (4 suits)
|
|
|
|
for (int i = 0; i < 4; ++i) {
|
|
|
|
m_foundation.append(QList<PlayingCard*>());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize the columns (7 piles)
|
|
|
|
for (int i = 0; i < 7; ++i) {
|
2024-12-02 15:10:51 +00:00
|
|
|
m_columns.append(QList<ColumnSlot*>());
|
2024-12-01 21:40:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GameState::dealCards()
|
|
|
|
{
|
|
|
|
QList<PlayingCard*> deck = PlayingCard::createDeck();
|
|
|
|
|
|
|
|
// Randomly shuffle the deck
|
|
|
|
std::random_device rd;
|
|
|
|
std::default_random_engine rng(rd());
|
|
|
|
std::shuffle(deck.begin(), deck.end(), rng);
|
|
|
|
|
|
|
|
// Deal the cards into the columns
|
|
|
|
int index = 0;
|
|
|
|
for (int i = 0; i < 7; i++) {
|
2024-12-02 15:10:51 +00:00
|
|
|
QList<ColumnSlot*> column;
|
|
|
|
|
2024-12-01 21:40:42 +00:00
|
|
|
// Deal exactly i+1 cards to the i-th column
|
2024-12-02 15:10:51 +00:00
|
|
|
for (int j = 0; j <= i; j++) {
|
|
|
|
bool revealed = (j == i);
|
|
|
|
ColumnSlot *col = new ColumnSlot(deck[index], revealed);
|
|
|
|
column.append(col);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_columns[i] = column;
|
2024-12-01 21:40:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Use the remaining cards as the draw pile
|
|
|
|
m_drawPile = deck.mid(index);
|
|
|
|
|
|
|
|
// Reset the foundation & throwaway pile
|
|
|
|
m_foundation.clear();
|
|
|
|
m_throwawayPile.clear();
|
|
|
|
|
2024-12-02 23:33:22 +00:00
|
|
|
emit drawPileChanged();
|
|
|
|
emit throwawayPileChanged();
|
|
|
|
emit columnsChanged();
|
|
|
|
emit foundationChanged();
|
2024-12-01 21:40:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void GameState::drawNextCard()
|
|
|
|
{
|
|
|
|
// If drawPile is empty, flip the throwawayPile to drawPile
|
|
|
|
if (m_drawPile.isEmpty()) {
|
|
|
|
m_drawPile = m_throwawayPile;
|
|
|
|
m_throwawayPile.clear();
|
|
|
|
std::reverse(m_drawPile.begin(), m_drawPile.end());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draw the top card from drawPile, dropping it into throwawayPile
|
|
|
|
m_throwawayPile.append(m_drawPile.takeFirst());
|
|
|
|
|
2024-12-02 23:33:22 +00:00
|
|
|
emit drawPileChanged();
|
|
|
|
emit throwawayPileChanged();
|
2024-12-01 21:40:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool GameState::moveCardToColumn(int columnId)
|
|
|
|
{
|
|
|
|
assert(columnId >= 0 && columnId < 7);
|
|
|
|
|
|
|
|
if (m_throwawayPile.isEmpty()) {
|
|
|
|
// Consider raising an exception here instead
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We'll be moving the last card in the throwaway pile (maybe)
|
|
|
|
PlayingCard *cardToMove = m_throwawayPile.last();
|
|
|
|
|
|
|
|
if (m_columns[columnId].isEmpty()) {
|
|
|
|
// If the column is empty, we can only place a king
|
|
|
|
if (cardToMove->value() != PlayingCard::Value::King)
|
|
|
|
return false;
|
|
|
|
|
2024-12-02 15:10:51 +00:00
|
|
|
ColumnSlot *col = new ColumnSlot(cardToMove, true);
|
|
|
|
m_columns[columnId].append(col);
|
2024-12-01 21:40:42 +00:00
|
|
|
m_throwawayPile.removeLast();
|
|
|
|
|
2024-12-02 23:33:22 +00:00
|
|
|
emit throwawayPileChanged();
|
|
|
|
emit columnsChanged();
|
2024-12-01 21:40:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We'll be comparing this card against the last card in the column
|
2024-12-02 15:10:51 +00:00
|
|
|
PlayingCard* columnCard = m_columns[columnId].last()->card();
|
2024-12-01 21:40:42 +00:00
|
|
|
|
|
|
|
// This card's value must be one less than the card in the column
|
|
|
|
if (cardToMove->value() != columnCard->value() - 1)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// This card must be of opposite color
|
|
|
|
if (!PlayingCard::areOppositeColors(*cardToMove, *columnCard))
|
|
|
|
return false;
|
|
|
|
|
2024-12-02 15:10:51 +00:00
|
|
|
ColumnSlot *col = new ColumnSlot(cardToMove, true);
|
|
|
|
m_columns[columnId].append(col);
|
2024-12-01 21:40:42 +00:00
|
|
|
m_throwawayPile.removeLast();
|
2024-12-02 15:10:51 +00:00
|
|
|
ensureColumnRevealed(columnId);
|
2024-12-01 21:40:42 +00:00
|
|
|
|
2024-12-02 23:33:22 +00:00
|
|
|
emit throwawayPileChanged();
|
|
|
|
emit columnsChanged();
|
2024-12-01 21:40:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GameState::moveThrownCardToFoundation(PlayingCard::Suit foundationId)
|
|
|
|
{
|
|
|
|
if (m_throwawayPile.isEmpty()) {
|
|
|
|
// Consider raising an exception here instead
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We'll be moving the last card in the foundation pile (maybe)
|
|
|
|
PlayingCard *cardToMove = m_throwawayPile.last();
|
|
|
|
|
|
|
|
// Try moving the card into the foundation
|
|
|
|
if (!tryMoveCardToFoundation(foundationId, cardToMove))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// We succeeded, the card is now in the appropriate foundation pile,
|
|
|
|
// let's remove the card from the throwaway pile.
|
|
|
|
m_throwawayPile.removeLast();
|
|
|
|
|
2024-12-02 23:33:22 +00:00
|
|
|
emit throwawayPileChanged();
|
2024-12-01 21:40:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GameState::moveColumnCardToFoundation(int columnId, PlayingCard::Suit foundationId)
|
|
|
|
{
|
|
|
|
assert(columnId >= 0 && columnId < 7);
|
|
|
|
|
|
|
|
if (m_columns[columnId].isEmpty()) {
|
|
|
|
// Consider raising an exception here instead
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We'll be moving the top card in the column (maybe)
|
2024-12-02 15:10:51 +00:00
|
|
|
PlayingCard *cardToMove = m_columns[columnId].first()->card();
|
2024-12-01 21:40:42 +00:00
|
|
|
|
|
|
|
// Try moving the card into the foundation
|
|
|
|
if (!tryMoveCardToFoundation(foundationId, cardToMove))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// We succeeded, the card is now in the appropriate foundation pile,
|
2024-12-02 15:10:51 +00:00
|
|
|
// let's remove the column slot.
|
2024-12-01 21:40:42 +00:00
|
|
|
m_columns[columnId].removeFirst();
|
2024-12-02 15:10:51 +00:00
|
|
|
ensureColumnRevealed(columnId);
|
2024-12-01 21:40:42 +00:00
|
|
|
|
2024-12-02 23:33:22 +00:00
|
|
|
emit columnsChanged();
|
2024-12-01 21:40:42 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GameState::tryMoveCardToFoundation(PlayingCard::Suit foundationId, PlayingCard* cardToMove)
|
|
|
|
{
|
|
|
|
assert(foundationId >= PlayingCard::Suit::Clubs && foundationId < PlayingCard::Suit::Spades);
|
|
|
|
|
|
|
|
if (cardToMove->suit() != foundationId)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
PlayingCard::Value requiredValue;
|
|
|
|
if (m_foundation[foundationId].isEmpty()) {
|
|
|
|
// If the pile is empty, only an ace can go in
|
|
|
|
requiredValue = PlayingCard::Value::Ace;
|
|
|
|
} else {
|
|
|
|
// Otherwise it's the next card by value, unless we're already at king
|
|
|
|
PlayingCard::Value curValue = m_foundation[foundationId].first()->value();
|
|
|
|
if (curValue == PlayingCard::Value::King)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Clever trick to get the next value. Note that this relies on the enum having
|
|
|
|
// the variants defined in correct order.
|
|
|
|
requiredValue = static_cast<PlayingCard::Value>(static_cast<int>(curValue) + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cardToMove->value() != requiredValue)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
m_foundation[foundationId].push_front(cardToMove);
|
|
|
|
|
2024-12-02 23:33:22 +00:00
|
|
|
emit foundationChanged();
|
2024-12-01 21:40:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-12-02 15:10:51 +00:00
|
|
|
void GameState::ensureColumnRevealed(int columnId)
|
|
|
|
{
|
|
|
|
assert(columnId >= 0 && columnId < 7);
|
|
|
|
|
|
|
|
// Nothing to reveal
|
|
|
|
if (m_columns[columnId].isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Get the top column slot
|
|
|
|
ColumnSlot *col = m_columns[columnId].first();
|
|
|
|
|
|
|
|
// If it's already revealed, there's nothing to do
|
|
|
|
if (col->isRevealed())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// First slot in the column must always be revealed, reveal it
|
|
|
|
col->reveal();
|
|
|
|
}
|
|
|
|
|
2024-12-01 21:40:42 +00:00
|
|
|
QList<PlayingCard *> GameState::drawPile() const
|
|
|
|
{
|
|
|
|
return m_drawPile;
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<PlayingCard *> GameState::throwawayPile() const
|
|
|
|
{
|
|
|
|
return m_throwawayPile;
|
|
|
|
}
|
|
|
|
|
2024-12-02 15:10:51 +00:00
|
|
|
QList<QList<ColumnSlot *> > GameState::columns() const
|
2024-12-01 21:40:42 +00:00
|
|
|
{
|
|
|
|
return m_columns;
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<QList<PlayingCard *> > GameState::foundation() const
|
|
|
|
{
|
|
|
|
return m_foundation;
|
|
|
|
}
|