#include "gamestate.h" #include #include GameState::GameState(QObject *parent) : QObject{parent} { // Initialize the foundation piles (4 suits) for (int i = 0; i < 4; ++i) { m_foundation.append(QList()); } // Initialize the columns (7 piles) for (int i = 0; i < 7; ++i) { m_columns.append(QList()); } } void GameState::dealCards() { QList 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++) { QList column; // Deal exactly i+1 cards to the i-th column 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; } // Use the remaining cards as the draw pile m_drawPile = deck.mid(index); // Reset the foundation & throwaway pile m_foundation.clear(); m_throwawayPile.clear(); // Note that we don't need to reset gameWon from here, as it's // auto-checked from onFoundationChanged, which the emits trigger emit drawPileChanged(); emit throwawayPileChanged(); emit columnsChanged(); emit foundationChanged(); } 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()); emit drawPileChanged(); emit throwawayPileChanged(); } bool GameState::moveCardToColumn(int columnId) { assert(columnId >= 0 && columnId < 7); if (m_throwawayPile.isEmpty()) { qWarning() << "Attempted to move thrown card to column with empty throwaway pile"; return false; } // We'll be moving the last card in the throwaway pile (maybe) PlayingCard *cardToMove = m_throwawayPile.last(); if (!isMoveToColumnLegal(cardToMove, columnId)) return false; ColumnSlot *col = new ColumnSlot(cardToMove, true); m_columns[columnId].append(col); m_throwawayPile.removeLast(); ensureColumnRevealed(columnId); emit throwawayPileChanged(); emit columnsChanged(); return true; } bool GameState::moveThrownCardToFoundation(PlayingCard::Suit foundationId) { if (m_throwawayPile.isEmpty()) { qWarning() << "Attempted to move thrown card to foundation with empty throwaway pile"; 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(); emit throwawayPileChanged(); return true; } bool GameState::moveColumnCardToFoundation(int columnId, PlayingCard::Suit foundationId) { assert(columnId >= 0 && columnId < 7); if (m_columns[columnId].isEmpty()) { qWarning() << "Attempted to move card to foundation from an empty column"; return false; } // We'll be moving the top card in the column (maybe) PlayingCard *cardToMove = m_columns[columnId].first()->card(); // 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 column slot. m_columns[columnId].removeFirst(); ensureColumnRevealed(columnId); emit columnsChanged(); return true; } bool GameState::autoMoveThrownCard() { // NOTE: This method is very similar to moveThrownCardToFoundation, // consider reducing the repetitino here somehow. if (m_throwawayPile.isEmpty()) { // Consider raising an exception here instead qWarning() << "Attempted to move thrown card to foundation with empty throwaway pile"; 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 (!tryAutoMoveCard(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(); emit throwawayPileChanged(); return true; } bool GameState::autoMoveColumnCard(int columnId) { // NOTE: This method is very similar to moveColumnCardToFoundation, // consider reducing the repetitino here somehow. assert(columnId >= 0 && columnId < 7); if (m_columns[columnId].isEmpty()) { // Consider raising an exception here instead qWarning() << "Attempted to move card to foundation from an empty column"; return false; } // We'll be moving the top card in the column (maybe) PlayingCard *cardToMove = m_columns[columnId].first()->card(); if (!tryAutoMoveCard(cardToMove)) return false; // We succeeded, the card is now in the appropriate foundation pile, // let's remove the column slot. m_columns[columnId].removeFirst(); ensureColumnRevealed(columnId); emit columnsChanged(); return true; } void GameState::onFoundationChanged() { // Check if the game is won (can only happen on a foundation pile change) bool gameWon = true; for (const QList &foundationPile : std::as_const(m_foundation)) { // The piles need to contain all 13 card values each, otherwise the game isn't won if (foundationPile.size() != 13) { gameWon = false; break; } } if (gameWon == m_gameWon) return; if (gameWon) qDebug() << "The game was won!"; m_gameWon = gameWon; emit gameWonChanged(); } 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(static_cast(curValue) + 1); } if (cardToMove->value() != requiredValue) return false; m_foundation[foundationId].push_front(cardToMove); emit foundationChanged(); return true; } bool GameState::tryAutoMoveCard(PlayingCard *cardToMove) { // 1. Try moving the card to the foundation for (PlayingCard::Suit suit : {PlayingCard::Suit::Clubs, PlayingCard::Suit::Diamonds, PlayingCard::Suit::Hearts, PlayingCard::Suit::Spades}) if (cardToMove->suit() == suit && tryMoveCardToFoundation(suit, cardToMove)) return true; // 2. Try moving the card to another column for (int columnId = 0; columnId < m_columns.size(); ++columnId) if (isMoveToColumnLegal(cardToMove, columnId)) { moveCardToColumn(columnId); return true; } // No available auto-move return false; } bool GameState::isMoveToColumnLegal(PlayingCard *cardToMove, int columnId) { assert(columnId >= 0 && columnId < 7); if (m_columns[columnId].isEmpty()) { // Column is empty: only a King can be placed in an empty column return cardToMove->value() == PlayingCard::Value::King; } // Compare against the last card in the column PlayingCard* columnCard = m_columns[columnId].last()->card(); // The card's value must be one less than the card in the column if (cardToMove->value() != columnCard->value() - 1) return false; // The card must be of opposite color return PlayingCard::areOppositeColors(*cardToMove, *columnCard); } 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(); } QList GameState::drawPile() const { return m_drawPile; } QList GameState::throwawayPile() const { return m_throwawayPile; } QList > GameState::columns() const { return m_columns; } QList > GameState::foundation() const { return m_foundation; } bool GameState::gameWon() const { return m_gameWon; }